libxpertmass-1.1.0/.gitignore000664 001750 001750 00000000125 14647464636 017450 0ustar00rusconirusconi000000 000000 TODO .cache .kdev4/ compile_commands.json *.kdev4 maintainer-scripts/ _clang-format libxpertmass-1.1.0/AUTHORS000664 001750 001750 00000000046 14647465366 016533 0ustar00rusconirusconi000000 000000 Filippo Rusconi libxpertmass-1.1.0/CMakeLists.txt000664 001750 001750 00000016363 14651507310 020211 0ustar00rusconirusconi000000 000000 ############################################################# ############################################################# # CMake configuration # 3.18.4 in oldstable as of 2024 - 07 - 15 cmake_minimum_required(VERSION 3.18.4) set(CMAKE_PROJECT_INCLUDE_BEFORE ${CMAKE_SOURCE_DIR}/CMakeStuff/ancillary-project-include-before.cmake) ############################################################ ############################################################ # Basic information about project project(XpertMass VERSION 1.1.0 DESCRIPTION "libraries [::Core (non-GUI) and ::GUI) for the MsXpertSuite project." HOMEPAGE_URL "http://wwww.msxpertsuite.org/libxpertmass" LANGUAGES CXX) set(LOWCASE_PROJECT_NAME xpertmass) # VERSION above sets: # PROJECT_VERSION, _VERSION # PROJECT_VERSION_MAJOR, _VERSION_MAJOR # PROJECT_VERSION_MINOR, _VERSION_MINOR # PROJECT_VERSION_PATCH, _VERSION_PATCH message("\n${BoldGreen}Building version ${PROJECT_VERSION} of \ ${PROJECT_NAME} (${CMAKE_PROJECT_DESCRIPTION})${ColourReset}\n") # Set additional project information set(COMPANY "MsXpertSuite.org") set(COPYRIGHT "Copyright (c) 2008-2024 Filippo Rusconi. Licensed under GPLv3+") set(IDENTIFIER "org.msxpertsuite.libxpertmass") include(GNUInstallDirs) include(FeatureSummary) set(HOME_DEVEL_DIR $ENV{HOME}/devel) message("\n${BoldYellow}The devel directory where all the development projects \ should reside: ${HOME_DEVEL_DIR}.${ColourReset}\n") set(CMAKE_COLOR_MAKEFILE ON) set(CMAKE_VERBOSE_MAKEFILE ON) message("\n${BoldGreen}Configuring build for project ${CMAKE_PROJECT_NAME}${ColourReset}\n") # This export will allow using the flags to be used by # youcompleteme (vim plugin) and by the Clang-based code analyzer.. set(CMAKE_EXPORT_COMPILE_COMMANDS 1) if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json" ) execute_process( COMMAND cmake -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json ) endif() # We want C++17 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) message(STATUS "CMAKE_CXX_STANDARD: ON") message(STATUS "CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}") ############################################################# # Setting command line compiler definitions: message(STATUS "${BoldYellow}Setting compiler command line definitions.${ColourReset}") # We do not want warnings for unknown pragmas: message("Setting definition -Wno-unknown-pragmas.") add_definitions(-Wno-unknown-pragmas) # Enable warnings and possibly treat them as errors message("Setting definition -Wall -pedantic.") add_definitions(-Wall -pedantic) message("Setting definition -Wextra.") add_definitions(-Wextra) if(WARN_AS_ERROR) message("Setting definition -Werror.") add_definitions(-Werror) else() message("NOT setting definition -Werror.") endif() message(STATUS "\nCMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}\n") option(MXE "Use the cross-compiling environment MXE" OFF) option(LOCAL_DEV "Use locally developped libpappsomspp" OFF) option(PAPPSO_LOCAL_DEV "Use locally developped libpappsomspp" OFF) if(LOCAL_DEV) set(PAPPSO_LOCAL_DEV ON) endif() message(STATUS "LOCAL_DEV: ${LOCAL_DEV}") message(STATUS "PAPPSO_LOCAL_DEV: ${PAPPSO_LOCAL_DEV}") ############################################################# ############################################################# # Platform-specific CMake configuration if(WIN32 OR _WIN32) if(MXE) # Run the following cmake command line: # x86_64-w64-mingw32.shared-cmake -DCMAKE_BUILD_TYPE=Release -DMXE=1 ../../development include(${CMAKE_TOOLCHAINS_PATH}/mxe-toolchain.cmake) # Set the name to the systemUname variable because in this situation that name # is not found, it it passed as a flag in the command line. set(SYSTEM_UNAME_S "mxe") elseif(WIN10MINGW64) include(${CMAKE_TOOLCHAINS_PATH}/win10-mingw64-toolchain.cmake) endif() elseif(UNIX) message("UNIX toolchain being selected") include(${CMAKE_TOOLCHAINS_PATH}/unix-toolchain-common.cmake) endif() message("") message(STATUS "${BoldGreen}Starting configuration of ${CMAKE_PROJECT_NAME}${ColourReset}") message("") message(STATUS "${BoldYellow}The build toolchain is: ${SYSTEM_UNAME_S}${ColourReset}") message("") ############################################################# ############################################################# # Essential software configuration message(STATUS "CMAKE_CURRENT_BINARY_DIR: " ${CMAKE_CURRENT_BINARY_DIR}) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Type of build, options are: None, Debug, Release, RelWithDebInfo, MinSizeRel." FORCE) endif(NOT CMAKE_BUILD_TYPE) if(CMAKE_BUILD_TYPE MATCHES "Release") message(STATUS "Compiling in release mode.") add_definitions("-DQT_NO_DEBUG_OUTPUT") endif() if(CMAKE_BUILD_TYPE MATCHES "Debug") message(STATUS "Compiling in debug mode.") message(STATUS "Add definition -ggdb3 to format debug output for GDB.") add_definitions(-ggdb3) endif() if(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo") message(STATUS "Compiling in release with debug info mode.") endif( CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo" ) message(STATUS "${BoldYellow}CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}.${ColourReset}") if(PROFILE) message(STATUS "${BoldYellow}Profiling is requested, adding -pg flag.${ColourReset}") add_definitions(-pg) endif() message(STATUS "${BoldYellow}Main CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}${ColourReset}") message(STATUS "${BoldYellow}CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}${ColourReset}") ############################################################# ################## # BUILD OF THE LIBRARIES message(STATUS "\n${BoldGreen}Adding subdirectory src for PROJECT: \ ${CMAKE_PROJECT_NAME}${ColourReset}\n") add_subdirectory(src) ############################################################# ###################################### # BUILD OF THE DEVELOPER DOCUMENTATION option(BUILD_DOC OFF) if(BUILD_DOC) add_subdirectory(doc) endif() ############################################################# #################### # BUILD OF THE TESTS if(BUILD_TESTS) message(STATUS "\n${BoldGreen}Build the tests ${ColourReset}\n") message("Adding subdirectory tests for PROJECT: ${CMAKE_PROJECT_NAME}") add_subdirectory(tests) endif() ############################################################# ################################ # Creation of the source archive include(${CMAKE_UTILS_PATH}/targz-source-package-creation.cmake) set(PARENT_DIR ${CMAKE_SOURCE_DIR}/..) set(TARBALL_DIR ${PARENT_DIR}/tarballs) add_custom_target(archive cpack -G TGZ --config CPackSourceConfig.cmake && mv ${CMAKE_BINARY_DIR}/${ARCHIVE_FILE_NAME_EXT} ${TARBALL_DIR} && ln -sf ${TARBALL_DIR}/${ARCHIVE_FILE_NAME_EXT} ${PARENT_DIR}/${DEBIAN_ORIG_FILE_NAME} && ln -sf ${TARBALL_DIR}/${ARCHIVE_FILE_NAME_EXT} ${TARBALL_DIR}/${DEBIAN_ORIG_FILE_NAME} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Creating .tar.gz" VERBATIM) #-----------------------------------------------------------# # Summary #-----------------------------------------------------------# message("\n\nBegin feature summary") message("---------------------\n") feature_summary(FATAL_ON_MISSING_REQUIRED_PACKAGES WHAT ALL) message("---------------------") message("End feature summary\n\n") libxpertmass-1.1.0/CMakeStuff/000775 001750 001750 00000000000 14651507337 017441 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/CMakeStuff/.targz-source-package-creation.cmake.swp000664 001750 001750 00000030000 14647465366 027154 0ustar00rusconirusconi000000 000000 b0VIM 9.1=e"Y3(rusconiroma~rusconi/devel/minexpert2/development/CMakeStuff/targz-source-package-creation.cmakeutf-8 3210#"! Utp#ad, #?d< O - u ] V 5 )    include(CPainclude(CPack)setinclude(CPack)set(CPACK_VERBATIM_VARIABLES YES) minexpert2-doc.pdf) libmassgui.a libmass.a doc/user-manual/DC-user-manual TODO compile_commands.json .*kdev.* maintainer-scripts/ /\\.kdev4/ \\.git/ .cache/set(CPACK_SOURCE_IGNORE_FILESset(CPACK_SOURCE_PACKAGE_FILE_NAME "${LOWCASE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")set(CPACK_SOURCE_GENERATOR "TGZ")set(CPACK_RESOURCE_FILE_AUTHORS "${CMAKE_SOURCE_DIR}/AUTHORS")set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Software for visualization and exploration of mass spectrometric data")set(CPACK_PACKAGE_VERSION "${VERSION}")set(CPACK_PACKAGE_VENDOR "msXpertSuite")set(CPACK_PACKAGE_NAME "minexpert2")# it is listed here.# possible that it be absent from source dir! Which is why# generated right while doing 'cmake archive', so it is not# in the tarball. However, because it is a configure_file it is # this is a generated file (configure_file) that needs not be# Listed below is the doc/user-manual/DC-user-manual:# Packagerlibxpertmass-1.1.0/CMakeStuff/XpertMassConfig.cmake.in000664 001750 001750 00000000224 14647465366 024134 0ustar00rusconirusconi000000 000000 @PACKAGE_INIT@ include("${CMAKE_CURRENT_LIST_DIR}/XpertMassStaticTargets.cmake") include("${CMAKE_CURRENT_LIST_DIR}/XpertMassSharedTargets.cmake") libxpertmass-1.1.0/CMakeStuff/ancillary-project-include-before.cmake000664 001750 001750 00000001006 14647465366 026755 0ustar00rusconirusconi000000 000000 # Add folder where are supportive functions set(CMAKE_UTILS_PATH ${CMAKE_SOURCE_DIR}/CMakeStuff) set(CMAKE_TOOLCHAINS_PATH ${CMAKE_UTILS_PATH}/toolchains) set(CMAKE_MODULE_PATH ${CMAKE_UTILS_PATH}/modules) # Include the system's uname that fills in SYSTEM_UNAME_S. # Sets WIN32 if SYSTEM_UNAME_S is "^.*MING64.*" # Note that WIN32 is set even on 64 bits systems. include(${CMAKE_UTILS_PATH}/systemUname.cmake) # Include the various colors we want to use in the output include(${CMAKE_UTILS_PATH}/outputColors.cmake) libxpertmass-1.1.0/CMakeStuff/outputColors.cmake000664 001750 001750 00000001065 14647465366 023201 0ustar00rusconirusconi000000 000000 # We want to use some colors for the message output. string(ASCII 27 Esc) set(ColourReset "${Esc}[m") set(ColourBold "${Esc}[1m") set(Red "${Esc}[31m") set(Green "${Esc}[32m") set(Yellow "${Esc}[33m") set(Blue "${Esc}[34m") set(Magenta "${Esc}[35m") set(Cyan "${Esc}[36m") set(White "${Esc}[37m") set(BoldRed "${Esc}[1;31m") set(BoldGreen "${Esc}[1;32m") set(BoldYellow "${Esc}[1;33m") set(BoldBlue "${Esc}[1;34m") set(BoldMagenta "${Esc}[1;35m") set(BoldCyan "${Esc}[1;36m") set(BoldWhite "${Esc}[1;37m") libxpertmass-1.1.0/CMakeStuff/systemUname.cmake000664 001750 001750 00000000752 14647465366 022773 0ustar00rusconirusconi000000 000000 # Ask that uname -s be performed and store the value in SYSTEM_UNAME_S for # later reference. macro(get_uname_string) execute_process(COMMAND uname -s OUTPUT_VARIABLE SYSTEM_UNAME_S) if(${SYSTEM_UNAME_S} MATCHES "^.*MINGW64.*") message(STATUS "System detected as Windows10, setting WIN32 AND WIN10MINGW64") # Note that WIN32 is set even on 64 bits systems. set(WIN32 1) set(WIN10MINGW64 1) #else() #message(STATUS "System is not Windows.") endif() endmacro() get_uname_string() libxpertmass-1.1.0/CMakeStuff/targz-source-package-creation.cmake000664 001750 001750 00000002320 14647465366 026272 0ustar00rusconirusconi000000 000000 # Packager # Listed below is the doc/user-manual/DC-user-manual: # this is a generated file (configure_file) that needs not be # in the tarball. However, because it is a configure_file it is # generated right while doing 'cmake archive', so it is not # possible that it be absent from source dir! Which is why # it is listed here. set(CPACK_PACKAGE_NAME "libxpertmass") set(CPACK_PACKAGE_VENDOR "MsXpertSuite") set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Libraries for mass spectrometry") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_AUTHORS "${CMAKE_SOURCE_DIR}/AUTHORS") set(CPACK_SOURCE_GENERATOR "TGZ") # Handy short names set(ARCHIVE_FILE_NAME "lib${LOWCASE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION}") set(DEBIAN_ORIG_FILE_NAME "lib${LOWCASE_PROJECT_NAME}_${CPACK_PACKAGE_VERSION}.orig.tar.gz") # The required name set(CPACK_SOURCE_PACKAGE_FILE_NAME ${ARCHIVE_FILE_NAME}) # Short name with the extension set(ARCHIVE_FILE_NAME_EXT ${ARCHIVE_FILE_NAME}.tar.gz) set(CPACK_SOURCE_IGNORE_FILES debian/ .cache/ \\.git/ /\\.kdev4/ .*kdev.* compile_commands.json TODO doc/html) set(CPACK_VERBATIM_VARIABLES YES) include(CPack) libxpertmass-1.1.0/CMakeStuff/toolchains/000775 001750 001750 00000000000 14651507337 021604 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/CMakeStuff/toolchains/mxe-toolchain.cmake000664 001750 001750 00000005352 14650463765 025367 0ustar00rusconirusconi000000 000000 # This file should be included if the command line reads like this: # x86_64-w64-mingw32.shared-cmake -DMXE=1 ... MESSAGE("MXE (M cross environment) https://mxe.cc/") message("Please run the configuration like this:") message("x86_64-w64-mingw32.shared-cmake -G \"Unix Makefiles\" -DCMAKE_BUILD_TYPE=Release ../../development") set(MXE_ROOT_DIR "${HOME_DEVEL_DIR}/mxe") set(MXE_SHIPPED_DLLS_DIR "${MXE_ROOT_DIR}/dlls-and-stuff-for-packages") # We are building the lib, so need to export the symbols. add_definitions(-D EXPORT_LIB_SYMBOLS) set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES ${HOME_DEVEL_DIR}/mxe/usr/x86_64-w64-mingw32.shared/include) set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES ${HOME_DEVEL_DIR}/mxe/usr/x86_64-w64-mingw32.shared/include) set(IsoSpec++_FOUND 1) set(IsoSpec++_INCLUDE_DIRS "${HOME_DEVEL_DIR}/isospec/development") set(IsoSpec++_LIBRARIES "${MXE_SHIPPED_DLLS_DIR}/libIsoSpec++.dll") if(NOT TARGET IsoSpec++::IsoSpec++) add_library(IsoSpec++::IsoSpec++ UNKNOWN IMPORTED) set_target_properties(IsoSpec++::IsoSpec++ PROPERTIES IMPORTED_LOCATION "${IsoSpec++_LIBRARIES}" INTERFACE_INCLUDE_DIRECTORIES "${IsoSpec++_INCLUDE_DIRS}") endif() set(PappsoMSpp_FOUND 1) set(PappsoMSpp_INCLUDE_DIRS "${HOME_DEVEL_DIR}/pappsomspp/development/src") set(PappsoMSpp_LIBRARIES "${MXE_SHIPPED_DLLS_DIR}/libpappsomspp.dll") if(NOT TARGET PappsoMSpp::Core) add_library(PappsoMSpp::Core UNKNOWN IMPORTED GLOBAL) set_target_properties(PappsoMSpp::Core PROPERTIES IMPORTED_LOCATION ${PappsoMSpp_LIBRARIES} INTERFACE_INCLUDE_DIRECTORIES ${PappsoMSpp_INCLUDE_DIRS}) endif() set(PappsoMSppWidget_FOUND 1) set(PappsoMSppWidget_LIBRARIES "${MXE_SHIPPED_DLLS_DIR}/libpappsomspp-widget.dll") if(NOT TARGET PappsoMSpp::Widget) add_library(PappsoMSpp::Widget UNKNOWN IMPORTED GLOBAL) set_target_properties(PappsoMSpp::Widget PROPERTIES IMPORTED_LOCATION ${PappsoMSppWidget_LIBRARIES} INTERFACE_INCLUDE_DIRECTORIES ${PappsoMSpp_INCLUDE_DIRS}) endif() include_directories(${include_directories} ${PappsoMSpp_INCLUDE_DIRS} ${PappsoMSpp_INCLUDE_DIRS}) #OPENMP message(STATUS "${BoldYellow}OpenMP support is compulsory.${ColourReset}") #message(STATUS "CMAKE_MODULE_PATH: ${CMAKE_MODULE_PATH}") #set(OpenMP_DIR ${CMAKE_MODULE_PATH}) find_package(OpenMP REQUIRED) ## We can build the package setup executable with this specific command. add_custom_target(dllinstall COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/src/XpertMass/libXpertMass.dll ${MXE_SHIPPED_DLLS_DIR} COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/src/XpertMassGui/libXpertMassGui.dll ${MXE_SHIPPED_DLLS_DIR} COMMENT "Copy the built dll files to their MXE destination." VERBATIM) # On Win10 all the code is relocatable. remove_definitions(-fPIC) libxpertmass-1.1.0/CMakeStuff/toolchains/win10-mingw64-toolchain.cmake000664 001750 001750 00000005720 14650440024 027003 0ustar00rusconirusconi000000 000000 message("WIN10-MINGW64 environment https://www.msys2.org/") message("Please run the configuration like this:") message("cmake -G \"Unix Makefiles\" -DCMAKE_BUILD_TYPE=Debug ../development") # Comment out these two lines that make the build fail with #include_next math.h # file not found error. # set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "c:/msys64/ucrt64/include") # set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "c:/msys64/ucrt64/include") # We are building the lib, so need to export the symbols. add_definitions(-D EXPORT_LIB_SYMBOLS) set(IsoSpec++_FOUND 1) set(IsoSpec++_INCLUDE_DIRS "${HOME_DEVEL_DIR}/isospec/development") set(IsoSpec++_LIBRARIES "${HOME_DEVEL_DIR}/isospec/build-area/mingw64/IsoSpec++/libIsoSpec++.dll") if(NOT TARGET IsoSpec++::IsoSpec++) add_library(IsoSpec++::IsoSpec++ UNKNOWN IMPORTED) set_target_properties(IsoSpec++::IsoSpec++ PROPERTIES IMPORTED_LOCATION "${IsoSpec++_LIBRARIES}" INTERFACE_INCLUDE_DIRECTORIES "${IsoSpec++_INCLUDE_DIRS}") endif() set(PappsoMSpp_FOUND 1) set(PappsoMSpp_INCLUDE_DIRS "${HOME_DEVEL_DIR}/pappsomspp/development/src") set(PappsoMSpp_LIBRARIES "${HOME_DEVEL_DIR}/pappsomspp/build-area/mingw64/src/libpappsomspp.dll") if(NOT TARGET PappsoMSpp::Core) add_library(PappsoMSpp::Core UNKNOWN IMPORTED GLOBAL) set_target_properties(PappsoMSpp::Core PROPERTIES IMPORTED_LOCATION ${PappsoMSpp_LIBRARIES} INTERFACE_INCLUDE_DIRECTORIES ${PappsoMSpp_INCLUDE_DIRS}) endif() set(PappsoMSppWidget_FOUND 1) set(PappsoMSppWidget_LIBRARIES "${HOME_DEVEL_DIR}/pappsomspp/build-area/mingw64/src/pappsomspp/widget/libpappsomspp-widget.dll") if(NOT TARGET PappsoMSpp::Widget) add_library(PappsoMSpp::Widget UNKNOWN IMPORTED GLOBAL) set_target_properties(PappsoMSpp::Widget PROPERTIES IMPORTED_LOCATION ${PappsoMSppWidget_LIBRARIES} INTERFACE_INCLUDE_DIRECTORIES ${PappsoMSpp_INCLUDE_DIRS}) endif() include_directories(${include_directories} ${PappsoMSpp_INCLUDE_DIRS} ${PappsoMSpp_INCLUDE_DIRS}) #OPENMP message(STATUS "${BoldYellow}OpenMP support is compulsory.${ColourReset}") #message(STATUS "CMAKE_MODULE_PATH: ${CMAKE_MODULE_PATH}") #set(OpenMP_DIR ${CMAKE_MODULE_PATH}) # Unfortunately that does not work as it does not find the # include directories. So we need to do the work manually. find_package(OpenMP REQUIRED) set(OpenMP_FOUND 1) set(OpenMP_INCLUDE_DIRS "C:/msys64/ucrt64/lib/gcc/x86_64-w64-mingw32/13.2.0/include/omp.h") set(OpenMP_LIBRARIES "C:/msys64/ucrt64/bin/libgomp-1.dll") if(NOT TARGET OpenMP::OpenMP_CXX) add_library(OpenMP::OpenMP UNKNOWN IMPORTED) set_target_properties(OpenMP::OpenMP_CXX PROPERTIES IMPORTED_LOCATION "${OpenMP_LIBRARIES}" INTERFACE_INCLUDE_DIRECTORIES "${OpenMP_INCLUDE_DIRS}" ) endif() message(STATUS "OpenMP libraries: ${OpenMP_LIBRARIES}") message(STATUS "OpenMP include dirs: ${OpenMP_INCLUDE_DIRS}") ## Platform-dependent compiler flags: include(CheckCXXCompilerFlag) # On Win10 all the code is relocatable. remove_definitions(-fPIC) libxpertmass-1.1.0/CMakeStuff/toolchains/unix-toolchain-common.cmake000664 001750 001750 00000010334 14650424263 027031 0ustar00rusconirusconi000000 000000 message(STATUS "UNIX toolchain") set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES /usr/include) set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES /usr/include) find_package(IsoSpec++ REQUIRED) if(PAPPSO_LOCAL_DEV) include(${CMAKE_TOOLCHAINS_PATH}/unix-toolchain-local-pappso.cmake) else() find_package(PappsoMSpp COMPONENTS Core Widget GLOBAL REQUIRED) endif() #OPENMP message(STATUS "${BoldYellow}OpenMP support is compulsory.${ColourReset}") #message(STATUS "CMAKE_MODULE_PATH: ${CMAKE_MODULE_PATH}") #set(OpenMP_DIR ${CMAKE_MODULE_PATH}) find_package(OpenMP REQUIRED) #We know this one is empty: #message(STATUS "OpenMP found with include dirs: ${OpenMP_CXX_INCLUDE_DIRS}") #Which is why we will reconstruct it for gcc #on the basis of the path to the libgomp.so library.The include #directory is nothing more than the path to that library with '/include' #added to it.This is what is done below. message(STATUS "OpenMP found with libraries: ${OpenMP_CXX_LIBRARIES}") # On mingw64 with ucrt64 environment, the include file is # /c/msys64/ucrt64/lib/gcc/x86_64-w64-mingw32/13.2.0/include/omp.h if(OpenMP_CXX_INCLUDE_DIRS STREQUAL "") message(STATUS "OpenMP include directories could not be found. Crafting them manually starting from the library directories.") if(CMAKE_COMPILER_IS_GNUCXX) message(STATUS "The compiler is CMAKE_COMPILER_IS_GNUCXX") #We now need to extract the libgomp.so library path, and craft the #include dir path which is replacing libgomp.so with include. set(OpenMP_CXX_INCLUDE_DIRS ${OpenMP_CXX_LIBRARIES}) list(FILTER OpenMP_CXX_INCLUDE_DIRS INCLUDE REGEX ".*libgomp.*") #message(STATUS "Started crafting OpenMP include dirs: ${OpenMP_CXX_INCLUDE_DIRS}") list(LENGTH OpenMP_CXX_INCLUDE_DIRS INCLUDE_DIRS_LIST_LENGTH) #message(STATUS "Remaining items in OpenMP_CXX_INCLUDE_DIRS: ${INCLUDE_DIRS_LIST_LENGTH}") list(GET OpenMP_CXX_INCLUDE_DIRS 0 OpenMP_CXX_INCLUDE_DIR) #message(STATUS "Started crafting OpenMP include dir: ${OpenMP_CXX_INCLUDE_DIR}") string(REGEX REPLACE "libgomp.so.*" "include" OpenMP_CXX_INCLUDE_SINGLE_DIR ${OpenMP_CXX_INCLUDE_DIR}) message(STATUS "OpenMP single include dir: ${OpenMP_CXX_INCLUDE_SINGLE_DIR}") if(EXISTS "${OpenMP_CXX_INCLUDE_SINGLE_DIR}") set(OpenMP_CXX_INCLUDE_DIRS ${OpenMP_CXX_INCLUDE_SINGLE_DIR}) message(STATUS "OpenMP found with include dirs: ${OpenMP_CXX_INCLUDE_DIRS}") else() set(OpenMP_CXX_INCLUDE_DIRS "") message(STATUS "OpenMP found but not the include directories.") endif() else() # Compiler is not CMAKE_COMPILER_IS_GNUCXX # Check if the compiler is clang++ #set(DEBUG_COMPILER "/usr/bin/clang") #string(REGEX MATCH ".*clang\\+\\+.*" CMAKE_CXX_COMPILER_IS_CLANG ${DEBUG_COMPILER}) string(REGEX MATCH ".*clang\\+\\+.*" CMAKE_CXX_COMPILER_IS_CLANG ${CMAKE_CXX_COMPILER}) #string(FIND ${CMAKE_CXX_COMPILER} "clang++" CMAKE_CXX_COMPILER_IS_CLANG) if(CMAKE_CXX_COMPILER_IS_CLANG STREQUAL "") message(STATUS "Failed to identify compiler different than g++ and clang++") else() message(STATUS "The compiler has been identified as clang++: ${CMAKE_CXX_COMPILER}") execute_process ( COMMAND bash -c "clang --print-resource-dir" OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE CLANG_RESOURCE_DIR ) if(${CLANG_RESOURCE_DIR} STREQUAL "") message(STATUS "Failed to determine the Clang resource dir") else() #message(STATUS "Clang resource dir: \"${CLANG_RESOURCE_DIR}\"") set(CLANG_RESOURCE_DIR "${CLANG_RESOURCE_DIR}/include") #message(STATUS "Clang resource dir: ${CLANG_RESOURCE_DIR}") if(EXISTS "${CLANG_RESOURCE_DIR}") set(OpenMP_CXX_INCLUDE_DIRS ${CLANG_RESOURCE_DIR}) message(STATUS "OpenMP found with include dirs: ${OpenMP_CXX_INCLUDE_DIRS}") else() set(OpenMP_CXX_INCLUDE_DIRS "") message(STATUS "OpenMP found but not the include directories.") endif() endif() endif() endif() endif() ## INSTALL directories set(BIN_DIR ${CMAKE_INSTALL_PREFIX}/bin) set(DOC_DIR ${CMAKE_INSTALL_PREFIX}/share/doc/${TARGET}) ## Platform-dependent compiler flags: include(CheckCXXCompilerFlag) if (WITH_FPIC) add_definitions(-fPIC) endif() libxpertmass-1.1.0/CMakeStuff/toolchains/unix-toolchain-local-pappso.cmake000664 001750 001750 00000002237 14650424253 030135 0ustar00rusconirusconi000000 000000 message("BEGIN - UNIX toolchain with local PAPPSO libraries") message("") # The development should use the locally built libs. set(PappsoMSpp_FOUND 1) set(PappsoMSpp_INCLUDE_DIRS "$ENV{HOME}/devel/pappsomspp/development/src") set(PappsoMSpp_LIBRARIES "$ENV{HOME}/devel/pappsomspp/build-area/unix/src/libpappsomspp.so.0") if(NOT TARGET PappsoMSpp::Core) add_library(PappsoMSpp::Core UNKNOWN IMPORTED GLOBAL) set_target_properties(PappsoMSpp::Core PROPERTIES IMPORTED_LOCATION ${PappsoMSpp_LIBRARIES} INTERFACE_INCLUDE_DIRECTORIES ${PappsoMSpp_INCLUDE_DIRS}) endif() set(PappsoMSppWidget_FOUND 1) set(PappsoMSppWidget_LIBRARIES "$ENV{HOME}/devel/pappsomspp/build-area/unix/src/pappsomspp/widget/libpappsomspp-widget.so.0") if(NOT TARGET PappsoMSpp::Widget) add_library(PappsoMSpp::Widget UNKNOWN IMPORTED GLOBAL) set_target_properties(PappsoMSpp::Widget PROPERTIES IMPORTED_LOCATION ${PappsoMSppWidget_LIBRARIES} INTERFACE_INCLUDE_DIRECTORIES ${PappsoMSpp_INCLUDE_DIRS}) endif() include_directories(${include_directories} ${PappsoMSpp_INCLUDE_DIRS} ${PappsoMSpp_INCLUDE_DIRS}) message("") message("END - UNIX toolchain with local PAPPSO libraries") libxpertmass-1.1.0/LICENSE000664 001750 001750 00000104404 14647465366 016473 0ustar00rusconirusconi000000 000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. libmassgui Copyright (C) 2023 msxpertsuite This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) 2023 msxpertsuite This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . libxpertmass-1.1.0/Makefile000664 001750 001750 00000000154 14647465366 017123 0ustar00rusconirusconi000000 000000 all: cd ../../build-area/unix && make -j1 .PHONY: clean clean: cd ../../build-area/unix && make clean libxpertmass-1.1.0/README.md000664 001750 001750 00000002705 14647465366 016746 0ustar00rusconirusconi000000 000000 libXpertMass ============ *libXpertMass* is a C++ shared library that is referenced from other projects. The *libXpertMass* shared library is designed to enshrine the non-GUI functionalities needed by the following two projects: * MsXpertSuite/massXpert2; * MsXpertSuite/mineXpert2. In particular, it contains abstractions for all the chemical entities required to fully characterize a polymer chemistry definition, as shown below: * Isotope * IsotopicData * Formula * Monomer * Oligomer * Polymer * Modif * CrossLinker * CrossLink * Aqueous chemical reactions (cleavages, enzymatic and non-enzymatic) * Gas-phase chemical reactions (fragmentations, with a sophisticated grammar to describe complex fragmentation patterns) * Isotopic cluster modelling/calculations for any chemical entity representable by an element composition formula and a charge. * ... *libXpertMass* has a companion GUI library, *libXpertMassGui* , that is designed to enshrine the GUI functionalities needed by the same two projects above. libXpertMassGui =============== *libXpertMassGui* is a C++ shared library that is referenced from other projects. The *libXpertMassGui* shared library is designed to enshrine the GUI functionalities needed by the following two projects: * MsXpertSuite/massXpert2; * MsXpertSuite/mineXpert2. *libXpertMassGui* has a companion GUI library, *libXpertMass* , that is designed to enshrine the non-GUI functionalities needed by the same two projects above. libxpertmass-1.1.0/doc/000775 001750 001750 00000000000 14651507337 016216 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/doc/CMakeLists.txt000664 001750 001750 00000001757 14647465366 021002 0ustar00rusconirusconi000000 000000 message("") message(STATUS "${BoldGreen}Starting documentation build configuration for \ ${CMAKE_PROJECT_NAME} ${ColourReset}") message("") # The output of QDoc will be in ${CMAKE_BINARY_DIR}/html. # We need to copy the images directory to the build directory. add_custom_target(doc COMMAND /usr/lib/qt6/bin/qdoc developer-documentation.qdocconf --outputdir ${CMAKE_CURRENT_BINARY_DIR}/html COMMAND cp -rpf ${CMAKE_CURRENT_LIST_DIR}/images ${CMAKE_CURRENT_BINARY_DIR}/ WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} COMMENT "Build of the QDoc-based developer documentation in ${CMAKE_CURRENT_BINARY_DIR}/html") # For convenience add_custom_target(documentation DEPENDS doc) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images DESTINATION ${CMAKE_INSTALL_DOCDIR}) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR}) message("") message(STATUS "${BoldGreen}Finished configuring the ${CMAKE_PROJECT_NAME} \ developer documentation.${ColourReset}") message("") libxpertmass-1.1.0/doc/developer-documentation.h000664 001750 001750 00000000067 14647465366 023240 0ustar00rusconirusconi000000 000000 #include "libXpertMass.h" #include "libXpertMassGui.h" libxpertmass-1.1.0/doc/groups/000775 001750 001750 00000000000 14651507337 017535 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/doc/groups/libXpertMass/000775 001750 001750 00000000000 14651507337 022152 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/doc/groups/libXpertMass/globals.qdoc000664 001750 001750 00000000176 14647465366 024463 0ustar00rusconirusconi000000 000000 /*! \group Globals \ingroup MsXpS::libXpertMass \inmodule MsXpS::libXpertMass \title Global variables, enums and functions */ libxpertmass-1.1.0/doc/groups/libXpertMass/pol-chem-def-aqueous-chemical-reactions.qdoc000664 001750 001750 00000000370 14647465366 032504 0ustar00rusconirusconi000000 000000 /*! \group PolChemDefAqueousChemicalReactions \ingroup MsXpS::libXpertMass \inmodule MsXpS::libXpertMass \title Aqueous chemical reactions These are the classes that are required to model the chemical reactions that occur in the aqueous phase. */ libxpertmass-1.1.0/doc/groups/libXpertMass/pol-chem-def-building-blocks.qdoc000664 001750 001750 00000000417 14647465366 030344 0ustar00rusconirusconi000000 000000 /*! \group PolChemDefBuildingdBlocks \ingroup MsXpS::libXpertMass \inmodule MsXpS::libXpertMass \title Polymer chemistry building blocks These are the classes that are required to make new polymer chemistry definitions that model the chemical structure of a polymer. */ libxpertmass-1.1.0/doc/groups/libXpertMass/pol-chem-def-gas-phase-chemical-reactions.qdoc000664 001750 001750 00000000367 14647465366 032700 0ustar00rusconirusconi000000 000000 /*! \group PolChemDefGasPhaseChemicalReactions \ingroup MsXpS::libXpertMass \inmodule MsXpS::libXpertMass \title Gas-phase chemical reactions These are the classes that are required to model the chemical reactions that occur in the gas phase. */ libxpertmass-1.1.0/doc/groups/libXpertMass/pol-chem-def.qdoc000664 001750 001750 00000000526 14647465366 025277 0ustar00rusconirusconi000000 000000 /*! \group PolChemDef \ingroup MsXpS::libXpertMass \inmodule MsXpS::libXpertMass \title Polymer chemistry definition These are the classes that enshrine the notion of \e{polymer chemistry definition} in the form of a complete set of chemical, functional and computational rules characterizing the chemical behaviour of chemical analytes. */ libxpertmass-1.1.0/doc/groups/libXpertMass/the-prop-system.qdoc000664 001750 001750 00000002153 14647465366 026115 0ustar00rusconirusconi000000 000000 /*! \group ThePropSystem \ingroup MsXpS::libXpertMass \inmodule MsXpS::libXpertMass \title The property system The property system developed in the context of the libmass library is aimed at easing the aggregation of typed data (integers, doubles, \l{MsXpS::libXpertMass::Modif}s, \l{MsXpS::libXpertMass::ChemicalGroup}s...) to objects that derive from the \l MsXpS::libXpertMass::PropListHolder class. The abstract base class is the \l MsXpS::libXpertMass::Prop class. The member data are the \l MsXpS::libXpertMass::Prop::m_name string and the \l MsXpS::libXpertMass::Prop::mpa_data pointer to void. From that class, type-specific classes are derived where the member data are of that specific type. For example, the data in the MsXpS::libXpertMass::ModifProp class are of type \l MsXpS::libXpertMass::Modif. The data in the Prop-derived instance are automatically deleted by the deleteData() function, like for \l{MsXpS::libXpertMass::ModifProp}. Only the \l{MsXpS::libXpertMass::NoDeletePointerProp} Prop-derived class has as member data a pointer to data that are not deleted when the property instance is destructed. */ libxpertmass-1.1.0/doc/groups/libXpertMass/xpertmass-mass-calculations.qdoc000664 001750 001750 00000000412 14647465366 030477 0ustar00rusconirusconi000000 000000 /*! \group XpertMassMassCalculations \ingroup MsXpS::libXpertMass \inmodule MsXpS::libXpertMass \title libXpertMass Mass calculations These are the classes that are required to actually calculate masses starting from formulas and a polymer chemistry definition. */ libxpertmass-1.1.0/doc/groups/libXpertMass/xpertmass-utilities.qdoc000664 001750 001750 00000000172 14647465366 027073 0ustar00rusconirusconi000000 000000 /*! \group XpertMassUtilities \ingroup MsXpS::libXpertMass \inmodule MsXpS::libXpertMass \title libXpertMass Utilities */ libxpertmass-1.1.0/doc/groups/libXpertMassGui/000775 001750 001750 00000000000 14651507337 022617 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/doc/groups/libXpertMassGui/color-selector.qdoc000664 001750 001750 00000000163 14647465366 026435 0ustar00rusconirusconi000000 000000 /*! \group ColorSelector \ingroup MsXpS::libXpertMassGui \inmodule MsXpS::libXpertMassGui \title Color selector */ libxpertmass-1.1.0/doc/groups/libXpertMassGui/xpertmassgui-mass-calculations.qdoc000664 001750 001750 00000000225 14647465366 031653 0ustar00rusconirusconi000000 000000 /*! \group XpertMassGuiMassCalculations \ingroup MsXpS::libXpertMassGui \inmodule MsXpS::libXpertMassGui \title libXpertMassGui Mass calculations */ libxpertmass-1.1.0/doc/groups/libXpertMassGui/xpertmassgui-utilities.qdoc000664 001750 001750 00000000206 14647465366 030243 0ustar00rusconirusconi000000 000000 /*! \group XpertMassGuiUtilities \ingroup MsXpS::libXpertMassGui \inmodule MsXpS::libXpertMassGui \title libXpertMassGui Utilities */ libxpertmass-1.1.0/doc/images/000775 001750 001750 00000000000 14651507337 017463 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/doc/images/flying-frog.jpg000664 001750 001750 00000107451 14647465366 022432 0ustar00rusconirusconi000000 000000 JFIFddExifMM*C  !"$"$ $,"B!1"AQ2aq#BRb$3r%4CS?&ı=BI?a'uskq~\%| dmR9uRn5Uu,9'>hKuèhdr|N*22H%YH 5IN̓mqêlL*"1Drx_O?/xWrUGJ 'WeoV *nsΫ49s VshcK|dA$lz?(Ԥ~#Λ$ rc$qA=q7jJ[v$2emyOQ|k]AC%rN\V#6ާVVR K6Ye2!P:5E+OK̒d]%]؏CGI$/rN/RQ>F 5P1F+& 03qߍCAt4Ui`.?8}'<}D /q܌ho[ZTHAl@'lk?jㆢx)OWs!o1*<=zXvIՕ=#!*qa{$ҽJ=^*"0LPG$lA_|jΤʹ570l0 0}TEY#mAGV00q?aO$o J1 >G-yI@vJƓW#xPxj[ <@=O0NL5rq|`R5#K1ѓ9%+)Õ`ò$Lp F}2u ;dq)`7 z' v?~uxǏ^}5 zWrGa kg -]C$;TKbRBd c' sYS|ODxJ22Ӿr1i3>! SثȬ0وC8r䖢ii $q8Gf!e>S^r?ֻb#fZg=UBgr̅_/$c?gYlj׀[(AO'Vؤz=U?̒LLc9ϩVjh(++cS:FN0924&IUUi ehh$e;F9'CC:rbJ*;3\i<९UM?$Ay0 'KhK%ؠk}bY&i*)@* (W:kr^MQ Y1d])#T +Ite;{=/PZL!PO.y .Iш]qpV̌QA]2Qwjodh Ȟ+I>RcFpr1Ƶ>{4rBfv 0eW n+1pNee?I:hUz̐r?}r"fy{GNhiu(+3D=,uUsDΥi)e4 c<w:!CSW30MFX:HA$;a;w$څv07m}c=(!-%DRE0:&:@Fs >+#BYd@+gOM~bQ RM@V0HSqOtq4Yiϖ#*e uNo-u7ZMi]:-T ?P8##=T$QGZV$doVpx;܌UJLI,hFYr70<3#g+k 8#.EKO* v\`cmI%I^n_XХ2N(8C +V-`$E̥=9֥0R_4QM[YQ% oG i.U5& I&]u2H3{ Yάm䢴x$bf;V#[`w+rKq,:$(+>R?qR5N/54" 2*̠~N=޷G,N@7,ۉ${^~eN<5ZYo O:Y'ҽ3RJȣس2Un9tV+QA-\XiQ*T .q]]S-L9E%X_(Co#?S,yMУ+ a, rIڣJm5ʝF~^ac *yArI֓еU O-Ypj`]6dIY P6ksk\Q b[,%]PsA:7 g:Aq R'9s!T-iApuRdRҽ>U9t"teQ{p "B@ î I {U꿆j;qDv˱# >_b;rۅ-2O[S |\>7qYbK 20չ  $ǮsG\NS7K(C@60H;xu/CSvc\䂧̄d@whmQPKt)$Re$-8ƞbߗP:R3gG9Y)CS3#ӂFS έ[VfHa嗐T7m<|/X'սT^#(@Zyj͌0VX+UuۢVII]v;AԒp [06k`BtEO@=y婂:JWd7 ؇'${M_QYb)\OT>d m.WS\kjZO e#gF8'Г@:]IJH`a$T7o|yQr^R iK>`>i C$2KJ ._&r#>;,1M";Fd32I[v{!®%\c$Ԥmk*v 6I։Rߺ9i(婄G:x-=s- X3Xʜ Fv(@3ke]Ze* e@SNO 'Y Ңk|I2oUprcnpTy cauj'fC {F6ǠAḚTQCB<*w x-. {q4ϒ]8$2? Tw l (p*Ѹ)29W EdTTJjtuMʜh r{MSCQ5O#ű~YhQP2OR[eƎJg*ѿypNH?Oc+9$ aԳ UbˆCH8 5*R+ͶD\v9Xz, %DP Gv푪92P\( R9q'M\i †;@͞FA;jz˛[*H噌m#x&Fv9$FOs.Zי4 uTg|5&l򺯗o 5S3OU R;sǔg^|=DdJLMdѱ c:窒c*BӜϷR'ie@`[$ 8[XZddo?V8^N9gTT]oav3y;"VGqdD*aTP/=NN("{\"iDd6w'@RHhj0N>sOnuҮsJƒ7Hb8껟QS5:Q$0I̞cJ-r3o'G_.<;gAؑ^ꤢIZơ@ʃ)lzTNʩEI)MS]PI=q=3Ó=i_jfUC=D1s³FnxPuܠކmE9vGhAPYXq7W]cgX#bR I8B}8:bՉR}uiFe$vlypAAǦ@^) MNG$NA?:ȥw>G e|Fb97=Uu u79(ac)JrTF. T4UKOzm{|+ȕ48Xn| gVt(P)1uBYnw`v+^-WSIB;D{ PuW룎=י'ąddT c^s㾝vm=5b7#g GimtUV !b98 @~t ۣnE=E^$;(U GRG*4RIe#2@|z滧 I_YB%r Hڒ9:IKEYr7vCX&ꮤh)Q5r$1 O:sΦZm=',6uLX+ xB

it˄ТՙDV98Ǘӏ\U\ )Y]$*8/sɱWnX3F欬7@#89R[-Q 9YaBX189t[:M]+4L_`9%S䏷mZ$h!Erw:- -׸(;$P-eMF܄_#~rILQEY-JLt1̭MCw1:SuUIJ`jpM*p! |ǃ# WYq450[ikxV' #oeS /PGC!/pW0*4qǽ X2:/K2ΙY_NeӻW$`g[JiKQ[,  8νg$CXXgr䓞4>: \BRYFX9`FI r+pתz`^Bܪ@ _ ոQ%H=;l)Na3 'f k6AMHTe|A Ϙ1ʜce8p+EE4 wG'$iLYZzz*X%/f!'sy&@TSռ[eH9U0N8ǔ`]+qIFQ$<`Ild`uڲyx%i6RP9NGw$5hiYoEYeg׶Ӟ.~\m-d\JdI$U&ܹ^q!w g] -H:jʒnnr0x \^&pYXĈаW ;v}OWn!wJdc锣&{;Cp8<v: m%‡Er㣕_\p Ǘ ƫcd9w|9`W$H#BiG,7Z"Y $1\;:'6x{Դd+&Qݱ8{iD P7𥉉TCG, ,*3 >5Y^"Ibb,QX+$ ΢kTIZ囉"eb2bsέ]-ȢUw:sO$Uk:fju%sYL)qP0N7v?`*EZ搴Lš2Hϯ8:N32R}0Lz?8$YajKZN]Uik))\en9m; mZ]lvxaf@X%H}0GU PCFQ3ISö| #r m5CKpTTȊc(<9pϥ*1- 'U e>v#ђׇ`1VvX:U%r1=A^祥_!\1`/4$V+])m>`ʅS_tyfHŒUAJ\S‚J% /FŽKrF==SkjJFB>ed$I^Kg9ܵxv@( i[kd>]zLR'ebfSǶ}*ykkNhs鳔Z&#P)lIa? ,ԚSU'H<vIiW-W2C!El*1mw$N 8ݪ};j\u=59A$$d#c82 z0O Z\F$D| 89Yjk)M,s'IP.O#kW:lA"#2~85sb*iṘiq]qL0Vnڍ)Z9KF$o|UP$5He71l9ېdtVOCSd*ᚏnQ8dqcAӢ:ZyRU9|31P!8 x9q`WKRs!UM?N s gpIdt()嬊g^",7vcWbaxu &ƄIjs?aԔ՗{LVS8vUWJȐ H~9.GSQS!" eF@gcc螸kTm\Di FrۻEe4TXjl\+V2K2;cPXhnUtG$5URi$=OGn}gv^&jVVQSxP2m]zZYVTo5Ӳd8 ~܏QΫOSS=†Rc` ˕H'OP+㢞X$ S-P Dd(aO}qm?tqQCYIjM/hؼN09$HzKkJL*HڌHռ0NϲQKHaCm0G9OMb,drW(rA~O+AS=7OQcfbd+v\)'F#O\֫2Uq;naAn~wTSMA:%& I/Ӑ@ʞrj'5U,®|3( bs$ TYsS1V pr €s 8x&ìS$uX7-;rc8{ډ)-rӬ-9My ry :/j_R+K$a&p$eG-`2N֛ T*N YێlxqFn4UYjå_"U0%O N X*d HNrphۍ1+.&ESKnm9{= #Vv3#V= >g$1QR# cН8)8m}Z˅5*f EBWdCT W)y=Yj)A'* px`Qu=nSđ@&USQ$Gq5/A1{'K(&%Wcq9 8aO@a$U@UmF0?lcیq=UpxO]] #gUJwwD=1:)p }x|Pj&jA~y#f2 :N*R|@o6U/I5.v*qcۏmhk_W WQV|ݽH5St+PEOqS[ $fi`[wG3tm8kS)MK!GpQ#gA[zXE](/$(ԌwpΘZzVI%k),G)`qU$ ;Օ tCRwڹ*`3BYNH,XlN1A grh5k$"jI9#psF4A- 㟾@'}4jaa#*"f ArH>mL#6Rq$1yǦqgC(%sB(88n;~YdǵWUHB:A,09YT;/' 1 u-lzU.# sp΢xEvS2}J%VHXM0Ǐֵ 5wDP*TI#ߞ/GӰ_[/OZxRFXsr3[eXdrDlRs:ij0WWWXȒ1L: 'FahSF!iv3qh\ݣQ ǿM cLoA;i-tS8H<QV\m<A$Lf+yeUUW5@TD YK_Etd%E<򡑎H q8ELOruG+#M,4P?yruJ.y7Y6V]aO墊(*I!T`P;wƎ|<]:jt)aIru(J)$'ĮJx]jǍ eqߜ0Y~xoV%JNUUX*iG y$5YlU,R^Y<\1px֑җ:[U‰Y#@Hf`rl==Θz^+Li1q7$wf=|aGoCS4Ē`={c\gfiP,XAzgs[xgF%wFTPM|ŞJDZU OùCKWIE1éhr*  W)2NKJR*UA߱u-R{n{ g\](%p*2>MāF'> Sw_ShC`RNܒW0s8:+5䜃CSL3H<g[x6JZEYȬr~'34J h$j,9qt/mUM|#/q {zѴ[`S"IyŁ#q]_=/o O%$FL۰H8lqG3U%-=uG7: OT&RYd4m%HpG8 ?':_}0z+gSN\ 7sFL.mud:QXI?m.GDZEG146oO!) GrưVe ɢm8z߶ KCQ]Y^pH!sT1هs\:M,M# vqԕ eU Lr@^1uҊqEHz+7LZIO'%$N9>n@:mZb C6K09 ,ǑmK8d2a\/YW55mZ ?+n89#X5=M 枮d`Y=cOtWKe b3W*'Cw45A%$v8r?gg+E+L"8YSII.qM  r`~7Pݬ:OJ9$r$Rǘ@0F׷KSUZJr;er;;gp維WuwR\)Z;:h(VܻjcN;ui:ϰ5]5_dH~=qLJSZ TT ӴcpU qsv-t7RI}J9ת*먦H\Px&5 ږd(r@Y%%o‹LFZRJ bAPà 1Jbv0Ǯ=<}<{阻-ߏ 8{+L[[ѢZVRͼϦ탎4b3IdtuO?H9$FO? S(؝mtxJF><}4SjR@4vr>ӊk,&6&z[q8z(/;UAjXg# ǘyGE cPIfH 0I#wySl yp;O^庂h+SLE13van$qU} Y8w}6l56+(鞦3Un9=|{#\z"oH 0,n e 3ܑK$*>ndfDXw98qt'"н%#SʡYXwÂ+tQښҁdbr8#A:BZ^ c80qOM\# Y$lʇ,)QUG]U<C%"#py~{MHZwBH'?Q7~tWW[('le n 5u»LE]E/X G#)%:bJ:eiKQ㔅G``1s[b *ҼoM[p=F.O/Or7l9nX0 ~05bf@/ZVmϸ!G}0tr5ְ Nx.w vF\8EJZbA+KNd`ۃp0!A#4UbFB\}8d]G? P +%su1!pH0A WAiRKW2~Cv~qq~{lYn#45YTD󣁴=3+!3x@/JcwG遮::fxT|\ vt;k*:TQRݎ1ƙ2m Pt$I O;{8|uGbr=:I1]ބ`q{ҕ}bAoqSTȃpO>\gZ} zK)V%flyvS\/5xǶa\`:DD0T\)R@8J'I,烀EmԴBƔ;E{d6M< pUUU"VRq%}y={ Q^ %#GGlz~XMVk dk:Γ@Fe!ru|BЧĈ!⚢8*Q*5.<p}}ޞnT >2J22yz;oL^]O70t@&Ф`#d >\wiIʉT#o{S?^#dCpvvfZAI݁߱4{]Smv)n8xz:1"ʫFY99pN5:"rwlmRWu=Lrgvce,T4-KRą.過r@8>j{GGu G,v'_Iњ,Y@NqqYϨM4 дw8||h/F MMQ R4{vcN8aAU[-w*C)$O9'| '-zj' q{BVt\b F@88Ch\hk7a p9lU\H vR 2QppN squ7Qi#2(U*97MO;ѫLDO+ op39ƺMN)>r+$;%-0PQǡ?YGluFF"O]-IhT,52<_C43k*[ŢJ <&n!~O9 v:i^@ȱ298'oN|?|][8 ߌSu,`(n Z3,09mNёns4ω5 . ɽT U3`Is}3ti *?\g'!#V/^% H)E zIHﵛiHurM= (BB8,88$ijkd%r,v܂۸8T:|lRTxPI qOqQӽx tQoXǯ֏>:mh˩|bHzHLY[]?/ie5vp+Sg7R06?'`݀o7-WjD<:xx“;[+:h/s0V~0ܓ5Ӫ,ݚoZ^)6(=DZGzj)cyYSpW>΢D"ŽK82HN~5Y;Ouڂq%Xmv\Iq4Fʨ*_ۏoN /l/XеҮ[!9#r22{@Z gn7'$}ΆX>{BS[%T|6>0w؞ĩIdwHp 2p 8myj|ZtcL! eO/K%YFc As1UZd\SԵʲ h`Mrw'<7MUuA^?QS-\8#. p4wyz[m{!EzV0nf“a'YeXka]BA'=v OYq48&+b8h4tUR[dH*X+gx[ۑ7VKLԴ2xy{{>K4%71 8?ޙ4? Ҫzb#!ܞg@=mZ꟪4I)hj|g)? žΖU4#GH|l<)瑒}\Ru1*JH\Ű{S#Kaz*ڦ4 V902'61%W&G1rU!Gn2sOz~C5cpI{zUWQDL>}8:-tV!ڋ&x܅"bX^> RvSE]+U!M\ eGv 1(V&$)Ò YwX 8lKqZ֍PGQr8BQ#c:ey6[mIշ`7rN+!#qL.|A-,{N4ӹ'0t[NfSEi$O >5uq 5(qldیc?{ v"dU9 9mznu:  Hy@wx {k6>PKQ4G]W$crđmݶsA=[d~f1J0`{MªiHxQ,3^m-#` (;8úIʔӬ1UtX Sh+ "(ʶQCgذ麎AOc$?bQ9f{ YvJYQ:o &VwbG FO8>¸hH(PPrʿ[}sžMODbIbU')a%RG&G>19jZJUAH9`?Hk(4eE|#n;]_c z4E]W,IOpNT?SEO?P4SZu4{a.I;HR}EEsImfvU)ܩFS ps9":>/s+hĝ3d nw :KAD:JՉ*!n\Fy؃Vi#4D@n=Fяh};'0C3Bx;b':u+^%V81伌:v9FWh_t6f1_K}%fی;%<1DNU]mUʂIOWૻVoDJ9RIcJR 23H])ac+b{GiԿWVxhcH[pBXD&:ۃp&y;ԯ;wq1;BzrK)QwΑZJ$X=V+mzU!& dr/m#\t3ENM4#K;N90;q^vHV2*@Q.9{ji-}EP'ST7)}{zˤ/ݺU0߂N8#ۃCErJu.Hy9}Nt? ~Aֹ˟Yd}[ږ+OCt4t[bu;| q6\>DӳK6I>Q:tж2/' ʾwzs ݅?LZ*>Lf(PγhVi"JݥI!@TUn%mUܬ<ʋX_F  =sQ[mԐCWTQO^*"jtFUٛ !QxlWuUO"U&W y+Fxljm5UC'S"~T} *8:_„=KC"MW5+3"ЧlϮ# 8Y/ƚ:J[[S%;#88Wv,8Ԍ1-9,Nm {h̑HJGYTFOoƕ:V.x <=ĀpsYQdRN? (]Km (gLK]TS-4=;F7%؞Ar6ޣtv:XjǑ>^zF-$13#.233ƧuLJ!*qXR1~qBT ڮ2Ξ2I'ۿRd_1P1BvqsdZk:Ji#˸eY}{*Tu4]1C>j[G!e=X.yg+]m :tA"9K [yǡރYiU v2тFq]g%a/,U7H =rZ斦POMnjV!jH驮4 lK,c9' 8׶mEWei VO 7#7Ic:hDTiE[fls#oh(BX"R7]rutǕlxPi5 qs=Ƅ_?%=m¡5$XIǮ[` CeiuERqс <9fsm Lxl,y@_4  XR02qIyJn-$,άQbW"^`7ޠU_ǹ$&# `>^05$-fL7;}ҮZڥD;2a\! Ā{ë_LQov(wjkj*a6`ĨIt˛d?)ѵ8n2HΉ9>O%o^j HiȪO2[9]-T74v vva YRj  ʕڻ!s$;Vl`~o\ddk PFԒL8 n2G2q Jrz6sZ<$G%} 3κRCIMR~l_0@R9''\4#UnP!!P{r l:dꊊY&35@ 8ףz0[ld,Qym$6ߚ8gTCoUv  9U-:Gqd|0<㓑2sM7{ek=dT1x(I9d{gxڝjI`Jx{v`1^9ƨxng暸Č;G*yB8'jҙ*+*HvQk?;uָA--ԥ:GHbROMޓj-AM"*54L+'@ف4O֕R|Q_, )$󃹰ǒQ\i,:0 ,Ckli GbV^.bĞb Ȳg1NBQ+Y~+z+j"jYUdj2r ސ{kMG?B&07l;kE-Ss2{M]RcO$jH%P'= jLRdvc12;jtO1`BN?===G_]QS_MS=L[z}d`c+f({\nsgCI@RrǩIT*+~~j4q#2{}JQCJnwCO RTfa9hm$*[`-[9~ڳ)Y p8i~Z <4Wjqk!램ZM6|/,=}5UUz @|8suBUq*%HTI$fRH˸"d=?n5n%A Ed~NɑryOa>hje1U#!C< {?r~9z$BƞdiU9_0 `=RU%)LWa&Դӈ'@e d;2Q:O,ׂT򃴂 e8`h]-K4Tӵ4JUHa*Pۏ AzgɼVWȣFĊb0#{ی o]EԴQQ2 *r8/l+igMJCL7܁GwV-$F8Տfb.Q*I@ɥYKYM=3 f])g}<)s8=SVt9$`MؑQ`@Nhk}<($*Ǜ[YEJ6j\G a9‚8/ (.^iXTȻ gqf%?O Ղb78LK(?8aߎ:~*Ji.P\@HD?H& *>bhBt5EZu;@!o8@A My-:FRz)ħx*Υ8Gm$4*dl{ ڪh/R$g>qKGweM`;SJ*İ#npb1#ŽIԆZB<$J*yz-UrfI!jՎBgV$HI$18_lƪ0;GP)9ӑ1;ntpW̵g2@eQkc ƄB\)1G4pwDG.$dA*?#Wm=:W91lJ#jI-SRS4Egu_H3,mݛۃqNudEGl O8`SО]qjiE2^2; KWDAj"2'D*d@AN7ysqG5<.2*HWK,wl !ۥx9MFeh8pO=:ήVJC=}tIC$aM݀}`Lh[}e4WV8S[g367g )}4AlWWzX#p6֫ru2Ix vV]w鎟ZqWIr9C驚黵5Ʒ4ȊX(J9@βmKT=!))Hn gifΓR\`o&InwO m {5d7RT^Pq#t}u,t(PqDž1n=>Z&v 4={K+UD`ȥG-{$8#Pc6+31qF;:~ڪmZt*W$x|͕PUmMTw*g`-Ix8:+njX̅Qјʎw OzLvTnD8)郇9jz 6ͭ(TmO~}1I6#0,p=#h_QS?Oc\qp?$~~ihꨨ,)Ձ,MI b" 1HlkGIswfYcF8\cƪAФjk R|>k"4ԡ#ҿBr8'{}訠*aꭆ#' ׮c=Dptp6Gd#KޱI)$Um3$ HOnAIi[lU6䩹FSI9R=kU*|ʊ5lFh9#ՇoWd1ykk$G4nYK)qqkk\uRvZE< *y Gm5[ME=&YV < sK`;zgݒgH$[ZfEP2(c= v@۶8$OTPGQl@FC`c׸ҖIKKWyI7UM6Fq$8:\u{L]7K/"^RFW?N}x_V-U{үIEHOr=F~! }5MvnO:m|QQ?NZxA8WheH$(R@xO MyHe&;;(Y@ }iKMHVE[v$DߠX/tO$*{d4o߾^~ ZmQ `Q} ҝStqӥ‹yZ=v?~u/Z Q|P[nΫܦk#ıM4q![G-}i䤏Q)!{`[$񭆦Ocǐvۖ.%FRᇆvu֚TG:(>~1롕)IpѶ<BcVJ$tcǑp9'V)lSOs-*X\qb^y$yZM }HE)hy`#4{N  tVvXcȿH&C8$z)o @hť'hnb('pI<>'L1YތJ`3; Qei%v>})?N5c5:vb&tZU9кYRcIg u7+mtr̹V x8aL|6hZI,]AW䤯sQDcHGWkPiV1ZAP[k _ÍYV)@N2Wq"fGãTp-;;䌁OG D^$1Qdw: Cн9GKW F9=Xm'EXJ*(].I$nO·IiK|#*9;-&y8!YAQZB3ߟJ<7Uẗ '_?lhkV7J u"%i}1)E9+ap?Զω:ojxc˰c`x>>nGANG4ew>V>EsJXPh:hjɪoVϗPLJ}҄1!'$ c[ʾqܦFsMq[%cq q'ڝdF#u®yY+Jc-=%@59|}*1edX॓枪AϧsH&6 v%XVWZXt"2F;Ft%-Tz+=[x@\o_mCUv ޖ"R}3SRSHGjJPI-9԰uߨnl&2 pg;F>i:[}\K4~^R{1õQhW0L Bp d)8]&=t O造0Sz֎TѠcGR8ήtuR,_rx鑤ZY.ʠp#VE%GRSR_j5I1@*LUrA OWɕ@j]k}H ]$nF9hGuZWHhd woeO)z-T/8 {<p1`DNWD $θ늬$AVf.DMxHq΄WnV\*\>ڄq,}rE#A&Xnolzj:0hey鲕jkIՕ&T8*}Gn!a$zX'hG$Ϯ=L-D4Ƞsx;?wDY,Ƥ2yM֋Qz H|(0 gxtm:vBI`'y9[?Idn'-w k re:[q?nuG'agSi`5$;ƺ"{QDSI)%itAWY]$9 d'a.Go}2P|JԫG=:դOTѽ-LR(7.3"/7]P'.Qh)oFqk8[F] &O8=mK2OAov%??w|Z֎*',pmuKAT 19VЍ*eQ]PÌ!X)UlMe]2 -ļRÃ:ծ6XzcJxXĒ$fDy\g$ L+*Ju b$FܒvyƏ߫)mveVoDx,U p?̺ 4S<`v~,Z&9d>hORP]<pن9A=~'%kVv;ch LITdi܍= =sHwGe"Xw184%I~6\sr:="-ev I#`cۑ\v](0xӿmEGzb(nD,˲gO­F RD>]j*KUm\X盓#-}qmI,}K@Tḥ(VیNO?sR+D$<'=9@Ni*EX79h:FU*Å 9,X=MQ$MIAnq#b}~uDT('s%@Tr4bZ lMÿ'[AQ=(hRǷ M}IpU*H?mv+`rs3 sc_cQa>xf4~܃CtjX $zJr#x+I!$m'^ݏlhL!2:"ZsĪxܯ TCxejA7|#8'5w$JCXf1 oPqu]znQ$XabN΃ -#cӌzzj)j5Y]S&H;R.ݲޘψǦ/mYC_l2ò5:ߨq]PO F,7R1wLyB| RBʤ" \>.ˊ-SL.ϣm˽8YA"eiG<ߝS; KD|.cU6sQ,TjʪlǠOMf:~yڶ[Pe|HN N~\Sxe$hزV;rV幬Pm^ b*DH1% j}TBȞBlsQ%=<N$>覆Mͨ2eg O}sv.i$jb6db]H:$]kVxH͌(w#X5Z*#R!Xik_NVW֠("8yݏ!9LH!)dN{sI<UJZx&G};=yφm5JL),TeY˶qL_t\Q l?X ૴N{I 6zNL!J>mXB85Cq鮌C}OqۓSuHsp {sθݸ1}| 㟾`70s\.<}odkdvGP\eU)sMv {ID.(Y&/neY}ՈJ-Q\mίC(τ?ن u_,N8f%@T6:jwZl29@c'êFD p89x*N ,L#>Y$sVV; x$}[ʍH'G()U-4P) V! .͑u!Ip}5G$qCmԓG9Ա@tPj=yUPXc z" 8+'ιu񮑪r`uT2)>xN$2.GUHﯤ%|8ǘcqƦFÎ9=:GpYsBȦR1?<x$Qoquc0F_m ~5Ċv(F9G\T0p;jb86cE`h1Flx*QW,ߍ}P8kD؃h!:% v`jA q])c?libxpertmass-1.1.0/doc/libXpertMass.h000664 001750 001750 00000002325 14647465366 021020 0ustar00rusconirusconi000000 000000 #include "Prop.hpp" #include "PropListHolder.hpp" #include "globals.hpp" #include "Isotope.hpp" #include "IsotopicData.hpp" #include "IsotopicDataBaseHandler.hpp" #include "IsotopicDataLibraryHandler.hpp" #include "IsotopicDataUserConfigHandler.hpp" #include "IsotopicDataManualConfigHandler.hpp" #include "Ponderable.hpp" #include "Formula.hpp" #include "ChemicalGroup.hpp" #include "ChemicalGroupRule.hpp" #include "PkaPhPi.hpp" #include "Monomer.hpp" #include "MonomerSpec.hpp" #include "Modif.hpp" #include "ModifSpec.hpp" #include "Polymer.hpp" #include "Oligomer.hpp" #include "Sequence.hpp" #include "Coordinates.hpp" #include "CalcOptions.hpp" #include "IonizeRule.hpp" #include "Ionizable.hpp" #include "CrossLinker.hpp" #include "CrossLinkerSpec.hpp" #include "CrossLink.hpp" #include "CleaveMotif.hpp" #include "CleaveRule.hpp" #include "CleaveSpec.hpp" #include "FragRule.hpp" #include "FragSpec.hpp" #include "CalcOptions.hpp" #include "MassPeakShaperConfig.hpp" #include "MassPeakShaper.hpp" #include "IsotopicClusterShaper.hpp" #include "IsotopicClusterGenerator.hpp" #include "PolChemDef.hpp" #include "PolChemDefEntity.hpp" #include "MassDataClient.hpp" #include "MassDataServer.hpp" #include "MassDataServerThread.hpp" libxpertmass-1.1.0/doc/libXpertMassGui.h000664 001750 001750 00000000610 14647465366 021460 0ustar00rusconirusconi000000 000000 #include "exportimportconfig.h" #include "ColorSelector.hpp" #include "IsotopicClusterGeneratorDlg.hpp" #include "IsotopicClusterGeneratorDlg.hpp" #include "IsotopicClusterShaperDlg.hpp" #include "IsotopicDataTableView.hpp" #include "IsotopicDataTableViewModel.hpp" #include "MassDataClientServerConfigDlg.hpp" #include "MassPeakShaperConfigDlg.hpp" #include "MassPeakShaperConfigWidget.hpp" libxpertmass-1.1.0/doc/main-page.qdoc000664 001750 001750 00000000463 14647465366 020741 0ustar00rusconirusconi000000 000000 /*! \title \page index.html This developer documentation to the MsXpertSuite libraries contains the following: \list \li \l{MS Xpert Suite Module}: The main namespace \li \l{Mass Library Module}: The namespace for non-GUI classes \li \l{Gui Mass Library Module}: The namespace for GUI classes \endlist */ libxpertmass-1.1.0/doc/modules/000775 001750 001750 00000000000 14651507337 017666 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/doc/modules/MsXps-module.qdoc000664 001750 001750 00000001131 14647465366 023101 0ustar00rusconirusconi000000 000000 /*! \module MsXpS \title MS Xpert Suite Module \brief the MS Xpert Suite module is the main module (with namespace MsXpS). The MS Xpert Suite module is the main module (with namespace MsXpS) that contains two modules with namespaces MsXpS::libXpertMass and MsXpS::libXpertMassGui for non-GUI and GUI classes, respectively: \list \li \l{Mass Library Module}: The namespace for non-GUI classes \li \l{Gui Mass Library Module}: The namespace for GUI classes \endlist */ /*! \namespace MsXpS \inmodule MsXpertSuite Shared Libraries \brief Contains libXpertMass and libXpertMassGui documentation. */ libxpertmass-1.1.0/doc/modules/libXpertMass-module.qdoc000664 001750 001750 00000002145 14647465366 024452 0ustar00rusconirusconi000000 000000 /*! \module libXpertMass \inmodule MsXpS \title Mass Library Module \brief Contains non-GUI classes for the modelling of linear polymer chemistries. The libXpertMass module provides classes to model all the chemical entities required to fully characterize the behaviour of polymer chemistries both in solution and in the gas phase. It offers both high-level classes such as Polymer and low-level classes such as Isotope. Other classes define how polymers of a given polymer chemistry might be modified, either in solution or in the gas phase (cleavages or fragmentations). \list \li \l{Global variables, enums and functions}, \li \l{The property system}, \li \l{Polymer chemistry definition}, \li \l{Polymer chemistry building blocks}, \li \l{Aqueous chemical reactions}, \li \l{Gas-phase chemical reactions}, \li \l{libXpertMass Mass calculations}: configuration of mass calculations,\ isotopic cluster calculations, peak shaping functions, \li \l{libXpertMass Utilities}: Network-related classes. \endlist */ /*! \namespace MsXpS::libXpertMass \inmodule MsXpS \brief Contains libXpertMass documentation. */ libxpertmass-1.1.0/doc/modules/libXpertMassGui-module.qdoc000664 001750 001750 00000001101 14647465366 025106 0ustar00rusconirusconi000000 000000 /*! \module libXpertMassGui \inmodule MsXpS \title Gui Mass Library Module \brief Contains GUI classes. The libXpertMassGui module provides GUI classes for isotopic cluster calculations, automatic selection/deselection of colors for plots, network configuration. \list \li \l{Color selector}, \li \l{libXpertMassGui Mass calculations}: isotopic cluster calculations, peak shaping functions, \li \l{libXpertMassGui Utilities}: Network-related classes. \endlist */ /*! \namespace MsXpS::libXpertMassGui \inmodule MsXpS \brief Contains libXpertMassGui documentation. */ libxpertmass-1.1.0/doc/developer-documentation.qdocconf000664 001750 001750 00000007501 14650422160 024560 0ustar00rusconirusconi000000 000000 # QDoc is a tool that constantly evolves and there may be compatibility issues # between old and new practices. For that reason, QDoc configuration files in # the Qt Project includes compat.qdocconf: include(/usr/share/qt6/doc/global/compat.qdocconf) # Give the documentation project a title: project = MsXpertSuite Shared Libraries moduleheader = developer-documentation.h # Pass additional include paths to QDoc when parsing C++ code for documentation # comments. # includepaths += -I/some/path includepaths += -I./ \ -I../includes/ \ -I../includes/pappsomspp \ -I../src/XpertMass/includes/libXpertMass \ -I../src/XpertMassGui/includes/libXpertMassGui \ -I/usr/lib/llvm-16/lib/clang/16/include \ -I/usr/lib/gcc/x86_64-linux-gnu/13/include \ -I/usr/include/c++/13 \ -I/usr/include /usr/include/linux \ -I/usr/include/x86_64-linux-gnu/qt6/ \ -I/usr/include/x86_64-linux-gnu/qt6/QtCore \ -I/usr/include/x86_64-linux-gnu/qt6/QtNetwork \ -I/usr/include/x86_64-linux-gnu/qt6/QtSvg \ -I/usr/include/x86_64-linux-gnu/qt6/QtXml # QDoc needs a lists of file extensions to know which files to process in # different situations. Uncomment the following include statement to get # a pre-defined list of file extensions. #include(fileextensions.qdocconf) # You can also specify file extensions manually. headers.fileextensions = "*.h *.hpp" sources.fileextensions = "*.cpp *.qml *.qdoc" # The outputdir variable specifies the directory where QDoc places the # generated documentation. outputdir = public # What format to use for generating documentation outputformats = HTML # The headerdirs variable specifies the directories that contain the header # files associated with the .cpp source files used in the documentation. headerdirs = ../src/XpertMass/includes/libXpertMass \ ../src/XpertMassGui/includes/libXpertMassGui # The sourcedirs variable specifies the directories that contain the .cpp or # .qdoc files used in the documentation. sourcedirs = ./ \ ./../src/XpertMass \ ./../src/XpertMassGui depends = qtdoc \ qtcore \ qtgui # apt install qt6-documentation-tools (for /usr/lib/qt6/bin/qdoc) # apt install qt6-base-doc-html for the qt.index files QT_INSTALL_DOCS = /usr/share/qt6/doc/ indexes = \ $QT_INSTALL_DOCS/qtcore/qtcore.index \ $QT_INSTALL_DOCS/qtgui/qtgui.index # The exampledirs variable specifies the directories that contain the source # code of the example files. # exampledirs = ./examples # The imagedirs variable specifies the directories that contain images used in # the documentation. imagedirs = ./images # Path to the CSS files if you would like to use HTML.stylesheets = /usr/share/qt6/doc/qtgui/style/offline.css # The string that actually includes the CSS into documentation file HTML.headerstyles = "\n" # What to append to every page after header HTML.postheader = "" \ "" \ "" \ "" \ "
" \ "" \ "" \ "" \ "libXpertMass Developer Documentation" \ "
" \ "
" # What to append to every page after the content HTML.footer = "


Filippo Rusconi

" # Set a warning limit. QDoc will exit with a non-zero exit code if it generates # documentation warnings during the documentation build. Useful for tracking # down documentation issues. #warninglimit = 0 #warninglimit.enabled = true libxpertmass-1.1.0/images/000775 001750 001750 00000000000 14651507337 016716 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/images/svg/000775 001750 001750 00000000000 14651507337 017515 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/images/svg/add-isotope.svg000664 001750 001750 00000004473 14647465366 022470 0ustar00rusconirusconi000000 000000 image/svg+xml libxpertmass-1.1.0/images/svg/remove-chemical-element.svg000664 001750 001750 00000004510 14647465366 024737 0ustar00rusconirusconi000000 000000 image/svg+xml libxpertmass-1.1.0/images/svg/remove-isotope.svg000664 001750 001750 00000004413 14647465366 023227 0ustar00rusconirusconi000000 000000 image/svg+xml libxpertmass-1.1.0/src/000775 001750 001750 00000000000 14651507337 016240 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/src/XpertMass/000775 001750 001750 00000000000 14651507337 020166 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/src/XpertMass/Averagine.cpp000664 001750 001750 00000011750 14647465366 022611 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2024 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #include #include "Averagine.hpp" namespace MsXpS { namespace libXpertMass { Averagine::Averagine(IsotopicDataCstSPtr isotopic_data_csp) : mcsp_isotopicData(isotopic_data_csp) { setupDefaultConfiguration(); } Averagine::Averagine(const Averagine &other) : mcsp_isotopicData(other.mcsp_isotopicData), m_avg(other.m_avg), m_symbolContentMap(other.m_symbolContentMap) { } Averagine::~Averagine() { } Averagine & Averagine::operator=(const Averagine &other) { if(&other == this) return *this; mcsp_isotopicData = other.mcsp_isotopicData; m_avg = other.m_avg; m_symbolContentMap = other.m_symbolContentMap; return *this; } void Averagine::setIsotopicData(IsotopicDataCstSPtr isotopic_data_csp) { assert(isotopic_data_csp != nullptr && isotopic_data_csp.get() != nullptr); mcsp_isotopicData = isotopic_data_csp; if(!validate()) qFatal("The Averagine validation failed after setting new isotopic data."); computeAvgMass(); } void Averagine::setContent(QString &symbol, double content) { m_symbolContentMap[symbol] = content; if(!validate()) qFatal("The Averagine validation failed after setting new symbol content."); computeAvgMass(); } double Averagine::getContent(QString &symbol) const { auto iter = m_symbolContentMap.find(symbol); assert(iter != m_symbolContentMap.end()); return iter->second; } void Averagine::setFormula(const QString formula_string) { Formula formula(formula_string); // The formula is asked to validate with storage of the found symbol/count // pairs and with resetting of the previous contents of the symbol/count map. if(!formula.validate(mcsp_isotopicData, true, true)) qFatal("The formula passed as argument did not validate."); m_formula = formula; } double Averagine::getAvgMass() const { return m_avg; } double Averagine::computeFormulaAveragineEquivalents(const QString &formula_string) { // If something is wrong, qFatal(). if(!formula_string.isEmpty()) setFormula(formula_string); // At this point, we do have m_formula fine and that has been validated. double formula_average_mass = 0; m_formula.accountMasses(mcsp_isotopicData, &formula_average_mass); // There are many checks in the code that m_avg is not 0. double averagine_equivs = formula_average_mass / m_avg; return averagine_equivs; } double Averagine::computeFormulaMonoMass(const QString &formula_string) { // If something is wrong, qFatal(). double averagine_equivs = computeFormulaAveragineEquivalents(formula_string); double mono_mass = 0; for(std::pair pair : m_symbolContentMap) { bool ok = false; mono_mass += mcsp_isotopicData->getMonoMassBySymbol(pair.first, &ok) * averagine_equivs; if(!ok) qFatal("Failed to get mono mass for symbol."); } return mono_mass; } bool Averagine::validate() { if(!m_avg) qFatal("It is not possible that m_avg of Averagine be 0."); for(std::pair pair : m_symbolContentMap) { if(!mcsp_isotopicData->containsSymbol(pair.first)) return false; } return true; } void Averagine::setupDefaultConfiguration() { m_symbolContentMap["C"] = 4.9384; m_symbolContentMap["H"] = 7.7583; m_symbolContentMap["N"] = 1.3577; m_symbolContentMap["O"] = 1.4773; m_symbolContentMap["S"] = 0.0417; computeAvgMass(); } double Averagine::computeAvgMass() { for(std::pair pair : m_symbolContentMap) { bool ok = false; m_avg += mcsp_isotopicData->getAvgMassBySymbol(pair.first, &ok) * pair.second; if(!ok) qFatal("Failed to get mono mass for symbol."); } if(!m_avg) qFatal("It is not possible that m_avg of Averagine be 0."); return m_avg; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/CMakeLists.txt000664 001750 001750 00000012054 14647465366 022742 0ustar00rusconirusconi000000 000000 set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES /usr/include) set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES /usr/include) # Whatever the platform, this always works. find_package(Qt6 COMPONENTS Core Svg Xml Network REQUIRED) message("\n${BoldGreen}Now configuring library libXpertMass${ColourReset}\n") ######################################################## # Files set(XpertMass_SRCS # # Most general features globals.cpp Prop.cpp PropListHolder.cpp # # The basic chemical features Isotope.cpp IsotopicData.cpp IsotopicDataBaseHandler.cpp IsotopicDataLibraryHandler.cpp IsotopicDataUserConfigHandler.cpp IsotopicDataManualConfigHandler.cpp IsotopicClusterGenerator.cpp IsotopicClusterShaper.cpp PeakCentroid.cpp Ponderable.cpp ChemicalGroup.cpp ChemicalGroupRule.cpp Modif.cpp ModifSpec.cpp Monomer.cpp MonomerDictionary.cpp MonomerSpec.cpp Oligomer.cpp Polymer.cpp CleaveMotif.cpp CleaveRule.cpp CleaveSpec.cpp Coordinates.cpp Formula.cpp FragRule.cpp FragSpec.cpp Ionizable.cpp IonizeRule.cpp CrossLink.cpp CrossLinker.cpp CrossLinkerSpec.cpp PolChemDef.cpp PolChemDefEntity.cpp PolChemDefSpec.cpp PkaPhPi.cpp PkaPhPiDataParser.cpp Sequence.cpp # # The calculation features CalcOptions.cpp MassPeakShaper.cpp MassPeakShaperConfig.cpp # # Envemind implementation Envemind.cpp # # The Averagine implementation Averagine.cpp # # The network functionality MassDataCborBaseHandler.cpp MassDataCborMassSpectrumHandler.cpp MassDataServer.cpp MassDataServerThread.cpp MassDataClient.cpp ) # Because the header files are in their own directory and not along the source # files, we need to have them listed explicitely for automoc to work properly # below. # Create a variable to hold the 'includes' directory *relative* to the # current CMakeLists.txt file. set(INCLUDES_DIR "${CMAKE_CURRENT_LIST_DIR}/includes/libXpertMass") file(GLOB XpertMass_HEADERS ${INCLUDES_DIR}/*.hpp) list(APPEND XpertMass_HEADERS ${INCLUDES_DIR}/exportimportconfig.h) message(STATUS "Included the header files from ${INCLUDES_DIR}: \n\ ${XpertMass_HEADERS}") # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) add_library(compiler_flags INTERFACE) target_compile_features(compiler_flags INTERFACE cxx_std_11) ############################################################### # Configuration of the binary to be built ############################################################### # Only now can we add the library, because we have defined the sources. ############################################################# # Build the static lib add_library(Core_static STATIC ${XpertMass_HEADERS} ${XpertMass_SRCS} ${PLATFORM_SPECIFIC_SOURCES}) set_target_properties(Core_static PROPERTIES OUTPUT_NAME XpertMass LINK_FLAGS "-Wl,--whole-archive") target_link_libraries(Core_static PappsoMSpp::Core IsoSpec++::IsoSpec++ Qt6::Core Qt6::Xml Qt6::Network) # The install interface that is ${prefix}/include # These include directories are for users of this lib. target_include_directories(Core_static PUBLIC $) # These include directories are for building of this lib. target_include_directories(Core_static PUBLIC $) get_target_property(Core_static_INCLUDES Core_static INCLUDE_DIRECTORIES) message(STATUS "Core_static_INCLUDES: \ ${Core_static_INCLUDES}") install(TARGETS Core_static ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) ############################################################# # Build the shared lib add_library(Core SHARED ${XpertMass_HEADERS} ${XpertMass_SRCS} ${PLATFORM_SPECIFIC_SOURCES}) set_target_properties(Core PROPERTIES OUTPUT_NAME XpertMass VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} LINK_FLAGS "-Wl,--no-as-needed" POSITION_INDEPENDENT_CODE ON) target_link_libraries(Core PappsoMSpp::Core IsoSpec++::IsoSpec++ Qt6::Core Qt6::Xml Qt6::Network ) # The install interface that is ${prefix}/include # These include directories are for users of this lib. target_include_directories(Core PUBLIC $) # These include directories are for building of this lib. target_include_directories(Core PUBLIC $) get_target_property(Core_INCLUDES Core INCLUDE_DIRECTORIES) message(STATUS "Core_INCLUDES: \ ${Core_INCLUDES}") target_compile_definitions(Core PRIVATE "EXPORT_LIB_SYMBOLS") # This is to avoid the "include_next(math.h) file not found" error. if(WIN32) set_target_properties(Core_static PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) set_target_properties(Core PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) endif() ############################################################# # Common installation configuration install(FILES ${XpertMass_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libXpertMass") message("") message(STATUS "${BoldGreen}Finished configuration of libXpertMass.${ColourReset}") message("") libxpertmass-1.1.0/src/XpertMass/CalcOptions.cpp000664 001750 001750 00000021606 14647465366 023127 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include #include #include /////////////////////// Local includes #include "CalcOptions.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::CalcOptions \inmodule libXpertMass \ingroup XpertMassMassCalculations \inheaderfile CalcOptions.hpp \brief The CalcOptions class provides the specifications that configure the way masses are calculated for \l{Oligomer}s, \l{Polymer}s and product ions. */ /*! \variable MsXpS::libXpertMass::CalcOptions::m_deepCalculation \brief Tells if the calculations must involve the recalculation of all the masses of the \l{Monomer}s in the sequence. */ /*! \variable MsXpS::libXpertMass::CalcOptions::m_coordinateList \brief The list of Coordinates. */ /*! \variable MsXpS::libXpertMass::CalcOptions::m_massType \brief The mass type, monoisotopic or average to compute. \sa MassType */ /*! \variable MsXpS::libXpertMass::CalcOptions::m_capping \brief The cap type, left or right to account for in the calculations. \sa CapType */ /*! \variable MsXpS::libXpertMass::CalcOptions::m_monomerEntities \brief The \l Monomer entities to account for in the calculations. \sa MonomerChemEnt */ /*! \variable MsXpS::libXpertMass::CalcOptions::m_polymerEntities \brief The \l Polymer entities to account for in the calculations. \sa PolymerChemEnt */ /*! \variable MsXpS::libXpertMass::CalcOptions::m_selectionType \brief The manner the monomers need to be accounted for: as residual chains or as finished-polymerization state \l{Oligomer}s. The calculations might consider only the residual chain. In that case, only the mass of the monomers is considered and then the polymer is not in its finished polymerization state. If that latter state is required, then, the residual chain must be capped with the left end cap and the right end cap. \sa MsXpS::libXpertMass::SelectionType */ /*! \brief Constructs a CalcOptions instance. */ CalcOptions::CalcOptions() { m_deepCalculation = false; m_massType = MassType::MASS_BOTH; m_capping = CAP_BOTH; m_monomerEntities = MONOMER_CHEMENT_NONE; m_polymerEntities = POLYMER_CHEMENT_NONE; } /*! \brief Constructs a CalcOptions instance. All the members are initialized using the parameters: \a deepCalculation: m_deepCalculation \a massType: m_massType \a capping: m_capping \a monomerEntities: m_monomerEntities \a polymerEntities: m_polymerEntities */ CalcOptions::CalcOptions(bool deepCalculation, int massType, int capping, int monomerEntities, int polymerEntities) : m_deepCalculation(deepCalculation), m_massType(massType), m_capping(capping), m_monomerEntities(monomerEntities), m_polymerEntities(polymerEntities) { } /*! \brief Construct a CalcOptions instance as a copy of \a other. */ CalcOptions::CalcOptions(const CalcOptions &other) : m_deepCalculation(other.m_deepCalculation), m_coordinateList(other.m_coordinateList), m_massType(other.m_massType), m_capping(other.m_capping), m_monomerEntities(other.m_monomerEntities), m_polymerEntities(other.m_polymerEntities), m_selectionType(other.m_selectionType) { } /*! \brief Destructs this CalcOptions object. All the Coordinates in the member CoordinateList are deleted. */ CalcOptions::~CalcOptions() { // Free all the coordinates from the list. while(!m_coordinateList.isEmpty()) delete(m_coordinateList.takeFirst()); } /*! \brief Assigns \a other to this CalcOptions instance. The copy is deep, with all the Coordinates in the \a{other}'s CoordinateList being copied to this CalcOptions instance. Returns a reference to this CalcOptions object. */ CalcOptions & CalcOptions::operator=(const CalcOptions &other) { if(&other == this) return *this; m_deepCalculation = other.m_deepCalculation; m_massType = other.m_massType; m_capping = other.m_capping; m_monomerEntities = other.m_monomerEntities; m_polymerEntities = other.m_polymerEntities; m_coordinateList.empty(); setCoordinateList(other.m_coordinateList); setSelectionType(other.m_selectionType); return *this; } /*! \brief Sets to \a deep the configuration defining if the mass calculations must involve the recalculation of the masses of all the monomers. \sa m_deepCalculation */ void CalcOptions::setDeepCalculation(bool deep) { m_deepCalculation = deep; } //! Returns if the calculation is deep. /*! \brief Returns if the calculation should be deep. \sa m_deepCalculation */ bool CalcOptions::isDeepCalculation() const { return m_deepCalculation; } /*! \brief Adds a copy of \a coordinates to the CoordinateList. \note The CoordinateList' is first emptied, essentially replacing its contents with a copy of \a coordinates. */ void CalcOptions::setCoordinateList(const Coordinates &coordinates) { m_coordinateList.setCoordinates(coordinates); } /*! \brief Allocates a copy of each Coordinates instance in \a list and adds it to the CoordinateList. \note The CoordinateList is first emptied, essentially replacing its contents with a copy of those in \a list. */ void CalcOptions::setCoordinateList(const CoordinateList &list) { m_coordinateList.setCoordinates(list); } /*! \brief Returns the CoordinateList member. */ const CoordinateList & CalcOptions::coordinateList() const { return m_coordinateList; } /*! \brief Sets the mass type to \a mass_type. \sa m_massType */ void CalcOptions::setMassType(int mass_type) { m_massType = mass_type; } /*! \brief Returns the mass type. \sa m_massType */ int CalcOptions::massType() const { return m_massType; } /*! \brief Set the selection type to \a type. \sa m_selectionType */ void CalcOptions::setSelectionType(SelectionType type) { m_selectionType = type; } /*! \brief Returns the selection type. \sa m_selectionType */ SelectionType CalcOptions::selectionType() const { return m_selectionType; } /*! \brief Set the cap type to \a cap_type. \sa m_capping */ void CalcOptions::setCapping(int cap_type) { m_capping = cap_type; } /*! \brief Returns the cap type . \sa m_capping */ int CalcOptions::capping() const { return m_capping; } /*! \brief Sets the monomer entities to \a entities. \sa m_monomerEntities */ void CalcOptions::setMonomerEntities(int entities) { m_monomerEntities = entities; } /*! \brief Returns the monomer entities. \sa m_monomerEntities */ int CalcOptions::monomerEntities() const { return m_monomerEntities; } /*! \brief Sets the polymer entities to \a entities. \sa m_polymerEntities */ void CalcOptions::setPolymerEntities(int entities) { m_polymerEntities = entities; } /*! \brief Returns the polymer entities. \sa m_polymerEntities */ int CalcOptions::polymerEntities() const { return m_polymerEntities; } /*! \brief Outputs a string describing this CalcOptions instance using qDebug(). */ void CalcOptions::debugPutStdErr() const { qDebug() << "\n~~~~~~~~~~~~~~CalcOptions instance:\n" << this << "\n" << "m_deepCalculation:" << m_deepCalculation << "\n" << "m_coordinateList: \n"; for(int iter = 0; iter < m_coordinateList.size(); ++iter) { Coordinates *coord = m_coordinateList.at(iter); qDebug() << "Iterated Coordinates at index" << iter << ":" << "[" << coord->start() << "-" << coord->end() << "]\n"; } qDebug() << "m_capping:" << m_capping << "\n" << "m_monomerEntities:" << m_monomerEntities << "\n" << "m_polymerEntities:" << m_polymerEntities << "\n" << "m_selectionType:" << m_selectionType << "\n" << "~~~~~~~~~~~~~~\n"; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/ChemicalGroup.cpp000664 001750 001750 00000050001 14647465366 023422 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "ChemicalGroup.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::ChemicalGroup \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile ChemicalGroup.hpp \brief The ChemicalGroup class provides a model for specifying the acido-basic behaviour of a chemical group of either a \l Monomer object or of a \l Modif object. If the ChemicalGroup does not prove sufficient to characterize precisely the acido-basic properties of an entity, \l ChemicalGroupRule instances can be added to that effect. In an pkaphpidata definition file, the following xml structure is encountered: \code A N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped C N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped Lateral SH2 8.3 FALSE never_trapped ..... Phosphorylation none_set 1.2 FALSE none_set 6.5 FALSE \endcode \sa ChemicalGroupRule, */ /*! \enum MsXpS::libXpertMass::ChemicalGroupTrapping This enum specifies how the chemical group behaves when the chemical entity that it holds polymerizes into a \l Polymer. One example will clear things out: An amino acid has a amino group and a carboxylic acid group. The amino group gets entrapped in the monomer-to-monomer bond (the peptide bond) if the polymerization occurs at the left of the monomer. That means that if the Monomer holding this ChemicalGroup is N-terminal, then the amino group should be accounted for because it is intact. Conversely, the carboxylic acid group gets entrapped in the peptide bond if the polymerization occurs at the right of the monomer. That means that if the Monomer holding this ChemicalGroup is C-terminal, then the carboxylic acid group should be accounted for because it is intact. \value NEVER_TRAPPED The chemical group is not lost upon polymerization, it should thus always be accounted for. \value LEFT_TRAPPED The chemical group gets trapped in the inter-monomer bond if polymerization occurs at left of the \l Monomer. \value RIGHT_TRAPPED The chemical group gets trapped in the inter-monomer bond if polymerization occurs at right of the \l Monomer. \omitvalue NOT_SET. */ /*! \variable MsXpS::libXpertMass::ChemicalGroup::m_name \brief The name of the ChemicalGroup instance. */ /*! \variable MsXpS::libXpertMass::ChemicalGroup::m_pka \brief The pKa of the ChemicalGroup instance. */ /*! \variable MsXpS::libXpertMass::ChemicalGroup::m_acidCharged \brief Tells if the group is charged when in acid conditions (that is, the pH is less than the pKa). */ /*! \variable MsXpS::libXpertMass::ChemicalGroup::m_polymerizationRule \brief The way this ChemicalGroup behaves upon polymerization of the chemical entity into a \l Polymer. */ /*! \variable MsXpS::libXpertMass::ChemicalGroup::m_ruleList \brief The list of \l ChemicalGroupRule instances. */ /*! \brief Constructs a ChemicalGroup instance. \a name The name of this ChemicalGroup. \a pka The pKa value of this ChemicalGroup. \a is_acid_charged Tells if the ChemicalGroup bears a charge when in acidic conditions. \a polymerization_rule Specifies the polymerization rule. */ ChemicalGroup::ChemicalGroup(QString name, float pka, bool is_acid_charged, ChemicalGroupTrapping polymerization_rule) : m_name(name), m_pka(pka), m_acidCharged(is_acid_charged), m_polymerizationRule(polymerization_rule) { Q_ASSERT(m_pka > 0 && m_pka < 14); } /*! \brief Construct a ChemicalGroup instance as a copy of \a other. */ ChemicalGroup::ChemicalGroup(const ChemicalGroup &other) : m_name(other.m_name), m_pka(other.m_pka), m_acidCharged(other.m_acidCharged), m_polymerizationRule(other.m_polymerizationRule) { } /*! \brief Destructs this ChemicalGroup instance. */ ChemicalGroup::~ChemicalGroup() { while(!m_ruleList.isEmpty()) delete m_ruleList.takeFirst(); } /*! \brief Assigns \a other to this ChemicalGroup instance. Returns a reference to this ChemicalGroup instance. */ ChemicalGroup & ChemicalGroup::operator=(const ChemicalGroup &other) { if(&other == this) return *this; m_name = other.m_name; m_pka = other.m_pka; m_acidCharged = other.m_acidCharged; m_polymerizationRule = other.m_polymerizationRule; qDeleteAll(m_ruleList); for(int iter = 0; iter < other.m_ruleList.size(); ++iter) m_ruleList.append(new ChemicalGroupRule(*other.m_ruleList.at(iter))); return *this; } /*! \brief Sets the \a name. */ void ChemicalGroup::setName(QString name) { m_name = name; } /*! \brief Returns the name. */ QString ChemicalGroup::name() const { return m_name; } /*! \brief Sets the pKa to \a pka. */ void ChemicalGroup::setPka(float pka) { Q_ASSERT(pka > 0 && pka < 14); m_pka = pka; } /*! \brief Returns the pKa. */ float ChemicalGroup::pka() const { return m_pka; } /*! \brief Sets the charge condition in acidic conditions to \a acid_charged. If true, the group bears a charge when the pH is less than the pKa. */ void ChemicalGroup::setAcidCharged(bool acid_charged) { m_acidCharged = acid_charged; } /*! \brief Returns the charge condition in acidic conditions. If true, the group bears a charge when the pH is less than the pKa. */ bool ChemicalGroup::isAcidCharged() const { return m_acidCharged; } /*! \brief Sets the polymerization rule to \a pol_rule. The polymerization rule determines if the chemical group is trapped upon formation of a Monomer-to-Monomer bond. */ void ChemicalGroup::setPolRule(ChemicalGroupTrapping pol_rule) { m_polymerizationRule = pol_rule; } /*! \brief Returns the polymerization rule. */ ChemicalGroupTrapping ChemicalGroup::polRule() const { return m_polymerizationRule; } /*! \brief Returns the list of ChemicalGroupRule instances. */ QList & ChemicalGroup::ruleList() { return m_ruleList; } /*! \brief Searches by \a entity for a ChemicalGroupRule instance. Returns the \a index of the ChemicalGroupRule instance in the member list of ChemicalGroupRule instances or -1 if not found. */ ChemicalGroupRule * ChemicalGroup::findRuleEntity(QString entity, int *index) const { int ruleIndex = 0; if(!index) ruleIndex = 0; else { if(*index < 0) return 0; else if(*index > m_ruleList.size()) return 0; ruleIndex = *index; } if(entity.isEmpty()) return 0; for(int iter = ruleIndex; iter < m_ruleList.size(); ++iter) { ChemicalGroupRule *rule = m_ruleList.at(iter); if(rule->entity() == entity) { if(index) *index = iter; return rule; } } return 0; } /*! \brief Searches by \a name for a ChemicalGroupRule instance. Returns the \a index of the ChemicalGroupRule instance in the member list of ChemicalGroupRule instances or -1 if not found. */ ChemicalGroupRule * ChemicalGroup::findRuleName(QString name, int *index) const { int ruleIndex = 0; if(!index) ruleIndex = 0; else { if(*index < 0) return 0; else if(*index > m_ruleList.size()) return 0; ruleIndex = *index; } if(name.isEmpty()) return 0; for(int iter = ruleIndex; iter < m_ruleList.size(); ++iter) { ChemicalGroupRule *rule = m_ruleList.at(iter); if(rule->name() == name) { if(index) *index = iter; return rule; } } return 0; } /*! \brief Searches by \a entity and \a name for a ChemicalGroupRule instance. Returns the \a index of the ChemicalGroupRule instance in the member list of ChemicalGroupRule instances or -1 if not found. */ ChemicalGroupRule * ChemicalGroup::findRule(QString entity, QString name, int *index) const { int ruleIndex = 0; if(!index) ruleIndex = 0; else { if(*index < 0) return 0; else if(*index > m_ruleList.size()) return 0; ruleIndex = *index; } if(entity.isEmpty() || name.isEmpty()) return 0; for(int iter = ruleIndex; iter < m_ruleList.size(); ++iter) { ChemicalGroupRule *rule = m_ruleList.at(iter); if(rule->entity() == entity && rule->name() == name) { if(index) *index = iter; return rule; } } return 0; } /*! \brief Parses the ChemicalGroup XML \a element \e{related to a \l Monomer}. Upon parsing of the \a element (tag \code{}), its data are validated and set to this ChemicalGroup instance, thus essentially initializing it. In an pkaphpidata definition file, the following xml structure is encountered: \code A N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped C N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped Lateral SH2 8.3 FALSE never_trapped \endcode Upon parsing of the \a element, all the data are validated and set to this ChemicalGroup instance, thus essentially initializing it. If there are \l{ChemicalGroupRule}s associated to the ChemicalGroup element, these are rendered also. Returns true if parsing and validation were successful, false otherwise. */ bool ChemicalGroup::renderXmlMnmElement(const QDomElement &element) { // The element the parameter points to is: // // // // Which means that element.tagName() == "mnmchemgroup" and that we'll // have to go one step down to the first child of the current node // in order to get to the \code\endcode element. QDomElement child; if(element.tagName() != "mnmchemgroup") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); child = child.nextSiblingElement(); if(child.isNull() || child.tagName() != "pka") return false; bool ok = false; m_pka = child.text().toFloat(&ok); if(!m_pka && !ok) return false; if(m_pka <= 0 || m_pka >= 14) return false; child = child.nextSiblingElement(); if(child.isNull() || child.tagName() != "acidcharged") return false; if(child.text() != "FALSE" && child.text() != "TRUE") return false; m_acidCharged = (child.text() == "FALSE" ? false : true); // And now the polrule element. There should be one, here, in fact, // because we are dealing with a monomer, and not a modification. child = child.nextSiblingElement(); if(child.isNull() || child.tagName() != "polrule") return false; if(child.text() == "never_trapped") m_polymerizationRule = ChemicalGroupTrapping::NEVER_TRAPPED; else if(child.text() == "left_trapped") m_polymerizationRule = ChemicalGroupTrapping::LEFT_TRAPPED; else if(child.text() == "right_trapped") m_polymerizationRule = ChemicalGroupTrapping::RIGHT_TRAPPED; else return false; // And finally the chemical group rules... There might be zero, one // or more. QDomElement childChemGroupRule = child.nextSiblingElement("chemgrouprule"); while(!childChemGroupRule.isNull()) { ChemicalGroupRule *rule = new ChemicalGroupRule(); if(!rule->renderXmlElement(childChemGroupRule)) { delete rule; return false; } m_ruleList.append(rule); childChemGroupRule = childChemGroupRule.nextSiblingElement(); } return true; } /*! \brief Parses the ChemicalGroup XML \a element \e{related to a \l Modif}. Upon parsing of the \a element (tag ), its data are validated and set to this ChemicalGroup instance, thus essentially initializing it. In an pkaphpidata definition file, the following xml structure is encountered: \code A N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped ....... Phosphorylation none_set 1.2 FALSE none_set 6.5 FALSE \endcode Upon parsing of the \a element, all the data are validated and set to this ChemicalGroup instance, thus essentially initializing it. If there are \l{ChemicalGroupRule}s associated to the ChemicalGroup element, these are rendered also. Returns true if parsing and validation were successful, false otherwise. */ bool ChemicalGroup::renderXmlMdfElement(const QDomElement &element) { QDomElement child; if(element.tagName() != "mdfchemgroup") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); child = child.nextSiblingElement(); if(child.isNull() || child.tagName() != "pka") return false; bool ok = false; m_pka = child.text().toFloat(&ok); if(!m_pka && !ok) return false; if(m_pka <= 0 || m_pka >= 14) return false; child = child.nextSiblingElement(); if(child.isNull() || child.tagName() != "acidcharged") return false; if(child.text() != "FALSE" && child.text() != "TRUE") return false; m_acidCharged = (child.text() == "FALSE" ? false : true); return true; } //////////////////////// ChemicalGroupProp //////////////////////// //////////////////////// ChemicalGroupProp //////////////////////// /*! \class MsXpS::libXpertMass::ChemicalGroupProp \inmodule libXpertMass \ingroup ThePropSystem \brief The ChemicalGroupProp class provides a Prop instance of which the member data points to a dynamically allocated \l ChemicalGroup instance. */ /*! \brief Constructs a ChemicalGroupProp instance using \a data and \a name. The \a data pointer is set to the \l mpa_data member. */ ChemicalGroupProp::ChemicalGroupProp(const QString &name, ChemicalGroup *data) { if(!name.isEmpty()) m_name = name; else m_name = QString(); mpa_data = static_cast(data); } /*! \brief Constructs a ChemicalGroupProp instance as a copy of \a other. The data in \a other are duplicated and set to this ChemicalGroupProp instance. */ ChemicalGroupProp::ChemicalGroupProp(const ChemicalGroupProp &other) : Prop(other) { if(other.mpa_data != nullptr) { ChemicalGroup *chemicalGroup = static_cast(other.mpa_data); mpa_data = static_cast(new ChemicalGroup(*chemicalGroup)); } else mpa_data = nullptr; } /*! \brief Destructs this ChemicalGroupProp instance. The deletion of the data are delegated to \l deleteData(). */ ChemicalGroupProp::~ChemicalGroupProp() { deleteData(); } /*! \brief Deletes the member data. */ void ChemicalGroupProp::deleteData() { if(mpa_data != nullptr) { delete static_cast(mpa_data); mpa_data = nullptr; } } /*! \brief Assigns \a other to this ChemicalGroupProp instance. The member data are first deleted and then set to a copy of those in \a other. */ ChemicalGroupProp & ChemicalGroupProp::operator=(const ChemicalGroupProp &other) { if(&other == this) return *this; Prop::operator=(other); if(mpa_data != nullptr) deleteData(); if(other.mpa_data != nullptr) { ChemicalGroup *chemicalGroup = static_cast(other.mpa_data); mpa_data = static_cast(new ChemicalGroup(*chemicalGroup)); } else mpa_data = nullptr; return *this; } /*! \brief Duplicates this ChemicalGroupProp instance and returns its pointer. */ ChemicalGroupProp * ChemicalGroupProp::cloneOut() const { ChemicalGroupProp *new_p = new ChemicalGroupProp(*this); return new_p; } bool ChemicalGroupProp::renderXmlElement([[maybe_unused]] const QDomElement &element, [[maybe_unused]] int version) { return false; } QString * ChemicalGroupProp::formatXmlElement([[maybe_unused]] int offset, [[maybe_unused]] const QString &indent) { return nullptr; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/ChemicalGroupRule.cpp000664 001750 001750 00000020004 14647465366 024252 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "ChemicalGroupRule.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::ChemicalGroupRule \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile ChemicalGroupRule.hpp \brief The ChemicalGroupRule class provides a model for refining the acido-basic behaviour of a chemical group of either a \l Monomer object or of a \l Modif object. In an pkaphpidata definition file, the following xml structure is encountered: \code A N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped C N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped Lateral SH2 8.3 FALSE never_trapped ..... Phosphorylation none_set 1.2 FALSE none_set 6.5 FALSE \endcode \sa ChemicalGroup, */ /*! \enum MsXpS::libXpertMass::ChemicalGroupRuleFate This enum specifies how the chemical group behaves when the chemical entity that it holds polymerizes into a \l Polymer. This example clarifies the concept: \code C N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST \endcode When the Cysteine's amino group is modified because the Cys residue on on the N-terminal end of the polymer, if it gets acetylated, then the amino group is lost because it is trapped in the amide bond. It is thus not accounted for when computing the pI of the protein. \value LOST The chemical group is lost upon modification of the \l Monomer. \value PRESERVED The chemical group is preserved upon modification of the \l Monomer. */ /*! \variable MsXpS::libXpertMass::ChemicalGroupRule::m_name \brief The name of the ChemicalGroupRule instance. */ /*! \variable MsXpS::libXpertMass::ChemicalGroupRule::m_entity \brief The entity of the ChemicalGroupRule instance, like LE_PLM_MODIF for \e{left end polymer modification}. */ /*! \variable MsXpS::libXpertMass::ChemicalGroupRule::m_chemicalGroupFate \brief The fate of the ChemicalGroupRule instance. \sa MsXpS::libXpertMass::ChemicalGroupRuleFate */ /*! \brief Constructs a ChemicalGroupRule instance. \list \li \a name: The name of this ChemicalGroupRule instance. \li \a entity: The entity of this ChemicalGroupRule instance. \li \a fate: The fate of this ChemicalGroupRule instance. \endlist */ ChemicalGroupRule::ChemicalGroupRule(QString name, QString entity, ChemicalGroupRuleFate fate) : m_name(name), m_entity(entity), m_chemicalGroupFate(fate) { Q_ASSERT(m_chemicalGroupFate == ChemicalGroupRuleFate::LOST || m_chemicalGroupFate == ChemicalGroupRuleFate::PRESERVED); } /*! \brief Sets the \a name. */ void ChemicalGroupRule::setName(QString name) { m_name = name; } /*! \brief Returns the name. */ QString ChemicalGroupRule::name() { return m_name; } /*! \brief Sets the \a entity. */ void ChemicalGroupRule::setEntity(QString entity) { m_entity = entity; } /*! \brief Returns the entity. */ QString ChemicalGroupRule::entity() { return m_entity; } /*! \brief Sets the \a fate. */ void ChemicalGroupRule::setFate(ChemicalGroupRuleFate fate) { m_chemicalGroupFate = fate; } /*! \brief Returns the fate. */ ChemicalGroupRuleFate ChemicalGroupRule::fate() { return m_chemicalGroupFate; } /*! \brief Parses the ChemicalGroupRule XML \a element. Upon parsing of the \a element, its data are validated and set to this ChemicalGroupRule instance, thus essentially initializing it. Returns true if parsing and validation were successful, false otherwise. */ bool ChemicalGroupRule::renderXmlElement(const QDomElement &element) { QDomElement child; // In an acidobasic definition file, the following xml structure // is encountered: // // C // // N-term NH2 // 9.6 // TRUE // left_trapped // // LE_PLM_MODIF // Acetylation // LOST // // // The relevant DTD line is: // // And the element the parameter points to is: // // Which means that element.tagName() == "chemgrouprule" and that we'll // have to go one step down to the first child of the current node // in order to get to the element. if(element.tagName() != "chemgrouprule") return false; child = element.firstChildElement("entity"); if(child.isNull()) return false; m_entity = child.text(); child = child.nextSiblingElement(); if(child.isNull() || child.tagName() != "name") return false; m_name = child.text(); child = child.nextSiblingElement(); if(child.isNull() || child.tagName() != "outcome") return false; if(child.text() == "LOST") m_chemicalGroupFate = ChemicalGroupRuleFate::LOST; else if(child.text() == "PRESERVED") m_chemicalGroupFate = ChemicalGroupRuleFate::PRESERVED; else return false; return true; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/CleaveMotif.cpp000664 001750 001750 00000022120 14647465366 023077 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "CleaveMotif.hpp" #include "Polymer.hpp" #include "PolChemDef.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::CleaveMotif \inmodule libXpertMass \ingroup PolChemDefAqueousChemicalReactions \inheaderfile CleaveMotif.hpp \brief The CleaveMotif class provides a model for specifying aqueous cleavage sites of \l{Polymer} \l{Sequence}s. When a polymer sequence cleavage occurs, using, for example, the specification "Lys/;Arg/;-Lys/Pro", a number of actions need be performed prior to listing the oligomers obtained as a result of the cleavage. The "Lys/;Arg/;-Lys/Pro" cleavage specification (\l CleaveSpec) gets crunched in a number of steps and cleavage motifs are generated for it. In this specific case we'll have three motifs with the following data: - First motif (or cleavage site): - "Lys/" - code list [0] = "Lys" - offset = 1 ('/' indicates that cut is right of monomer) - is for cleavage ? = true - Second motif (or cleavage site): - "Arg/" - code list [0] = "Arg" - offset = 1 ('/' indicates that cut is right of monomer) - is for cleavage ? = true - Third motif (or non-cleavage site): - "-Lys/Pro" - code list [0] = "Lys", [1] = "Pro" - offset = 1 ('/' indicates that cut is right of monomer) - is for cleavage ? = false Thanks to this deconstruction (from "Lys/;Arg/;-Lys/Pro" to the 3 motifs above) is the polymer sequence cleaved according to the specification. \sa CleaveSpec, CleaveRule */ /*! \variable MsXpS::libXpertMass::CleaveMotif::m_motif \brief The string motif that describes a specific cleavage site. The "Lys/" cleavage motif is part of the cleavage specification that describes the model for the Trypsin cleavage in the proteinaceous world. */ /*! \variable MsXpS::libXpertMass::CleaveMotif::m_codeList \brief The list of \l Monomer codes that are in the string motif. For the "-Lys/Pro" motif, also from the Trypsin cleavage specification, there are two codes: "Lys" and "Pro". */ /*! \variable MsXpS::libXpertMass::CleaveMotif::m_offset \brief The cleavage site offset with respect to the first monomer code of the motif. In the The "Lys/", "Arg/" and "-Lys/Pro" examples, the offset would be 1 for each motif, because each time the cleavage occurs after the first monomer code: Lys/, or Arg/ or Lys/Pro. */ /*! \variable MsXpS::libXpertMass::CleaveMotif::m_isForCleave \brief Tells if the motif is for cleavage. In the "Lys/" and "Arg/" motif, that would be true; for "-Lys/Pro", that would be false, because Trypsin does not cleave after a Lysil residue if it is followed by a Prolyl residue. */ /*! \brief Constructs a cleavage motif. \a pol_chem_def_csp Polymer chemistry definition. Cannot be nullptr. \a name Name. Cannot be empty. \a motif Motif in the form of "Lys/" or "-Lys/Pro". \a offset Offset position of the cleavage to the first monomer code in the motif. \a is_for_cleavage Tells if motif is for cleavage (for example, "Lys/") or not for cleavage (for example, "-Lys/Pro"). */ CleaveMotif::CleaveMotif(PolChemDefCstSPtr pol_chem_def_csp, QString name, const QString &motif, int offset, bool is_for_cleavage) : PolChemDefEntity(pol_chem_def_csp, name), m_motif(motif), m_offset(offset), m_isForCleave(is_for_cleavage) { } /*! \brief Constructs a CleaveMotif instance as a copy of \a other. */ CleaveMotif::CleaveMotif(const CleaveMotif &other) : PolChemDefEntity(other), m_motif(other.m_motif), m_codeList(other.m_codeList), m_offset(other.m_offset), m_isForCleave(other.m_isForCleave) { } /*! \brief Destructs this CleaveMotif instance. */ CleaveMotif::~CleaveMotif() { } /*! \brief Assigns \a other to this CleaveMotif instance. Returns a reference to this CleaveMotif instance. */ CleaveMotif & CleaveMotif::operator=(const CleaveMotif &other) { if(&other == this) return *this; PolChemDefEntity::operator=(other); m_motif = other.m_motif; m_codeList = other.m_codeList; m_offset = other.m_offset; m_isForCleave = other.m_isForCleave; return *this; } /*! \brief Sets the \a motif. */ void CleaveMotif::setMotif(const QString &motif) { m_motif = motif; } /*! \brief Returns the motif. */ const QString & CleaveMotif::motif() { return m_motif; } /*! \brief Returns the string list of codes in the motif. */ const QStringList & CleaveMotif::codeList() const { return m_codeList; } /*! \brief Sets the \a offset. */ void CleaveMotif::setOffset(int offset) { m_offset = offset; } /*! \brief Returns the offset. */ int CleaveMotif::offset() { return m_offset; } /*! \brief Sets if motif is for cleavage to \a for_cleave. */ void CleaveMotif::setForCleave(bool for_cleave) { m_isForCleave = for_cleave; } /*! \brief Returns if motif is for cleavage. */ bool CleaveMotif::isForCleave() { return m_isForCleave; } /*! \brief Parses the cleavage \a site. Upon parsing of the cleavage \a site, this CleaveMotif instance is filled with data. Returns the number of monomer codes in the motif, or -1 upon error. */ int CleaveMotif::parse(const QString &site) { const QList &refList = mcsp_polChemDef->monomerList(); QString code; QString error; QString local = site; int index = 0; int count = 0; // We get something like "Lys/Pro" or something like "KKGK/RRGK" and // we have to make three things: // // 1. change the site "KKGK/RRGK" to a motif string(KKGKRRGK). // // 2. set the offset member to the index of '/' in the initial site // string. // // 3. make an array of codes with the motif. Sequence sequence(local); while(1) { code.clear(); if(sequence.nextCode( &code, &index, &error, mcsp_polChemDef->codeLength()) == -1) { if(error == "/") { m_offset = count; // qDebug() << __FILE__ << __LINE__ // << "Found / at code position" << m_offset; // Increment index so that we iterate in the next code // at next round. ++index; continue; } // There was an error parsing the site sequence. If the err // string contains a space, that is not serious, we could // skip that. qDebug() << __FILE__ << __LINE__ << "Failed to parse cleavage site" << site; return -1; } // There might have been no error, but that does not means that // we necessarily got a code. if(code.isEmpty()) { // We arrived at the end of a code parsing step(either // because the current 'local' cleavage site(that is // 'sequence') was finished parsing or because we finished // parsing one of its monomer codes. break; } // At this point we actually had a code. // qDebug() << __FILE__ << __LINE__ // << "Got next code:" << code.toAscii(); // At this point, 'code' contains something that looks like a // valid code, but we still have to make sure that this code // actually is in our list of monomer codes... if(Monomer::isCodeInList(code, refList) == -1) { qDebug() << __FILE__ << __LINE__ << "Monomer code" << code << " is not in the monomer list."; return -1; } // At the end m_motif will contain KKGKRRGK, while site was // "KKGK/RRGK" KKGKRRGK m_motif += code; // Add the newly parsed code to the code list. m_codeList << code; ++count; // Increment index so that we iterate in the next code at next // round. ++index; } // Return the number of successfully parsed monomer codes for the // 'site' string. Q_ASSERT(count == m_codeList.size()); return count; } bool CleaveMotif::validate() { Q_ASSERT(0); return true; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/CleaveRule.cpp000664 001750 001750 00000030725 14647465366 022742 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "CleaveRule.hpp" #include "PolChemDef.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::CleaveRule \inmodule libXpertMass \ingroup PolChemDefAqueousChemicalReactions \inheaderfile CleaveRule.hpp \brief The CleaveRule class provides a model for specifying aqueous cleavage rules for refining cleavage specifications (\l CleaveSpec) of \l{Polymer} \l{Sequence}s. Cleavage rules help refine the description of the chemical reaction that is the basis of a cleavage (either enzymatic or chemical). While a number of cleavage agents (like a number of enzymes) do not make unexpected reactions upon the cleavage (enzymes usually hydrolyze their substrates), there are chemical agents that while cleaving their polymer sequence substrate chemically modify the ends of the generated oligomers. One notorious example is the case of cyanogen bromide, that cleaves proteins right of methionyl residues. Upon such cleavage, the monomer at the right side of the generated oligomer (methionyl residue) gets modified according to this actionformula: "-CH2S+O". This reaction is modelled using a CleaveRule. \sa CleaveMotif, CleaveSpec */ /*! \variable MsXpS::libXpertMass::CleaveRule::m_leftCode \brief The \l Monomer code at the left of the cleavage site. */ /*! \variable MsXpS::libXpertMass::CleaveRule::m_leftFormula \brief The \l Formula to be applied onto the left monomer code. */ /*! \variable MsXpS::libXpertMass::CleaveRule::m_rightCode \brief The \l Monomer code at the right of the cleavage site. */ /*! \variable MsXpS::libXpertMass::CleaveRule::m_rightFormula \brief The \l Formula to be applied onto the right monomer code. */ /*! \brief Constructs a CleaveRule instance \list \li \a pol_chem_def_csp: Polymer chemistry definition. Cannot be nullptr. \li \a name: the name. \li \a leftCode: The \l Monomer code at the left of the cleavage site. \li \a leftFormula: .The \l Formula to be applied onto the left monomer code. \li \a rightCode: .The \l Monomer code at the right of the cleavage site. \li \a rightFormula: .The \l Formula to be applied onto the right monomer code. \endlist */ CleaveRule::CleaveRule(PolChemDefCstSPtr pol_chem_def_csp, QString name, QString leftCode, QString leftFormula, QString rightCode, QString rightFormula) : PolChemDefEntity(pol_chem_def_csp, name), m_leftCode(leftCode), m_leftFormula(leftFormula), m_rightCode(rightCode), m_rightFormula(rightFormula) { } /*! \brief Constructs a CleaveRule instance as a copy of \a other. */ CleaveRule::CleaveRule(const CleaveRule &other) : PolChemDefEntity(other), m_leftCode(other.m_leftCode), m_leftFormula(other.m_leftFormula), m_rightCode(other.m_rightCode), m_rightFormula(other.m_rightFormula) { } /*! \brief Destructs this CleaveRule instance */ CleaveRule::~CleaveRule() { } /*! \brief Assigns to \a other to this CleaveRule instance. Returns a reference to this CleaveRule instance. */ CleaveRule & CleaveRule::operator=(const CleaveRule &other) { if(&other == this) return *this; PolChemDefEntity::operator=(other); m_leftCode = other.m_leftCode; m_leftFormula = other.m_leftFormula; m_rightCode = other.m_rightCode; m_rightFormula = other.m_rightFormula; return *this; } /*! \brief Sets the left \a code. */ void CleaveRule::setLeftCode(const QString &code) { m_leftCode = code; } /*! \brief Returns the left code. */ const QString & CleaveRule::leftCode() { return m_leftCode; } /*! \brief Sets the right \a code. */ void CleaveRule::setRightCode(const QString &code) { m_rightCode = code; } /*! \brief Returns the right code. */ const QString & CleaveRule::rightCode() { return m_rightCode; } /*! \brief Sets the left \a formula. */ void CleaveRule::setLeftFormula(const Formula &formula) { m_leftFormula = formula; } /*! \brief Returns the left formula. */ const Formula & CleaveRule::leftFormula() { return m_leftFormula; } /*! \brief Sets the right \a formula. */ void CleaveRule::setRightFormula(const Formula &formula) { m_rightFormula = formula; } /*! \brief Returns the right formula. */ const Formula & CleaveRule::rightFormula() { return m_rightFormula; } /*! \brief Searches for a CleaveRule instance by \a name in \a cleave_rule_list. If the instance is found, and \a other is non-nullptr, it is copied to \a other. Returns the index of the found CleaveRule instance in \a cleave_rule_list or -1 is the cleavage rule was not found. */ int CleaveRule::isNameInList(const QString &name, const QList &cleave_rule_list, CleaveRule *other) { CleaveRule *cleaveRule = 0; if(name.isEmpty()) return -1; for(int iter = 0; iter < cleave_rule_list.size(); ++iter) { cleaveRule = cleave_rule_list.at(iter); Q_ASSERT(cleaveRule); if(cleaveRule->m_name == name) { if(other) *other = *cleaveRule; return iter; } } return -1; } /*! \brief Validates this CleaveRule instance. Validation entails the following: \list \li If the left monomer code is not empty, it must be known to the polymer chemistry definition. In that case, if the left formula is not empty, it needs to validate successfully. \li The same logic is applied to the monomer at the right hand side of the cleavage site. \endlist Returns true if the validation is successful, false otherwise. */ bool CleaveRule::validate() { IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); const QList &monomerRefList = mcsp_polChemDef->monomerList(); if(!m_leftCode.isEmpty()) { if(Monomer::isCodeInList(m_leftCode, monomerRefList) == -1) return false; if(m_leftFormula.toString().isEmpty()) return false; if(!m_leftFormula.validate(isotopic_data_csp)) return false; } if(!m_rightCode.isEmpty()) { if(Monomer::isCodeInList(m_rightCode, monomerRefList) == -1) return false; if(m_rightFormula.toString().isEmpty()) return false; qDebug() << "Validating right end cleave rule formula:" << m_rightFormula.toString(); if(!m_rightFormula.validate(isotopic_data_csp)) return false; } return true; } /*! \brief Parses the CleaveRule XML \a element using a \a{version}ed function. Upon parsing of the \a element, its data are validated and set to this CleaveRule instance, thus essentially initializing it. Returns true if parsing and validation were successful, false otherwise. */ bool CleaveRule::renderXmlClrElement(const QDomElement &element, int version) { QDomElement child; bool leftCodeSet = false; bool leftFormulaSet = false; bool rightCodeSet = false; bool rightFormulaSet = false; /* The xml node we are in is structured this way: * * * Homeseryl * M * -C1H2S1+O1 * M * -C1H2S1+O1 * * * And the element parameter points to the * * element tag: * ^ * | * +----- here we are right now. * * Which means that xml_node->name == "clr" and that * we'll have to go one step down to the first child of the * current node in order to get to the element. * * Note that the DTD stipulates that there can be no or one at most * of each left end and/or right end set of data. So be careful * with the assertions ! * This is the DTD material: * */ if(element.tagName() != "clr") return false; child = element.firstChildElement(); if(version == 1) { // no-op version = 1; } if(child.tagName() != "name") return false; m_name = child.text(); child = child.nextSiblingElement(); while(!child.isNull()) { // OK, apparently there is a child element, so let's try to see // what's going on. It can either be "le-mnm-code" or "re-mnm-code". if(child.tagName() == "le-mnm-code") { m_leftCode = child.text(); leftCodeSet = true; } else if(child.tagName() == "le-formula") { m_leftFormula.setFormula(child.text()); leftFormulaSet = true; } else if(child.tagName() == "re-mnm-code") { m_rightCode = child.text(); rightCodeSet = true; } else if(child.tagName() == "re-formula") { m_rightFormula.setFormula(child.text()); rightFormulaSet = true; } child = child.nextSiblingElement(); } // OK, we just finished parsing this element. Check what we // got. if(leftCodeSet) { if(!leftFormulaSet) return false; } if(rightCodeSet) { if(!rightFormulaSet) return false; } // It cannot be that no single code could be set. if(!leftCodeSet && !rightCodeSet) return false; if(!validate()) return false; return true; } /*! \brief Formats a string representing this CleaveRule instance suitable to use as an XML element. The typical cleavage rule element that is generated in this function looks like this: \code M -CH2S+O \endcode The formatting of the XML element takes into account \a offset and \a indent by prepending the string with \a offset * \a indent character substring. \a indent defaults to two spaces. Returns a dynamically allocated string that needs to be freed after use. */ QString * CleaveRule::formatXmlClrElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } /* M -CH2S+O */ *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); if(!m_leftCode.isEmpty()) { Q_ASSERT(!m_leftFormula.toString().isEmpty()); *string += QString("%1%2\n").arg(lead).arg(m_leftCode); *string += QString("%1%2\n") .arg(lead) .arg(m_leftFormula.toString()); } if(!m_rightCode.isEmpty()) { Q_ASSERT(!m_rightFormula.toString().isEmpty()); *string += QString("%1%2\n").arg(lead).arg(m_rightCode); *string += QString("%1%2\n") .arg(lead) .arg(m_rightFormula.toString()); } // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/CleaveSpec.cpp000664 001750 001750 00000034717 14647465366 022732 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "CleaveSpec.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::CleaveSpec \inmodule libXpertMass \ingroup PolChemDefAqueousChemicalReactions \inheaderfile CleaveSpec.hpp \brief The CleaveSpec class provides a model for specifying aqueous cleavage specifications (patterns) of \l{Polymer} \l{Sequence}s. Cleavage specifications determine the specificity of cleavage in a polymer sequence using a simple syntax. For example, Trypsin is able to cleave after lysyl and arginyl residues. Its cleavage pattern is thus "Lys/;Arg/". However, it is known that Trypsin fails to cleave after Lys if that monomer is followed by a Prolyl residue, thus the complete cleavage specification for Trypsin comprises three cleavage motifs: "Lys/;Arg/;-Lys/Pro". A cleavage specification might not be enough information to determine the manner in which a polymer is cleaved. Cleavage rules might be required to refine the specification. A cleavage specification might hold as many cleavage rules as required. \sa CleaveMotif, CleaveRule */ /*! \variable MsXpS::libXpertMass::CleaveSpec::m_pattern \brief The cleavage pattern, that might comprise more than one \l CleaveMotif. \sa CleaveMotif, CleaveRule */ /*! \variable MsXpS::libXpertMass::CleaveSpec::m_motifList \brief The list of \l{CleaveMotif}s that together make the CleaveSpec. \sa CleaveMotif */ /*! \variable MsXpS::libXpertMass::CleaveSpec::m_ruleList \brief The list of \l{CleaveRule}s that might be requied to refine the CleaveSpec. \sa CleaveRule */ /*! \brief Constructs a CleaveSpec instance. \list \li \a pol_chem_def_csp: The polymer chemistry definition. \li \a name: The name of the CleaveSpec. \li \a pattern: The pattern of the CleaveSpec, like ("Lys/;Arg/;-Lys/Pro"). \endlist */ CleaveSpec::CleaveSpec(PolChemDefCstSPtr pol_chem_def_csp, QString name, QString pattern) : PolChemDefEntity(pol_chem_def_csp, name), m_pattern(pattern) { } /*! \brief Constructs a CleaveSpec instance as a copy of \a other. */ CleaveSpec::CleaveSpec(const CleaveSpec &other) : PolChemDefEntity(other), m_pattern(other.m_pattern) { for(int iter = 0; iter < other.m_motifList.size(); ++iter) { CleaveMotif *cleaveMotif = new CleaveMotif(*other.m_motifList.at(iter)); m_motifList.append(cleaveMotif); } for(int iter = 0; iter < other.m_ruleList.size(); ++iter) { CleaveRule *cleaveRule = new CleaveRule(*other.m_ruleList.at(iter)); m_ruleList.append(cleaveRule); } } /*! \brief Destructs this CleaveSpec instance. */ CleaveSpec::~CleaveSpec() { while(!m_motifList.isEmpty()) delete m_motifList.takeFirst(); while(!m_ruleList.isEmpty()) delete m_ruleList.takeFirst(); } /*! \brief Assigns \a other to this CleaveSpec instance. Returns a reference to this CleaveSpec instance. */ CleaveSpec & CleaveSpec::operator=(const CleaveSpec &other) { if(&other == this) return *this; PolChemDefEntity::operator=(other); m_pattern = other.m_pattern; while(!m_ruleList.isEmpty()) delete m_ruleList.takeFirst(); for(int iter = 0; iter < other.m_ruleList.size(); ++iter) { CleaveRule *cleaveRule = new CleaveRule(*other.m_ruleList.at(iter)); m_ruleList.append(cleaveRule); } while(!m_motifList.isEmpty()) delete m_motifList.takeFirst(); for(int iter = 0; iter < other.m_motifList.size(); ++iter) { CleaveMotif *cleaveMotif = new CleaveMotif(*other.m_motifList.at(iter)); m_motifList.append(cleaveMotif); } return *this; } /*! \brief Sets the \a pattern. */ void CleaveSpec::setPattern(const QString &pattern) { m_pattern = pattern; } /*! \brief Returns the pattern. */ const QString & CleaveSpec::pattern() { return m_pattern; } /*! \brief Returns the list of CleaveMotif instances. */ QList * CleaveSpec::motifList() { return &m_motifList; } /*! \brief Returns the list of CleaveRule instances. */ QList * CleaveSpec::ruleList() { return &m_ruleList; } /*! \brief Searches for a CleaveSpec instance by \a name in \a cleave_spec_list. If the CleaveSpec instance is found, it is copied into \a other. Returns the index of the found cleavage specification or -1 if none is found or if \a other is empty. */ int CleaveSpec::isNameInList(const QString &name, const QList &cleave_spec_list, CleaveSpec *other) { CleaveSpec *cleaveSpec = 0; if(name.isEmpty()) return -1; for(int iter = 0; iter < cleave_spec_list.size(); ++iter) { cleaveSpec = cleave_spec_list.at(iter); Q_ASSERT(cleaveSpec); if(cleaveSpec->m_name == name) { if(other) other = new CleaveSpec(*cleaveSpec); return iter; } } return -1; } /*! \brief Parses this CleaveSpec instance. The parsing involves separating the components found in the m_pattern string and making CleaveMotif instances out of them. Starting from a pattern "Lys/;Arg/;-Lys/Pro", the parsing would first split it into three site strings: \list \li "Lys/"; \li "Arg/"; \li "-Lys/Pro" \endlist Each of these site strings will be deconstructed into motifs, stored in CleaveMotif objects: \list \li First motif has a code list "[0] Lys", an offset of 1 and is for cleavage; \li Second motif has a code list "[0] Arg", an offset of 1 and is for cleavage; \li Third motif has a code list "[0] Lys, [1] Pro", an offset of 1 and is not for cleavage (see the '-') \endlist Returns true if parsing is successful, false otherwise. */ bool CleaveSpec::parse() { if(m_pattern.isEmpty()) return false; // The 'm_pattern' is a ';'-delimited string, in which each // sub-string is a 'site'. Each site is in turn constituted by a // motif and a '/' that indicates where the motif is actually // cleaved. // // For example the "-Lys/Pro" site is actually a motif of sequence // "LysPro" and the site holds two more informations with respect to // the mere motif: it says that the motif should not be cleaved //('-') and that if the '-' were not there, the cleavage would // occur between the Lys and the Pro('/' symbolizes the cleavage). // For example, if the cleavespec had a "Lys/;Arg/;-Lys/Pro" string, // it would be split into 3 strings: "Lys/" and "Arg/" and // "-Lys/Pro"(these are 'site' strings). These three site string // would further be deconstructed into motif string(removal of '-' // and '/' characters). Where would these 3 motif strings be stored? // They would be set into one cleavemotif instance for each // motif. Thus, for example, "-Lys/Pro" would yield a cleavemotif of // 'motif' LysPro, with a FALSE cleave member and a 1 offset member. // Will return the number of cleavemotif instances that were created. // Upon error -1 is returned. // "Lys/;Arg/;-Lys/Pro" --> [0] Lys/, [1] Arg/, [2] -Lys/Pro QStringList sites = m_pattern.split(";", Qt::SkipEmptyParts); // for (int iter = 0; iter < sites.size() ; ++iter) // qDebug() << __FILE__ << __LINE__ // << sites.at(iter); bool isForCleave = true; for(int iter = 0; iter < sites.size(); ++iter) { isForCleave = true; QString currSite = sites.at(iter); if(currSite.length() < 2) { qDebug() << currSite << "is an invalid cleavage site"; return false; } int count = currSite.count(QChar('/')); if(count != 1) { qDebug() << "The must be one and only one '/' " "in the cleavage site"; return false; } // Remove spaces. currSite.remove(QRegularExpression("\\s+")); // Is there a '-' in the site string indicating that this site // is NOT for cleavage? If so, there should be only one such // sign and in position 0. count = currSite.count(QChar('-')); if(count > 1) return false; else if(count == 1) { if(currSite.indexOf(QChar('-'), 0, Qt::CaseInsensitive) != 0) return false; isForCleave = false; // We can remove the '-' character, as we now know // that the site is NOT for cleavage. currSite.remove(0, 1); } // else: site is for cleavage: no '-' found. // We can create a new cleavage motif. CleaveMotif *motif = new CleaveMotif(mcsp_polChemDef, "NOT_SET"); motif->setForCleave(isForCleave); if(motif->parse(currSite) == -1) { qDebug() << "Failed to parse site" << currSite; delete motif; return false; } // qDebug() << __FILE__ << __LINE__ // << "Appending motif" << motif->motif(); // Append the newly created motif to the list. m_motifList.append(motif); } return true; } /*! \brief Validates this CleaveSpec instance. The validation involves checking that: \list \li The name is not empty. \li The pattern is nt empty. \li The parsing of the pattern is successful. \li Each CleaveRule instance (if any) validates successfully. \endlist Returns true if the validation is successful, false otherwise. */ bool CleaveSpec::validate() { if(m_name.isEmpty()) return false; if(m_pattern.isEmpty()) return false; if(!parse()) return false; // If there are rules, we have to check them all. for(int iter = 0; iter < m_ruleList.size(); ++iter) { if(!m_ruleList.at(iter)->validate()) return false; } return true; } /*! \brief Parses a cleavage specification XML \a element using a \a{version}ed function. The data in the element are validated and if successful they are set to this instance, thus initializing it. Returns true upon success, false otherwise. */ bool CleaveSpec::renderXmlClsElement(const QDomElement &element, int version) { // For the time being the version is not necessary here. As of // version up to 2, the current function works ok. if(version == 1) { // NoOp version = 1; } QDomElement child; QDomElement childRule; CleaveRule *cleaveRule = 0; /* The xml node we are in is structured this way: * * * CyanogenBromide * M/ * * M * -C1H2S1+O1 * M * -C1H2S1+O1 * * * * And the element parameter points to the * * element tag: * ^ * | * +----- here we are right now. * * Which means that element.tagName() == "cls" and that * we'll have to go one step down to the first child of the * current node in order to get to the element. */ if(element.tagName() != "cls") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); //qDebug() << "CleaveSpec name is" << m_name; child = child.nextSiblingElement("pattern"); if(child.isNull()) return false; m_pattern = child.text(); // At this point there might be 0, 1 or more cleavage rules. child = child.nextSiblingElement("clr"); while(!child.isNull()) { cleaveRule = new CleaveRule(mcsp_polChemDef, "NOT_SET"); if(!cleaveRule->renderXmlClrElement(child, version)) { delete cleaveRule; return false; } if(!cleaveRule->validate()) { delete cleaveRule; return false; } m_ruleList.append(cleaveRule); child = child.nextSiblingElement("clr"); } if(!validate()) return false; return true; } /*! \brief Formats a string describing this CleaveSpec instance suitable to be used as an XML element. The XML element is typically used in a polymer chemistry defintion and looks like this: \code CyanogenBromide M/ M -CH2S+O \endcode The formatting of the XML element takes into account \a offset and \a indent by prepending the string with \a offset * \a indent character substring. \a indent defaults to two spaces. Returns a dynamically allocated string that needs to be freed after use. */ QString * CleaveSpec::formatXmlClsElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); *string += QString("%1%2\n").arg(lead).arg(m_pattern); for(int iter = 0; iter < m_ruleList.size(); ++iter) { QString *ruleString = m_ruleList.at(iter)->formatXmlClrElement(newOffset); *string += *ruleString; delete ruleString; } // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Coordinates.cpp000664 001750 001750 00000044224 14647465366 023164 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "Coordinates.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::Coordinates \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile Coordinates.hpp \brief The Coordinates class provides the localization of a sequence region in a \l Polymer \l Sequence. The localization of the sequence region is performed by using \e indices pointing to the location in the Polymer sequence. */ /*! \variable MsXpS::libXpertMass::Coordinates::m_start \brief The index of the first monomer in the sequence region described by this Coordinates instance. */ /*! \variable MsXpS::libXpertMass::Coordinates::m_end \brief The index of the last monomer in the sequence region described by this Coordinates instance. */ ///////////////////////// Coordinates ///////////////////////// ///////////////////////// Coordinates ///////////////////////// ///////////////////////// Coordinates ///////////////////////// ///////////////////////// Coordinates ///////////////////////// ///////////////////////// Coordinates ///////////////////////// /*! \brief Constructs a Coordinates object with \a index_start and \a index_end indices. The indices define a region in the \l Sequence. They are sorted so as to ensure that index_start <= index_end. */ Coordinates::Coordinates(int index_start, int index_end) { if(index_start > index_end) { m_start = index_end; m_end = index_start; } else { m_start = index_start; m_end = index_end; } } /*! \brief Constructs a Coordinates object as a copy of \a other. After assigning \a other to this Coordinates, the indices are sorted so as to ensure that index_start <= index_end. */ Coordinates::Coordinates(const Coordinates &other) : m_start(other.m_start), m_end(other.m_end) { int temp; if(m_start > m_end) { temp = m_end; m_end = m_start; m_start = temp; } } /*! \brief Destructs this Coordinates instance. */ Coordinates::~Coordinates() { } /*! \brief Set the start index to \a value. Ensures that m_start is <= m_end. */ void Coordinates::setStart(int value) { if(value > m_end) { m_start = m_end; m_end = value; } else { m_start = value; } } /*! \brief Returns the member start index. */ int Coordinates::start() const { return m_start; } /*! \brief Increments by one unit the member end index. */ void Coordinates::incrementEnd() { ++m_end; } /*! \brief Set the end index to \a value. Ensures that m_start is <= m_end. */ void Coordinates::setEnd(int value) { m_end = value; } /*! \brief Returns the member end index. */ int Coordinates::end() const { return m_end; } /*! \brief Returns the number of monomer codes in the region described by this Coordinates instance. \code return (m_end - m_start + 1) \endcode */ int Coordinates::length() const { return (m_end - m_start + 1); } /*! \brief Returns a string with the region described by this Coordinates instance. The values are the member indices and the format is \code [125--259] \endcode */ QString Coordinates::indicesAsText() const { QString text = QString("[%1--%2]").arg(m_start).arg(m_end); return text; } /*! \brief Returns a string with the region described by this Coordinates instance. The values are the member indices \e{incremented} by one unit (thus being \e{positions} and not \e{indices}) and the format is \code [126--260] \endcode */ QString Coordinates::positionsAsText() const { QString text = QString("[%1--%2]").arg(m_start + 1).arg(m_end + 1); return text; } /*! \brief Resets the member indices to 0. */ void Coordinates::reset() { m_start = 0; m_end = 0; } /*! \class MsXpS::libXpertMass::CoordinateList \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \brief The CoordinateList class provides a list of \l Coordinates allocated instances. The Coordinates instances are allocated on the heap and stored as a QList. */ /*! \variable int MsXpS::libXpertMass::CoordinateList::m_comment \brief A comment string. */ ///////////////////////// CoordinateList ///////////////////////// ///////////////////////// CoordinateList ///////////////////////// ///////////////////////// CoordinateList ///////////////////////// ///////////////////////// CoordinateList ///////////////////////// ///////////////////////// CoordinateList ///////////////////////// /*! \brief Constructs a CoordinateList using \a comment and \a list. The \l Coordinates instances in \a list are duplicated and stored in this CoordinateList. */ CoordinateList::CoordinateList(QString comment, QList *list) : m_comment(comment) { if(list) { for(int iter = 0; iter < list->size(); ++iter) { Coordinates *iterCoordinates = list->at(iter); Coordinates *coordinates = new Coordinates(*iterCoordinates); append(coordinates); } } } /*! \brief Constructs a CoordinateList as a copy of \a other. The copy is a deep copy, with all the \l Coordinates instances in \a other duplicated and stored in this CoordinateList. */ CoordinateList::CoordinateList(const CoordinateList &other) : QList(), m_comment(other.m_comment) { for(int iter = 0; iter < other.size(); ++iter) { Coordinates *iterCoordinates = other.at(iter); Coordinates *coordinates = new Coordinates(*iterCoordinates); append(coordinates); } } /*! \brief Destructs this CoordinateList. The \l Coordinates instances are freed. */ CoordinateList::~CoordinateList() { // The members of the list were allocated with new()... qDeleteAll(begin(), end()); clear(); } /*! \brief Assigns to this CoordinateList the members of \a other. The copy involves duplicationg all the Coordinates objects in \a other and all other members. Returns a reference to this CoordinateList instance. */ CoordinateList & CoordinateList::operator=(const CoordinateList &other) { if(&other == this) return *this; m_comment = other.m_comment; empty(); for(int iter = 0; iter < other.size(); ++iter) { Coordinates *iterCoordinates = other.at(iter); Coordinates *coordinates = new Coordinates(*iterCoordinates); append(coordinates); } return *this; } /*! \brief Adds a copy of \a coordinates to this CoordinateList instance. \note This CoordinateList's list of Coordinates instances is first emptied, essentially replacing its contents with a copy of \a coordinates. */ void CoordinateList::setCoordinates(const Coordinates &coordinates) { empty(); Coordinates *newCoordinates = new Coordinates(coordinates); append(newCoordinates); } /*! \brief Allocates a copy of each Coordinates instance in \a list and adds it to this CoordinateList instance. \note This CoordinateList's list of Coordinates instances is first emptied, essentially replacing its contents with a copy of those in \a list. */ void CoordinateList::setCoordinates(const CoordinateList &list) { empty(); qDebug() << "The list of Coordinates:" << list.size(); for(int iter = 0; iter < list.size(); ++iter) { Coordinates *coordinates = new Coordinates(*list.at(iter)); qDebug() << "Created new copy of Coordinates:" << coordinates->indicesAsText(); append(coordinates); } } /*! \brief Add a copy of \a coordinates this CoordinateList instance. */ void CoordinateList::appendCoordinates(const Coordinates &coordinates) { Coordinates *newCoordinates = new Coordinates(coordinates); append(newCoordinates); } /*! \brief Creates the Coordinates instances based on \a coordinates_string and add them to this CoordinateList's list of Coordinates. \note This CoordinateList's list of Coordinates instances is first emptied The format of the \a coordinates_string is \code "[228-246]" \endcode if there are not multiple regions. If there are multiple regions (for example when a cross-link exists), the format changes to account for the multiple regions: \code "[228-246][276-282][247-275]" \endcode \note It is expected that the values in the coordinates_string are \e positions strings and not \e indices. Returns the count of added Coordinates instances or -1 if an error occurred. */ int CoordinateList::setCoordinates(const QString &coordinates_string) { // We get a string in the form [xxx-yyy] (there can be more than // one such element, if cross-linked oligomers are calculated. // "[228-246][276-282][247-275]". Because that string comes from // outside of massXpert, it is expected that it contains positions // and not indexes. So we have to decrement the start and end // values by one. // qDebug() <<__FILE__ << __LINE__ // << "string:" << string; // Whatever will happen, we wanto set coordinates, not append, // thus first empty(). empty(); if(!coordinates_string.contains('[') || !coordinates_string.contains(']') || !coordinates_string.contains('-')) return -1; QStringList coordinateList = coordinates_string.split(']', Qt::SkipEmptyParts); // qDebug() << __FILE__ << __LINE__ // << "coordinateList:" << coordinateList; for(int iter = 0; iter < coordinateList.size(); ++iter) { QString coordinates = coordinateList.at(iter); coordinates = coordinates.remove('['); QStringList positionList = coordinates.split('-'); // qDebug() << __FILE__ << __LINE__ // << "positionList:" << positionList; if(positionList.size() != 2) { // Error. // qDebug() << __FILE__<< __LINE__ // << "error: return -1"; return -1; } // At this point we should have two numeric values in the the // positionList. bool ok = false; int start = positionList.at(0).toInt(&ok); // The index might well be 0, but the, ok should be true. if(!--start && !ok) return -1; ok = false; int end = positionList.at(1).toInt(&ok); if(!--end && !ok) return -1; Coordinates *newCoordinates = 0; if(start > end) newCoordinates = new Coordinates(end, start); else newCoordinates = new Coordinates(start, end); append(newCoordinates); } QString text = positionsAsText(); // qDebug() << __FILE__ << __LINE__ // << "positionsAsText: " << text; return size(); } /*! \brief Sets the comment to \a text. */ void CoordinateList::setComment(QString text) { m_comment = text; } /*! \brief Returns the comment. */ QString CoordinateList::comment() const { return m_comment; } /*! \brief Searches all the Coordinates that have the smallest m_start value. Searches all the Coordinates instances in this CoordinateList that share the same Coordinates::m_start value that is actually the smallest such value in the list. Each found Coordinates instance's index in this CoordinateList is added to \a index_list. \note \a index_list is first emptied. Returns the count of Coordinates instances added to \a index_list. \sa rightMostCoordinates(), isLeftMostCoordinates(), isRightMostCoordinates */ int CoordinateList::leftMostCoordinates(QList &index_list) const { if(isEmpty()) return 0; while(!index_list.isEmpty()) index_list.removeFirst(); int leftMostValue = first()->start(); for(int iter = 0; iter < size(); ++iter) { Coordinates *coordinates = at(iter); int start = coordinates->start(); if(start < leftMostValue) leftMostValue = start; } // At this point we now what's the leftmost index. We can use that // index to now search for all the items that are also leftmost. for(int iter = 0; iter < size(); ++iter) { if(at(iter)->start() == leftMostValue) index_list.append(iter); } return index_list.size(); } /*! \brief Returns true if \a coordinates is the left-most Coordinates in this CoordinateList. false otherwise. */ bool CoordinateList::isLeftMostCoordinates(Coordinates *coordinates) const { Q_ASSERT(coordinates); // Are the coordinates the leftmost coordinates of *this // CoordinateList ? int value = coordinates->start(); for(int iter = 0; iter < size(); ++iter) { Coordinates *coordinates = at(iter); if(value > coordinates->start()) return false; } return true; } /*! \brief Searches all the Coordinates that have the greatest m_end value. Searches all the Coordinates instances in this CoordinateList that share the same Coordinates::m_end value that is actually the greatest such value in the list. Each found Coordinates instance's index in this CoordinateList is added to \a index_list. \note \a index_list is first emptied. Returns the count of Coordinates instances added to \a index_list. \sa leftMostCoordinates(), isLeftMostCoordinates(), isRightMostCoordinates */ int CoordinateList::rightMostCoordinates(QList &index_list) const { if(isEmpty()) return 0; while(!index_list.isEmpty()) index_list.removeFirst(); int rightMostValue = first()->end(); for(int iter = 0; iter < size(); ++iter) { Coordinates *coordinates = at(iter); int end = coordinates->end(); if(end > rightMostValue) rightMostValue = end; } // At this point we now what's the rightmost index. We can use // that index to now search for all the items that are also // rightmost. for(int iter = 0; iter < size(); ++iter) { if(at(iter)->end() == rightMostValue) index_list.append(iter); } return index_list.size(); } /*! \brief Returns true if \a coordinates is the right-most Coordinates in this CoordinateList. false otherwise. */ bool CoordinateList::isRightMostCoordinates(Coordinates *coordinates) const { Q_ASSERT(coordinates); // Are the coordinates the rightmost coordinates of *this // CoordinateList ? int value = coordinates->end(); for(int iter = 0; iter < size(); ++iter) { Coordinates *coordinates = at(iter); if(value < coordinates->end()) return false; } return true; } /*! \brief Returns true if \a index is found to be between the start and end indices of at least one Coordinates instance in this CoordinateList, false otherwise. */ bool CoordinateList::encompassIndex(int index) const { for(int iter = 0; iter < size(); ++iter) { Coordinates *coordinates = at(iter); if(index >= coordinates->start() && index <= coordinates->end()) return true; } return false; } /*! \brief Returns true if at least two Coordinates instances overlap. Two Coordinates instances overlap if the second's m_start member is less than the first's m_end and greater than the first's m_start. \sa encompassIndex() */ bool CoordinateList::overlap() const { // Return true if there are overlapping regions in this // coordinate list. if(size() <= 1) return false; for(int iter = 0; iter < size(); ++iter) { Coordinates coords1 = *at(iter); int start1 = coords1.start(); int end1 = coords1.end(); for(int jter = 0; jter < size(); ++jter) { // Do not compare one item to itself. if(jter == iter) continue; Coordinates coords2 = *at(jter); int start2 = coords2.start(); if(start2 <= end1 && start2 >= start1) return true; } } return false; } /*! \brief Returns a string documenting the Coordinates in this CoordinateList. Each Coordinates instance is described like the following with the values being the indices m_start and m_end: \code [156-350] \endcode \sa positionsAsText() */ QString CoordinateList::indicesAsText() const { QString text; for(int iter = 0; iter < size(); ++iter) { Coordinates *coordinates = at(iter); text += QString("[%1-%2]").arg(coordinates->start()).arg(coordinates->end()); } return text; } /*! \brief Returns a string documenting the Coordinates in this CoordinateList. Each Coordinates instance is described like the following with the values being the indices m_start+1 and m_end+1: \code [157-351] \endcode \note The values reported are not \e indices, but \e positions. \sa indicesAsText() */ QString CoordinateList::positionsAsText() const { QString text; for(int iter = 0; iter < size(); ++iter) { Coordinates *coordinates = at(iter); text += QString("[%1-%2]") .arg(coordinates->start() + 1) .arg(coordinates->end() + 1); } return text; } /*! \brief Clears this CoordinateList of all its Coordinates. The Coordinates instances are \c{free}'d. */ void CoordinateList::empty() { qDeleteAll(begin(), end()); clear(); } /*! \brief Outputs a string listing all the Coordinates using qDebug(). */ void CoordinateList::debugPutStdErr() { qDebug() << __FILE__ << __LINE__ << "CoordinateList:"; QString text; for(int iter = 0; iter < size(); ++iter) { Coordinates *coordinates = at(iter); text += coordinates->indicesAsText(); } qDebug() << __FILE__ << __LINE__ << text; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/CrossLink.cpp000664 001750 001750 00000062032 14647465366 022616 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "Polymer.hpp" #include "CrossLink.hpp" namespace MsXpS { namespace libXpertMass { ///////////////////////////// CrossLink //////////////////////// ///////////////////////////// CrossLink //////////////////////// ///////////////////////////// CrossLink //////////////////////// ///// Look down for CrossLinkList /*! \class MsXpS::libXpertMass::CrossLink \inmodule libXpertMass \ingroup PolChemDefAqueousChemicalReactions \inheaderfile CrossLink.hpp \brief The CrossLink class provides abstractions to work with a cross-link entity between \l Monomer instances. The notion of a cross-link is that it is a chemical reaction that involves at least two monomers in a \l Polymer or in an \l Oligomer sequence. Polymer sequences might contain more than one CrossLink and these are stored as a \l CrossLinkList. */ /*! \enum MsXpS::libXpertMass::CrossLinkEncompassed This enum type specifies the manner in which a sequence region in a \l Polymer or in an \l Oligomer contains fully, or partially not does not contain all the monomers involved in a CrossLink: \value CROSS_LINK_ENCOMPASSED_NO The region does not contain any \l Monomer involved in a CrossLink. \value CROSS_LINK_ENCOMPASSED_PARTIAL The region contains one or more \l{Monomer}s involved in a CrossLink but not all. \value CROSS_LINK_ENCOMPASSED_FULL All the \l{Monomer}s involved in the CrossLink are contained in the \l Polymer or \l Oligomer region. */ /*! \variable MsXpS::libXpertMass::CrossLink::mp_polymer \brief The \l Polymer instance of which this CrossLink is part. This CrossLink does not own the Polymer. */ /*! \variable MsXpS::libXpertMass::CrossLink::m_monomerList \brief The list of \l Monomer instances that are involved in the formation of this CrossLink. The monomers are the \l Monomer instances found in the mp_polymer \l Polymer instance. This CrossLink does not own these \l{Monomer}s. The reason the CrossLink lists the involved \l Monomer instances as pointers to these very monomers in the polymer sequence is that in this way, even if the sequence is edited the cross-link does not loose the relation to the monomers. The only way that the sequence editing modifies a CrossLink instance is by removing a \l Monomer instance involved in the CrossLink. If that occurs, the CrossLink is informed and its destructed. */ /*! \variable MsXpS::libXpertMass::CrossLink::m_comment \brief The comment that might be associated to this CrossLink. */ /*! \brief Constructs a CrossLink instance \list \li \a pol_chem_def_csp: the polymer chemistry definition (\l PolChemDef). \li \a polymer: the \l Polymer instance in which this CrossLink was formed. \li \a name: the name of this CrossLink instance. \li \a formula: the \l Formula that describes the reaction that is the basis of the chemical reaction leading to the formation of this CrossLink. \li \a comment: a comment that might be associated to this CrossLink. \endlist */ CrossLink::CrossLink(PolChemDefCstSPtr pol_chem_def_csp, Polymer *polymer, const QString &name, const QString &formula, const QString &comment) : CrossLinker(pol_chem_def_csp, name, formula), mp_polymer(polymer), m_comment(comment) { } /*! \brief Constructs a CrossLink instance \list \li \a cross_linker: CrossLinker instance used to initialize this CrossLink. \li \a polymer: the \l Polymer instance in which this CrossLink was formed \li \a comment: a comment that might be associated to this CrossLink \endlist */ CrossLink::CrossLink(const CrossLinker &cross_linker, Polymer *polymer, const QString &comment) : CrossLinker(cross_linker), mp_polymer(polymer), m_comment(comment) { } /*! \brief Constructs a CrossLink instance as a copy of \a other. */ CrossLink::CrossLink(const CrossLink &other) : CrossLinker(other), mp_polymer(other.mp_polymer), m_comment(other.m_comment) { } /*! \brief Destructs this CrossLink instance. No entity needs to be destructed, since the \l Monomer instances in the list are not owned by this CrossLinker instance. */ CrossLink::~CrossLink() { } /*! \brief Sets \a monomer to this CrossLink at \a index of the member list of Monomer instances, effectively replacing the \a monomer instance currently at that index. \a monomer cannot be nullptr and \a index cannot be out-of-bounds. This CrossLinker instance does not take ownership of \a monomer. Returns true. */ bool CrossLink::setMonomerAt(Monomer *monomer, int index) { Q_ASSERT(monomer); Q_ASSERT(index >= 0 && index < m_monomerList.size()); m_monomerList.replace(index, monomer); return true; } /*! \brief Adds \a monomer to the member list of Monomer instances. \a monomer cannot be nullptr. Returns true. */ bool CrossLink::appendMonomer(const Monomer *monomer) { Q_ASSERT(monomer); m_monomerList.append(monomer); return true; } /*! \brief Returns the monomer at \a index in the member list of \l Monomer instances. \a index cannot be out-of-bounds. */ const Monomer * CrossLink::monomerAt(int index) { Q_ASSERT(index >= 0 && index < m_monomerList.size()); return m_monomerList.at(index); } /*! \brief Removes the monomer at \a index in the member list of \l Monomer instances. \a index cannot be out-of-bounds. Returns true. */ bool CrossLink::removeMonomerAt(int index) { Q_ASSERT(index < m_monomerList.size()); m_monomerList.removeAt(index); return true; } /*! \brief Set \a polymer as the \l Polymer sequence in which this CrossLink has been formed. \a polymer cannot be nullptr. */ void CrossLink::setPolymer(Polymer *polymer) { Q_ASSERT(polymer); mp_polymer = polymer; } /*! \brief Returns the \l Polymer instance in which this CrossLink has been formed. */ Polymer * CrossLink::polymer() { return mp_polymer; } /*! \brief Set the member comment to \a comment. */ void CrossLink::setComment(const QString &comment) { m_comment = comment; } /*! \brief Returns the member comment. */ const QString & CrossLink::comment() const { return m_comment; } /*! \brief Returns true if this CrossLink instance and \a other are identical, false otherwise. */ bool CrossLink::operator==(const CrossLink &other) const { int tests = 0; tests += CrossLinker::operator==(other); tests += mp_polymer == other.mp_polymer; if(m_monomerList.size() != other.m_monomerList.size()) return false; for(int iter = 0; iter < m_monomerList.size(); ++iter) { if(m_monomerList.at(iter) != other.m_monomerList.at(iter)) return false; } tests += m_comment == other.m_comment; if(tests < 3) return false; else return true; } /*! \brief Sets to the member monomer list all the \l Monomer instances referenced in \a text as indices to monomer in the \l Polymer sequence. \a text contains a list of \l Monomer instance indices separated by ';' characters. The corresponding monomers found in the member Polymer are copied to the member list of \l Monomer instances, effectively documenting a CrossLink in that member Polymer sequence. This CrossLink instance does not own the \l Monomer instances pointers, as they effectively point to monomer that belong to the member \l Polymer. */ int CrossLink::populateMonomerList(QString text) { // We do not own the monomers pointed to by the pointers in // m_monomerList. while(m_monomerList.size()) m_monomerList.removeFirst(); QStringList stringList = text.split(';', Qt::SkipEmptyParts, Qt::CaseSensitive); // There must be at least 2 monomers to make a crossLink ! if(stringList.size() < 2) return -1; for(int iter = 0; iter < stringList.size(); ++iter) { QString value = stringList.at(iter); bool ok = false; int index = value.toInt(&ok); if(!index && !ok) return -1; Q_ASSERT(index >= 0 && index < mp_polymer->size()); m_monomerList.append(mp_polymer->at(index)); } return m_monomerList.size(); } /*! \brief Returns the member list of \l Monomer instances. */ QList * CrossLink::monomerList() { return &m_monomerList; } /*! \brief Lists in \a list the indices of all the monomers involved in this CrossLink instance. Returns the count of indices set to \a list. */ int CrossLink::monomerIndexList(QList *list) { if(!list) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); int count = 0; QList localIndexList; for(int iter = 0; iter < m_monomerList.size(); ++iter) { const Monomer *monomer = m_monomerList.at(iter); int index = mp_polymer->monomerIndex(monomer); // qDebug() << __FILE__ << __LINE__ // << "Other monomer index:" << index; localIndexList.append(index); ++count; } std::sort(localIndexList.begin(), localIndexList.end()); // Now that we have the minIndex and the maxIndex of the region // involved by the cross-link, we can fill-in the integer list // with all the values contained between these two borders.i for(int iter = localIndexList.first(); iter <= localIndexList.last(); ++iter) { // If we had a cross-link between monomers [4] and [10] of the // polymer, then the indices appended to the list would be 4, // 5, 6, 7, 8, 9 and 10. list->append(iter); } return count; } /*! \brief Returns a string containing a ';'-separated list of the indices of all the \l Monomer instances involved in this CrossLink. */ QString CrossLink::monomerIndexText() { QString text = ";"; for(int iter = 0; iter < m_monomerList.size(); ++iter) { const Monomer *monomer = m_monomerList.at(iter); int index = mp_polymer->monomerIndex(monomer); text += QString("%1;").arg(index); } return text; } /*! \brief Returns a string containing a ';'-separated list of the positions of all the \l Monomer instances involved in this CrossLink. */ QString CrossLink::monomerPosText() { QString text = ";"; for(int iter = 0; iter < m_monomerList.size(); ++iter) { const Monomer *monomer = m_monomerList.at(iter); int index = mp_polymer->monomerIndex(monomer); text += QString("%1;").arg(index + 1); } return text; } /*! \brief Returns the index of \a monomer found in the member \l Polymer sequence that is involved in this CrossLink, or -1 if that is not found. */ int CrossLink::involvesMonomer(const Monomer *monomer) const { Q_ASSERT(monomer); for(int iter = 0; iter < m_monomerList.size(); ++iter) { if(m_monomerList.at(iter) == monomer) { return iter; } } return -1; } /*! \brief Returns the first \l Monomer instance listed in the member list of monomers. If the list is empty; returns nullptr. */ const Monomer * CrossLink::firstMonomer() const { if(!m_monomerList.size()) return nullptr; return m_monomerList.first(); } /*! \brief Tells if this CrossLink instance is encompassed (partially or fully) or not at all by the Polymer sequence region defined by the [\a start - \a end ] \l Monomer index range. The count of monomers involved in this cross-link that: \list \li Are contained in the range is stored in \a in. \li Are not contained in the range is stored in \a out. \endlist If all the monomers are listed as \a in, then this CrossLink is fully encompassed by the Polymer sequence region defined by the [\a start - \a end ] \l Monomer index range and CROSS_LINK_ENCOMPASSED_FULL is returned. If all the monomers are listed as \a out, then this CrossLink is not at all encompassed by the Polymer sequence region and CROSS_LINK_ENCOMPASSED_NO is returned. If both kinds of monmers were found, then CROSS_LINK_ENCOMPASSED_PARTIAL is returned. */ int CrossLink::encompassedBy(int start, int end, int *in, int *out) { // Iterate in the list of monomers, and for each monomer check if // their index is contained in the stretch. int countIn = 0; int countOut = 0; int localStart = std::min(start, end); int localEnd = std::max(start, end); for(int iter = 0; iter < m_monomerList.size(); ++iter) { const Monomer *monomer = m_monomerList.at(iter); int index = mp_polymer->monomerIndex(monomer); if(index >= localStart && index <= localEnd) ++countIn; else ++countOut; } Q_ASSERT((countIn + countOut) == m_monomerList.size()); if(in) *in = countIn; if(out) *out = countOut; if(countOut && countIn) return CROSS_LINK_ENCOMPASSED_PARTIAL; if(countOut) return CROSS_LINK_ENCOMPASSED_NO; if(countIn) return CROSS_LINK_ENCOMPASSED_FULL; return CROSS_LINK_ENCOMPASSED_NO; } /*! \brief Tells if this CrossLink instance is encompassed (partially or fully) or not at all by various \l Coordinates instances in the \a coordinate_list \l CoordinateList. The count of monomers involved in this cross-link that: \list \li Are contained in the regions defined in \a coordinate_list is stored in \a in. \li Are not contained in the range is stored in \a out. \endlist If all the monomers are listed as \a in, then this CrossLink is fully encompassed by the Polymer sequence regions defined in \a coordinate_list and CROSS_LINK_ENCOMPASSED_FULL is returned. If all the monomers are listed as \a out, then this CrossLink is not at all encompassed by the Polymer sequence region and CROSS_LINK_ENCOMPASSED_NO is returned. If both kinds of monmers were found, then CROSS_LINK_ENCOMPASSED_PARTIAL is returned. */ int CrossLink::encompassedBy(const CoordinateList &coordinate_list, int *in, int *out) { // Iterate in the list of monomers involved in *this crossLink, // and for each monomer check if their index is contained in any // of the Coordinates [start--end] of the coordinateList passed as // parameter. int countIn = 0; int countOut = 0; for(int iter = 0; iter < m_monomerList.size(); ++iter) { const Monomer *monomer = m_monomerList.at(iter); int index = mp_polymer->monomerIndex(monomer); // qDebug() << __FILE__ << __LINE__ // << " monomer at index:" << index // << "tested:"; // Is the index encompassed by any of the Coordinates of the // CoordinateList ? bool countedIn = false; for(int jter = 0; jter < coordinate_list.size(); ++jter) { Coordinates *coordinates = coordinate_list.at(jter); if(index >= coordinates->start() && index <= coordinates->end()) { ++countIn; countedIn = true; // qDebug() << __FILE__ << __LINE__ // << " encompassedBy YES by coordinates" // << "coordinates->start()" << coordinates->start() // << "coordinates->end()" << coordinates->end(); break; } else { // qDebug() << __FILE__ << __LINE__ // << " encompassedBy NO by coordinates" // << "coordinates->start()" << coordinates->start() // << "coordinates->end()" << coordinates->end(); } } if(!countedIn) ++countOut; } Q_ASSERT((countIn + countOut) == m_monomerList.size()); if(in) *in = countIn; if(out) *out = countOut; if(countOut && countIn) return CROSS_LINK_ENCOMPASSED_PARTIAL; if(countOut) return CROSS_LINK_ENCOMPASSED_NO; if(countIn) return CROSS_LINK_ENCOMPASSED_FULL; return CROSS_LINK_ENCOMPASSED_NO; } /*! \brief Returns true if this CrossLink instance validates successfully, false otherwise. The validation is successful if: \list \li The count of \l Monomer instances listed in the member list is > 1. \li The member \l Polymer instance exists. \li The count of \l Monomer instances listed in the member list is equal to the number of \l Modif instance in the \l CrossLinker base class. \li If the list of \l Modif instances in the \l CrossLinker base class is not empty, there must be a colinearity between the order in which these instances are listed and the order in which the \l Monomer instances are listed in the member list. The monomer instance must be a target of the modification. \endlist */ bool CrossLink::validate() { if(m_monomerList.size() <= 1) { qDebug() << __FILE__ << __LINE__ << "A crossLink with a single monomer " "cannot be created."; return false; } if(mp_polymer == nullptr) return false; // If the crossLinker has at least one modif in its definition, then // we have to make sure that the modification has as one of its // targets the corresponding monomer in the list of monomer // pointers. // Indeed, there is some logic here. Either the m_modifList in the // parent class CrossLinker contains no item, in which case // everything is fine, or it does contain items. In the latter case, // then, the number of items must match the number of monomers being // crosslinked, and then we get to know which modif is attributable // to which monomer, hence the possibility of a check. if(m_modifList.size()) { if(m_modifList.size() != m_monomerList.size()) { qDebug() << __FILE__ << __LINE__ << QObject::tr( "The number of modification items " "does not match the number of " "monomers. This is an error."); return false; } // At this point, we can make the check for each modif: for(int iter = 0; iter < m_modifList.size(); ++iter) { Modif *modif = m_modifList.at(iter); const Monomer *monomer = m_monomerList.at(iter); if(!monomer->isModifTarget(*modif)) { // The monomer is not target for the modification that is // implied by the crossLinking. qDebug() << __FILE__ << __LINE__ << QObject::tr( "The monomer '%1' is not target of " "crossLinker modif '%2'") .arg(monomer->name()) .arg(modif->name()); return false; } } } return true; } /*! \brief Calculates the masses of this CrossLink and set them anew to the member masses (\l Ponderable base class). Returns true. */ bool CrossLink::calculateMasses() { return CrossLinker::calculateMasses(); } /*! \brief Adds the member m_mono and m_avg masses to \a mono and \a avg, as compounded by the \a times factor. Returns true. */ bool CrossLink::accountMasses(double *mono, double *avg, int times) { return CrossLinker::accountMasses(mono, avg, times); } /*! \brief Adds the member m_mono and m_avg masses to \a ponderable, as compounded by the \a times factor. Returns true. */ bool CrossLink::accountMasses(Ponderable *ponderable, int times) { return CrossLinker::accountMasses(ponderable, times); } /*! \brief Returns an allocated string describing this CrossLink instance. */ QString * CrossLink::prepareResultsTxtString() { QString *text = new QString(); *text += QObject::tr( "\nCross-link:\n" "===============\n" "Cross-linker name: %1\n" "Cross-link comment: %2\n") .arg(m_name) .arg(m_comment); for(int iter = 0; iter < m_monomerList.size(); ++iter) { const Monomer *monomer = m_monomerList.at(iter); int index = mp_polymer->monomerIndex(monomer); *text += QObject::tr("Partner %1: %2 at position: %3\n") .arg(iter + 1) .arg(monomer->code()) .arg(index + 1); } return text; } ///////////////////////////// CrossLinkList //////////////////////// ///////////////////////////// CrossLinkList //////////////////////// ///////////////////////////// CrossLinkList //////////////////////// /*! \class MsXpS::libXpertMass::CrossLinkList \inmodule libXpertMass \ingroup PolChemDefAqueousChemicalReactions \brief The CrossLinkList class provides a list of CrossLink instances. */ /*! \variable MsXpS::libXpertMass::CrossLinkList::m_name \brief The name of the list of \l CrossLink instances. */ /*! \variable MsXpS::libXpertMass::CrossLinkList::mp_polymer \brief The \l Polymer instance of which this CrossLinkList is part. This CrossLinkList does not own the Polymer. */ /*! \variable MsXpS::libXpertMass::CrossLinkList::m_comment \brief The comment that might be associated to this CrossLinkList. */ /*! \brief Constructs an empty CrossLinkList instance. */ CrossLinkList::CrossLinkList() { } /*! \brief Constructs a CrossLinkList instance. \list \li \a name: the name of this CrossLinkList instance. \li \a polymer: the \l Polymer instance in which this CrossLinkList was formed. \li \a comment: a comment that might be associated to this CrossLinkList. \endlist */ CrossLinkList::CrossLinkList(const QString &name, Polymer *polymer, const QString &comment) : m_name(name), mp_polymer(polymer), m_comment(comment) { } /*! \brief Constructs a CrossLinkList instance as a copy of \a other. */ CrossLinkList::CrossLinkList(const CrossLinkList *other) : QList(*other), m_name(other->m_name), mp_polymer(other->mp_polymer), m_comment(other->m_comment) { // And now copy all the items from other in here: for(int iter = 0; iter < other->size(); ++iter) { CrossLink *crossLink = other->at(iter); CrossLink *crossLinkNew = new CrossLink(crossLink->getPolChemDefCstSPtr(), crossLink->polymer(), crossLink->name(), crossLink->formula(), crossLink->comment()); append(crossLinkNew); } } /*! \brief Destructs thisCrossLinkList instance. Deletes all the CrossLink instances in this list. */ CrossLinkList::~CrossLinkList() { qDeleteAll(begin(), end()); clear(); } /*! \brief Assigns \a other to this CrossLinkList instance. Returns a reference to this CrossLinkList instance. */ CrossLinkList & CrossLinkList::operator=(const CrossLinkList &other) { if(&other == this) return *this; m_name = other.m_name; mp_polymer = other.mp_polymer; m_comment = other.m_comment; // Now empty and clear this list. qDeleteAll(begin(), end()); clear(); // And now copy all the items from other in here: for(int iter = 0; iter < other.size(); ++iter) { CrossLink *crossLink = other.at(iter); CrossLink *crossLinkNew = new CrossLink(*crossLink); append(crossLinkNew); } return *this; } /*! \brief Sets the \a name. */ void CrossLinkList::setName(QString name) { if(!name.isEmpty()) m_name = name; } /*! \brief Returns the name. */ QString CrossLinkList::name() { return m_name; } /*! \brief Sets the \a comment. */ void CrossLinkList::setComment(QString comment) { if(!comment.isEmpty()) m_comment = comment; } /*! \brief Returns the comment. */ const QString & CrossLinkList::comment() const { return m_comment; } /*! \brief Sets the \a polymer. */ void CrossLinkList::setPolymer(Polymer *polymer) { mp_polymer = polymer; } /*! \brief Gets the polymer. */ const Polymer * CrossLinkList::polymer() const { return mp_polymer; } int CrossLinkList::crossLinksInvolvingMonomer(const Monomer *monomer, QList *list) const { int count = 0; for(int iter = 0; iter < size(); ++iter) { CrossLink *crossLink = at(iter); for(int jter = 0; jter < crossLink->monomerList()->size(); ++jter) { const Monomer *iterMonomer = crossLink->monomerList()->at(jter); if(monomer == iterMonomer) { count++; if(list) list->append(iter); } } } return count; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/CrossLinker.cpp000664 001750 001750 00000040115 14647465366 023143 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "CrossLinker.hpp" #include "PolChemDef.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::CrossLinker \inmodule libXpertMass \ingroup PolChemDefAqueousChemicalReactions \inheaderfile CrossLinker.hpp \brief The CrossLinker class provides abstractions to define the chemical basis of a cross-linking reaction. The notion of a cross-linker is that it is the description of the chemical reaction that is carried out by \l{Monomer}s in a \l Polymer or in an \l Oligomer sequence to form a \l CrossLink. There are two different kinds of chemical entities potentially involved in the description of a CrossLinker: \list \a Modif instances that are applied to \l Monomer instances. \a the \l Formula base class that completes the chemical reactions described by the \l Modif instances. \endlist */ /*! \variable MsXpS::libXpertMass::CrossLinker::m_modifList \brief The list of \l Modif instances that describe the reaction (or the reactions) involved in the formation of a CrossLink between \l{Monomer}s of a \l Polymer (or of an \l Oligomer) sequence. */ /*! \brief Constructs a CrossLinker instance. \list \li \a pol_chem_def_csp: the polymer chemistry definition (\l PolChemDef). \li \a name: the name of this CrossLinker. \li \a formula: the \l Formula that potentially complements the description of the reaction that is the basis of this CrossLinker (used to initialize the Formula base class). \endlist */ CrossLinker::CrossLinker(PolChemDefCstSPtr pol_chem_def_csp, const QString &name, const QString &formula) : PolChemDefEntity(pol_chem_def_csp, name), Formula(formula) { } /*! \brief Constructs a CrossLinker instance as a copy of \a other. */ CrossLinker::CrossLinker(const CrossLinker &other) : PolChemDefEntity(other), Formula(other), Ponderable(other) { for(int iter = 0; iter < other.m_modifList.size(); ++iter) m_modifList.append(other.m_modifList.at(iter)); } /*! \brief Destructs this CrossLinker instance */ CrossLinker::~CrossLinker() { // We do not own the modifications in m_modifList! } /*! \brief Sets the Modif at index \a index to \a modif. \a index cannot be out-of-bounds. Returns true. */ bool CrossLinker::setModifAt(Modif *modif, int index) { Q_ASSERT(modif); Q_ASSERT(index >= 0 && index < m_modifList.size()); m_modifList.replace(index, modif); return true; } /*! \brief Adds \a modif to this CrossLinker instance. Returns true. */ bool CrossLinker::appendModif(Modif *modif) { Q_ASSERT(modif); m_modifList.append(modif); return true; } /*! \brief Returns the \l Modif instance at \a index. \a index cannot be out-of-bounds. */ const Modif * CrossLinker::modifAt(int index) const { Q_ASSERT(index >= 0 && index < m_modifList.size()); return m_modifList.at(index); } /*! \brief Removes the \l Modif instance at \a index. \a index cannot be out-of-bounds. Returns true. */ bool CrossLinker::removeModifAt(int index) { Q_ASSERT(index < m_modifList.size()); m_modifList.removeAt(index); return true; } /*! \brief Returns the \l Formula. */ QString CrossLinker::formula() const { return Formula::toString(); } /*! \brief Returns the list of \l Modif instances. */ QList & CrossLinker::modifList() { return m_modifList; } /*! \brief Returns true if a \l Modif instance by \a modif_name is in the list of Modif instances, false otherwise. */ int CrossLinker::hasModif(const QString &modif_name) { // Iterate in the list of modifications, and check if one of these // has the name passed as argument. If so return the index of the // found item in the list, otherwise return -1. for(int iter = 0; iter < m_modifList.size(); ++iter) { Modif *modif = m_modifList.at(iter); if(modif->name() == modif_name) return iter; } return -1; } /*! \brief Assigns \a other to this CrossLinker instance. Returns a reference to this CrossLinker instance. */ CrossLinker & CrossLinker::operator=(const CrossLinker &other) { if(&other == this) return *this; PolChemDefEntity::operator=(other); Formula::operator =(other); Ponderable::operator =(other); while(!m_modifList.isEmpty()) m_modifList.removeFirst(); for(int iter = 0; iter < other.m_modifList.size(); ++iter) m_modifList.append(other.m_modifList.at(iter)); return *this; } /*! \brief Returns true if this CrossLinker and \a other are identical. */ bool CrossLinker::operator==(const CrossLinker &other) const { int tests = 0; tests += PolChemDefEntity::operator==(other); tests += Formula::operator==(other); tests += Ponderable::operator==(other); if(m_modifList.size() != other.m_modifList.size()) return false; // FIXME: at written now, we compare pointers, not objects. // There should be an indirection. for(int iter = 0; iter < m_modifList.size(); ++iter) { if(m_modifList.at(iter) != other.m_modifList.at(iter)) return false; } if(tests < 3) return false; else return true; } /*! \brief Returns true if this CrossLinker instance is found in the member polymer chemistry definition, false otherwise. */ int CrossLinker::isNameKnown() { const QList &refList = mcsp_polChemDef->crossLinkerList(); if(m_name.isEmpty()) return -1; for(int iter = 0; iter < refList.size(); ++iter) { if(refList.at(iter)->m_name == m_name) return iter; } return -1; } /*! \brief Returns the index of a CrossLinker found in the \a cross_linker_list list of CrossLinker instances by name \a name, -1 otherwise. If \a other is non-nullptr, the found CrossLinker instance is copied into it. */ int CrossLinker::isNameInList(const QString &name, const QList &cross_linker_list, CrossLinker *other) { CrossLinker *crossLinker = 0; if(name.isEmpty()) return -1; for(int iter = 0; iter < cross_linker_list.size(); ++iter) { crossLinker = cross_linker_list.at(iter); Q_ASSERT(crossLinker); if(crossLinker->m_name == name) { if(other != nullptr) *other = *crossLinker; return iter; } } return -1; } /*! \brief Returns true if this CrossLinker instance validates successfully, false otherwise. The validation involves the following: \list \li The member polymer chemistry definition must exist. \li The name cannot be empty. \li The formula (if not empty) must validate successfully agains the member polymer chemistry definition. \li The list of \l Modif instances must contain either no or more than two Modif instances. In the latter case, the Modif instances must be found in the member polymer chemistry definition and must validate successfully. \endlist */ bool CrossLinker::validate() { if(!mcsp_polChemDef) return false; if(m_name.isEmpty()) return false; // Remember that the formula of the crosslinker might be empty because the // crosslinker might be fully accounted for by the modifications (below). if(!m_formula.isEmpty()) { Formula formula(m_formula); IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); if(!formula.validate(isotopic_data_csp)) return false; } // This is mainly a sanity check, as the pointers to Modif in the // list all point to modification objects in the polymer chemistry // definition, which have been validated already... // The validation actually is simple, it might be that there are NO // modifs, or if this is not the case there must be at least // 2. Indeed, either none of the monomers in the crosslink get // modified, or each one has to be(otherwise we cannot know which // modif goes to which monomer). int size = m_modifList.size(); if(size > 0 && size < 2) return false; for(int iter = 0; iter < size; ++iter) { // Make sure the modification is known to the polymer chemistry // definition. This check is not performed by the modif's // validation function. if(!mcsp_polChemDef->referenceModifByName(m_modifList.at(iter)->name())) return false; if(!m_modifList.at(iter)->validate()) return false; } return true; } /*! \brief Returns true if the mass calculations for this CrossLinker instance are performed without error, false otherwise. The calculation involved accounting masses for the Modifs (if any) and for the Formula (if non-empty). The member masses (\l Ponderable base class) are updated. */ bool CrossLinker::calculateMasses() { // qDebug() << "Calculating masses for the cross-link"; m_mono = 0; m_avg = 0; // Account the masses of the formula parent class. IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); // Here is a special case because the formula for the crosslinker might be // empty because the crosslinker might be accounted for only but the // modifications below. if(!m_formula.isEmpty()) { // qDebug() << "The formula of the crosslinker is:" << m_formula //<< "accounting for its masses."; if(!Formula::accountMasses(isotopic_data_csp, &m_mono, &m_avg)) return false; } else { // qDebug() << "The formula of the crosslinker is empty."; } // Now, for each modif in the crossLinker, have to account their // mass. for(int iter = 0; iter < m_modifList.size(); iter++) { Modif *modif = m_modifList.at(iter); // qDebug() << "Accounting for crosslinker modif's" << modif->name() //<< "masses"; if(!modif->accountMasses(&m_mono, &m_avg)) { qDebug() << "Failed to account for masses of the cross-linker's modif."; return false; } } // qDebug() << "At this point, the masses of the CrossLinker are:" << m_mono //<< "/" << m_avg; return true; } /*! \brief Copies the member masses (\l Ponderable base class) to \a mono and \a avg if these are non-nullptr. Upon copying, the masses are multiplied by the compounding factor \a times. Returns true. */ bool CrossLinker::accountMasses(double *mono, double *avg, int times) { // qDebug() << "Accounting masses for crossliker -- mono:" << m_mono //<< "avg:" << m_avg; if(mono) *mono += m_mono * times; if(avg) *avg += m_avg * times; return true; } /*! \brief Copies the member masses (\l Ponderable base class) to \a ponderable. \a ponderable cannot be nullptr. Upon copying, the masses are multiplied by the compounding factor \a times. Returns true. */ bool CrossLinker::accountMasses(Ponderable *ponderable, int times) { Q_ASSERT(ponderable); ponderable->rmono() += m_mono * times; ponderable->ravg() += m_avg * times; return true; } /*! \brief Returns true if parsing of XML \a element, using a \a{version}ed function was successful, false otherwise. The data in \a element are set to this CrossLinker instance if they validated successfully, thus essentially initializing this CrossLinker instance. */ bool CrossLinker::renderXmlClkElement(const QDomElement &element, int version) { if(element.tagName() != "clk") return false; if(version == 1) { // no-op version = 1; } QDomElement child; if(element.tagName() != "clk") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); if(m_name.isEmpty()) return false; // qDebug() << "The crosslinker name:" << m_name; child = child.nextSiblingElement("formula"); // Here, it is possible that the formula element be empty because the // crosslinker might be accounted for by using the modifications in it. if(!child.isNull()) { QString formula = child.text(); if(!formula.isEmpty()) { if(!Formula::renderXmlFormulaElement(child)) return false; } // else // qDebug() << "The formula element of crosslinker is empty."; } else qDebug() << "The formula element of crosslinker is null."; const QList &refList = mcsp_polChemDef->modifList(); // At this point there might be 0, 1 or more "modifname" elements. child = child.nextSiblingElement("modifname"); while(!child.isNull()) { // qDebug() << "Now handling CrossLinker modif:" << child.text(); int index = Modif::isNameInList(child.text(), refList); if(index == -1) { qDebug() << "Failed to parse one modification of the crosslink:" << m_name; return false; } else { // qDebug() //<< "Found the CrossLinker modification in the reference list:" //<< m_name; } // Modif *modif = mcsp_polChemDef->modifList().at(index); // qDebug() << "The found modif has name:" << modif->name() //<< "and masses:" << modif->mono() << "/" << modif->avg(); m_modifList.append(mcsp_polChemDef->modifList().at(index)); child = child.nextSiblingElement("modifname"); } if(!calculateMasses()) { qDebug() << __FILE__ << __LINE__ << "Failed to calculate masses for crossLinker" << m_name; return false; } if(!validate()) return false; return true; } /*! \brief Formats this CrossLinker instance in a heap-allocated string to be used as an XML element in the polymer chemistry definition. The formatting of the XML element takes into account \a offset and \a indent by prepending the string with \a offset * \a indent character substring. \a indent defaults to two spaces. Returns a dynamically allocated string that needs to be freed after use. */ QString * CrossLinker::formatXmlClkElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } /* We are willing to create an node that should look like this: * Phosphorylation -H+H2PO3 Phosphorylation Acetylation * */ *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); *string += QString("%1%2\n").arg(lead).arg(m_formula); for(int iter = 0; iter < m_modifList.size(); ++iter) { *string += QString("%1%2\n") .arg(lead) .arg(m_modifList.at(iter)->name()); } // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/CrossLinkerSpec.cpp000664 001750 001750 00000015526 14647465366 023766 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "CrossLinkerSpec.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::CrossLinkerSpec \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile CrossLinkerSpec.hpp \brief The CrossLinkerSpec class provides the specification about how \l CrossLinker objects are represented. The CrossLinkerSpec class specifies how a \l CrossLinker object is represented graphically, mainly by connecting its name to a graphics SVG file that is located in the Polymer chemistry definition directory. That connection is performed in the "cross_linker_dictionary" dictionary file itself also located in the polymer chemistry definition directory. Its contents look like this: \code DisulfideBond%disulfidebond-cross-link.svg CFP-chromophore%cfp-chromophore.svg \endcode The \c{DisulfideBond%disulfidebond-cross-link.svg} line indicates that, when a CrossLinker object by name "DisulfideBond" is to be rendered graphically, the corresponding vignette to be used is in the file named "disulfidebond-cross-link.svg" in the polymer chemistry definition directory. */ /*! \variable int MsXpS::libXpertMass::CrossLinkerSpec::m_name \brief The name of the cross-linker. */ /*! \variable int MsXpS::libXpertMass::CrossLinkerSpec::m_vector \brief The filename of the vector representation of the cross-linker. */ /*! \variable int MsXpS::libXpertMass::CrossLinkerSpec::m_sound \brief The file name of the sound for the cross-linker. */ /*! \brief Constructs a CrossLinkerSpec instance. */ CrossLinkerSpec::CrossLinkerSpec() { } /*! \brief Destructs this CrossLinkerSpec instance. */ CrossLinkerSpec::~CrossLinkerSpec() { } /*! \brief Sets the cross-linker \a name. */ void CrossLinkerSpec::setName(const QString &name) { m_name = name; } /* \brief Gets the cross-linker name. */ const QString & CrossLinkerSpec::name() { return m_name; } /*! \brief Sets the vector image file name to \a vector. */void CrossLinkerSpec::setVector(const QString &vector) { m_vector = vector; } /*! \brief Returns the vector image file name. */ const QString & CrossLinkerSpec::vector() { return m_vector; } /*! \brief Sets the file name of the CrossLinkerSpec's \a sound file. */ void CrossLinkerSpec::setSound(const QString &sound) { m_sound = sound; } /*! \brief Returns the file name of the CrossLinkerSpec's sound file. */ const QString & CrossLinkerSpec::sound() { return m_sound; } /*! \brief Parses the \a file_path dictionary containing the \l CrossLinker specifications. At the moment the file has this format: \code DisulfideBond%disulfidebond-cross-link.svg CFP-chromophore%cfp-chromophore.svg \endcode Upon parsing, the \a cross_linker_spec_list of CrossLinkerSpec instances will be filled with instances created on the basis of each parsed line in the file. Returns true if the parsing was successful, false otherwise. */ bool CrossLinkerSpec::parseFile(QString &file_path, QList *cross_linker_spec_list) { CrossLinkerSpec *crossLinkerSpec = 0; qint64 lineLength; QString line; QString temp; char buffer[1024]; int percentSignIdx = 0; Q_ASSERT(cross_linker_spec_list != 0); if(file_path.isEmpty()) return false; QFile file(file_path); if(!file.open(QFile::ReadOnly)) return false; // The lines we have to parse are of the following type: // DisulfideBon%disulfidebond.svg // Any line starting with ' ' or '#' are not parsed. // Get the first line of the file. Next we enter in to a // while loop. lineLength = file.readLine(buffer, sizeof(buffer)); while(lineLength != -1) { // The line is now in buffer, and we want to convert // it to Unicode by setting it in a QString. line = buffer; // The line that is in line should contain something like: // DisulfideBon%disulfidebond.svg // // Note, however that lines beginning with either '\n'(newline) // or '#' are comments. // Remove all the spaces from the borders: Whitespace means any // character for which QChar::isSpace() returns true. This // includes the ASCII characters '\t', '\n', '\v', '\f', '\r', // and ' '. line = line.trimmed(); if(line.isEmpty() || line.startsWith('#', Qt::CaseInsensitive)) { lineLength = file.readLine(buffer, sizeof(buffer)); continue; } // Now some other checks. Remember the format of the line: // DisulfideBon%disulfidebond.svg percentSignIdx = line.indexOf('%', 0, Qt::CaseInsensitive); if(percentSignIdx == -1 || line.count('%', Qt::CaseInsensitive) > 2) return false; // OK, we finally can allocate a new CrossLinkerSpec *. crossLinkerSpec = new CrossLinkerSpec(); crossLinkerSpec->m_name = line.left(percentSignIdx); // Remove from the line the "DisulfideBond%" substring, as we // do not need it anymore. line.remove(0, percentSignIdx + 1); // Now we can go on with the graphics files stuff. At this point // all that remains up to the end of the line is the filename // with extension .svg. if(!line.endsWith(".svg", Qt::CaseSensitive)) { delete crossLinkerSpec; return false; } // Ok that's done, we can get the vector graphics filename: crossLinkerSpec->m_vector = line; cross_linker_spec_list->append(crossLinkerSpec); // Continue to next line to further parse the file. lineLength = file.readLine(buffer, sizeof(buffer)); } return true; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Envemind.cpp000664 001750 001750 00000004770 14647465366 022461 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ***************************************************************************** * This specific code is a port to C++ of the Envemind Python code by Radzinski * and colleagues of IsoSpec fame (Lacki, Startek and company :-) * * See https://github.com/PiotrRadzinski/envemind. * ***************************************************************************** * * END software license */ #include #include #include "Envemind.hpp" namespace MsXpS { namespace libXpertMass { Envemind::Envemind() { } Envemind::Envemind(const pappso::Trace &trace) : m_trace(trace) { } Envemind::Envemind(const Envemind &other) { m_trace = other.m_trace; } Envemind::~Envemind() { } Envemind & Envemind::operator=(const Envemind &other) { m_trace = other.m_trace; return *this; } double Envemind::monoIsotopicMassPrediction() { double mass = 0.0; return mass; } double Envemind::getMostAbundant(bool *ok) { if(ok == nullptr) qFatal("Pointer cannot be nullptr."); if(!m_trace.size()) { *ok = false; return 0.0; } std::vector::iterator iter = maxYDataPoint(m_trace.begin(), m_trace.end()); if(iter == m_trace.end()) qFatal("Failed to find the most intense data point."); return iter->x; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Formula.cpp000664 001750 001750 00000153561 14650435554 022312 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "globals.hpp" #include "Formula.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::Formula \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile Formula.hpp \brief The Formula class provides sophisticated abstractions to work with formulas. There are two peculiarities with this Formula implementation: \list \li The \e{Actionformula} \li The \e{Title} \endlist \e{\b{The actionformula}}: the main textual element in this Formula class is the \e{actionformula} (member m_formula). A formula is the description of the atomic composition of a compound. For example, the string \e{C2H6} is a formula. While the previous \e{C2H6} example describes a static chemical object, a Formula can also describe a dynamic chemical event, like a reaction, by describing what chemical entities are gained by the molecule during the chemical reaction (the "plus" component of the actionformula) and what chemical entities are lost by the molecule (the "minus" component). For example, an acetylation reaction can be described by the loss of \e{H2O} with gain of \e{CH3COOH}. The net chemical gain on the molecule will be \e{CH3CO}. In this example, one would thus define an actionformula in the following way: \e{-H20+CH3COOH}. The "minus" formula associated with the '-' action accounts for the leaving group of the reaction, while the "plus" formula associated with the '+' action accounts for the entering group of the reaction. Note that there is no limitation on the amount of such actions, as one could have an action formula like this \e{-H+CO2-H2O+C2H6}. An \e{actionformula} does not need to have any action sign (+ or -), and if it has no sign, the actionformula is a plus-signed formula by default, which is what the reader would expect for a standard formula. \e{\b{The title}}: the actionformula may be documented with a title: a prefix text enclosed in double quotes, like the following: \e{"Decomposed adenine" C5H4N5 +H}. This documentation element is called the \e{title}. Note that the presence of a title in a formula does not change anything to its workings as long as the \e{title} is effectively enclosed in double quotes. The title is by no means a required textual element for an actionformula to work correctly. It is mainly used in some particular context, like the calculator. An actionformula behaves exactly the same as a simple formula from an end user perspective. When created, a Formula has its formula string containing the formula (be it a pure formula or an actionformula). Behind the scenes, functions are called to separate all the '+'-associated formulas from all the '-'-associated formulas so that masses are correctly associated to each "leaving" or "entering" chemical groups. Formulas that are '-'-associated are stored in the so-called "minus formula", while '+'-associated ones are stored in the "plus formula". Note that all the formulas in Formula are QString objects. Upon parsing of the formula, the m_minusFormula and the m_plusFormula members are populated with formulas (in the \e{-H+CO2-H2O+C2H6} example, the "minus formula" would contain "H1H2O", while the "plus formula" would contain "CO2C2H6") and these are next used to account for the net formula. */ /*! \enum MsXpS::libXpertMass::FormulaSplitResult This enum type specifies the result of an actionformula parsing process: \value NOT_SET The value was not set \value FAILURE The splitting work failed \value HAS_PLUS_COMPONENT The action formula has a plus component \value HAS_MINUS_COMPONENT The action formula has a minus component \value HAS_BOTH_COMPONENTS The action formula has both plus and minus components */ /*! \variable MsXpS::libXpertMass::Formula::m_formula \brief String representing the actionformula. */ /*! \variable MsXpS::libXpertMass::Formula::m_plusFormula \brief String representing the "plus" component of the main m_formula. This member datum is set upon parsing of m_formula. */ /*! \variable MsXpS::libXpertMass::Formula::m_minusFormula \brief String representing the "minus" component of the main m_minusFormula. This member datum is set upon parsing of m_formula. */ /*! \variable MsXpS::libXpertMass::Formula::m_symbolCountMap \brief Map relating the symbols (as keys) found in the formula and their counts (atoms, in fact, as values). Note that the count value type is double, which allows for interesting things to be done with Formula. Also, the count value might be negative if the net mass of an actionformula is negative. \sa Formula::splitActionParts() */ /*! \variable MsXpS::libXpertMass::Formula::mcsp_isotopicData \brief The isotopic data that the formula is based on. */ /*! \variable int MsXpS::libXpertMass::Formula::m_forceCountIndex \brief The m_forceCountIndex tells if when defining a chemical composition formula, the index '1' is required when the count of a symbol is not specified and thus considered to be '1' by default. If true, water should be described as "H2O1", if false, it might be described as "H2O". */ /*! \brief Constructs a formula initialized with the \a formula actionformula string. \a formula needs not be an actionformula, but it might be an actionformula. This formula gets copied into the \c m_formula without any processing afterwards. */ Formula::Formula(const QString &formula) : m_formula{formula} { } /*! \brief Constructs a formula as a copy of \a other. The copy is deep with \e{all} the data copied from \a other to the new formula. There is no processing afterwards. */ Formula::Formula(const Formula &other) : m_formula{other.m_formula}, m_plusFormula{other.m_plusFormula}, m_minusFormula{other.m_minusFormula} { m_symbolCountMap = other.m_symbolCountMap; } /*! \brief Destructs this formula. There is nothing to be delete explicitly. */ Formula::~Formula() { } /*! \brief Initializes all the member data of this formula by copying to it the data from \a other. The copy is deep with \e{all} the data from \a other being copied into this formula. There is no processing afterwards. */ Formula & Formula::operator=(const Formula &other) { if(&other == this) return *this; m_formula = other.m_formula; m_plusFormula = other.m_plusFormula; m_minusFormula = other.m_minusFormula; m_symbolCountMap = other.m_symbolCountMap; return *this; } /*! Sets the actionformula \a formula to this Formula. The \a formula is copied to this m_formula. No other processing is performed afterwards. */ void Formula::setFormula(const QString &formula) { m_formula = formula; } /*! Sets the actionformula from \a formula to this Formula. The actionformula from \a formula is copied to this m_formula. No processing is performed afterwards. */ void Formula::setFormula(const Formula &formula) { m_formula = formula.m_formula; } /*! \brief Appends to this formula the \a formula. The \a formula string is appended to the m_formula without check. No processing is performed afterwards. The \a formula is copied to a temporary formula that is stripped of its spaces, both in the formula and before and after it before it is appended to m_formula. */ void Formula::appendFormula(const QString &formula) { QString local_formula = formula.simplified(); // Remove the spaces before appending. local_formula.remove(QRegularExpression("\\s+")); m_formula.append(local_formula); } /*! \brief Returns the actionformula. */ QString Formula::toString() const { return m_formula; } /*! \brief Sets m_forceCountIndex to \a forceCountIndex. When a formula contains a chemical element in a single copy, it is standard practice to omit the count index: H2O is the same as H2O1. If forceCountIndex is true, then the formula has to be in the form H2O1. This is required for some specific calculations. */ void Formula::setForceCountIndex(bool forceCountIndex) { m_forceCountIndex = forceCountIndex; } /*! \brief Clears \e{all} the formula member data. */ void Formula::clear() { m_formula.clear(); m_plusFormula.clear(); m_minusFormula.clear(); m_symbolCountMap.clear(); } /*! \brief Sets the m_plusFormula formula to \a formula. */ void Formula::setPlusFormula(const QString &formula) { m_plusFormula = formula; } /*! \brief Returns the m_plusFormula formula. */ const QString & Formula::plusFormula() const { return m_plusFormula; } /*! \brief Sets the m_minusFormula formula to \a formula. */ void Formula::setMinusFormula(const QString &formula) { m_minusFormula = formula; } /*! \brief Returns the m_minusFormula formula. */ const QString & Formula::minusFormula() const { return m_minusFormula; } /*! Returns true if this Formula and \a other are identical, false otherwise. The comparison is only performed on the m_formula actionformula, not on any other member data that derived from processing of m_formula. */ bool Formula::operator==(const Formula &other) const { return (m_formula == other.m_formula); } /*! Returns true if this Formula and \a other are different, false otherwise. The comparison is only performed on the m_formula actionformula, not on any other member data that derived from processing of m_formula. */ bool Formula::operator!=(const Formula &other) const { return (m_formula != other.m_formula); } /*! \brief Calls actions(const QString &formula) on this Formula's actionformula m_formula. Returns '+' if it only contains "plus" elements or '-' if at least one "minus" element was found. If m_formula contains no sign at all, then it is considered to contain only '+' elements and the function returns '+'. If at least one element is found associated to a '-', then the "minus" action prevails and the function returns '-'. \sa actions(const QString &formula), splitActionParts() */ QChar Formula::actions() const { return actions(m_formula); } /*! \brief Returns '+' if \a formula only contains "plus" elements or '-' if at least one "minus" element was found. If \a formula contains no sign at all, then it is considered to contain only '+' elements and the function returns '+'. If at least one element is found associated to a '-', then the "minus" action prevails and the function returns '-'. \sa actions(), splitActionParts() */ QChar Formula::actions(const QString &formula) { double minusCount = formula.count('-', Qt::CaseInsensitive); return (minusCount == 0 ? '+' : '-'); } /*! \brief Removes the title from the member actionformula. The \e{title} of a formula is the string, enclosed in double quotes, that is located in front of the actual chemical actionformula. This function removes that \e{title} string from the member actionformula using a QRegularExpression. Returns the count of removed characters. */ int Formula::removeTitle() { int length = m_formula.length(); m_formula.remove(QRegularExpression("\".*\"")); return (length - m_formula.length()); } /*! \brief Removes \e{all} the space characters from the member actionformula. Spaces can be placed anywhere in formula for more readability. However, it might be required that these character spaces be removed. This function does just this, using a QRegularExpression. Returns the number of removed characters. */ int Formula::removeSpaces() { int length = m_formula.length(); // We want to remove all the possibly-existing spaces. m_formula.remove(QRegularExpression("\\s+")); // Return the number of removed characters. return (length - m_formula.length()); } /*! \brief Tells the "plus" ('+') and "minus" ('-') parts in the member actinformula. Parses the m_formula actionformula and separates all the minus components of that actionformula from all the plus components. The different components are set to their corresponding formula (m_minusFormula and m_plusFormula). At the end of the split work, each sub-formula (plus- and/or minus-) is actually parsed for validity, using the \a isotopic_data_csp IsotopicData as reference. If \a times is not 1, then the accounting of the plus/minus formulas is compounded by this factor. If \a store is true, the symbol/count data obtained while parsing of the plus/minus actionformula components are stored (m_symbolCountMap). If \a reset is true, the symbol/count data are reset before the parsing of the actionformula. Setting this parameter to false may be useful if the caller needs to "accumulate" the accounting of the formulas. The parsing of the actionformula is performed by performing its deconstruction using m_subFormulaRegExp. Returns MsXpS::libXpertMass::FormulaSplitResult::FAILURE if the splitting failed, MsXpS::libXpertMass::FormulaSplitResult::HAS_PLUS_COMPONENT if at least one of the components of the actionformula was found to be of type plus, MsXpS::libXpertMass::FormulaSplitResult::HAS_MINUS_COMPONENT if at least one of the components of the actionformula was found to be of type minus. The result can be an OR'ing of both values (MsXpS::libXpertMass::FormulaSplitResult::HAS_BOTH_COMPONENTS) in the m_formula actionformula. */ int Formula::splitActionParts(IsotopicDataCstSPtr isotopic_data_csp, double times, bool store, bool reset) { if(!isotopic_data_csp->size()) qFatal("Programming error. The isotopic data cannot be empty."); int formula_split_result = static_cast(FormulaSplitResult::NOT_SET); // We are asked to put all the '+' components of the formula // into corresponding formula and the same for the '-' components. m_plusFormula.clear(); m_minusFormula.clear(); if(reset) m_symbolCountMap.clear(); // Because the formula that we are analyzing might contain a title // and spaces , we first remove these. But make a local copy of // the member datum. QString formula = m_formula; // qDebug() << "splitActionParts before working:" // << "m_formula:" << m_formula; // One formula can be like this: // "Decomposed adenine" C5H4N5 +H // The "Decomposed adenine" is the title // The C5H4N5 +H is the formula. formula.remove(QRegularExpression("\".*\"")); // We want to remove all the possibly-existing spaces. formula.remove(QRegularExpression("\\s+")); #if 0 // This old version tried to save computing work, but it then anyways // calls for parsing of the formula which is the most computing-intensive // part. Thus we now rely on the regular expression to simultaneously // check the syntax, divide the formula into its '+' and '-' parts, // and finally check that the symbol is known to the isotopic data. ^ // If the formula does not contain any '-' character, then we // can approximate that all the formula is a '+' formula, that is, a // plusFormula: if(actions() == '+') { // qDebug() << "Only plus actions."; m_plusFormula.append(formula); // At this point we want to make sure that we have a correct // formula. Remove all the occurrences of the '+' sign. m_plusFormula.replace(QString("+"), QString("")); if(m_plusFormula.length() > 0) { // qDebug() << "splitActionParts: with m_plusFormula:" << // m_plusFormula; if(!parse(isotopic_data_csp, m_plusFormula, times, store, reset)) return FormulaSplitResult::FAILURE; else return FORMULA_SPLIT_PLUS; } } // End of // if(actions() == '+') // If we did not return at previous block that means there are at least one // '-' component in the formula. we truly have to iterate in the formula... #endif // See the explanations in the header file for the member datum // m_subFormulaRegExp and its use with globalMatch(). One thing that is // important to see, is that the RegExp matches a triad : [ [sign or not] // [symbol] [count] ], so, if we have, says, formula "-H2O", it would match: // First '-' 'H' '2' // Second 'O' // The problem is that at second match, the algo thinks that O1 is a + // formula, while in fact it is part of a larger minus formula: -H2O. So we // need to check if, after a '-' in a formula, there comes or not a '+'. If so // we close the minus formula and start a plus formula, if not, we continue // adding matches to the minus formula. // qDebug() << "Now regex parsing with globalMatch feature of formula:" // << formula; bool was_minus_formula = false; for(const QRegularExpressionMatch &match : subFormulaRegExp.globalMatch(formula)) { QString sub_match = match.captured(0); qDebug() << "Entering [+-]? sub-match:" << sub_match; QString sign = match.captured(1); QString symbol = match.captured(2); QString count_as_string = match.captured(3); double count_as_double = 1.0; if(!count_as_string.isEmpty()) { bool ok = false; count_as_double = count_as_string.toDouble(&ok); if(!ok) return static_cast(FormulaSplitResult::FAILURE); } else { count_as_string = "1"; } // Check that the symbol is known to the isotopic data. // qDebug() << "The symbol:" << symbol << "with count:" << // count_as_double; if(!isotopic_data_csp->containsSymbol(symbol)) { // qDebug() << "The symbol is not known to the Isotopic data table."; return static_cast(FormulaSplitResult::FAILURE); } // Determine if there was a sign if(sign == "-") { // qDebug() << "Appending found minus formula:" // << QString("%1%2").arg(symbol).arg(count_as_string); m_minusFormula.append( QString("%1%2").arg(symbol).arg(count_as_string)); formula_split_result |= static_cast(FormulaSplitResult::HAS_MINUS_COMPONENT); if(store) { // qDebug() << "Accounting symbol / count pair:" << symbol << "/" // << count_as_double * static_cast(times); accountSymbolCountPair( symbol, -1 * count_as_double * static_cast(times)); // qDebug() << " ...done."; } // Let next round know that we are inside a minus formula group. was_minus_formula = true; } else if(sign.isEmpty() && was_minus_formula) { // qDebug() << "Appending new unsigned formula to the minus formula:" // << QString("%1%2").arg(symbol).arg(count_as_string); m_minusFormula.append( QString("%1%2").arg(symbol).arg(count_as_string)); if(store) { // qDebug() << "Accounting symbol / count pair:" << symbol << "/" // << count_as_double * static_cast(times); accountSymbolCountPair( symbol, -1 * count_as_double * static_cast(times)); // qDebug() << " ...done."; } // Let next round know that we are still inside a minus formula group. was_minus_formula = true; } else // Either there was a '+' sign or there was no sign, but // we were not continuing a minus formula, thus we are parsing // a true '+' formula. { // qDebug() << "Appending found plus formula:" // << QString("%1%2").arg(symbol).arg(count_as_string); m_plusFormula.append( QString("%1%2").arg(symbol).arg(count_as_string)); formula_split_result |= static_cast(FormulaSplitResult::HAS_PLUS_COMPONENT); if(store) { // qDebug() << "Accounting symbol / count pair:" << symbol << "/" // << count_as_double * static_cast(times); accountSymbolCountPair( symbol, count_as_double * static_cast(times)); // qDebug() << " ...done."; } was_minus_formula = false; } } // qDebug() << "Formula" << formula << "splits" //<< "(+)" << m_plusFormula << "(-)" << m_minusFormula; return formula_split_result; } /*! \brief Accounts for \a symbol and corresponding \a count in the member map. The m_symbolCountMap relates each atom (chemical element) symbol with its occurrence count as encountered while parsing the member actionformula. If the symbol was not encountered yet, a new key/value pair is created. Otherwise, the count value is updated. Returns the new count status for \a symbol. */ double Formula::accountSymbolCountPair(const QString &symbol, double count) { // We receive a symbol and we need to account for it count count in the member // symbol count map (count might be < 0). // Try to insert the new symbol/count pairinto the map. Check if that was done // or not. If the result.second is false, then that means the the insert // function did not perform because a pair by that symbol existed already. In // that case we just need to increment the count for the the pair. // qDebug() << "Accounting symbol:" << symbol << "for count:" << count; double new_count = 0; std::pair::iterator, bool> res = m_symbolCountMap.insert(std::pair(symbol, count)); if(!res.second) { // qDebug() << "The symbol was already in the symbol/count map. with // count:" // << res.first->second; // One pair by that symbol key existed already, just update the count and // store that value for reporting. res.first->second += count; new_count = res.first->second; // new_count might be <= 0. // qDebug() << "For symbol" << symbol << "the new count:" << new_count; } else { // qDebug() << "Symbol" << symbol // << "was not there already, setting the count to:" << count; // We just effectively added during the insert call above a new pair to // the map by key symbol with value count. new_count = count; } // We should check if the symbol has now a count of 0. In that case, we remove // the symbol altogether because we do not want to list naught symbols in the // final formula. if(!new_count) { // qDebug() << "For symbol" << symbol //<< "the new count is 0. Thus we erase the map item altogether."; m_symbolCountMap.erase(symbol); } // Update what's the text of the formula to represent what is in // atomCount list. // qDebug() << "Now computing the new formula as an elemental composition."; m_formula = elementalComposition(); // qDebug() << "... Done with a new elemental composition formula:" << // m_formula; return new_count; } /*! \brief Returns a reference to the atom symbol / count member map. */ const std::map Formula::getSymbolCountMap() const { return m_symbolCountMap; } /*! \brief Accounts the actionformula in \a text using \a isotopic_data_csp as reference data using \a times as a compounding factor. The \a text formula is converted into a temporary Formula and processed: \list \li First validate() is called on the temporary formula, with storage of the symbol/count data; \li The symbol/count data thus generated is used to update the member map. \endlist Returns the member symbol/count m_symbolCountMap size. */ std::size_t Formula::accountFormula(const QString &text, IsotopicDataCstSPtr isotopic_data_csp, double times) { // qDebug() << "Accounting in this formula:" << m_formula //<< "the external formula:" << text; // qDebug() << "Before having merged the external formula's map into this one, // " "this one has size:" //<< m_symbolCountMap.size(); // We get a formula as an elemental composition text string and we want to // account for that formula in *this formula. // First off, validate the text. Formula formula(text); // The formula is asked to validate with storage of the found symbol/count // pairs and with resetting of the previous contents of the symbol/count map. if(!formula.validate(isotopic_data_csp, true, true)) { qDebug() << "Formula:" << text << "failed to validate."; return -1; } // Now, for each item in the formula's symbol/count map, aggregate the found // data to *this symbol/count map. We'll have "merged" or "aggreated" the // other formula into *this one. std::map map_copy = formula.getSymbolCountMap(); // qDebug() //<< "The external formula, after validation has a symbol/count map of size:" //<< map_copy.size(); std::map::const_iterator iter = map_copy.cbegin(); std::map::const_iterator iter_end = map_copy.cend(); while(iter != iter_end) { accountSymbolCountPair(iter->first, iter->second * times); ++iter; } // qDebug() << "After having merged the external formula's map into this one, // " "this one has size:" //<< m_symbolCountMap.size(); // Update what's the text of the formula to represent what is in // atomCount list. m_formula = elementalComposition(); // qDebug() << "And now this formula has text: " << m_formula; return m_symbolCountMap.size(); } #if 0 Old version that parsed the actionformula char by char. bool Formula::checkSyntax(const QString &formula, bool forceCountIndex) { // Static function. // qDebug() << "Checking syntax with formula:" << formula; QChar curChar; bool gotUpper = false; bool wasSign = false; bool wasDigit = false; // Because the formula that we are analyzing might contain a title // and spaces , we first remove these. But make a local copy of // the member datum. QString localFormula = formula; // One formula can be like this: // "Decomposed adenine" C5H4N5 +H // The "Decomposed adenine" is the title // The C5H4N5 +H is the formula. localFormula.remove(QRegularExpression("\".*\"")); // We want to remove all the possibly-existing spaces. localFormula.remove(QRegularExpression("\\s+")); for(int iter = 0; iter < localFormula.length(); ++iter) { curChar = localFormula.at(iter); // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()" //<< "Current character:" << curChar; // FIXME One improvement that would ease modelling the Averagine would // be to silently allow double formula indices (that is, double atom // counts). They would not be compulsory if(curChar.category() == QChar::Number_DecimalDigit) { // We are parsing a digit. // We may not have a digit after a +/- sign. if(wasSign) return false; wasSign = false; wasDigit = true; continue; } else if(curChar.category() == QChar::Letter_Lowercase) { // Current character is lowercase, which means we are inside // of an atom symbol, such as Ca(the 'a') or Nob(either // 'o' or 'b'). Thus, gotUpper should be true ! if(!gotUpper) return false; // We may not have a lowercase character after a +/- sign. if(wasSign) return false; // Let the people know that we have parsed a lowercase char // and not a digit. wasSign = false; wasDigit = false; } else if(curChar.category() == QChar::Letter_Uppercase) { // Current character is uppercase, which means that we are // at the beginning of an atom symbol. // There are two cases: // 1. We are starting for the very beginning of the formula, and // nothing came before this upper case character. That's fine. // 2. We had previously parsed a segment of the formula, and in this // case, we are closing a segment. If the parameter // obligatoryCountIndex is true, then we need to ensure that the // previous element had an associated number, even it the count // element is 1. This is required for the IsoSpec stuff in the gui // programs. if(iter > 0) { if(forceCountIndex) { if(!wasDigit) { qDebug() << "Returning false because upper case char was not" "preceded by digit while not at the first char of " "the formula"; return false; } } } // Let the people know what we got: wasSign = false; gotUpper = true; wasDigit = false; } else { if(curChar != '+' && curChar != '-') return false; else { // We may not have 2 +/- signs in a raw. if(wasSign) return false; } wasSign = true; gotUpper = false; wasDigit = false; } } // end for (int iter = 0 ; iter < localFormula.length() ; ++iter) // Note that if we want an obligatory count index, then, at the end of the // formula, *compulsorily* we must have parsed a digit. if(forceCountIndex && !wasDigit) { qDebug() << "Returning false because the formula does not end with a digit."; return false; } // At this point we found no error condition. return true; } #endif /*! \brief Returns true if the member actionformula is syntactically valid, false otherwise. \sa checkSyntax(const QString &formula, bool force_count_index) */ bool Formula::checkSyntax() const { // The default formula is always m_formula. return checkSyntax(m_formula, m_forceCountIndex); } /*! \brief Returns true if the \a formula actionformula is syntactically valid, false otherwise. If \a force_count_index is true, the syntax check accounts for the requirement that all the symbols in the formula must be indexed, even if that symbol's count is 1. This means that H2O would not pass the check, while H2O1 would. The formula is first stripped of its title (if any), then all the spaces are removed. MsXpS::libXpertMass::Formula::subFormulaRegExp is then used to extract each "plus" and / or "minus" component while checking its syntactic validity. \note The syntax checking code does not verify that the actionformula is chemically valid, that is, the "Cz4" symbol / count pair would check even if the Cz chemical element does not exist. \sa validate() */ bool Formula::checkSyntax(const QString &formula, bool force_count_index) { // Because the formula that we are analyzing might contain a title // and spaces , we first remove these. But make a local copy of // the member datum. QString localFormula = formula; // One formula can be like this: // "Decomposed adenine" C5H4N5 +H // The "Decomposed adenine" is the title // The C5H4N5 +H is the formula. localFormula.remove(QRegularExpression("\".*\"")); // We want to remove all the possibly-existing spaces. localFormula.remove(QRegularExpression("\\s+")); // qDebug() << "The formula is:" << localFormula; // The raw formula might include: // +/- sign before the symbol // then the symbol (one uppercase any lowercase) // then the count as an integer or a double. for(const QRegularExpressionMatch &match : subFormulaRegExp.globalMatch(localFormula)) { QString full_match = match.captured(0); // qDebug() << "The full sub-match:" << full_match; QString sign = match.captured(1); QString symbol = match.captured(2); QString count_string = match.captured(3); if(!count_string.isEmpty()) { bool ok = false; // Verify that it correctly converts to double. count_string.toDouble(&ok); if(!ok) return false; } else { if(force_count_index) { qDebug() << "Error: symbol" << symbol << "has no index."; return false; } else { // qDebug() << "Symbol" << symbol // << "has no index but that is tolerated."; } } // qDebug() << "Sign:" << match.captured(1) << "Symbol:" << // match.captured(2) // << "Count:" << match.captured(3); } return true; } /*! Returns true if the formula validates successfully, false otherwise. The polymorphic function validate(IsotopicDataCstSPtr isotopic_data_csp, bool store, bool reset) is called with both arguments set to false. The validation of this Formula instance is performed against the \a isotopic_data_csp isotopic reference data. */ bool Formula::validate(IsotopicDataCstSPtr isotopic_data_csp) { return validate(isotopic_data_csp, false, false); } /*! Returns true if the formula validates successfully, false otherwise. The validation of the formula involves: \list \li Checking that the member actionformula is not empty. Returns false otherwise; \li Splitting the actionformula into "plus" and "minus" components (\l{splitActionParts}). If that steps fails, returns false; \li Verifying that both the m_plusFormula and the m_minusFormula are not empty. Returns false otherwise; \endlist If \a store is true, the symbol / count data obtained while splitting the "plus" and "minus" components of the actionformula are stored in the member m_symbolCountMap map. If \a reset is true, the member symbol / count is first reset. \a isotopic_data_csp are the isotopic data used as reference to ensure chemical validity of the formula components. */ bool Formula::validate(IsotopicDataCstSPtr isotopic_data_csp, bool store, bool reset) { if(isotopic_data_csp == nullptr || isotopic_data_csp.get() == nullptr) qFatal("Programming error. The isotopic data pointer cannot be nullptr."); if(!isotopic_data_csp->size()) qFatal("Programming error. The isotopic data cannot be empty."); qDebug() << "isotopic_data_csp.get():" << isotopic_data_csp.get(); if(!m_formula.size()) { qDebug() << "The formula is empty."; return false; } // qDebug() << "Now splitting formula" << m_formula << "into its action // parts."; int result = splitActionParts(isotopic_data_csp, 1, store, reset); if(result == static_cast(FormulaSplitResult::FAILURE)) { qDebug() << "Failed splitting the formula into its action parts."; return false; } // Both the action formulas cannot be empty. if(!m_plusFormula.size() && !m_minusFormula.size()) { qDebug() << "Both the plus and minus formulas are empty."; return false; } // qDebug() << "Success: the formula validated fine."; return true; } /*! \brief Accounts this formula's monoisotopic and average masses into \a mono and \a avg, using \a times as a compounding factor. The masses corresponding to the member actionformula m_formula are calculated first and then the \a mono and \a avg parameters are updated by incrementing their value with the calculated values. This incrementation might be compounded by that \a times factor. The masses of m_formula are computed using data from \a isotopic_data_csp. Returns true if no error was encountered, false otherwise. \sa splitActionParts() */ bool Formula::accountMasses(IsotopicDataCstSPtr isotopic_data_csp, double *mono, double *avg, double times) { // Note the 'times' param below that ensures we create proper symbol/count // map items by taking that compounding factor into account. qDebug() << qSetRealNumberPrecision(6) << "We get two mono and avg variables with values:" << *mono << "-" << *avg << "and times:" << times; if(isotopic_data_csp == nullptr) qFatal("Programming error. The pointer cannot be nullptr."); if(splitActionParts( isotopic_data_csp, times, true /* store */, true /* reset */) == static_cast(FormulaSplitResult::FAILURE)) return false; // qDebug() << "Formula::accountMasses:" //<< "after splitActionParts:" //<< "store: true ; reset: true" //<< "m_formula:" << m_formula << "text" << Formula::toString(); // At this point m_symbolCountMap has all the symbol/count pairs needed to // account for the masses. std::map::const_iterator iter = m_symbolCountMap.cbegin(); std::map::const_iterator iter_end = m_symbolCountMap.cend(); // for(auto item : m_symbolCountMap) // qDebug() << "One symbol count item:" << item.first << "/" << item.second; bool ok = false; while(iter != iter_end) { QString symbol = iter->first; // qDebug() << "Getting masses for symbol:" << symbol; if(mono != nullptr) { double mono_mass = isotopic_data_csp->getMonoMassBySymbol(iter->first, &ok); if(!ok) { qDebug() << "Failed to get the mono mass."; return false; } *mono += mono_mass * iter->second; } ok = false; if(avg != nullptr) { double avg_mass = isotopic_data_csp->getAvgMassBySymbol(iter->first, &ok); if(!ok) return false; *avg += avg_mass * iter->second; } ++iter; } return true; } /*! \brief Accounts this formula's monoisotopic and average masses into \a ponderable, using \a times as a compounding factor. This function uses \l{accountMasses()}. \sa splitActionParts() */ bool Formula::accountMasses(IsotopicDataCstSPtr isotopic_data_csp, Ponderable *ponderable, double times) { if(ponderable == nullptr) qFatal("Fatal error: pointer cannot be nullptr. Program aborted."); return accountMasses( isotopic_data_csp, &ponderable->rmono(), &ponderable->ravg(), times); //// Note the 'times' param below. // if(splitActionParts(isotopic_data_csp, times, true, true) == // FormulaSplitResult::FAILURE) // return false; //// At this point m_symbolCountMap has all the symbol/count pairs needed to //// account for the masses. // std::map::const_iterator iter = // m_symbolCountMap.cbegin(); std::map::const_iterator iter_end // = m_symbolCountMap.cend(); // while(iter != iter_end) //{ // double mono_mass = isotopic_data_csp->getMonoMassBySymbol(iter->first); // ponderable->rmono() += mono_mass * iter->second; // double avg_mass = isotopic_data_csp->getAvgMassBySymbol(iter->first); // ponderable->ravg() += avg_mass * iter->second; //++iter; //} return true; } //! Account the atoms in \c this \c m_formula. /*! \brief Accounts this Formula's actionformula m_formula in the symbol / count member m_symbolCountMap. Calls splitActionParts() to actually parse m_formula and account its components to m_symbolCountMap. The accounting of the symbol / count can be compounded by the \a times factor. While splitting the "plus" and "minus" components of the actionformula, their validity is checked against the reference isotopic data \a isotopic_data_csp. This function is used when processively accounting many different formulas into the symbol / count map. The formula is set to a new value and this function is called without resetting the symbol / count map, effectively adding formulas onto formulas sequentially. Returns true if no error was encountered, false otherwise. \sa splitActionParts(), Polymer::elementalComposition() */ bool Formula::accountSymbolCounts(IsotopicDataCstSPtr isotopic_data_csp, int times) { // Note the 'times' param below. if(splitActionParts(isotopic_data_csp, times, true, false) == static_cast(FormulaSplitResult::FAILURE)) return false; return true; } /*! \brief Returns a formula matching the contents of the symbol / count member map. The returned formula is formatted according to the IUPAC convention about the ordering of the chemical elements: CxxHxxNxxOxxSxxPxx. The "plus" components are output first and the "minus" components after. If \a symbol_count_pairs_p is not nullptr, each symbol / count pair is added to it. */ QString Formula::elementalComposition( std::vector> *symbol_count_pairs_p) const { // Iterate in the symbol count member map and for each item output the symbol // string accompanied by the corresponding count. Note that the count for any // given symbol might be negative. We want to craft an elemental composition // that accounts for "actions", that is a +elemental formula and a -elemental // formula. std::map::const_iterator iter = m_symbolCountMap.cbegin(); std::map::const_iterator iter_end = m_symbolCountMap.cend(); #if 0 qDebug() << "While computing the elemental composition corresponding to the " "symbol/count map:"; for(auto pair : m_symbolCountMap) qDebug().noquote() << "(" << pair.first << "," << pair.second << ")"; #endif QStringList negativeStringList; QStringList positiveStringList; while(iter != iter_end) { QString symbol = iter->first; double count = iter->second; if(count < 0) { negativeStringList.append( QString("%1%2").arg(symbol).arg(-1 * count)); } else { positiveStringList.append(QString("%1%2").arg(symbol).arg(count)); } ++iter; } // We want to provide a formula that lists the positive component // first and the negative component last. // Each positive/negative component will list the atoms in the // conventional order : CxxHxxNxxOxx and all the rest in // alphabetical order. // We want to provide for each positive and negative components of the // initial formula object, an elemental formula that complies with the // convention : first the C atom, next the H, N, O, S, P atoms and all the // subsequent ones in alphabetical order. // Sort the lists. negativeStringList.sort(); positiveStringList.sort(); // Thus we look for the four C, H, N, O, S,P atoms, and we create the // initial part of the elemental formula. Each time we find one // such atom we remove it from the list, so that we can later just // append all the remaining atoms, since we have sorted the lists // above. // The positive component // ====================== int symbol_index_in_list = 0; QString positiveComponentString; // Carbon symbol_index_in_list = positiveStringList.indexOf(QRegularExpression("C\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { positiveComponentString += positiveStringList.at(symbol_index_in_list); positiveStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("C", m_symbolCountMap.at("C"))); } // Hydrogen symbol_index_in_list = positiveStringList.indexOf(QRegularExpression("H\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { positiveComponentString += positiveStringList.at(symbol_index_in_list); positiveStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("H", m_symbolCountMap.at("H"))); } // Nitrogen symbol_index_in_list = positiveStringList.indexOf(QRegularExpression("N\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { positiveComponentString += positiveStringList.at(symbol_index_in_list); positiveStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("N", m_symbolCountMap.at("N"))); } // Oxygen symbol_index_in_list = positiveStringList.indexOf(QRegularExpression("O\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { positiveComponentString += positiveStringList.at(symbol_index_in_list); positiveStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("O", m_symbolCountMap.at("O"))); } // Sulfur symbol_index_in_list = positiveStringList.indexOf(QRegularExpression("S\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { positiveComponentString += positiveStringList.at(symbol_index_in_list); positiveStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("S", m_symbolCountMap.at("S"))); } // Phosphorus symbol_index_in_list = positiveStringList.indexOf(QRegularExpression("P\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { positiveComponentString += positiveStringList.at(symbol_index_in_list); positiveStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("P", m_symbolCountMap.at("P"))); } // Go on with all the other ones, if any... for(int iter = 0; iter < positiveStringList.size(); ++iter) { positiveComponentString += positiveStringList.at(iter); QRegularExpression regexp("([A-Z][a-z]*)(\\d*[\\.]?\\d*)"); QRegularExpressionMatch match = regexp.match(positiveStringList.at(iter)); if(match.hasMatch()) { QString symbol = match.captured(1); QString howMany = match.captured(2); bool ok = false; double count = howMany.toDouble(&ok); if(!count && !ok) qFatal( "Fatal error at %s@%d -- %s(). " "Failed to parse an atom count." "Program aborted.", __FILE__, __LINE__, __FUNCTION__); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair(symbol, count)); } } // qDebug() << __FILE__ << __LINE__ //<< "positiveComponentString:" << positiveComponentString; // The negative component // ====================== symbol_index_in_list = 0; QString negativeComponentString; // Carbon symbol_index_in_list = negativeStringList.indexOf(QRegularExpression("C\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { negativeComponentString += negativeStringList.at(symbol_index_in_list); negativeStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("C", m_symbolCountMap.at("C"))); } // Hydrogen symbol_index_in_list = negativeStringList.indexOf(QRegularExpression("H\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { negativeComponentString += negativeStringList.at(symbol_index_in_list); negativeStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("H", m_symbolCountMap.at("H"))); } // Nitrogen symbol_index_in_list = negativeStringList.indexOf(QRegularExpression("N\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { negativeComponentString += negativeStringList.at(symbol_index_in_list); negativeStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("N", m_symbolCountMap.at("N"))); } // Oxygen symbol_index_in_list = negativeStringList.indexOf(QRegularExpression("O\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { negativeComponentString += negativeStringList.at(symbol_index_in_list); negativeStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("O", m_symbolCountMap.at("O"))); } // Sulfur symbol_index_in_list = negativeStringList.indexOf(QRegularExpression("S\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { negativeComponentString += negativeStringList.at(symbol_index_in_list); negativeStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("S", m_symbolCountMap.at("S"))); } // Phosphorus symbol_index_in_list = negativeStringList.indexOf(QRegularExpression("P\\d*[\\.]?\\d*")); if(symbol_index_in_list != -1) { negativeComponentString += negativeStringList.at(symbol_index_in_list); negativeStringList.removeAt(symbol_index_in_list); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair("P", m_symbolCountMap.at("P"))); } // Go on with all the other ones, if any... for(int iter = 0; iter < negativeStringList.size(); ++iter) { negativeComponentString += negativeStringList.at(iter); QRegularExpression regexp("([A-Z][a-z]*)(\\d*[\\.]?\\d*)"); QRegularExpressionMatch match = regexp.match(negativeStringList.at(iter)); if(match.hasMatch()) { QString symbol = match.captured(1); QString howMany = match.captured(2); bool ok = false; double count = howMany.toInt(&ok, 10); if(!count && !ok) qFatal( "Fatal error at %s@%d -- %s(). " "Failed to parse an atom count." "Program aborted.", __FILE__, __LINE__, __FUNCTION__); if(symbol_count_pairs_p) symbol_count_pairs_p->push_back( std::pair(symbol, count)); } } // qDebug() << __FILE__ << __LINE__ //<< "negativeComponentString:" << negativeComponentString; // Create the final elemental formula that comprises both the // positive and negative element. First the positive element and // then the negative one. Only append the negative one, prepended // with '-' if the string is non-empty. QString elementalComposition = positiveComponentString; if(!negativeComponentString.isEmpty()) elementalComposition += QString("-%1").arg(negativeComponentString); // qDebug() << __FILE__ << __LINE__ // <<"elementalComposition:" << elementalComposition; return elementalComposition; } /*! \brief Returns the total count of symbols (atoms) in this formula. The determination is performed by summing up all the count values for all the symbols in the member symbol / count pairs in the member map m_symbolCountMap. */ double Formula::totalAtoms() const { double total_atom_count = 0; std::map::const_iterator iter = m_symbolCountMap.cbegin(); std::map::const_iterator iter_end = m_symbolCountMap.cend(); while(iter != iter_end) { total_atom_count += iter->second; ++iter; } return total_atom_count; } /*! \brief Returns the total count of isotopes in this formula using \a isotopic_data_csp as the reference isotopic data. The determination is performed by summing up all the isotope counts for all the symbols keys in the member symbol / count map m_symbolCountMap. */ double Formula::totalIsotopes(IsotopicDataCstSPtr isotopic_data_csp) const { double total_isotope_count = 0; std::map::const_iterator iter = m_symbolCountMap.cbegin(); std::map::const_iterator iter_end = m_symbolCountMap.cend(); while(iter != iter_end) { total_isotope_count += iter->second * isotopic_data_csp->getIsotopeCountBySymbol(iter->first); ++iter; } return total_isotope_count; } /*! \brief Returns the count value associated with key \a symbol in the symbol / count member map m_symbolCountMap. */ double Formula::symbolCount(const QString &symbol) const { // Return the symbol index. std::map::const_iterator iter_end = m_symbolCountMap.cend(); std::map::const_iterator iter = m_symbolCountMap.find(symbol); if(iter == iter_end) return 0; return iter->second; } /*! \brief Returns true if the member "minus" formula component is not empty, false otherwise. */ bool Formula::hasNetMinusPart() { return m_minusFormula.size(); } /*! \brief Parses a formula XML \a element, sets the data to the member actionformula m_formula and checks it syntax. Returns true if parsing and syntax checking were successful, false otherwise. \sa checkSyntax() */ bool Formula::renderXmlFormulaElement(const QDomElement &element) { if(element.tagName() != "formula") return false; m_formula = element.text(); // qDebug() << "Rendering formula element with text:" << m_formula; // Do not forget that we might have a title associated with the // formula and spaces. checkSyntax() should care of removing these // title and spaces before checking for chemical syntax // correctness. return checkSyntax(); } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/FragRule.cpp000664 001750 001750 00000031066 14647465366 022421 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "FragRule.hpp" #include "PolChemDef.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::FragRule \inmodule libXpertMass \ingroup PolChemDefGasPhaseChemicalReactions \inheaderfile FragRule.hpp \brief The FragRule class provides a model for specifying gas phase fragmentation rules for refining fragmentation specifications (\l FragSpec) of \l{Oligomer} \l{Sequence}s. Fragmentation rules characterize in more detail the chemical reaction that governs the fragmentation of the polymer in the gas-phase. The rule is a conditional rule. Its logic is based on the presence of specified monomers right at the place of the fragmentation and before or after that precise location. In saccharide chemistry, fragmentations are a very complex topic. This is because a given monomer will fragment according to a given chemistry if it is preceded in the sequence by a monomer of a given identity and according to another chemistry if its direct environment is different. This paradigm is implemented using a sequence environment logic based on conditions that can be formulated thanks to three monomer codes: \list \li The monomer at which the fragmentation takes place(current code); \li The monomer preceeding the current code (previous code); \li The monomer following the current code (following code); \endlist The use of these codes is typically according to this logic: \e{If current monomer is Glu and that previous monomer is Gly and following monomer is Arg, then fragmentation should occur according to this formula : "-H2O"}. \sa FragSpec */ /*! \variable MsXpS::libXpertMass::FragRule::m_prevCode \brief The \l Monomer code located before the actual fragmentation site. */ /*! \variable MsXpS::libXpertMass::FragRule::m_currCode \brief The \l Monomer code at the actual fragmentation site. */ /*! \variable MsXpS::libXpertMass::FragRule::m_nextCode \brief The \l Monomer code located after the actual fragmentation site. */ /*! \variable MsXpS::libXpertMass::FragRule::m_comment \brief A comment associated to the FragRule. */ /*! \brief Constructs a fragmentation rule. \a pol_chem_def_csp Polymer chemistry definition. Cannot be nullptr. \a name Name. Cannot be empty. \a prevCode Previous monomer code. Defaults to the null string. \a currCode Current monomer code. Defaults to the null string. \a nextCode Next monomer code. Defaults to the null string. \a formula Formula. Defaults to the null string. \a comment Comment. Defaults to the null string. */ FragRule::FragRule(PolChemDefCstSPtr pol_chem_def_csp, QString name, QString prevCode, QString currCode, QString nextCode, QString formula, const QString &comment) : PolChemDefEntity(pol_chem_def_csp, name), Formula(formula), m_prevCode(prevCode), m_currCode(currCode), m_nextCode(nextCode), m_comment(comment) { } /*! \brief Constructs a FragRule instance as a copy of \a other. */ FragRule::FragRule(const FragRule &other) : PolChemDefEntity(other), Formula(other), m_prevCode(other.m_prevCode), m_currCode(other.m_currCode), m_nextCode(other.m_nextCode), m_comment(other.m_comment) { } /*! \brief Destructs the fragmentation rule. */ FragRule::~FragRule() { } /*! \brief Assigns \a other to this FragRule instance. Returns a reference to this fragmentation rule. */ FragRule & FragRule::operator=(const FragRule &other) { if(&other == this) return *this; PolChemDefEntity::operator=(other); Formula::operator =(other); m_prevCode = other.m_prevCode; m_currCode = other.m_currCode; m_nextCode = other.m_nextCode; m_comment = other.m_comment; return *this; } /*! \brief Sets the previous monomer \a code. */ void FragRule::setPrevCode(const QString &code) { m_prevCode = code; } /*! \brief Returns the previous monomer code. */ QString FragRule::prevCode() const { return m_prevCode; } /*! \brief Sets the current monomer \a code. */ void FragRule::setCurrCode(const QString &code) { m_currCode = code; } /*! \brief Returns the current monomer code. */ QString FragRule::currCode() const { return m_currCode; } /*! \brief Sets the next monomer \a code. */ void FragRule::setNextCode(const QString &code) { m_nextCode = code; } /*! \brief Returns the next monomer code. */ QString FragRule::nextCode() const { return m_nextCode; } /*! \brief Sets the \a comment. */ void FragRule::setComment(const QString &comment) { m_comment = comment; } /*! \brief Returns the comment. */ QString FragRule::comment() const { return m_comment; } /*! \brief Returns the \l Formula as a string. */ QString FragRule::formula() const { return Formula::toString(); } /*! \brief Searches by \a name a FragRule in \a frag_rule_list. If a FragRule instance is found and \a other is non-nullptr, the found fragmentation rule's data are copied into \a other. Returns the index of the found FragRule or -1 if none is found or if \a name is empty. */ int FragRule::isNameInList(const QString &name, const QList &frag_rule_list, FragRule *other) { FragRule *fragRule = 0; if(name.isEmpty()) return -1; for(int iter = 0; iter < frag_rule_list.size(); ++iter) { fragRule = frag_rule_list.at(iter); Q_ASSERT(fragRule); if(fragRule->m_name == name) { if(other) *other = *fragRule; return iter; } } return -1; } /*! \brief Validates the FragRule. The validation involves checking that: \list \li The name is not empty. \li The previous code is valid if non empty. \li The current code is valid if non empty. \li The next code is valid if non empty. \endlist Returns true upon success, false otherwise. */ bool FragRule::validate() { const QList &monomerRefList = mcsp_polChemDef->monomerList(); if(m_name.isEmpty()) return false; if(!m_prevCode.isEmpty()) if(Monomer::isCodeInList(m_prevCode, monomerRefList) == -1) return false; if(!m_currCode.isEmpty()) if(Monomer::isCodeInList(m_currCode, monomerRefList) == -1) return false; if(!m_nextCode.isEmpty()) if(Monomer::isCodeInList(m_nextCode, monomerRefList) == -1) return false; IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); return Formula::validate(isotopic_data_csp); return true; } /*! \brief Parses the FragRule XML \a element. Upon parsing and validation of the parsed data, the member data are updated, thus essentially initializing this FragRule instance. Returns true if parsing and formula validation were successful, false otherwise. */ bool FragRule::renderXmlFgrElement(const QDomElement &element) { QDomElement child; bool prevSet = false; bool currSet = false; bool nextSet = false; bool commentSet = false; /* The xml node we are in is structured this way: * * * one_rule * +H2O * M * Y * T * opt_comment * * * And the element parameter points to the * * element tag: * ^ * | * +----- here we are right now. * * Which means that element.tagName() == "fgr" and that * we'll have to go one step down to the first child of the * current node in order to get to the element. * * The DTD: * */ if(element.tagName() != "fgr") return false; child = element.firstChildElement(); if(child.isNull() || child.tagName() != "name") return false; m_name = child.text(); child = child.nextSiblingElement(); if(child.isNull() || child.tagName() != "formula") return false; if(!Formula::renderXmlFormulaElement(child)) return false; // Since the following items are not obligatory, we have to while() // until we have no more items... child = child.nextSiblingElement(); while(!child.isNull()) { if(child.tagName() == "prev-mnm-code") { if(prevSet) return false; else { m_prevCode = child.text(); prevSet = true; } } else if(child.tagName() == "curr-mnm-code") { if(currSet) return false; else { m_currCode = child.text(); currSet = true; } } else if(child.tagName() == "next-mnm-code") { if(nextSet) return false; else { m_nextCode = child.text(); nextSet = true; } } else if(child.tagName() == "comment") { if(commentSet) return false; else { m_comment = child.text(); commentSet = true; } } child = child.nextSiblingElement(); } if(!validate()) return false; return true; } /*! \brief Formats a string suitable to use as an XML element. The string is suitable to be used as an XML element in a polymer chemistry definition file. The typical fragmentation rule element that is generated in this function looks like this: \code a-fgr-2 +H100 F D E comment here! \endcode The formatting of the XML element takes into account \a offset and \a indent by prepending the string with \a offset * \a indent character substring. \a indent defaults to two spaces. Returns a dynamically allocated string that needs to be freed after use. */ QString * FragRule::formatXmlFgrElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); *string += QString("%1%2\n").arg(lead).arg(m_formula); if(!m_prevCode.isEmpty()) *string += QString("%1%2\n") .arg(lead) .arg(m_prevCode); if(!m_currCode.isEmpty()) *string += QString("%1%2\n") .arg(lead) .arg(m_currCode); if(!m_nextCode.isEmpty()) *string += QString("%1%2\n") .arg(lead) .arg(m_nextCode); if(!m_comment.isEmpty()) *string += QString("%1%2\n").arg(lead).arg(m_comment); // Prepare the lead. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/FragSpec.cpp000664 001750 001750 00000034643 14647465366 022410 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "FragSpec.hpp" #include "PolChemDef.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::FragSpec \inmodule libXpertMass \ingroup PolChemDefGasPhaseChemicalReactions \inheaderfile FragSpec.hpp \brief The FragSpec class provides a model for specifying gas phase fragmentations of \l{Oligomer} \l{Sequence}s. The FragSpec class provides a fragmentation specification. Fragmentation specifications determine the chemical reaction that governs the fragmentation of the polymer in the gas-phase. The chemical reaction is embodied by a formula. The side of the polymer (left or right) that makes the fragment after the fragmentation has occurred is described by a fragmentation-end enumeration. A fragmentation specification might not be enough information to determine the manner in which a polymer fragments in the gas-phase. Fragmentation rules (\l{FragRule}s) might be required to refine the specification. A fragmentation specification might hold as many \l{FragRule}s as required. */ /*! \enum MsXpS::libXpertMass::FragEnd This enum specifies the end of the \l Oligomer that will be contained in the fragment resulting from its fragmentation. In protein chemistry, for example, a,b and x fragments are N-terminal fragments, that is, they contain the left end of the framgented Oligomer's sequence. \value FRAG_END_NONE The value is not set. \value FRAG_END_LEFT The generated fragment contains the left end of the oligomer \value FRAG_END_RIGHT The generated fragment contains the right end of the oligomer \value FRAG_END_BOTH The generated fragment contains both the left and the right end of the oligomer (that is, is all the oligomer) */ /*! \brief Constructs a FragSpec instance. \a pol_chem_def_csp Polymer chemistry definition. Cannot be nullptr. \a name Name. Cannot be empty. \a formula Formula. Defaults to the null string. \a frag_end Fragmentation end. Defaults to FRAG_END_NONE. \a comment Comment. Defaults to the null string. */ FragSpec::FragSpec(PolChemDefCstSPtr pol_chem_def_csp, QString name, QString formula, FragEnd frag_end, const QString &comment) : PolChemDefEntity(pol_chem_def_csp, name), Formula(formula), m_fragEnd(frag_end), m_comment(comment) { m_fragEnd = FRAG_END_NONE; m_monomerContribution = 0; } /*! \brief Constructs a fragmentation specification. \a pol_chem_def_csp Polymer chemistry definition. Cannot be nullptr. \a name Name. Cannot be empty. \a formula Formula. */ FragSpec::FragSpec(PolChemDefCstSPtr pol_chem_def_csp, QString name, QString formula) : PolChemDefEntity(pol_chem_def_csp, name), Formula(formula) { m_fragEnd = FRAG_END_NONE; m_monomerContribution = 0; } /*! \brief Constructs a FragSpec instance as a copy of \a other. */ FragSpec::FragSpec(const FragSpec &other) : PolChemDefEntity(other), Formula(other), m_fragEnd(other.m_fragEnd), m_monomerContribution(other.m_monomerContribution), m_comment(other.m_comment) { for(int iter = 0; iter < other.m_ruleList.size(); ++iter) { FragRule *fragRule = new FragRule(*other.m_ruleList.at(iter)); m_ruleList.append(fragRule); } } /*! \brief Destroys this FragSpec instance. */ FragSpec::~FragSpec() { while(!m_ruleList.isEmpty()) delete m_ruleList.takeFirst(); } /*! \brief Assigns \a other to this FragSpec instance. Returns a reference to this FragSpec. */ FragSpec & FragSpec::operator=(const FragSpec &other) { if(&other == this) return *this; PolChemDefEntity::operator=(other); Formula::operator =(other); m_fragEnd = other.m_fragEnd; m_monomerContribution = other.m_monomerContribution; m_comment = other.m_comment; while(!m_ruleList.isEmpty()) delete m_ruleList.takeFirst(); for(int iter = 0; iter < other.m_ruleList.size(); ++iter) { FragRule *fragRule = new FragRule(*other.m_ruleList.at(iter)); m_ruleList.append(fragRule); } return *this; } /*! \brief Returns the list of FragRule instances. */ QList & FragSpec::ruleList() { return m_ruleList; } /*! \brief Adds the \a frag_rule FragRule instance to the member list of FragRule instances. */ void FragSpec::appendRule(FragRule *frag_rule) { Q_ASSERT(frag_rule); m_ruleList.append(frag_rule); } /*! \brief Inserts in the member list of FragRule instances at \a index the \a frag_rule FragRule instance. */ void FragSpec::insertRuleAt(int index, FragRule *frag_rule) { Q_ASSERT(frag_rule); m_ruleList.insert(index, frag_rule); } /*! \brief Removes from the member list of FragRule instances the item at index \a index. */ void FragSpec::removeRuleAt(int index) { m_ruleList.removeAt(index); } /*! \brief Sets the fragmentation end to \a frag_end. */ void FragSpec::setFragEnd(FragEnd frag_end) { m_fragEnd = frag_end; } /*! \brief Returns the fragmentation end. */ FragEnd FragSpec::fragEnd() const { return m_fragEnd; } void FragSpec::setMonomerContribution(int value) { m_monomerContribution = value; } int FragSpec::monomerContribution() { return m_monomerContribution; } /*! \brief Returns the \l Formula as a string. */ QString FragSpec::formula() const { return Formula::toString(); } /*! \brief Sets the \a comment. */ void FragSpec::setComment(const QString &comment) { m_comment = comment; } /*! \brief Returns the comment. */ QString FragSpec::comment() const { return m_comment; } /*! \brief Searches by \a name for a FragSpec in the \a frag_spec_list. If such fragmentation specification is found, and if \a other is non-0, the found fragmentation specification's data are copied into \a other. Returns the index of the found FragSpec instance or -1 if none is found or if \a name is empty. */ int FragSpec::isNameInList(const QString &name, const QList &frag_spec_list, FragSpec *other) { FragSpec *fragSpec = 0; if(name.isEmpty()) return -1; for(int iter = 0; iter < frag_spec_list.size(); ++iter) { fragSpec = frag_spec_list.at(iter); Q_ASSERT(fragSpec); if(fragSpec->m_name == name) { if(other) *other = *fragSpec; return iter; } } return -1; } /*! \brief Validates the FragSpec. The validation involves checking that: \list \li The name is not empty. \li The fragmentation end must have been set. \li The FragRule instances (if any) are valid. \li The Formula is valid. \endlist Returns true upon success, false otherwise. */ bool FragSpec::validate() { IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); if(m_name.isEmpty()) return false; if(m_fragEnd != FRAG_END_NONE && m_fragEnd != FRAG_END_LEFT && m_fragEnd != FRAG_END_RIGHT) return false; for(int iter = 0; iter < m_ruleList.size(); ++iter) { if(!m_ruleList.at(iter)->validate()) return false; } return Formula::validate(isotopic_data_csp); } /*! \brief Parses a fragmentation specification XML \a element using a \a{version}ed function. Upon parsing and validation of the parsed data, the member data are updated, thus essentially initializing this FragSpec instance. Returns true if parsing and formula validation were successful, false otherwise. */ bool FragSpec::renderXmlFgsElement(const QDomElement &element, int version) { if(version == 1) { //no-op version = 1; } QDomElement child; QDomElement childRule; FragRule *fragRule = 0; bool commentParsed = false; /* The xml node we are in is structured this way: * * * a * LE * -C1O1 * opt_comment * * one_rule * +H2O * M * Y * T * opt_comment * * other fgr allowed, none possible also * * * And the element parameter points to the * * element tag: * ^ * | * +----- here we are right now. * * Which means that element.tagName() == "fgs" and that * we'll have to go one step down to the first child of the * current node in order to get to the element. * * The DTD says this: * */ if(element.tagName() != "fgs") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); child = child.nextSiblingElement(); if(child.isNull() || child.tagName() != "end") return false; if(child.text() == "NE") m_fragEnd = FRAG_END_NONE; else if(child.text() == "LE") m_fragEnd = FRAG_END_LEFT; else if(child.text() == "RE") m_fragEnd = FRAG_END_RIGHT; else if(child.text() == "BE") m_fragEnd = FRAG_END_BOTH; else return false; child = child.nextSiblingElement(); if(child.isNull() || child.tagName() != "formula") return false; if(!Formula::renderXmlFormulaElement(child)) return false; // The next element must be child = child.nextSiblingElement(); if(child.tagName() != "sidechaincontrib") return false; QString text = child.text(); bool ok = false; m_monomerContribution = text.toInt(&ok); if(!m_monomerContribution && !ok) return false; // The next element might be either comment or(none, one or more) // fgr. child = child.nextSiblingElement(); while(!child.isNull()) { // Is it a comment or the first of one|more elements ? // Remember: if(child.tagName() == "comment") { if(commentParsed) return false; m_comment = child.text(); commentParsed = true; child = child.nextSiblingElement(); continue; } // At this point, yes, if there is still a sibling, then it // has to be one , either alone or the first of multiple. while(!child.isNull()) { fragRule = new FragRule(mcsp_polChemDef, "NOT_SET"); if(!fragRule->renderXmlFgrElement(child)) { delete fragRule; return false; } else m_ruleList.append(fragRule); child = child.nextSiblingElement(); } } if(!validate()) return false; return true; } /*! \brief Formats a string representing this FragSpec instance suitable to use as an XML element. The typical fragmentation specification element that is generated in this function looks like this: \code a LE -C1O1 a-fgr-1 +H200 E D F comment here! a-fgr-2 +H100 F D E comment here! \endcode The formatting of the XML element takes into account \a offset and \a indent by prepending the string with \a offset * \a indent character substring. \a indent defaults to two spaces. Returns a dynamically allocated string that needs to be freed after use. */ QString * FragSpec::formatXmlFgsElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); if(m_fragEnd == FRAG_END_NONE) *string += QString("%1%2\n").arg(lead).arg("NE"); else if(m_fragEnd == FRAG_END_BOTH) *string += QString("%1%2\n").arg(lead).arg("BE"); else if(m_fragEnd == FRAG_END_LEFT) *string += QString("%1%2\n").arg(lead).arg("LE"); else if(m_fragEnd == FRAG_END_RIGHT) *string += QString("%1%2\n").arg(lead).arg("RE"); *string += QString("%1%2\n").arg(lead).arg(m_formula); *string += QString("%1%2\n") .arg(lead) .arg(m_monomerContribution); if(!m_comment.isEmpty()) *string += QString("%1%2\n").arg(lead).arg(m_comment); for(int iter = 0; iter < m_ruleList.size(); ++iter) { QString *ruleString = m_ruleList.at(iter)->formatXmlFgrElement(newOffset); *string += *ruleString; delete ruleString; } // Prepare the lead. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Ionizable.cpp000664 001750 001750 00000055503 14647465366 022630 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include /////////////////////// Local includes #include "Ionizable.hpp" #include "PolChemDef.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::Ionizable \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile Ionizable.hpp \brief The Ionizable class provides abstractions to work with entities that have masses (base class \l Ponderable) and that can be ionized. An Ionizable is a \l Ponderable that might undergo ionization, using either its own member \l IonizeRule or an IonizeRule passed as argument to one of its functions. An Ionizable \e is also a \l PolChemDefEntity, as it has to know at each instant what polymer chemistry definition it is based upon. The ionization status of the Ionizable can be checked at each moment and a call to an ionizing function will first trigger deionization if the Ionizable is already ionized. The main members of the Ionizable class are the following: \list \li a IonizeRule; \li a boolean member stating if the entity has actually been ionized; \endlist Upon creation of an Ionizable (without use of the copy constructor), all the data required for the full qualification of the new instance should be passed to the constructor. Default parameters ensure that the Ionizable is set to a consistent status (that is its IonizeRule member is \e invalid and that its m_isIonized flag is false). The caller is responsible for seeding correct and consistent values into the constructor for proper operation of the class instances. For the ionization to be actually performed, and the masses to be effectively updated to account for that ionization, the Ionizable instance must be ionize()d. It is possible to deionize() an Ionizable instance as it is possible to reionize the instance with another IonizeRule, which will replace the member IonizeRule if the reionization succeeds. The deionize() call effects the state of the Ionizable instance only if the m_isIonized boolean is true. */ /*! \variable MsXpS::libXpertMass::Ionizable::m_ionizeRule \brief The ionization rule that defines the way to ionize this Ionizable. \sa IonizeRule */ /*! \variable MsXpS::libXpertMass::Ionizable::m_isIonized \brief Tells if this Ionizable has undergone an ionization. */ /*! \brief Constructs an Ionizable. The Ionizable instance will be an ionized entity if \a is_ionized is true. The \a ponderable's mono and avg masses must thus be masses which take (is_ionized is true) or do not take (is_ionized is false) into account the data located in \a ionize_rule. If \a is_ionized is true, the \a ionize_rule is validated and the level of the ionization must not be 0. If one of these two tests fails, this is an error and the program aborts. \a pol_chem_def_csp Polymer chemistry definition (cannot be nullptr); \a name Name of this Ionizable (defaults to "NOT_SET"); \a ponderable \l Ponderable (mono and avg masses); \a ionize_rule \l IonizeRule (defaults to IonizeRule(), that is, an invalid IonizeRule). \a is_ionized Tells if the Ionizable to be constructed should be considered as an ionized chemical entity. */ Ionizable::Ionizable(PolChemDefCstSPtr pol_chem_def_csp, const QString &name, const Ponderable &ponderable, const IonizeRule &ionize_rule, bool is_ionized) : PolChemDefEntity(pol_chem_def_csp, name), Ponderable(ponderable), m_ionizeRule(ionize_rule), m_isIonized(is_ionized) { if(m_isIonized) { // If the Ionizable is ionized, then that means that its // IonizeRule should validate and also that the ionization // level should not be 0. if(!m_ionizeRule.isValid() || m_ionizeRule.level() == 0) { qFatal("Failure to construct Ionizable"); } } } /*! \brief Constructs an Ionizable as a copy of \a other. No assumption should be made as to the status of the created Ionizable. The caller should characterize the ionization status of the Ionizable with isIonized(). */ Ionizable::Ionizable(const Ionizable &other) : PolChemDefEntity(other), Ponderable(other), m_ionizeRule(other.m_ionizeRule), m_isIonized(other.m_isIonized) { } /*! \brief Destructs this Ionizable. */ Ionizable::~Ionizable() { } /*! \brief Assigns \a other to this Ionizable. Return A reference to this Ionizable instance. */ Ionizable & Ionizable::operator=(const Ionizable &other) { if(&other == this) return *this; PolChemDefEntity::operator=(other); Ponderable::operator=(other); m_ionizeRule = other.m_ionizeRule; m_isIonized = other.m_isIonized; return *this; } /*! \brief Returns a constant reference to this IonizeRule. */ const IonizeRule & Ionizable::ionizeRule() const { return m_ionizeRule; } /*! \brief Returns a pointer to this IonizeRule. */ IonizeRule * Ionizable::ionizeRule() { return &m_ionizeRule; } /*! \brief Returns true if this Ionizable is ionized, false otherwise. The ionization status is returned on the basis of the m_isIonized member boolean value. */ bool Ionizable::isIonized() const { return m_isIonized; } /*! \brief Returns the ionization charge of this Ionizable. The charge is returned as a positive number (0 allowed) or as -1 if the IonizeRule is not valid or if m_isIonized is false. \note The charge is returned as the compound product of the IonizeRule's m_charge and m_level members. */ int Ionizable::charge() const { // If the member ionizeRule is not valid, then return -1, that is // inform the caller that something is not correct in the // "workflow". if(!m_ionizeRule.isValid()) return -1; // Now, the ionizeRule is correct, but the ionizable does not // advertise that it has been ionized. In this case, its charge is // 0. if(!m_isIonized) return 0; // Finally, the ionizable is effectively // ionized, that is, its ionizeRule is valid and it advertises that // it is ionized, in which case we can return its charge. return (m_ionizeRule.charge() * m_ionizeRule.level()); } /*! \brief Sets the charge of this Ionizable to \a charge. The charge of an ionizable is the compound product of m_charge and m_level in its \l IonizeRule member. The value passed as \a charge is \e that compound product. Thus, the member IonizeRule is updated to reflect the new \c charge using the following code: \code int level = charge / m_ionizeRule.charge(); m_ionizeRule.setLevel(level); \endcode Only the level value of IonizeRule is changed to reflect the change of the \a charge because the chemistry of the ionization rule itself must not be changed. The following logic is applied: \list 1 \li If the member ionizeRule is not valid, this function returns -1 because it does not make sense to try to change the charge of an Ionizable if its member IonizeRule is not valid. \li This Ionizable is first deionized if it is ionized. If the deionization fails -1 is returned. \li This Ionizable is ionized with the level value in its member IonizeRule. If that ionization fails, -1 is returned. \endlist At this point all the process went fine and 1 is returned. */ int Ionizable::setCharge(int charge) { if(!m_ionizeRule.isValid()) return -1; // If *this Ionizable is ionized, first deionize it with its own // m_ionizeRule, so that we get back to M, as we would say in // front of a mass spectrometer. if(m_isIonized) { if(!deionize()) return -1; else // Make clear that at this point we are not ionized. m_isIonized = false; } // At this point, if charge is 0, then we are done, and we can // return a success. if(!charge) return 1; // At this point we can compute what IonizeRule's level should // be //(taking into account its charge, that is its unitary charge) so // that the total charge is identical to the parameter. int level = charge / m_ionizeRule.charge(); m_ionizeRule.setLevel(level); if(ionize() == -1) return -1; // Return 1 because we know we changed something: return 1; } /*! \brief Ionizes this Ionizable. Ionization is performed on this Ionizable using the member IonizeRule. The following logic is applied: \list 1 \li If the member ionizeRule is not valid, this function returns -1 because it does not make sense to try to change the charge of an Ionizable if its member IonizeRule is not valid. \li If this Ionizable is ionized, return 0 (already ionized, then nothing to do). \li The mass status of this Ionizable (\l Ponderable base class) is stored for later comparison. \li The ionization process is carried out and the new mass status is compared to the older one. If the new status differs from the previous one, than 1 is returned, otherwise -1 is returned. \endlist */ int Ionizable::ionize() { if(!m_ionizeRule.isValid()) return -1; if(m_isIonized) return 0; // At this point perform the ionization proper. Formula formula(m_ionizeRule.formula()); qDebug() << "The formula right before ionizing:" << formula.toString(); Ponderable temp(*this); qDebug() << __FILE__ << __LINE__ << "Before ionization:\n" << "Ionizable's charge / level:" << m_ionizeRule.charge() << "/" << m_ionizeRule.level() << " -- " << "Ionizable's whole charge:" << m_ionizeRule.charge() * m_ionizeRule.level() << " -- " << "Mono:" << temp.mono() << "Avg:" << temp.avg() << "\n\n"; double localMono = 0; double localAvg = 0; IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); // Note the times(localIonizeRule.level()) param to call below. if(!formula.accountMasses( isotopic_data_csp, &localMono, &localAvg, m_ionizeRule.level())) return -1; qDebug() << qSetRealNumberPrecision(6) << "Right after accounting formula masses:" << localAvg << "-" << localMono; // OK, the accounting of the masses worked ok, we can update the // values in the mono and avg params(note that we know that the // charge and level values of m_ionizeRule cannot be <= 0 because // otherwise the m_ionizRule would not have validated. int ionCharge = m_ionizeRule.charge() * m_ionizeRule.level(); qDebug() << "The ion charge:" << ionCharge; qDebug() << qSetRealNumberPrecision(6) << "m_mono:" << m_mono << "m_avg:" << m_avg; m_mono += localMono; m_mono = m_mono / ionCharge; m_avg += localAvg; m_avg = m_avg / ionCharge; // Of course, we now are ionized. m_isIonized = true; qDebug() << __FILE__ << __LINE__ << "After ionization:\n" << "Ionizable's charge / level:" << m_ionizeRule.charge() << "/" << m_ionizeRule.level() << " -- " << "Ionizable's whole charge:" << m_ionizeRule.charge() * m_ionizeRule.level() << " -- " << "Mono:" << m_mono << "Avg:" << m_avg << "\n\n"; // If something changed in the masses, then return 1, otherwise // return 0. if(temp != static_cast(*this)) { return 1; } return 0; } /*! \brief Ionizes this Ionizable using \a ionize_rule. The following logic is applied: \list 1 \li If \a ionize_rule is not valid, this function returns -1 because it does not make sense to try to change the charge of an Ionizable if that ionization rule is not valid. \li If this Ionizable is ionized, first \l{deionize()} it. If this step fails, returns -1. \li The ionization process is carried out. If the process is successful, 1 is returned, otherwise -1 is returned. \endlist \sa ionize() */ int Ionizable::ionize(const IonizeRule &ionize_rule) { if(!ionize_rule.isValid()) return -1; // If *this Ionizable is ionized, first deionize it with its own // m_ionizeRule, so that we get back to M, as we would say in // front of a mass spectrometer. Ponderable temp(*this); if(m_isIonized) { qDebug() << "this before deionization:" << toString(); if(!deionize()) return -1; else // Make clear that at this point we are not ionized. m_isIonized = false; qDebug() << "this after deionization:" << toString(); } else { qDebug() << "this is not ionized:" << toString(); } // At this point perform the ionization proper using 'ionizeRule'. Formula formula(ionize_rule.formula()); qDebug() << "The ionization formula is:" << formula.toString(); double localMono = 0; double localAvg = 0; IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); // Note the times(ionizeRule.level()) param to call below. if(!formula.accountMasses( isotopic_data_csp, &localMono, &localAvg, ionize_rule.level())) return -1; qDebug() << qSetRealNumberPrecision(6) << "Right after accounting the ionization masses:" << localMono << "-" << localAvg; // OK, the accounting of the masses worked ok, we can update the // values in the mono and avg params(note that we know that the // charge and level values of ionizeRule cannot be <= 0 because // otherwise the ionizRule would not have validated. qDebug() << qSetRealNumberPrecision(6) << "The ionizable masses before addition of the masses due to ionization:" << m_mono << "-" << m_avg; m_mono += localMono; m_avg += localAvg; qDebug() << qSetRealNumberPrecision(6) << "Right after having added the ionization masses:" << m_mono << "-" << m_avg; // If the ionization rule is for actually ionizing, then perform // the division. if(ionize_rule.level()) { m_mono = m_mono / (ionize_rule.charge() * ionize_rule.level()); m_avg = m_avg / (ionize_rule.charge() * ionize_rule.level()); qDebug() << qSetRealNumberPrecision(6) << "And now true m/z values:" << m_mono << "-" << m_avg; m_isIonized = true; } else { m_isIonized = false; } // Update the internal IonizeRule. m_ionizeRule = ionize_rule; // qDebug() << __FILE__ << __LINE__ // << "After ionization: Mono:" << *mono << "Avg:" << avg; // If something changed in the masses, then return 1, otherwise // return 0. if(temp != *this) return 1; return 0; } /*! \brief Ionizes the \a ionizable using \a ionize_rule. The following logic is applied: \list 1 \li If \a ionize_rule is not valid, this function returns -1 because it does not make sense to try to change the charge of an Ionizable if that ionization rule is not valid. \li If \a ionizable is ionized, first \l{deionize()} it. If this step fails, returns -1. \li The ionization process is carried out. If the process is successful, 1 is returned, otherwise -1 is returned. \endlist \sa ionize() */ int Ionizable::ionize(Ionizable *ionizable, const IonizeRule &ionize_rule) { if(!ionize_rule.isValid()) return -1; // If *this Ionizable is ionized, first deionize it with its own // m_ionizeRule, so that we get back to M, as we would say in // front of a mass spectrometer. if(ionizable->m_isIonized) { if(!ionizable->deionize()) return -1; else // Make clear that at this point we are not ionized. ionizable->m_isIonized = false; } // At this point perform the ionization proper using 'ionizeRule'. Formula formula(ionizable->ionizeRule()->formula()); Ponderable temp(*ionizable); double localMono = 0; double localAvg = 0; IsotopicDataCstSPtr isotopic_data_csp = ionizable->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr(); // Note the times(ionizeRule.level()) param to call below. if(!formula.accountMasses( isotopic_data_csp, &localMono, &localAvg, ionize_rule.level())) return -1; // OK, the accounting of the masses worked ok, we can update the // values in the mono and avg params(note that we know that the // charge and level values of ionizeRule cannot be <= 0 because // otherwise the ionizRule would not have validated. ionizable->m_mono += localMono; ionizable->m_avg += localAvg; // If the ionization rule is for actually ionizing, then perform // the division. if(ionize_rule.level()) { ionizable->m_mono = ionizable->m_mono / (ionize_rule.charge() * ionize_rule.level()); ionizable->m_avg = ionizable->m_avg / (ionize_rule.charge() * ionize_rule.level()); ionizable->m_isIonized = true; } else { ionizable->m_isIonized = false; } // Update the internal IonizeRule. ionizable->m_ionizeRule = ionize_rule; // qDebug() << __FILE__ << __LINE__ // << "After ionization: Mono:" << *mono << "Avg:" << avg; // If something changed in the masses, then return 1, otherwise // return 0. if(temp != *ionizable) return 1; return 0; } /*! \brief Deionizes this Ionizable. This Ionizable is deionized using its member m_ionizeRule. The following logic is applied: \list 1 \li If this Ionizable is not ionized, this function returns 0. \li If the member ionizeRule is not valid, this function returns -1 because it does not make sense to try to change the ionization of an Ionizable if its member IonizeRule is not valid. \li \li The deionization process is carried out. If the process is successful, 1 is returned, otherwise -1 is returned. \endlist */ int Ionizable::deionize() { if(!m_isIonized) { // The Ionizable is not ionized, nothing to do, return true. return 0; } Ponderable temp(*this); qDebug() << "this before deionizing:" << toString(); // At this point we know the Ionizable is ionized, thus it is an // error that m_ionizeRule is not valid. if(!m_ionizeRule.isValid()) return -1; // Now, reverse the usual M+zH/z(proteins) stuff. double localMono = m_mono * abs(m_ionizeRule.charge() * m_ionizeRule.level()); qDebug() << "set to a variable the m_mono * ion charge:" << localMono; double localAvg = m_avg * abs(m_ionizeRule.charge() * m_ionizeRule.level()); qDebug() << "set to a variable the m_avg * ion charge:" << localAvg; Formula formula(m_ionizeRule.formula()); qDebug() << "The ionizerule formula:" << formula.toString(); IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); // Note the negated 'times'(- m_ionizeRule.level())param to call // below so that we revert the chemical action that led level // times to the ionization of the analyte. Note that we do not // have any compound(level * charge) because we are dealing with // matter here, not charges, thus only 'level' is to be taken into // consideration. if(!formula.accountMasses( isotopic_data_csp, &localMono, &localAvg, -m_ionizeRule.level())) return -1; qDebug() << "After having accounted the ionization formula masses into the " "previously computed mono and avg masses:" << localMono << "-" << localAvg; // At this point we can update the member masses; m_mono = localMono; m_avg = localAvg; m_isIonized = false; // If something changed in the masses, then return 1, otherwise // return 0. if(temp != *this) return 1; return 0; } /*! \brief Returns the molecular mass (either monoisotopic or average, depending on \a mass_type) A copy of this Ionizable is first made, then it is deionized and its mass is returned. */ double Ionizable::molecularMass(MassType mass_type) { Ionizable temp(*this); if(!temp.deionize()) return -1; return temp.mass(mass_type); } /*! \brief Validates this Ionizable. The member IonizeRule needs to validate successfully. Returns true if the validation is successfull, false otherwise. */ bool Ionizable::validate() { int tests = 0; tests += PolChemDefEntity::validate(); // If this Ionizable is ionized, then it is an error if the // m_ionizeRule is not valid ! tests += (m_isIonized && m_ionizeRule.isValid()); if(tests < 2) return false; return true; } /*! \brief Returns true if this Ionizable is identical to \a other, false otherwise. */ bool Ionizable::operator==(const Ionizable &other) const { int tests = 0; tests += (m_mono == other.m_mono); tests += (m_avg == other.m_avg); tests += m_ionizeRule == other.m_ionizeRule; tests += m_isIonized == other.m_isIonized; if(tests < 4) return false; return true; } /*! \brief Returns true if this Ionizable is different than \a other, false otherwise. */ bool Ionizable::operator!=(const Ionizable &other) const { int tests = 0; tests += (m_mono != other.m_mono); tests += (m_avg != other.m_avg); tests += m_ionizeRule != other.m_ionizeRule; tests += m_isIonized != other.m_isIonized; if(tests > 0) return true; return false; } /*! \brief Calculates the masses of this Ionizable. If this Ionizable is ionized, the masses of the \l Ponderable base class are let unchanged. If this Ionizable is not ionized and the IonizeRule is valid, perform the ionization, which calculates the masses. This function returns false if the member IonizeRule is not valid or if the ionization failed, true otherwise. \sa ionize() */ bool Ionizable::calculateMasses() { if(!Ponderable::calculateMasses()) return false; if(m_isIonized) { // Because the Ionizable is ionized, we have nothing more to // do. return true; } else { // The Ionizable is not ionized. If the IonizeRule is valid, // then we just ionize it. if(m_ionizeRule.isValid()) { if(ionize() == -1) return false; else return true; } } // We should not be here. return false; } /*! \brief Returns a string describing this Ionizable. */ QString Ionizable::toString() { QString text; text += m_ionizeRule.toString(); text += "\n"; text += Ponderable::monoString(6); text += "-"; text += Ponderable::avgString(6); text += "\n"; text += (m_isIonized ? "ionized" : "not ionized"); text += "\n"; return text; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/IonizeRule.cpp000664 001750 001750 00000031410 14647465366 022770 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "IonizeRule.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::IonizeRule \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile IonizeRule.hpp \brief The IonizeRule class provides an ionization rule. Ionizations are chemical reactions that bring a charge (or more charges) to an analyte. The chemical reaction is described by the inherited \l Formula class. The electric charge that is brought by this reaction is described by a member of the class, m_charge. It might happen that for polymers, ionization reactions might occur more than once. This is described by one member of the class, m_level. An IonizeRule like the following, if used to ionize a molecule of Mr 1000 \list \li Formula +H \li Charge 1 \li Level 1 \endlist would lead to an ion of mass 1001 and of m/z 1001. In protein chemistry, the ionization reaction is mainly a protonation reaction, which brings a single charge. Thus, the Formula would be "+H" and the charge 1. In MALDI, we would have a single protonation, thus level would be set to 1 by default. In electrospray ionization, more than one ionization reaction occurs, and we could have a protein that is 25+, thus having an ionization level of 25, for example. An IonizeRule like the following, if used to ionize a molecule of Mr 1000 \list \li Formula +H \li Charge 1 \li Level 4 \endlist would lead to an ion of mass 1004 and of m/z 251 (1004 / 4). An IonizeRule like the following, if used to ionize a molecule of Mr 1000 \list \li Formula +Mg (in fact Mg2+) \li Charge 2 \li Level 4 \endlist would lead to an ion of mass 1000 + (24 * 4) and of m/z 137 = (1096 / 8). \note Both the charge and level should have positive values, since the real nature of the ionization is beared by the Formula (with, for example in protein chemistry, "+H" for protonation and, in nucleic acids chemistry, "-H" for deprotonation). Thus, an IonizeRule is valid if it generates a m/z ratio after ionization of the analyte that is different than the previous (M) and if its Formula validates. This means that the following should be true: \list \li The Formula should be valid (that is, should contain at least one symbol (which might have a very small mass, like when the ionization is done by electron gain or loss); \li The charge is > 0 (the ionization event should bring one charge, otherwise there is no ionization). To reset the ionization to 0 (that is to deionize the analyte, set the level to 0); \li The level is >= 0(if the level is 0, then the analyte is considered not ionized); \endlist \note We do not consider compulsory that the Formula brings a mass difference whatsoever, because some ionizations might not involve heavy mass transfers, like electron gain or electron loss. However, the Formula must validate successfully and that means thit it cannot be empty. In that case, use the Nul chemical element that has not weight from the polymer chemistry definitions shipped with the software package. */ /*! \variable int MsXpS::libXpertMass::IonizeRule::m_charge \brief The charge that is brought to the molecule when it is ionized by the IonizeRule with an ionization level (m_level) of 1. */ /*! \variable int MsXpS::libXpertMass::IonizeRule::m_level \brief The number of times this IonizRule is used to ionize a molecule. For example, applying this IonizeRule to a molecule \list \li Formula +H \li Charge 1 \li Level 4 \endlist would protonate it 4 times, with a charge of 4 and an increment in mass of the mass of 4 protons. */ /*! \variable int MsXpS::libXpertMass::IonizeRule::m_isValid \brief Tells if this IonizeRule is valid, \sa isValid */ /*! \brief Constructs an IonizeRule initialized as an empty object. */ IonizeRule::IonizeRule() : Formula(), m_charge(0), m_level(0) { m_isValid = false; } /*! \brief Constructs an IonizeRule as a copy of \a other. */ IonizeRule::IonizeRule(const IonizeRule &other) : Formula(other), m_charge(other.m_charge), m_level(other.m_level), m_isValid(other.m_isValid) { } /*! \brief Assigns \a other to this IonizeRule. Returns a reference to this ionization rule. */ IonizeRule & IonizeRule::operator=(const IonizeRule &other) { if(&other == this) return *this; Formula::operator=(other); m_charge = other.m_charge; m_level = other.m_level; m_isValid = other.m_isValid; return *this; } /*! \brief Sets the charge to \a value. An IonizeRule with a(charge <= 0) is invalid by definition. If so, the m_isValid member is set to false. */ void IonizeRule::setCharge(int value) { m_charge = value; if(m_charge <= 0) m_isValid = false; } /*! \brief Returns the charge. */ int IonizeRule::charge() const { return m_charge; } /*! \brief Sets the ionzation level to \a value. An IonizeRule might have an ionization level == 0 but not level < 0, as this means that the analyte is not ionized at all. If (level < 0), then the m_isValid member is set to false. */ void IonizeRule::setLevel(int value) { m_level = value; if(m_level < 0) m_isValid = false; } /*! \brief Returns the ionization level. */ int IonizeRule::level() const { return m_level; } /*! \brief Returns the formula. */ QString IonizeRule::formula() const { return Formula::toString(); } /*! \brief Returns true if this IonizeRule is identical to \a other, false otherwise. */ bool IonizeRule::operator==(const IonizeRule &other) const { int tests = 0; tests += Formula::operator==(other); tests += (m_charge == other.m_charge); tests += (m_level == other.m_level); tests += (m_isValid == other.m_isValid); if(tests < 4) return false; return true; } /*! \brief Returns true if this IonizeRule is different than \a other, false otherwise. */ bool IonizeRule::operator!=(const IonizeRule &other) const { int tests = 0; tests += Formula::operator!=(other); tests += (m_charge != other.m_charge); tests += (m_level != other.m_level); tests += (m_isValid != other.m_isValid); if(tests > 0) return true; return false; } /*! \brief Validates this IonizeRule against the \a isotopic_data_csp. An IonizeRule is valid if it generates a new m/z ratio after ionization (or deionization if (\c m_level == 0) of the analyte that is different than the previous one and if its \l Formula validates successfully. This means that the following should be true: \list \li The Formula should be valid (that is, should contain at least one atom (use a 0-weighing atom if necessary, like Nul from the polymer chemistry definitions shipped with the software); \li The charge should > 0; \li The level should >= 0; \endlist If these three tests do not fail, the IonizeRule is considered valid and the m_isValid boolean value is set to true; false otherwise. Returns true if validation succeeds, false otherwise. \sa Formula::validate() */ bool IonizeRule::validate(IsotopicDataCstSPtr isotopic_data_csp) { if(isotopic_data_csp == nullptr) qFatal("Programming error. The pointer cannot be nullptr."); int tests = 0; tests += Formula::validate(isotopic_data_csp); tests += (m_charge > 0); tests += (m_level >= 0); if(tests < 3) { m_isValid = false; return false; } m_isValid = true; return true; } /*! \brief Returns true if this IonizeRule is valid, false otherwise. \sa validate() */ bool IonizeRule::isValid() const { return m_isValid; } /*! \brief Renders the ionization rule XML \a element. The XML element is parsed and the data extracted from the XML data are set to this IonizeRule instance. The DTD says this: A typical ionization rule element looks like this: \code +H 1 1 \endcode Note that this IonizeRule is not valid, as it has not been validated by calling validate(). The caller is reponsible for checking the validity of the IonizeRule prior use. Returns true if the parsing is successful, false otherwise. \sa formatXmlIonizeRuleElement(int offset, const QString &indent) */ bool IonizeRule::renderXmlIonizeRuleElement(const QDomElement &element) { QDomElement child; // // +H // 1 // 1 // if(element.tagName() != "ionizerule") return false; // child = element.firstChildElement(); if(child.tagName() != "formula") return false; if(!renderXmlFormulaElement(child)) return false; // child = child.nextSiblingElement(); if(child.tagName() != "charge") return false; bool ok = false; m_charge = child.text().toInt(&ok); if(!m_charge && !ok) return false; // child = child.nextSiblingElement(); if(child.tagName() != "level") return false; ok = false; m_level = child.text().toInt(&ok); if(!m_level && !ok) return false; // We have not validated this IonizeRule, as we should have the // reference list of atoms to do that. The caller is responsible // for the validate() call. m_isValid = false; return true; } /*! \brief Formats a string suitable to use as an XML element. Formats a string suitable to be used as an XML element in a polymer chemistry definition file. The typical ionization rule element that is generated in this function looks like this: The DTD says this: A typical ionization rule element looks like this: \code ~~+H ~~1 ~~1 \endcode \a offset times the \a indent string must be used as a lead in the formatting of elements. Returns a dynamically allocated string that needs to be freed after use. \sa renderXmlIonizeRuleElement(const QDomElement &element) */ QString * IonizeRule::formatXmlIonizeRuleElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } /* We are willing to create an node that should look like this: * * * +H * 1 * 1 * * */ *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(formula()); *string += QString("%1%2\n").arg(lead).arg(m_charge); *string += QString("%1%2\n").arg(lead).arg(m_level); // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } /*! \brief Returns a string holding a textual representation of the member data. */ QString IonizeRule::toString() const { QString text; text += QString("Formula: %1").arg(Formula::toString()); text += QString(" - charge: %1 -- level: %2").arg(m_charge).arg(m_level); return text; } /* \brief Outputs a string holding a textual representation of the member data using qDebug(). */ void IonizeRule::debugPutStdErr() { qDebug() << __FILE__ << __LINE__ << QString("Ionizerule: charge=%1; level=%2; formula=%3") .arg(m_charge) .arg(m_level) .arg(m_formula); } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Isotope.cpp000664 001750 001750 00000044730 14647465366 022336 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2024 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "Isotope.hpp" namespace MsXpS { namespace libXpertMass { // #include // // extern const int elem_table_ID[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int // elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // This is the order of the columns in the gui TableView // ID // ELEMENT, // SYMBOL, // ATOMIC_NO, // MASS, // MASS_NO, // EXTRA_NEUTRONS, // PROBABILITY, // LN_PROBABILITY, // RADIOACTIVE, /*! \enum MsXpS::libXpertMass::IsotopeFields \brief This enum type documents the various member data in \l{Isotope}. The values assigned to the various enum members are used to specify the columsn in the GUI table view. They are also used to access substrings in the proper order in the \l{Isotope::initialize()}. \value ID Indicates Isotope::m_id. \value ELEMENT Indicates Isotope::m_element. \value SYMBOL Indicates the Isotope::m_symbol. \value ATOMIC_NUMBER Indicates the Isotope::m_atomicNo. \value MASS Indicates the Isotope::m_mass. \value MASS_NUMBER Indicates the Isotope::m_massNo. \value EXTRA_NEUTRONS Indicates the Isotope::m_extraNeutrons. \value PROBABILITY Indicates the Isotope::m_probability. \value LN_PROBABILITY Indicates the Isotope::m_lnProbability. \value RADIOACTIVE Indicates the Isotope::m_radioactive. \omitvalue LAST */ /*! \class MsXpS::libXpertMass::Isotope \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile Isotope.hpp \brief The Isotope class models an isotope. The Isotope class models an Isotope by featuring all the methods and member data required to fully characterize an isotope. The member data in this class have been inspired by the element tables from the IsoSpec library. Please, see \l{https://github.com/MatteoLacki/IsoSpec/}. */ /*! \variable int MsXpS::libXpertMass::Isotope::m_id \brief The unambiguous id for the chemical element (Hydrogen: 1, Helium, 2, Carbon 6, for example). The \a id is repeated for each isotope of each chemical element. */ /*! \variable int MsXpS::libXpertMass::Isotope::m_element \brief The element, like "carbon" or "nitrogen" (lowercase). */ /*! \variable int MsXpS::libXpertMass::Isotope::m_symbol \brief The symbol, like "C" or "N". */ /*! \variable int MsXpS::libXpertMass::Isotope::m_atomicNo \brief The atomic number (Z), like 6 for "C", number of protons. */ /*! \variable int MsXpS::libXpertMass::Isotope::m_mass \brief The mass. */ /*! \variable int MsXpS::libXpertMass::Isotope::m_massNo \brief The mass number, (A) like 12 for "C", number of (protons + neutrons). */ /*! \variable int MsXpS::libXpertMass::Isotope::m_extraNeutrons \brief The extra neutrons in the isotope's nucleus. */ /*! \variable int MsXpS::libXpertMass::Isotope::m_probability \brief The probability of this isotope, that is, its abundance. */ /*! \variable int MsXpS::libXpertMass::Isotope::m_lnProbability \brief The ln (natural logarithm) of the probability \c p (e\sup{ln(p)}=p). */ /*! \variable int MsXpS::libXpertMass::Isotope::m_radioactive \brief The true if the isotope is unstable (disintegrates), false otherwise. */ /*! \typedef IsotopeSPtr \relates Isotope Synonym for std::shared_ptr. */ /*! \typedef IsotopeCstSPtr \relates Isotope Synonym for std::shared_ptr. */ /*! \brief Constructs the \l{Isotope} with all the required arguments. The isotope is created as a fully documented instance if all the following parameters a correctly set: \a id \l{MsXpS::libXpertMass::Isotope::m_id} \a element \l{MsXpS::libXpertMass::Isotope::m_element} \a symbol \l{MsXpS::libXpertMass::Isotope::m_symbol} \a atomicNo \l{MsXpS::libXpertMass::Isotope::m_atomicNo} \a mass \l{MsXpS::libXpertMass::Isotope::m_mass} \a massNo \l{MsXpS::libXpertMass::Isotope::m_massNo} \a extraNeutrons \l{MsXpS::libXpertMass::Isotope::m_extraNeutrons} \a probability \l{MsXpS::libXpertMass::Isotope::m_probability} \a lnProbability \l{MsXpS::libXpertMass::Isotope::m_lnProbability} \a radioactive \l{MsXpS::libXpertMass::Isotope::m_radioactive} */ Isotope::Isotope(int id, QString element, QString symbol, int atomicNo, double mass, int massNo, int extraNeutrons, double probability, double lnProbability, bool radioactive) : m_id(id), m_element(element), m_symbol(symbol), m_atomicNo(atomicNo), m_mass(mass), m_massNo(massNo), m_extraNeutrons(extraNeutrons), m_probability(probability), m_lnProbability(lnProbability), m_radioactive(radioactive) { } /*! \brief Constructs the \l{Isotope} as a copy of \a other. */ Isotope::Isotope(const Isotope &other) { m_id = other.m_id; m_element = other.m_element; m_symbol = other.m_symbol; m_atomicNo = other.m_atomicNo; m_mass = other.m_mass; m_massNo = other.m_massNo; m_extraNeutrons = other.m_extraNeutrons; m_probability = other.m_probability; m_lnProbability = other.m_lnProbability; m_radioactive = other.m_radioactive; } /*! \brief Constructs the \l{Isotope} using all the data in the \a text string. The strings contains all the Isotope data, separated by a comma ',' exactly with the same format as that implemented by Isotope::toString(). \sa Isotope::initialize(), Isotope::toString() */ Isotope::Isotope(const QString &text) { // This is somehow the reverse of toString(). if(!initialize(text)) qFatal("Failed to initialize an isotope with text."); } /*! \brief Destructs the \l{Isotope}. */ Isotope::~Isotope() { } /*! Initializes the \l{Isotope} using all the data in the \a text string. The string passed as argument is QString::simplified() and QString::split() with ',' as the delimiter. The obtained strings are converted to the corresponding numerical or textual values to initalize all the member data. The code is similar to: \code m_radioactive = string_list[static_cast(IsotopeFields::RADIOACTIVE)].toInt(&ok); if(!ok) { qDebug() << "Failed to extract the isotope radioactive."; return false; } \endcode Returns true if the string contained valid substrings that successfully initialized the \l{Isotope}, false otherwise. \sa Isotope::Isotope(const QString &text) */ bool Isotope::initialize(const QString &text) { if(text.isEmpty()) return false; // At this point deconstruct the line. Make sure we remove all spaces from // beginning and end. QString local_text = text.simplified(); // At this point, use a regular expression to match the text. QStringList string_list = local_text.split(','); // qDebug() << "Expecting " << static_cast(IsotopeFields::LAST) //<< "comma-separated fields for isotope-describing text line." //<< "QStringList is:" << string_list; if(string_list.size() != static_cast(IsotopeFields::LAST)) { qDebug() << "The text does not match an Isotope definition."; return false; } bool ok = false; m_id = string_list[static_cast(IsotopeFields::ID)].toInt(&ok); if(!ok) { qDebug() << "Failed to extract the isotope ID."; return false; } m_element = string_list[static_cast(IsotopeFields::ELEMENT)]; if(m_element.isEmpty()) { qDebug() << "Failed to extract the element name."; return false; } m_symbol = string_list[static_cast(IsotopeFields::SYMBOL)]; if(m_symbol.isEmpty()) { qDebug() << "Failed to extract the element symbol."; return false; } m_atomicNo = string_list[static_cast(IsotopeFields::ATOMIC_NUMBER)].toInt(&ok); if(!ok) { qDebug() << "Failed to extract the isotope atomic number."; return false; } m_mass = string_list[static_cast(IsotopeFields::MASS)].toDouble(&ok); if(!ok) { qDebug() << "Failed to extract the isotope mass."; return false; } m_massNo = string_list[static_cast(IsotopeFields::MASS_NUMBER)].toDouble(&ok); if(!ok) { qDebug() << "Failed to extract the isotope mass number."; return false; } m_extraNeutrons = string_list[static_cast(IsotopeFields::EXTRA_NEUTRONS)].toInt(&ok); if(!ok) { qDebug() << "Failed to extract the isotope extra neutrons."; return false; } m_probability = string_list[static_cast(IsotopeFields::PROBABILITY)].toDouble(&ok); if(!ok) { qDebug() << "Failed to extract the isotope probability."; return false; } m_lnProbability = string_list[static_cast(IsotopeFields::LN_PROBABILITY)].toDouble(&ok); if(!ok) { qDebug() << "Failed to extract the isotope log probability."; return false; } m_radioactive = string_list[static_cast(IsotopeFields::RADIOACTIVE)].toInt(&ok); if(!ok) { qDebug() << "Failed to extract the isotope radioactive."; return false; } return true; } /*! \brief Sets the id of the isotope to \a id. \sa getId() */ void Isotope::setId(int id) { m_id = id; } /*! \brief Returns the id of the isotope. \sa setId() */ int Isotope::getId() const { return m_id; } /*! \brief Sets the element of the isotope to \a element. \sa getElement() */ void Isotope::setElement(const QString &element) { m_element = element; } /*! \brief Returns the element of the isotope. \sa setElement() */ QString Isotope::getElement() const { return m_element; } /*! \brief Sets the symbol of the isotope to \a symbol. \sa getSymbol() */ void Isotope::setSymbol(const QString &symbol) { m_symbol = symbol; } /*! \brief Returns the symbol of the isotope. \sa setSymbol() */ QString Isotope::getSymbol() const { return m_symbol; } /*! \brief Sets the the atomic number of the isotope to \a atomic_number. \sa getAtomicNo() */ void Isotope::setAtomicNo(int atomic_number) { m_atomicNo = atomic_number; } /*! \brief Returns the atomic number of the isotope. \sa setAtomicNo() */ int Isotope::getAtomicNo() const { return m_atomicNo; } /*! \brief Sets the the mass of the isotope to \a mass. \sa getMass() */ void Isotope::setMass(double mass) { m_mass = mass; } /*! \brief Returns the mass of the isotope. \sa setMass() */ double Isotope::getMass() const { return m_mass; } /*! \brief Sets the the mass number of the isotope to \a mass_number. \sa getMassNo() */ void Isotope::setMassNo(int mass_number) { m_massNo = mass_number; } /*! \brief Returns the mass number of the isotope. \sa setMassNo() */ int Isotope::getMassNo() const { return m_massNo; } /*! \brief Sets the extra neutrons of the isotope to \a extra_neutrons. \sa getExtraNeutrons() */ void Isotope::setExtraNeutrons(int extra_neutrons) { m_extraNeutrons = extra_neutrons; } /*! \brief Returns the extra neutrons of the isotope. \sa setExtraNeutrons() */ int Isotope::getExtraNeutrons() const { return m_extraNeutrons; } /*! \brief Sets the probability (the abundance) of the isotope to \a probability. \sa getProbability() */ void Isotope::setProbability(double probability) { m_probability = probability; } /*! \brief Returns the probability (the abundance) of the isotope. \sa setProbability() */ double Isotope::getProbability() const { return m_probability; } /*! \brief Sets the ln(probability) of the isotope to \a ln_probability. \sa getLnProbability() */ void Isotope::setLnProbability(double ln_probability) { m_lnProbability = ln_probability; } /*! \brief Returns the ln(probability) of the isotope. \sa setLnProbability() */ double Isotope::getLnProbability() const { return m_lnProbability; } /*! \brief Sets if the isotope is radioactive to \a is_radioactive. \sa getRadioactive() */ void Isotope::setRadioactive(bool is_radioactive) { m_radioactive = is_radioactive; } /*! \brief Returns true if the isotope is radioactive, false otherwise. \sa setRadioactive() */ bool Isotope::getRadioactive() const { return m_radioactive; } /*! \brief Validates the isotope. The symbol, mass and probability member data are scrutinized and if errors are detected an error counter is incremented. \a errors_p Pointer to a string where the detected errors (if any) are stored as meaningful strings. If \a errors_p is nullptr, the errors are not stored. \code if(m_symbol.isEmpty()) { ++error_count; errors += "The symbol is not set."; } \endcode Returns the error count. If no error occurred, the returned value is 0. */ int Isotope::validate(QString *errors_p) const { int error_count = 0; QString errors; // The minimal set of data that an isotope needs to have is the symbol, the // mass and the probability. if(m_symbol.isEmpty()) { ++error_count; errors += "The symbol is not set."; } if(m_mass <= 0) { ++error_count; errors += "The mass is not set."; } if(m_probability > 1 || m_probability <= 0) { ++error_count; errors += "The probability is not set."; } // Now, if there were errors, we would like to document them with the symbol // name if it was set. if(error_count) { if(errors_p) { if(m_symbol.isEmpty()) *errors_p = errors + "\n"; else // Document the name of the symbol to make the errors more // meaningful. *errors_p += "For symbol " + m_symbol + ": " + errors + "\n"; } } return error_count; } /*! \brief Assigns to this isotope the \a other isotope's member data. Each member datum in \a other is copied to this isotope. Returns a reference to this isotope. */ Isotope & Isotope::operator=(const Isotope &other) { if(&other == this) return *this; m_id = other.m_id; m_element = other.m_element; m_symbol = other.m_symbol; m_atomicNo = other.m_atomicNo; m_mass = other.m_mass; m_massNo = other.m_massNo; m_extraNeutrons = other.m_extraNeutrons; m_probability = other.m_probability; m_lnProbability = other.m_lnProbability; m_radioactive = other.m_radioactive; return *this; } /*! \brief Tests the equality between this isotope and \a other. Each member datum in \a other is compared to this isotope's member datum. If a difference is detected, a counter is incremented. Returns true if no difference has been encountered (the counter is 0) and false otherwise. \sa operator!=() */ bool Isotope::operator==(const Isotope &other) const { int errors = 0; errors += (m_id == other.m_id ? 0 : 1); errors += (m_element == other.m_element ? 0 : 1); errors += (m_symbol == other.m_symbol ? 0 : 1); errors += (m_atomicNo == other.m_atomicNo ? 0 : 1); errors += (m_mass == other.m_mass ? 0 : 1); errors += (m_massNo == other.m_massNo ? 0 : 1); errors += (m_extraNeutrons == other.m_extraNeutrons ? 0 : 1); errors += (m_probability == other.m_probability ? 0 : 1); errors += (m_lnProbability == other.m_lnProbability ? 0 : 1); errors += (m_radioactive == other.m_radioactive ? 0 : 1); return (errors > 0 ? false : true); } /*! \brief Tests the inequality between this isotope and \a other. Computes the negation of the result obtained by calling \l{operator==()}. Returns true if at lease one difference has been encountered and false otherwise. \sa operator==() */ bool Isotope::operator!=(const Isotope &other) const { return !(*this == other); } /*! \brief Returns a string containing a comma-separated textual representation of all the member data values. All the member data values are separated using commas ','. Only the values are stored in the string, without naming the variables: \code return QString("%1,%2,%3,%4,%5,%6,%7,%8,%9,%10") .arg(m_id) .arg(m_element) .arg(m_symbol) .arg(m_atomicNo) .arg(m_mass, 0, 'f', 60) .arg(m_massNo) .arg(m_extraNeutrons) .arg(m_probability, 0, 'f', 60) .arg(m_lnProbability, 0, 'f', 60) .arg(m_radioactive ? 1 : 0); \endcode Returns a string. */ QString Isotope::toString() const { // We need to use CSV because there might be spaces in the // text in the IsoSpec tables. return QString("%1,%2,%3,%4,%5,%6,%7,%8,%9,%10") .arg(m_id) .arg(m_element) .arg(m_symbol) .arg(m_atomicNo) .arg(m_mass, 0, 'f', 60) .arg(m_massNo) .arg(m_extraNeutrons) .arg(m_probability, 0, 'f', 60) .arg(m_lnProbability, 0, 'f', 60) .arg(m_radioactive ? 1 : 0); } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/IsotopicClusterGenerator.cpp000664 001750 001750 00000102644 14650426761 025703 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Std lib includes #include /////////////////////// Qt includes #include /////////////////////// IsoSpec #include #include // extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int // elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; /////////////////////// pappsomspp includes #include "pappsomspp/trace/trace.h" #include "pappsomspp/types.h" /////////////////////// Local includes #include "globals.hpp" #include "PeakCentroid.hpp" #include "Formula.hpp" #include "IsotopicClusterGenerator.hpp" #include "IsotopicDataLibraryHandler.hpp" #include "IsotopicDataUserConfigHandler.hpp" #include "IsotopicDataManualConfigHandler.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::IsotopicClusterGenerator \inmodule libXpertMass \ingroup XpertMassMassCalculations \inheaderfile IsotopicClusterGenerator.hpp \brief The IsotopicClusterGenerator class provides the features needed to model isotopic clusters starting from (elemental-composition, charge) pairs. The modelling uses the member isotopic data. The generated isotopic clusters only contain cluster centroid peaks. If peaks should have a profile, then they need to be shaped. \sa IsotopicClusterShaper, MassPeakShaper */ /*! \enum MsXpS::libXpertMass::IsotopicDataType This enum specifies the type of isotopic data. \value NOT_SET: Not configured. . \value LIBRARY_CONFIG: The isotopic data are loaded intact from the IsoSpec library data and are considered pristine natural abundance data. . \value USER_CONFIG: The isotopic data are in the same format as for LIBRARY_CONFIG but might have been modified by the user to configure new abundances. . \value MANUAL_CONFIG: The isotopic data are in a specific format, different than the two above, that actually crafts the isotopes starting from scratch. */ /*! \typealias FormulaChargePair Alias for std::pair. */ /*! \typealias IsotopicClusterChargePair Alias for std::pair. */ /*! \typedef IsotopicClusterGeneratorSPtr \relates IsotopicClusterGenerator. Synonym for std::shared_ptr. */ IsotopicClusterGenerator::IsotopicClusterGenerator() { } /*! \typedef IsotopicClusterGeneratorCstSPtr \relates IsotopicClusterGenerator. Synonym for std::shared_ptr. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterGenerator::msp_isotopicData \brief The isotopic data needed for the computations. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterGenerator::m_isotopicDataType \brief The \l IsotopicDataType type of data. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterGenerator::m_maxSummedProbability \brief The summed probability of all the isotopic cluster peaks. The computation stops when this probability is reached. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterGenerator::m_normalizeIntensity \brief The most intense cluster peak's intensity that is used to normalize all the other cluster peaks. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterGenerator::m_sortType \brief The type of sorting required for the generated cluster peak centroids. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterGenerator::m_sortOrder \brief The order of the sorting for the generated cluster peak centroids. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterGenerator::m_formulaChargePairs \brief The set of (elemental composition, charge) pairs. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterGenerator::m_isotopicClusterChargePairs \brief The set of (isotopic cluster, charge) pairs. */ /*! \brief Constructs a IsotopicClusterGenerator instance. \list \li \a isotopic_data_sp: The isotopic data used for the calculations. \endlist */ IsotopicClusterGenerator::IsotopicClusterGenerator( libXpertMass::IsotopicDataSPtr isotopic_data_sp) : msp_isotopicData(isotopic_data_sp) { } /*! \brief Destructs this IsotopicClusterGenerator instance. */ IsotopicClusterGenerator::~IsotopicClusterGenerator() { // qDebug(); } /*! \brief Sets the isotopic data type to \a isotopic_data_type. */ void IsotopicClusterGenerator::setIsotopicDataType( IsotopicDataType isotopic_data_type) { m_isotopicDataType = isotopic_data_type; } /*! \brief Sets the isotopic data to \a isotopic_data_sp. */ void IsotopicClusterGenerator::setIsotopicData( libXpertMass::IsotopicDataSPtr isotopic_data_sp) { msp_isotopicData = isotopic_data_sp; } /*! \brief Returns the isotopic data. */ libXpertMass::IsotopicDataSPtr IsotopicClusterGenerator::getIsotopicData() const { return msp_isotopicData; } /*! \brief Adds the (elemental composition, charge) pair \a formula_charge_pair to the member list of FormulaChargePair instances. The member list of FormulaChargePair instances is first cleared. */ void IsotopicClusterGenerator::setFormulaChargePair( FormulaChargePair &formula_charge_pair) { m_formulaChargePairs.clear(); m_formulaChargePairs.push_back(formula_charge_pair); // qDebug() << formula_charge_pair.first << formula_charge_pair.second; } /*! \brief Adds the (elemental composition, charge) pair \a formula_charge_pair to the member list of FormulaChargePair instances. */ void IsotopicClusterGenerator::appendFormulaChargePair( FormulaChargePair &formula_charge_pair) { m_formulaChargePairs.push_back(formula_charge_pair); // qDebug() << formula_charge_pair.first << formula_charge_pair.second; } /*! \brief Adds the (elemental composition, charge) pairs \a formula_charge_pairs to the member list of FormulaChargePair instances. The member list of FormulaChargePair instances is first cleared. */ void IsotopicClusterGenerator::setFormulaChargePairs( const std::vector &formula_charge_pairs) { m_formulaChargePairs.clear(); m_formulaChargePairs.assign(formula_charge_pairs.begin(), formula_charge_pairs.end()); // qDebug() << "Set" << m_formulaChargePairs.size() << "formula/charge pairs"; // for(auto pair : m_formulaChargePairs) // qDebug() << pair.first << "/" << pair.second; } /*! \brief Sets the summed probability maximum value to \a max_probability. */ void IsotopicClusterGenerator::setMaxSummedProbability(double max_probability) { m_maxSummedProbability = max_probability; } /*! \brief Sets the normalization intensity to \a normalize_intensity. */ void IsotopicClusterGenerator::setNormalizationIntensity(int normalize_intensity) { m_normalizeIntensity = normalize_intensity; } /*! \brief Sets the \a sort_type. */ void IsotopicClusterGenerator::setSortType(pappso::SortType sort_type) { m_sortType = sort_type; } /*! \brief Sets the \a sort_order. */ void IsotopicClusterGenerator::setSortOrder(pappso::SortOrder sort_order) { m_sortOrder = sort_order; } /*! \brief Validates the elemental composition \a formula. The \a formula needs to be fully indexed, that is, even an atom present only once needs to be indexed with '1', like this \c H2O1. Returns true if validation was successful, false otherwise. \sa Formula::validate() */ bool IsotopicClusterGenerator::validateFormula(Formula &formula) { // qDebug() << "Checking formula:" << formula.toString() //<< "against an isotopic data set of " << msp_isotopicData->size() //<< "isotopes"; // Check the syntax of the formula. Note that we need an obligatory count // index even for elements that are present a single time (H2O1 note the // 1). // IsoSpec requires that even single-count element be qualified with an // index (H2O1) formula.setForceCountIndex(true); // We have to validate because the formula might be "C5H6N3-O1", in which case // if would fail the simple checkSyntax() call (that call needs to be used on // already split parts of a formula). if(!formula.validate( msp_isotopicData, true /*store atom count*/, true /*reset counts*/)) return false; return true; } /*! \brief Validates all the elemental compositions in this IsotopicClusterGenerator instance. Each \l FormulaChargePair in m_formulaChargePairs is validated for its elemental composition by first creating a \l Formula out of it. Returns true if validation was successful, false otherwise. \sa validateFormula, Formula::validate() */ bool IsotopicClusterGenerator::validateAllFormulas() { for(FormulaChargePair &pair : m_formulaChargePairs) { Formula formula(pair.first); if(!validateFormula(formula)) return false; } return true; } /*! \brief Runs the IsoSpec-based isotopic calculations. \list \li \a element_count: the number of elements in the chemical composition. \li \a charge: the charge of the analyte. \li \a per_element_isotopes_count_array_p: pointer to int array. This array lists the number of isotopes that each element has. Typically, C has 2, O has 3, P has 1... \li \a per_element_symbol_count_array_p: pointer to int array. This array lists the count of atoms of each element. Typically H20 will have 2 for H and 1 for O. \li \a per_element_isotope_masses_arrays_p_p: pointer to pointer to double array (array of arrays of double). Each array contains a new sub-array for each symbol. The sub-array contains the isotopic masses for one of the element symbols. \li \a per_element_isotope_probs_arrays_p_p: pointer to pointer to double array (array of arrays of double). Each array contains a new sub-array for each symbol. The sub-array contains the isotopic probs for one of the element symbols. \endlist Returns a pappso::Trace with the calculated isotopic cluster. */ pappso::TraceSPtr IsotopicClusterGenerator::runIsotopicDataCalculations( std::size_t element_count, int charge, int *per_element_isotopes_count_array_p, int *per_element_symbol_count_array_p, double **per_element_isotope_masses_arrays_p_p, double **per_element_isotope_probs_arrays_p_p) { // We get all the isotopic data relevant to the isotopic cluster modelling // calculation as performed by the IsoSpec library. if(per_element_isotopes_count_array_p == nullptr || per_element_symbol_count_array_p == nullptr || per_element_isotope_masses_arrays_p_p == nullptr || per_element_isotope_probs_arrays_p_p == nullptr) qFatal("Programming error. The pointers cannot be nullptr."); if(m_maxSummedProbability <= 0 || m_maxSummedProbability > 1) { qDebug() << "The maximum summed probability has an incorrect value:" << m_maxSummedProbability; return nullptr; } IsoSpec::IsoLayeredGenerator iso( IsoSpec::Iso(element_count, per_element_isotopes_count_array_p, per_element_symbol_count_array_p, per_element_isotope_masses_arrays_p_p, per_element_isotope_probs_arrays_p_p), // The three values below are from the documentation (default values in the // constructor). We have added them 20230329 because we discovered that they // were needed on minGW64, otherwise we would experience crashes. 1000, 1000, true, m_maxSummedProbability); // qDebug() << "iso's mono peak mass:" << iso.getMonoisotopicPeakMass(); // Each time we run a calculation, we do store the results in a new // IsotopicCluster. pappso::TraceSPtr isotopic_cluster_sp = std::make_shared(); // We store the results as std::vector> // because we'll want to sort the values according to the user's requirements. double effective_summed_probs = 0; // Iterate in all the cluster configurations and output all the ones that // summatively make a total probability <= to the probability set by the // user. /////////////// ATTENTION //////////////// // The loop below is tricky, while(iso.advanceToNextConfiguration()) { double iso_prob = iso.prob(); double iso_mz = iso.mass() / charge; // qDebug() << "For current configuration (charge accounted for):" << // iso_mz //<< iso_prob //<< "and effective_probs_sum : " << effective_summed_probs; // Create a peak centroid and store it (remark that we change the mass // of the ion into m/z because the user had set the charge corresponding // to the formula for which the isotopic cluster is being computed. isotopic_cluster_sp->push_back(pappso::DataPoint(iso_mz, iso_prob)); // qDebug() << "Pushed back new peak centroid:" << iso_mz << "/" << // iso_prob; // We do this increment at the end of the block. Indeed, if we had set up // at the top of the block, then, if the very first centroid had already a // prob > m_maxSummedProbability, then we would end up with an empty // cluster! effective_summed_probs += iso_prob; if(effective_summed_probs > m_maxSummedProbability) { // qDebug() << "Reached the max value: effective_summed_probs:" //<< effective_summed_probs //<< "and m_maxSummedProbability:" << m_maxSummedProbability //<< "BREAKING."; break; } else { // qDebug() << "Not yet reached the max value: effective_summed_probs // : " //<< effective_summed_probs //<< "and m_maxSummedProbability:" << m_maxSummedProbability; } } // Now perform the normalization to the Gaussian apex intensity value if // so is requested by the user For this we first need to find // what is the most intensity peak centroid. Then, we'll normalize against // it by dividing all intensity that that most intense value and // multiplying by the requested gaussian apex intensity value. // qDebug() << "Now asking for normalization."; // Just a debug check. // qDebug() << "Before normalizing, first data point:" //<< isotopic_cluster_sp->front().toString(); normalizeIntensities(isotopic_cluster_sp); // Just a debug check. // qDebug() << "After normalizing, first data point:" //<< isotopic_cluster_sp->front().toString(); // Now check if the user requests to kind of sorting of the PeakCentroid // instances. sortPeakCentroids(isotopic_cluster_sp); // qDebug() << "Now returning a cluster of size:" << // isotopic_cluster_sp->size(); return isotopic_cluster_sp; } /*! \brief Calculates the isotopic cluster's peak centroids for \a formula_charge_pair. \list 1 \li The elemental composition formula string is converted to a \l Formula and validated. \li The proper isotopic data handler is allocated (\l IsotopicDataType). \li The number of symbols in the elemental composition is determined. \li The int arrays and arrays of double arrays are allocated. \li The arrays are filled-in with \l configureIsotopicData(). \li The calculations are performed on these arrays with \l runIsotopicDataCalculations(). \endlist Returns the results of the computation in the form of a IsotopicClusterChargePair instance. */ IsotopicClusterChargePair IsotopicClusterGenerator::generateIsotopicClusterCentroids( FormulaChargePair formula_charge_pair) { // qDebug() << "Starting generation of isotopic cluster for formula:" //<< formula_charge_pair.first; Formula formula(formula_charge_pair.first); // The check will generate useful data inside the Formula! if(!validateFormula(formula)) return std::pair(std::make_shared(), 0); // Use the correct handler! std::unique_ptr isotopic_data_handler_up = nullptr; if(m_isotopicDataType == IsotopicDataType::LIBRARY_CONFIG) { isotopic_data_handler_up = std::make_unique(msp_isotopicData); } else if(m_isotopicDataType == IsotopicDataType::USER_CONFIG) { isotopic_data_handler_up = std::make_unique(msp_isotopicData); } else if(m_isotopicDataType == IsotopicDataType::MANUAL_CONFIG) { isotopic_data_handler_up = std::make_unique(msp_isotopicData); } else qFatal("Programming error. The isotopic data type is not correct."); // At this point we need to create the arrays exactly as we do in the user // manual config. So we need to know how many different chemical element // symbols we have in the formula. std::map symbol_double_count_map = formula.getSymbolCountMap(); std::size_t element_count = symbol_double_count_map.size(); // qDebug() << "Number of different symbols in the formula:" << element_count; if(!element_count) { qDebug() << "There is not a single element in the Formula."; return std::pair(std::make_shared(), 0); } // qDebug() << "The validated formula has a symbol/count map size:" //<< element_count; // We have to copy the symbol/count map obtained by validating // the formula into the isotopic data handler. That map is essential for the // crafting by the handler of the different IsoSpec arrays. // At this point, all the elements defined by the user have been completed and // we'll have to create the static arrays that are needed by IsoSpec. // We now need to construct the C arrays for IsoSpec. The arrays need to // be filled-in very accurately. // This array lists the number of isotopes that each element has. // Typically, C has 2, O has 3, P has 1... int *per_element_isotopes_count_array_p = nullptr; // This array lists the count of atoms of each element. // Typically H20 will have 2 for H and 1 for O. int *per_element_symbol_count_array_p = nullptr; // These are arrays of arrays! Each array contains a new sub-array for each // symbol. The sub-array contains the isotopic masses (or probs) for one of // the element symbols. The sub-arrays are allocated by the handler below. double **per_element_isotope_masses_arrays_p_p = nullptr; double **per_element_isotope_probs_arrays_p_p = nullptr; // The isotopic cluster calculations do not understand // formulas with double counts for the symbols!! // We thus need to convert the symbol_count_map that is // -based to a map that has its second // member, not of double type but of int type. std::map symbol_int_count_map; std::map::const_iterator iter = symbol_double_count_map.begin(); while(iter != symbol_double_count_map.end()) { symbol_int_count_map[iter->first] = static_cast(iter->second); // qDebug() << "Old " << iter->first << "double version:" << iter->second // << "new int version:" << symbol_int_count_map[iter->first]; ++iter; } // We pass the array pointers by reference. if(!configureIsotopicData(symbol_int_count_map, per_element_isotopes_count_array_p, per_element_symbol_count_array_p, per_element_isotope_masses_arrays_p_p, per_element_isotope_probs_arrays_p_p)) { qDebug() << "Failed to actually prepare the isotopic data tables for the " "computation."; return std::pair(std::make_shared(), 0); } // At this point we have all the arrays needed to work. pappso::TraceSPtr isotopic_cluster_sp = runIsotopicDataCalculations(element_count, formula_charge_pair.second, per_element_isotopes_count_array_p, per_element_symbol_count_array_p, per_element_isotope_masses_arrays_p_p, per_element_isotope_probs_arrays_p_p); // FIXME // To avoid a memory leak, we need to delete the mass and prob heap-allocated // arrays. // delete[] per_element_isotopes_count_array_p; // delete[] per_element_symbol_count_array_p; // for(std::size_t iter = 0; iter < element_count; ++iter) //{ // delete[] per_element_isotope_masses_arrays_p_p[iter]; // delete[] per_element_isotope_probs_arrays_p_p[iter]; //} if(isotopic_cluster_sp == nullptr) qDebug() << "Failed to compute an isotopic cluster for formula:" << formula_charge_pair.first; // Just a debug check. // qDebug() << "After normalizing, first data point:" //<< isotopic_cluster_sp->front().toString(); // qDebug() << "Done generating cluster for formula:" //<< formula_charge_pair.first; return std::pair(isotopic_cluster_sp, formula_charge_pair.second); } /*! \brief Configures the isotopic data in a set of arrays for the (symbol,count) pairs in \a symbol_count_map. \list \li \a per_element_isotopes_count_array_p: pointer to int array. This array lists the number of isotopes that each element has. Typically, C has 2, O has 3, P has 1... \li \a per_element_symbol_count_array_p: pointer to int array. This array lists the count of atoms of each element. Typically H20 will have 2 for H and 1 for O. \li \a per_element_isotope_masses_arrays_p_p: pointer to pointer to double array (array of arrays of double). Each array contains a new sub-array for each symbol. The sub-array contains the isotopic masses for one of the element symbols. \li \a per_element_isotope_probs_arrays_p_p: pointer to pointer to double array (array of arrays of double). Each array contains a new sub-array for each symbol. The sub-array contains the isotopic probs for one of the element symbols. \endlist Returns a pappso::Trace with the calculated isotopic cluster. */ bool IsotopicClusterGenerator::configureIsotopicData( std::map &symbol_count_map, int *&per_element_isotopes_count_array_p, int *&per_element_symbol_count_array_p, double **&per_element_isotope_masses_arrays_p_p, double **&per_element_isotope_probs_arrays_p_p) { // Start by allocating the arrays we'll have to feed in this configuration // work. // However, we need to ensure that we actually have some stuff to work on. // That stuff is kind of a formula in the form of the std::map // m_symbolCountMap that pairs the symbols of the atoms in the formula and // the count for each symbol. if(!symbol_count_map.size()) return false; // qDebug() << "The isotopic data have" << msp_isotopicData->size() //<< "isotopes"; // Example used in the comments below: glucose, C6H12O6. // How many isotopes of each element symbols are there? // C:2, H:2, O:3 per_element_isotopes_count_array_p = new int[symbol_count_map.size()]; // How many atoms of each chemical element symbol are there? // C:6, H:12, O:6 per_element_symbol_count_array_p = new int[symbol_count_map.size()]; // Each subarray contains the mass of one of the isotopes for the element // symbol. // First array for C (two masses), second array for H (two masses), third // array for O (three masses). per_element_isotope_masses_arrays_p_p = new double *[symbol_count_map.size()]; // Each subarray contains the prob of one of the isotopes for the element // symbol. // First array for C (two probs), second array for H (two probs), third // array for O (three probs). per_element_isotope_probs_arrays_p_p = new double *[symbol_count_map.size()]; // Index that will allow to address the right slot in the arrays being // filled with isotopic data. int current_symbol_index = 0; for(auto item : symbol_count_map) { QString symbol = item.first; int symbol_count = item.second; // qDebug() << "Iterating in symbol/count:" << symbol << "/" << // symbol_count; // Immediately fill-in the symbol count value. per_element_symbol_count_array_p[current_symbol_index] = symbol_count; // Now get iterator bounding the isotopes for this symbol. std::pair::const_iterator, std::vector::const_iterator> iter_pair = msp_isotopicData->getIsotopesBySymbol(symbol); // Handy shortcuts std::vector::const_iterator iter = iter_pair.first; std::vector::const_iterator iter_end = iter_pair.second; std::size_t isotope_count = std::distance(iter, iter_end); // qDebug() << "For symbol:" << symbol << "there are:" << isotope_count //<< "isotopes"; // Sanity check if(isotope_count != msp_isotopicData->getIsotopeCountBySymbol(symbol)) qFatal( "Programming error. The is something wrong with the isotopic " "data."); // Fill-in the isotope count for the current symbol. per_element_isotopes_count_array_p[current_symbol_index] = isotope_count; // Fill-in the isotopes (mass/prob). For each element symbol in the // symbol/count map, we allocate a double array the size of the number // of isotopes so as to store the mass of each isotope (same for the // probs later). Once we have done that fill-in, we can set the address // of the array of the array of array below. // Allocate a double array for the masses and another one for the probs. double *masses_p = new double[isotope_count]; double *probs_p = new double[isotope_count]; int current_isotope_index = 0; while(iter != iter_end) { IsotopeSPtr isotope_sp = *iter; // qDebug() << "For symbol" << symbol << "iterating with index" //<< current_isotope_index << "with Isotope *" //<< isotope_sp.get(); masses_p[current_isotope_index] = isotope_sp->getMass(); probs_p[current_isotope_index] = isotope_sp->getProbability(); // Increment the iterator ++iter; // Increment the isotope index so that we fill next array cell. ++current_isotope_index; } // At this time the masses and probs for the isotopes of current symbol // have been filled-in. per_element_isotope_masses_arrays_p_p[current_symbol_index] = masses_p; per_element_isotope_probs_arrays_p_p[current_symbol_index] = probs_p; // We need to increment this index so as to address the right slot in // the arrays! ++current_symbol_index; } // At this point we have documented all the isotopic data required to // perform a calculation with IsoSpec. return true; } /*! \brief Normalizes the intensities of the isotopic cluster's peak centroids in \a isotopic_cluster_sp. If normalization is asked for, the most intense peak centroid in \a isotopic_cluster_sp is determined. That intensity becomes the m_normalizeIntensity value and all the other peak centroids' intensities are normalized. \note The normalization occurs \e{in place}. */ void IsotopicClusterGenerator::normalizeIntensities( pappso::TraceSPtr &isotopic_cluster_sp) { // qDebug() << "The normalize_intensity is:" << m_normalizeIntensity; // The most intense peak centroid's intensity value needs to be set to the // requested value and the same change ratio needs to be applied to all the // other peak centroids. // No normalization is asked for. if(m_normalizeIntensity == std::numeric_limits::min()) { // qDebug() << "No normalization was asked for. Skipping."; return; } if(!isotopic_cluster_sp->size()) { qDebug() << "The isotopic cluster has not a single data point."; return; } // First get the most intense centroid. double max_found_intensity = 0; // qDebug() << "isotopic_cluster_sp->size():" << isotopic_cluster_sp->size(); pappso::Trace::iterator vector_iterator = std::max_element( isotopic_cluster_sp->begin(), isotopic_cluster_sp->end(), [](const pappso::DataPoint first, const pappso::DataPoint second) { return first.y < second.y; }); if(vector_iterator == isotopic_cluster_sp->end()) qFatal("Programming error"); max_found_intensity = (*vector_iterator).y; if(!max_found_intensity) qFatal("The maximum intensity of the whole isotopic cluster is 0."); // qDebug().noquote() << "Peak centroid with maximum intensity: " //<< vector_iterator->toString(); // Calculate the ratio between the final requested intensity and the greatest // intensity. That ratio will be multiplied to intensity of the each peak // centroid, which is why we call it a factor. double intensity_factor = m_normalizeIntensity / max_found_intensity; // qDebug() << "Will multiply this intensity factor to each data point's " //"intensity value:" //<< intensity_factor; // Just a debug check. // qDebug() << "Before normalizing, first data point:" //<< isotopic_cluster_sp->front().toString(); std::for_each( isotopic_cluster_sp->begin(), isotopic_cluster_sp->end(), [intensity_factor](pappso::DataPoint &data_point) // modify in-place { // qDebug() << "Before normalization:" << // data_point.toString(); double normalized_intensity = data_point.y * intensity_factor; data_point.y = normalized_intensity; // qDebug() << "After normalization:" << // data_point.toString(); }); // At this point the centroids' intensity have been normalized. // Just a debug check. // qDebug() << "After normalizing, first data point:" //<< isotopic_cluster_sp->front().toString(); } /*! \brief Sorts the peak centroids of the isotopic cluster \a isotopic_cluster_sp. The sort is performed according to \l m_sortType. */ void IsotopicClusterGenerator::sortPeakCentroids( pappso::TraceSPtr &isotopic_cluster_sp) { if(m_sortType == pappso::SortType::no_sort) return; return isotopic_cluster_sp->sort(m_sortType, m_sortOrder); } /*! \brief Returns a string containing a space-separated set of m/z, intensity pairs, representing the isotopic cluster in \a isotopic_cluster_sp. */ QString IsotopicClusterGenerator::clusterToString( const pappso::TraceCstSPtr &isotopic_cluster_sp) const { // Export the results as a string. Note how we do export the relative // intensity. If there was normalization, that value was updated, otherwise it // had been initialized identical to the intensity upon creation of the // PeakCentroid instances in the vector. QString text; for(const pappso::DataPoint &dp : *isotopic_cluster_sp) { text += QString("%1 %2\n").arg(dp.x, 0, 'f', 30).arg(dp.y, 0, 'f', 30); } // qDebug().noquote() << "Cluster to string: " << text; return text; } /*! \brief Returns a string containing a space-separated set of m/z, intensity pairs, representing the isotopic clusters in the member isotopic clusters \l m_isotopicClusterChargePairs. */ QString IsotopicClusterGenerator::clustersToString() const { // Export the results as a string. Note how we do export the relative // intensity. If there was normalization, that value was updated, otherwise it // had been initialized identical to the intensity upon creation of the // PeakCentroid instances in the vector. QString text; for(auto isotopic_cluster_charge_pair : m_isotopicClusterChargePairs) { text += clusterToString(isotopic_cluster_charge_pair.first); } // qDebug().noquote() << text; return text; } /*! \brief Starts the computations. The member m_isotopicClusterChargePairs are first cleared. Returns the count of IsotopicClusterChargePair instances generated upon the calculations. */ std::size_t IsotopicClusterGenerator::run() { // Iterate in all the formula/charge pairs and for each compute an isotopic // cluster. m_isotopicClusterChargePairs.clear(); for(auto formula_charge_pair : m_formulaChargePairs) { IsotopicClusterChargePair pair = generateIsotopicClusterCentroids(formula_charge_pair); if(!pair.first->size()) { qFatal("The isotopic cluster is empty for formula: %s", formula_charge_pair.first.toLatin1().data()); } m_isotopicClusterChargePairs.push_back(pair); } return m_isotopicClusterChargePairs.size(); } /*! \brief Returns the member list ofIsotopicClusterChargePair instances. */ const std::vector & IsotopicClusterGenerator::getIsotopicClusterChargePairs() const { return m_isotopicClusterChargePairs; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/IsotopicClusterShaper.cpp000664 001750 001750 00000050156 14647465366 025211 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// StdLib includes #include #include #include // for std::numeric_limits /////////////////////// Qt includes /////////////////////// pappsomspp includes #include #include #include #include #include #include /////////////////////// Local includes #include "globals.hpp" #include "MassDataCborMassSpectrumHandler.hpp" #include "IsotopicClusterShaper.hpp" #include "MassPeakShaper.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::IsotopicClusterShaper \inmodule libXpertMass \ingroup XpertMassMassCalculations \inheaderfile IsotopicClusterShaper.hpp \brief The IsotopicClusterShaper class provides the features needed to shape sets of (peak centroid m/z, intensity) pairs associated to a given charge into a mass spectral pappso;:Trace. Each set of (peak centroid m/z, intensity) pairs corresponds to an isotopic cluster that is associated to a charge. The configuration of the peak shaping process is held in a specific \l MassPeakShaperConfig class. The output of the computation is a pappso::Trace obtained by combining all the different shapes obtained for the different peak centroids of all the sets of (peak centroid m/z, intensity) pairs. If binning was requested, the obtained Trace is the result of a combination accounting for the required bin size, otherwise the obtained Trace is the result of the mere addition of all the points in the different traces. \sa MassPeakShaperConfig */ /*! \variable MsXpS::libXpertMass::IsotopicClusterShaper::m_isotopicClusterChargePairs \brief Vector of pappso::Trace instances in pair with charges. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterShaper::m_config \brief The configuration driving the mass peak shaping process. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterShaper::m_mapTrace \brief The map relating a m/z value to its intensity. This map is a variant of pappso::Trace that is designed to allow for easy mass spectrum combination. It is generally used only for computations and is converted to a pappso::Trace once all the computations have been carried out. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterShaper::m_finalTrace \brief The pappso::Trace holding the final results of the computations. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterShaper::m_mzIntegrationParams \brief The configuration of the mass spectral combinations (for example, determines the bins, if binning is required). */ /*! \variable MsXpS::libXpertMass::IsotopicClusterShaper::m_mostIntensePeakMz \brief The most intense peak encountered during the calculations. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterShaper::m_smallestMz \brief The smallest m/z value encountered during the calculations. This value is required for the crafting of the bins. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterShaper::m_greatestMz \brief The greatest m/z value encountered during the calculations. This value is required for the crafting of the bins. */ /*! \variable MsXpS::libXpertMass::IsotopicClusterShaper::m_normalizeIntensity \brief The value by which all the peak shapes need to be normalized. */ /*! \brief Constructs a IsotopicClusterShaper instance. \list \li \a isotopic_cluster: The peak centroids belonging to an isotopic cluster in the form of a pappso::Trace. \li \a charge: The charge of the analyte below the isotopic cluster peaks. \li \a config: The mass peak shaping process configuration. \endlist Each pappso::DataPoint in the \a isotopic_cluster pappso::Trace corresponds to a (m/z, intensity) peak centroid belonging to the isotopic cluster. */ IsotopicClusterShaper::IsotopicClusterShaper( const pappso::Trace &isotopic_cluster, int charge, const MassPeakShaperConfig &config) : m_config(config) { // No need to reset, we are constructing. setIsotopicCluster(isotopic_cluster, charge, false); } /*! \brief Constructs a IsotopicClusterShaper instance. \list \li \a isotopic_cluster_charge_pairs: The pairs associating a pappso::Trace instance to its corresponding charge. \li \a config: The mass peak shaping process configuration. \endlist In this constructor, a set of sets of (m/z, charge) peak centroids is passed as argument. The pappso::Trace instances in \a isotopic_cluster_charge_pairs might correspond to all the isotopic clusters representing a given analyte at different charges. */ IsotopicClusterShaper::IsotopicClusterShaper( const std::vector &isotopic_cluster_charge_pairs, const MassPeakShaperConfig &config) : m_config(config) { // No need to reset, we are constructing. setIsotopicClusterChargePairs(isotopic_cluster_charge_pairs, false); } /*! \brief Destructs this IsotopicClusterShaper instance. */ IsotopicClusterShaper::~IsotopicClusterShaper() { } /*! \brief Sets the peak shaping process \a{config}uration. */ void IsotopicClusterShaper::setConfig(MassPeakShaperConfig config) { m_config = config; } /*! \brief Gets the peak shaping process configuration. */ MassPeakShaperConfig IsotopicClusterShaper::getConfig() const { return m_config; } /*! \brief Sets the intensity normalization value to \a max_intensity. */ void IsotopicClusterShaper::setNormalizeIntensity(int max_intensity) { m_normalizeIntensity = max_intensity; } /*! \brief Gets the intensity normalization value. */ int IsotopicClusterShaper::getNormalizeIntensity() const { return m_normalizeIntensity; } /*! \brief Runs the mass peak shaping process for all the \l IsotopicClusterChargePair objects in \l m_isotopicClusterChargePairs. If \a reset is true, the member \l m_mapTrace and \l m_finalTrace are cleared before starting the computations. The \l m_config member is first \l{MassPeakShaperConfig::resolve()}d to check that all the parameters have been properly set and are valid. Returns the obtained pappso::Trace corresponding to the combination of all the individual traces obtained for the various isotopic clusters and their corresponding charge. */ pappso::Trace & IsotopicClusterShaper::run(bool reset) { if(reset) { // Clear the map trace that will receive the results of the combinations. m_mapTrace.clear(); m_finalTrace.clear(); } if(!m_config.resolve()) { qDebug() << "The peak shaper configuration failed to resolve."; return m_finalTrace; } // This class works on a vector of pairs containing the following: // 1. a pappso::TraceCstSPtr // 2. a charge // We will process each pair in turn. If the integration requires bin, then // each shaped isotopic cluster will be combined into a mass spectrum. // Otherwise a trace combiner will be used. // When setting the data (either by construction or using the set<> functions, // we had monitored the smallest and the greatest m/z value over the whole set // of the DataPoint objects in the isotopic clusters (centroid data). This is // because we need, in case binning is required, these values to craft the // bins. // We will need to perform combinations, positive combinations. // This mass spectrum combiner is in case we need binning. pappso::MassSpectrumPlusCombiner mass_spectrum_plus_combiner; // This trace combiner is in case do *not* need binning. pappso::TracePlusCombiner trace_plus_combiner(-1); // Configure the mass spectrum combiner in case we need binning. if(m_config.withBins()) { // Bins were requested. // qDebug() << "Bins are required."; // Get the bin size out of the configuration. double bin_size = m_config.getBinSize(); // qDebug() << "The bin size in the config is:" << bin_size; // Because we had monitored the m/z values of all the shapes generated // above, we know the smallest and greatest m/z value that were // encountered in all that peak shaping activity. We thus can create the // bins faithfully. MassPeakWidthLogic logic = m_config.getMassPeakWidthLogic(); // The m_smallestMz and m_greatestMz values have been determined by // looking into the unshaped isotopic clusters passed to this object // either by construction or with functions. These two mz values are thus // peak centroids, not data points belonging to a shaped peak since we // have not yet started shaping the peaks. This means that we cannot // create bins start / ending at these values because we would loose the // first half of the first shaped peak centroid and the second half of the // last shaped peak centroid (a shaped peak goes left *and* right of the // peak centroid otherwise there would be no shape). // // This is why we provide a confortable margin fo the bin creation below // by removing 1 Th on the left of the smallest mz and by adding 1 Th on // right of the greatest mz. if(logic == MassPeakWidthLogic::FWHM) { m_mzIntegrationParams = pappso::MzIntegrationParams( m_smallestMz - 1, m_greatestMz + 1, pappso::BinningType::ARBITRARY, -1, pappso::PrecisionFactory::getDaltonInstance(bin_size), 1, true); } else if(logic == MassPeakWidthLogic::RESOLUTION) { double resolution = m_config.getResolution(); m_mzIntegrationParams = pappso::MzIntegrationParams( m_smallestMz - 1, m_greatestMz + 1, pappso::BinningType::ARBITRARY, -1, pappso::PrecisionFactory::getResInstance(resolution), m_config.getBinSizeDivisor(), true); } else qFatal( "Programming error. By this time, the mass peak width logic should " "have been defined."); // qDebug() << "The mz integration params:" //<< m_mzIntegrationParams.toString(); // Now compute the bins. std::vector bins = m_mzIntegrationParams.createBins(); // qDebug() << "The bins:" << bins; mass_spectrum_plus_combiner.setBins(bins); // qDebug() << "Set bins to the mass spectrum combiner:" //<< mass_spectrum_plus_combiner.getBins().size(); } std::size_t peak_centroid_count = 0; std::size_t isotopic_cluster_count = 0; // Iterate in the isotopic cluster/charge pairs. for(auto isotopic_cluster_charge_pair : m_isotopicClusterChargePairs) { int charge = isotopic_cluster_charge_pair.second; // Iterate in the data points of the current centroid data isotopic // cluster. for(auto data_point : *isotopic_cluster_charge_pair.first) { // Note the division by m_charge below! pappso::Trace trace = MassPeakShaper::computePeakShape( data_point.x / charge, data_point.y, m_config); // qDebug() << "The shaped isotopic cluster has size:" << // trace.size(); if(trace.size()) { if(m_config.withBins()) mass_spectrum_plus_combiner.combine(m_mapTrace, trace); else trace_plus_combiner.combine(m_mapTrace, trace); // qDebug() << qSetRealNumberPrecision(15) //<< "The map trace for combination has size:" //<< m_mapTrace.size() //<< "and starting m/z:" << m_mapTrace.begin()->first //<< "and ending m/z:" //<< std::prev(m_mapTrace.end())->first; ++peak_centroid_count; } } ++isotopic_cluster_count; } // qDebug() << QString( //"Successfully processed %1 isotopic clusters for a total of %2 " //"shaped peak centroids") //.arg(isotopic_cluster_count) //.arg(peak_centroid_count); // The user might have asked that the most intense m/z peak centroid be used // for normalization. In that case that peak centroid's intensity is brought // to m_normalizeIntensity and the ratio between its current intensity and // m_normalizeIntensity is used to normalize all the other data points in the // trace. if(m_normalizeIntensity != 1) { // qDebug() << "Now normalizing to intensity = " << m_normalizeIntensity; pappso::Trace trace = m_mapTrace.toTrace(); m_finalTrace = trace.filter(pappso::FilterNormalizeIntensities(m_normalizeIntensity)); // double max_int = normalized_trace.maxYDataPoint().y; // qDebug() << "After normalization max int:" << max_int; } else m_finalTrace = m_mapTrace.toTrace(); // qDebug() << "Returning a trace of size:" << m_finalTrace.size(); // pappso::Utils::writeToFile(m_finalTrace.toString(), "/tmp/mass/trace.txt"); return m_finalTrace; } /*! \brief Handles the \a isotopic_cluster_sp input isotopic cluster as a pappso::Trace. \a isotopic_cluster_sp is associated to a \a charge. If \a reset is true, the member vector of \l IsotopicClusterChargePair instances is cleared before the computations. This function is the workhorse for all the functions used to set the initial data for the computations. Its main task is to scrutinize the data in \a isotopic_cluster_sp and update the \l m_smallestMz, \l m_greatestMz and \l m_mostIntensePeakMz values based on the data passed as argument. */ void IsotopicClusterShaper::setIsotopicCluster( pappso::TraceCstSPtr isotopic_cluster_sp, int charge, bool reset) { if(reset) m_isotopicClusterChargePairs.clear(); double min_x = isotopic_cluster_sp->minX(); m_smallestMz = std::min(m_smallestMz, min_x); double max_x = isotopic_cluster_sp->maxX(); m_greatestMz = std::max(m_greatestMz, max_x); m_mostIntensePeakMz = isotopic_cluster_sp->maxYDataPoint().x; // qDebug() << qSetRealNumberPrecision(15) << "m_smallestMz:" << m_smallestMz //<< "m_greatestMz:" << m_greatestMz //<< "m_mostIntensePeakMz:" << m_mostIntensePeakMz; m_isotopicClusterChargePairs.push_back( IsotopicClusterChargePair(isotopic_cluster_sp, charge)); m_config.setReferencePeakMz(m_mostIntensePeakMz); } /*! \brief Handles the \a isotopic_cluster_sp input isotopic cluster as a pappso::Trace. \a isotopic_cluster_sp is associated to a \a charge. The member vector of \l IsotopicClusterChargePair instances is cleared before the computations. */ void IsotopicClusterShaper::setIsotopicCluster( pappso::TraceCstSPtr isotopic_cluster_sp, int charge) { setIsotopicCluster(isotopic_cluster_sp, charge, true); } /*! \brief Handles the \a isotopic_cluster input isotopic cluster as a pappso::Trace. \a isotopic_cluster is associated to a \a charge. If \a reset is true, the member vector of \l IsotopicClusterChargePair instances is cleared before the computations. */ void IsotopicClusterShaper::setIsotopicCluster(const pappso::Trace &isotopic_cluster, int charge, bool reset) { setIsotopicCluster( std::make_shared(isotopic_cluster), charge, reset); } /*! \brief Handles the \a isotopic_cluster input isotopic cluster as a pappso::Trace. \a isotopic_cluster is associated to a \a charge. The member vector of \l IsotopicClusterChargePair instances is cleared before the computations. */ void IsotopicClusterShaper::setIsotopicCluster(const pappso::Trace &isotopic_cluster, int charge) { setIsotopicCluster( std::make_shared(isotopic_cluster), charge, true); } /*! \brief Handles the \a isotopic_cluster_charge_pair input isotopic cluster as a IsotopicClusterChargePair. The member vector of \l IsotopicClusterChargePair instances is cleared before the computations. */ void IsotopicClusterShaper::setIsotopicCluster( IsotopicClusterChargePair isotopic_cluster_charge_pair) { setIsotopicCluster(isotopic_cluster_charge_pair.first, isotopic_cluster_charge_pair.second, true); } /*! \brief Handles the \a isotopic_cluster_charge_pair input isotopic cluster as a IsotopicClusterChargePair. If \a reset is true, the member vector of \l IsotopicClusterChargePair instances is cleared before the computations. */ void IsotopicClusterShaper::setIsotopicCluster( IsotopicClusterChargePair isotopic_cluster_charge_pair, bool reset) { setIsotopicCluster(isotopic_cluster_charge_pair.first, isotopic_cluster_charge_pair.second, reset); } /*! \brief Handles the \a isotopic_cluster_charge_pairs input isotopic cluster as a vector of IsotopicClusterChargePair instances. If \a reset is true, the member vector of \l IsotopicClusterChargePair instances is cleared before the computations. */ void IsotopicClusterShaper::setIsotopicClusterChargePairs( const std::vector &isotopic_cluster_charge_pairs, bool reset) { for(auto cluster_charge_pair : isotopic_cluster_charge_pairs) setIsotopicCluster( cluster_charge_pair.first, cluster_charge_pair.second, reset); // qDebug() << qSetRealNumberPrecision(15) << "m_smallestMz:" << m_smallestMz //<< "m_greatestMz:" << m_greatestMz //<< "m_mostIntensePeakMz:" << m_mostIntensePeakMz; } /*! \brief Handles the \a isotopic_cluster_charge_pairs input isotopic cluster as a vector of IsotopicClusterChargePair instances. The member vector of \l IsotopicClusterChargePair instances is cleared before the computations. */ void IsotopicClusterShaper::setIsotopicClusterChargePairs( const std::vector &isotopic_cluster_charge_pairs) { setIsotopicClusterChargePairs(isotopic_cluster_charge_pairs, true); // qDebug() << qSetRealNumberPrecision(15) << "m_smallestMz:" << m_smallestMz //<< "m_greatestMz:" << m_greatestMz //<< "m_mostIntensePeakMz:" << m_mostIntensePeakMz; } /*! \brief Adds an isotopic cluster in the form of the \a isotopic_cluster pappso::Trace associated to the corresponding \a charge. The member vector of \l IsotopicClusterChargePair instances is \e{not} cleared before the computations. */ void IsotopicClusterShaper::appendIsotopicCluster( const pappso::Trace &isotopic_cluster, int charge) { // Do not clear the isotopic clusters! setIsotopicCluster(isotopic_cluster, charge, false); } /*! \brief Adds isotopic clusters in the form of the \a isotopic_cluster_charge_pairs vector of IsotopicClusterChargePair instances. The member vector of \l IsotopicClusterChargePair instances is \e{not} cleared before the computations. */ void IsotopicClusterShaper::appendIsotopicClusterChargePairs( const std::vector &isotopic_cluster_charge_pairs) { setIsotopicClusterChargePairs(isotopic_cluster_charge_pairs, false); // qDebug() << "m_smallestMz:" << m_smallestMz << "m_greatestMz:" << // m_greatestMz //<< "m_mostIntensePeakMz:" << m_mostIntensePeakMz; } /*! \brief Returns the final result of all the computations as a string. */ QString IsotopicClusterShaper::shapeToString() { return m_finalTrace.toString(); } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/IsotopicData.cpp000664 001750 001750 00000134275 14647465366 023303 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2024 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Std lib includes #include #include /////////////////////// Qt includes #include /////////////////////// IsoSpec #include #include // extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int // elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; /////////////////////// Local includes #include "globals.hpp" #include "PeakCentroid.hpp" #include "IsotopicData.hpp" #include "IsotopicDataUserConfigHandler.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::IsotopicData \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile IsotopicData.hpp \brief The IsotopicData class provides a collection of \l{Isotope}s and associated methods to access them in various ways. The IsotopicData class provides a collection of \l{Isotope}s and provides methods to access them in various ways. Methods are available to return the monoisotopic mass of an isotope or the average mass calculated from the data of all the isotopes listed for a given chemical element. */ /*! \variable MsXpS::libXpertMass::IsotopicData::m_isotopes \brief The vector of \l{MsXpS::libXpertMass::IsotopeSPtr}. */ /*! \variable MsXpS::libXpertMass::IsotopicData::m_symbolMonoMassMap \brief The map relating the Isotope::m_symbol to the monoisotopic mass. */ /*! \variable MsXpS::libXpertMass::IsotopicData::m_symbolAvgMassMap \brief The map relating the Isotope::m_symbol to the average mass. */ /*! \typedef IsotopicDataSPtr \relates IsotopicData Synonym for std::shared_ptr. */ /*! \typedef IsotopicDataCstSPtr \relates IsotopicData Synonym for std::shared_ptr. */ /*! \typealias IsotopicData::IsotopeVectorCstIterator */ /*! \brief Constructs the \l{IsotopicData}. The instance will have empty member data. */ IsotopicData::IsotopicData() { } /*! \brief Constructs the \l{IsotopicData} as a copy of \a other. This is a deep copy with all the data in the containers copied from \a other to this IsotopicData. */ IsotopicData::IsotopicData(const IsotopicData &other) : m_isotopes(other.m_isotopes), m_symbolMonoMassMap(other.m_symbolMonoMassMap), m_symbolAvgMassMap(other.m_symbolAvgMassMap) { } /*! \brief Destructs the \l{IsotopicData}. Nothing is explicitely deleted in the destructor. */ IsotopicData::~IsotopicData() { // qDebug(); } /*! \brief Appends a new \l{IsotopeSPtr} to this \l{IsotopicData}. \a isotope_sp The new isotope to be added to this collection. The isotope is added to the end of the collection using \code m_isotopes.push_back(isotope_sp); \endcode Each time a new isotope is added to this collection, the chemical signification of the corresponding chemical element changes at heart. It might thus be required that the data in the two m_symbolMonoMassMap and m_symbolAvgMassMap maps be recalculated. \a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need to be updated with the new collection of isotopes. \sa updateMassMaps(), appendNewIsotopes() */ void IsotopicData::appendNewIsotope(IsotopeSPtr isotope_sp, bool update_maps) { m_isotopes.push_back(isotope_sp); // We have modified the fundamental data, we may need to recompute some data. // update_maps might be false when loading data from a file, in which case it // is the responsibility of the user to call updateMassMaps() at the end of // the file loading. if(update_maps) updateMassMaps(); } /*! \brief Appends a collection of new \l{IsotopeSPtr} to this \l{IsotopicData}. \a isotopes The collection () of new isotopes to be added to this collection. The isotope is added to the end of the collection using \code m_isotopes.insert(m_isotopes.end(), isotopes.begin(), isotopes.end()); \endcode Each time new isotopes are added to this collection, the chemical signification of all the corresponding chemical elements changes at heart. It might thus be required that the data in the two m_symbolMonoMassMap and m_symbolAvgMassMap maps be recalculated. Internally, this function calls .insert() to append all the isotopes in \a isotopes to the end of m_isotopes. \a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need to be updated with the new collection of isotopes. \sa updateMassMaps(), appendNewIsotope() */ void IsotopicData::appendNewIsotopes(const std::vector &isotopes, bool update_maps) { std::size_t count_before = m_isotopes.size(); m_isotopes.insert(m_isotopes.end(), isotopes.begin(), isotopes.end()); std::size_t count_after = m_isotopes.size(); if(count_after - count_before != isotopes.size()) qFatal("Programming error."); if(update_maps) updateMassMaps(); } /*! \brief Inserts a new \l{IsotopeSPtr} to this \l{IsotopicData} at index \a index. \a isotope_sp The new isotope to be inserted in this collection. If \a index is out of bounds or this collection is empty, the isotope is appended to this collection. Otherwise, the isotope is inserted at the requested index, which means that the new isotope displaces to the bottom (aka back) the isotope currently at \a index. Each time a new isotope is added to this collection, the chemical signification of the corresponding chemical element changes at heart. It might thus be required that the data in the two m_symbolMonoMassMap and m_symbolAvgMassMap maps be recalculated. \a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need to be updated with the new collection of isotopes. Returns true if the iterator at the inserted position is not m_isotopes.end(). \sa updateMassMaps(), appendNewIsotope(), appendNewIsotopes() */ bool IsotopicData::insertNewIsotope(IsotopeSPtr isotope_sp, std::size_t index, bool update_maps) { // qDebug() << "the size of the data:" << size() << "and" << m_isotopes.size() //<< "requested index:" << index; // We define that we insert the new isotope before the one at index, as is the // convention in the STL and in Qt code. if(!m_isotopes.size() || index > m_isotopes.size() - 1) { appendNewIsotope(isotope_sp, update_maps); return true; } // Convert the index to an iterator. std::vector::const_iterator iter = m_isotopes.begin() + index; // Finally, do the insertion. iter = m_isotopes.insert(iter, isotope_sp); // qDebug() << "Inserted isotope:" << (*iter)->getSymbol(); // If inserting an empty isotope in relation to a row insertion in the table // view, then update_maps needs to be false because update_maps needs valid // symbols for isotopes! if(update_maps) updateMassMaps(); // iter points to the inserted isotope. return iter != m_isotopes.end(); } /*! \brief Removes the isotopes located between \a begin_index and \a end_index. The removed isotopes are contained inclusively between the two indices passed as parameters. Each time isotopes are removed from this collection, the chemical signification of the corresponding chemical elements changes at heart. It might thus be required that the data in the two m_symbolMonoMassMap and m_symbolAvgMassMap maps be recalculated. \a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need to be updated with the new collection of isotopes. Returns an iterator to the end of this collection if either \a begin_index is out of bounds or this collection is empty. Otherwise, returns an iterator to the collection at the position below the last removed item. */ std::vector::const_iterator IsotopicData::eraseIsotopes(std::size_t begin_index, std::size_t end_index, bool update_maps) { // qDebug() << "Erasing isotopes in inclusive index range: [" << begin_index //<< "-" << end_index << "] - range is fully inclusive."; if(!m_isotopes.size() || begin_index > m_isotopes.size() - 1) return m_isotopes.cend(); std::vector::const_iterator iter_begin = m_isotopes.cbegin() + begin_index; // Sanity check, as this is equivalent to begin_index > m_isotopes.size() -1. if(iter_begin == m_isotopes.end()) return m_isotopes.cend(); // Let's say that by default, we remove until the last inclusively: std::vector::const_iterator iter_end = m_isotopes.cend(); // But, if end_index is less than the last index, then end() has to be the // next item after the one at that index. if(end_index < m_isotopes.size() - 1) iter_end = std::next(m_isotopes.begin() + end_index); // At this point we are confident we can assign the proper end() iterator // value for the erase function below. auto iter = m_isotopes.erase(iter_begin, iter_end); if(update_maps) { // qDebug() << "Now updating masses"; updateMassMaps(); } // else // qDebug() << "Not updating masses"; #if 0 if(m_isotopes.size()) { qDebug() << "The avg mass of the first isotope symbol in the vector:" << computeAvgMass( getIsotopesBySymbol(m_isotopes.front()->getSymbol())); } #endif return iter; } /*! \brief Redefines the monoisotopic mass of the chemical element specified by \a symbol. For the set of isotopes corresponding to \a symbol, set the most abundant isotope's mass as the value for key \a symbol in m_symbolMonoMassMap. Returns true if the map pair was actually inserted in m_symbolMonoMassMap or false if the monoisotopic mass value was set to an existing key. */ bool IsotopicData::updateMonoMassMap(const QString &symbol) { // We do only work with a single symbol here. if(symbol.isEmpty()) qFatal("Programming error. The symbol cannot be empty."); double greatest_abundance = std::numeric_limits::min(); double mass = 0.0; IsotopeCstIteratorPair iter_pair = getIsotopesBySymbol(symbol); while(iter_pair.first != iter_pair.second) { if((*iter_pair.first)->getProbability() > greatest_abundance) { greatest_abundance = (*iter_pair.first)->getProbability(); mass = (*iter_pair.first)->getMass(); } ++iter_pair.first; } // At this point we have the mono mass of the currently iterated symbol. std::pair::const_iterator, bool> res_pair = m_symbolMonoMassMap.insert_or_assign(symbol, mass); // Return true if was inserted (that is, the symbol was not there) or // false if the value was assigned to an existing key. return res_pair.second; } /*! \brief Redefines the monoisotopic mass of all the chemical elements in this collection of isotopes. This function is generally called by default by all the functions that add new isotopes to this collection [via updateMassMaps()]. First, a list of all the unique element symbols in this collection is crafted. Then for each symbol in that list, updateMonoMassMap(symbol) is called. Returns the number of updated symbols, that is, the unique symbol count in this collection. \sa updateMonoMassMap(const QString &symbol), updateMassMaps() */ std::size_t IsotopicData::updateMonoMassMap() { // For all the common chemical elements found in organic substances, the // monoisotopic mass is the mass of the most abundant isotope which // happens to be also the lightest isotope. However that is not true for // *all* the chemical elements. We thus need to iterate in the isotopes // of each symbol in the vector of isotopes and record the mass of the // isotope that is most abundant. m_symbolMonoMassMap.clear(); // Get the list of all the isotope symbols. std::size_t count = 0; std::vector all_symbols = getUniqueSymbolsInOriginalOrder(); for(auto symbol : all_symbols) { updateMonoMassMap(symbol); ++count; } return count; } /*! \brief Recalculates the average mass of the chemical element specified by \a symbol. For the set of isotopes corresponding to \a symbol, compute the average mass and set it in m_symbolAvgMassMap as the value for key \a symbol. Returns true if the map pair was actually inserted in m_symbolAvgMassMap or false if the average mass value was set to an already existing key. \sa updateMonoMassMap(const QString &symbol), updateMonoMassMap(), updateAvgMassMap(const QString &symbol), updateMassMaps() */ bool IsotopicData::updateAvgMassMap(const QString &symbol) { // For each chemical element (that is either name or symbol), we need to // compute the sum of the probabilities of all the corresponding // isotopes. Once that sum (which should be 1) is computed, it is // possible to compute the averag mass of "that symbol", so to say. // We do only work with a single symbol here. if(symbol.isEmpty()) qFatal("Programming error. The symbol cannot be empty."); #if 0 // Now this is in a function per se: // computeAvgMass(IsotopeCstIteratorPair iter_pair, bool *ok) double cumulated_probabilities = 0.0; double avg_mass = 0.0; IsotopeCstIteratorPair pair = getIsotopesBySymbol(symbol); // We need to use that iterator twice, so we do make a copy. IsotopeCstIterator local_iter = pair.first; while(local_iter != pair.second) { cumulated_probabilities += (*pair.first)->getProbability(); ++local_iter; } // Sanity check if(!cumulated_probabilities) qFatal("Programming error. The cumulated probabilities cannot be naught."); // And at this point we can compute the average mass. local_iter = pair.first; while(local_iter != pair.second) { avg_mass += (*local_iter)->getMass() * ((*local_iter)->getProbability() / cumulated_probabilities); ++local_iter; } #endif IsotopeCstIteratorPair iter_pair = getIsotopesBySymbol(symbol); // qDebug() << "For symbol" << symbol << "the iter range was found to // be of distance:" //<< std::distance(iter_pair.first, iter_pair.second) //<< "with symbol: " << (*iter_pair.first)->getSymbol(); std::vector errors; double avg_mass = computeAvgMass(iter_pair, errors); if(errors.size()) { qFatal( "The calculation of the average mass for a given " "symbol failed."); } std::pair::const_iterator, bool> res_pair = m_symbolAvgMassMap.insert_or_assign(symbol, avg_mass); // return true if was inserted (that is, the symbol was not there) or // false if the value was assigned to an existing key. return res_pair.second; } /*! \brief Recalculates the average mass of all the chemical elements in this collection of isotopes. This function is generally called by default by all the functions that add new isotopes to this collection [via updateMassMaps()]. First, a list of all the unique element symbols in this collection is crafted. Then for each symbol in that list, updateAvgMassMap(symbol) is called. Returns the number of updated symbols, that is, the unique symbol count in this collection. \sa updateMonoMassMap(const QString &symbol), updateMassMaps() */ std::size_t IsotopicData::updateAvgMassMap() { // For each chemical element (that is either name or symbol), we need to // compute the sum of the probabilities of all the corresponding // isotopes. Once that sum (which should be 1) is computed, it is // possible to compute the averag mass of "that symbol", so to say. m_symbolAvgMassMap.clear(); // Get the list of all the isotope symbols. std::size_t count = 0; std::vector all_symbols = getUniqueSymbolsInOriginalOrder(); for(auto symbol : all_symbols) { updateAvgMassMap(symbol); ++count; } return count; } /*! \brief Compute the average mass for isotopes contained in the \a iter_pair iterator range. \a iter_pair pair of [begin -- end[ iterators to the isotopes in this collection \a errors vector of strings in which to store error messages There are no sanity checks performed. The iterator pair should hold two iterator values that frame isotopes of the same chemical element. The average mass is computed on the basis of the isotopes contained in the [\a iter_pair .first -- \a iter_pair .second[ range. Returns 0 if the first member of \a iter_pair is the collection's end iterator, the average mass otherwise. */ double IsotopicData::computeAvgMass(IsotopeCstIteratorPair iter_pair, std::vector &errors) const { // We get an iterator range for which we need to compute the average // mass. No check whatsoever, we do what we are asked to do. This // function is used to check or document user's actions in some places. double avg_mass = 0.0; // qDebug() << "Computing avg mass for iter range of distance:" //<< std::distance(iter_pair.first, iter_pair.second) //<< "with symbol: " << (*iter_pair.first)->getSymbol(); if(iter_pair.first == m_isotopes.cend()) { qDebug() << "First iterator is actually end of m_isotopes."; errors.push_back( QString("First iterator is actually end of m_isotopes.")); return avg_mass; } std::size_t previous_error_count = errors.size(); double cumulated_probabilities = getCumulatedProbabilities(iter_pair, errors); if(errors.size() > previous_error_count || !cumulated_probabilities) { // There was an error. We want to report it. errors.push_back( QString("Failed to compute the cumulated probabilities needed to " "compute the average mass.")); return avg_mass; } // At this point, compute the average mass. while(iter_pair.first != iter_pair.second) { avg_mass += (*iter_pair.first)->getMass() * ((*iter_pair.first)->getProbability() / cumulated_probabilities); // qDebug() << "avg_mass:" << avg_mass; ++iter_pair.first; } return avg_mass; } /*! \brief Update the monoisotopic and average symbol-mass maps only for \a symbol. \sa updateMonoMassMap(const QString &symbol), updateAvgMassMap(const QString &symbol) */ void IsotopicData::updateMassMaps(const QString &symbol) { updateMonoMassMap(symbol); updateAvgMassMap(symbol); } /*! \brief Update the monoisotopic and average symbol-mass maps for all the symbols in the collection. This function is typically called each time new isotopes are added to this collection. Returns the count of updated symbols, that is, the unique symbol count in this collection. \sa updateMonoMassMap(), updateAvgMassMap() */ std::size_t IsotopicData::updateMassMaps() { std::size_t count = updateMonoMassMap(); if(!count) qDebug("There are no isotopes. Cleared the mono mass map."); count = updateAvgMassMap(); if(!count) qDebug("There are no isotopes. Cleared the avg mass map."); // Number of symbols for which the mass was updated. return count; } /*! \brief Returns the monoisotopic mass for element of \a symbol. Returns 0 if \a symbol was not found in this Isotope collection and sets \a ok to false if \a ok is not nullptr; returns the monoisotopic mass for element \a symbol otherwise and sets \a ok to true if \a ok is not nullptr. */ double IsotopicData::getMonoMassBySymbol(const QString &symbol, bool *ok) const { if(symbol.isEmpty()) qFatal("Programming error. The symbol cannot be empty."); // qDebug() << "The symbol/mono mass map has size:" //<< m_symbolMonoMassMap.size(); std::map::const_iterator found_iter = m_symbolMonoMassMap.find(symbol); if(found_iter == m_symbolMonoMassMap.cend()) { qDebug() << "Failed to find the symbol in the map."; if(ok != nullptr) { *ok = false; return 0.0; } } if(ok) *ok = true; // qDebug() << "The mono mass is found to be" << found_iter->second; return found_iter->second; } /*! \brief Returns the mass of the most abundant isotope in a range of isotopes. The range of isotopes is defined by \a iter_pair, that is [ \a iter_pair .first -- \a iter_pair .second [. If errors are encountered, these are appended to \a errors. For all the common chemical elements found in organic substances, the monoisotopic mass is the mass of the most abundant isotope which happens to be also the lightest isotope. However that is not true for *all* the chemical elements. We thus need to iterate in the isotopes of each symbol in the vector of isotopes and record the mass of the isotope that is most abundant. */ double IsotopicData::getMonoMass(IsotopeCstIteratorPair iter_pair, std::vector &errors) const { // The mono mass of a set of isotopes is the mass of the most abundant // isotope (not the lightest!). double mass = 0.0; double greatest_abundance = 0.0; if(iter_pair.first == m_isotopes.cend()) { qDebug() << "First iterator is actually end of m_isotopes."; errors.push_back( QString("First iterator is actually end of m_isotopes.")); return mass; } while(iter_pair.first != iter_pair.second) { if((*iter_pair.first)->getProbability() > greatest_abundance) { greatest_abundance = (*iter_pair.first)->getProbability(); mass = (*iter_pair.first)->getMass(); } ++iter_pair.first; } // qDebug() << "The mono mass is found to be" << mass; return mass; } /*! \brief Returns the average mass of \a symbol. The returned mass is found as the value for key \a symbol in m_symbolAvgMassMap. If \a ok is not nullptr, it is set to true. If the symbol is not found, 0 is returned and \a ok is set to false if \a ok is not nullptr. */ double IsotopicData::getAvgMassBySymbol(const QString &symbol, bool *ok) const { if(symbol.isEmpty()) qFatal("Programming error. The symbol cannot be empty."); // qDebug() << "The symbol/avg mass map has size:" << // m_symbolAvgMassMap.size(); std::map::const_iterator found_iter = m_symbolAvgMassMap.find(symbol); if(found_iter == m_symbolAvgMassMap.cend()) { qDebug() << "Failed to find the symbol in the map."; if(ok != nullptr) { *ok = false; return 0.0; } } if(ok) *ok = true; // qDebug() << "The avg mass is found to be" << found_iter->second; return found_iter->second; } /*! \brief Returns the sum of the probabilities of all the isotopes of \a symbol. If errors occur, they will be described as strings appended in \a errors. */ double IsotopicData::getCumulatedProbabilitiesBySymbol( const QString &symbol, std::vector &errors) const { // qDebug() << "symbol: " << symbol; double cumulated_probabilities = 0.0; // We'll need this to calculate the indices of the isotopes in the // m_isotopes vector. IsotopeVectorCstIterator iter_begin = m_isotopes.cbegin(); // Get the isotopes iter range for symbol. IsotopeCstIteratorPair iter_pair = getIsotopesBySymbol(symbol); while(iter_pair.first != iter_pair.second) { QString error_text = ""; int error_count = (*iter_pair.first)->validate(&error_text); if(error_count) { error_text.prepend( QString("Isotope symbol %1 at index %2: ") .arg(symbol) .arg(std::distance(iter_begin, iter_pair.first))); errors.push_back(error_text); cumulated_probabilities = 0.0; } else cumulated_probabilities += (*iter_pair.first)->getProbability(); ++iter_pair.first; } return cumulated_probabilities; } /*! \brief Returns the sum of the probabilities of all the isotopes in the \a iter_pair range of iterators. The range of isotopes is defined by \a iter_pair, that is [ \a iter_pair .first -- \a iter_pair .second [. If errors are encountered, these are appended to \a errors. */ double IsotopicData::getCumulatedProbabilities(IsotopeCstIteratorPair iter_pair, std::vector &errors) const { double cumulated_probabilities = 0.0; if(iter_pair.first == m_isotopes.cend()) { qDebug() << "First iterator is actually end of m_isotopes."; errors.push_back( QString("First iterator is actually end of m_isotopes.")); return cumulated_probabilities; } while(iter_pair.first != iter_pair.second) { cumulated_probabilities += (*iter_pair.first)->getProbability(); ++iter_pair.first; } // qDebug() << "cumulated_probabilities:" << cumulated_probabilities; return cumulated_probabilities; } /*! \brief Returns a range of iterators framing the isotopes of \a symbol. \note The order of the isotopes in the collection is not alphabetical (it is the order of the atomic number. This function works on the assumption that all the isotopes of a given symbol are clustered together in the isotopes vector with *no* gap in between. If \a symbol is empty, the iterators are set to be end() of the Isotopes collection. The returned pair of iterators frame the isotopes of \a symbol as a [begin,end[ pair of iterators. */ IsotopeCstIteratorPair IsotopicData::getIsotopesBySymbol(const QString &symbol) const { if(symbol.isEmpty()) return IsotopeCstIteratorPair(m_isotopes.cend(), m_isotopes.cend()); // We want to "extract" from the vector of isotopes, the ones that share // the same symbol under the form of a [begin,end[ pair of iterators. //////////////////////////// ASSUMPTION ///////////////////////// //////////////////////////// ASSUMPTION ///////////////////////// // The order of the isotopes is not alphabetical (it is the order of the // atomic number. This function works on the assumption that all the // isotopes of a given symbol are clustered together in the isotopes // vector with *no* gap in between. //////////////////////////// ASSUMPTION ///////////////////////// //////////////////////////// ASSUMPTION ///////////////////////// // We will start iterating in the vector of isotopes at the very // beginning. std::vector::const_iterator iter = m_isotopes.cbegin(); // Never reach the end of the isotopes vector. std::vector::const_iterator iter_end = m_isotopes.cend(); // This iterator will be the end iterator of the range that comprises // the isotopes all sharing the same symbol. We initialize it to // iter_end in case we do not find the symbol at all. Otherwise it will // be set to the right value. std::vector::const_iterator symbol_iter_end = iter_end; while(iter != iter_end) { QString current_symbol = (*iter)->getSymbol(); if(current_symbol != symbol) { // qDebug() << "Current isotope has symbol" << current_symbol //<< "and we are looking for" << symbol //<< "incrementing to next position."; ++iter; } else { // qDebug() << "Current isotope has symbol" << current_symbol //<< "and we are looking for" << symbol //<< "with mass:" << (*iter)->getMass() //<< "Now starting inner iteration loop."; // At this point we encountered one isotope that has the right // symbol. The iterator "iter" will not change anymore because // of the inner loop below that will go on iterating in vector // using another set of iterators. "iter" will thus point // correctly to the first isotope in the vector having the right // symbol. // Now in this inner loop, continue iterating in the vector, // starting at the present position and go on as long as the // encountered isotopes have the same symbol. // Set then end iterator to the current position and increment // to the next one, since current position has been iterated // into already (position is stored in "iter") and go on. This // way, if there was a single isotope by given symbol, // "symbol_iter_end" rightly positions at the next isotope. If // that is not the case, its value updates and is automatically // set to the first isotope that has not the right symbol (or // will be set to iter_end if that was the last set of isotopes // in the vector). symbol_iter_end = iter; ++symbol_iter_end; while(symbol_iter_end != iter_end) { // qDebug() << "Inner loop iteration in: " //<< (*symbol_iter_end)->getSymbol() //<< "while we search for" << symbol //<< "Iterated isotope has mass:" //<< (*symbol_iter_end)->getMass(); if((*symbol_iter_end)->getSymbol() == symbol) { // We can iterate further in the isotopes vector because // the current iterator pointed to an isotope that still // had the right symbol. qDebug() //<< "Good symbol, going to next inner iteration // position."; ++symbol_iter_end; } else { // We currently iterate in an isotope that has a symbol // different from the searched one: the symbol_iter_end // thus effectively plays the role of the // iterator::end() of the isotopes range having the // proper symbol. qDebug() //<< "The symbols do not match, breaking the inner // loop."; break; } } // We can break the outer loop because we have necessarily gone // through the isotopes of the requested symbol at this point. // See at the top of this outer loop that when an isotope has // not the right symbol, the iter is incremented break; } // End of block "the iterated symbol is the searched one" } // End of outer while loop // qDebug() << "For symbol" << symbol << "the iter range was found to be:" //<< std::distance(iter, symbol_iter_end); return IsotopeCstIteratorPair(iter, symbol_iter_end); } /*! \brief Returns the count of isotopes of \a symbol. */ std::size_t IsotopicData::getIsotopeCountBySymbol(const QString &symbol) const { IsotopeCstIteratorPair iter_pair = getIsotopesBySymbol(symbol); return std::distance(iter_pair.first, iter_pair.second); } /*! \brief Returns a range of iterators framing the isotopes of element \a name. \note The order of the isotopes in the collection is not alphabetical (it is the order of the atomic number. This function works on the assumption that all the isotopes of a given symbol are clustered together in the isotopes vector with *no* gap in between. If \a name is empty, the iterators are set to be end() of the Isotopes collection. The returned pair of iterators frame the isotopes of \a name as a [begin,end[ pair of iterators. */ IsotopeCstIteratorPair IsotopicData::getIsotopesByName(const QString &name) const { if(name.isEmpty()) return IsotopeCstIteratorPair(m_isotopes.cend(), m_isotopes.cend()); // We want to "extract" from the vector of isotopes, the ones that share // the same name under the form of a [begin,end[ pair of iterators. //////////////////////////// ASSUMPTION ///////////////////////// //////////////////////////// ASSUMPTION ///////////////////////// // The order of the isotopes is not alphabetical (it is the order of the // atomic number. This function works on the assumption that all the // isotopes of a given symbol are clustered together in the isotopes // vector with *no* gap in between. //////////////////////////// ASSUMPTION ///////////////////////// //////////////////////////// ASSUMPTION ///////////////////////// // We will start iterating in the vector of isotopes at the very // beginning. std::vector::const_iterator iter = m_isotopes.cbegin(); // Never reach the end of the isotoes vector. std::vector::const_iterator iter_end = m_isotopes.cend(); // This iterator will be the end iterator of the range that comprises // the isotopes all sharing the same name. We initialize it to iter_end // in case we do not find the name at all. Otherwise it will be set to // the right value. std::vector::const_iterator name_iter_end = iter_end; while(iter != iter_end) { QString current_name = (*iter)->getElement(); if(current_name != name) ++iter; // At this point we encountered one isotope that has the right name. // The iterator "iter" will not change anymore because of the inner // loop below that will go on iterating in vector using another set // of iterators. "iter" will thus point correctly to the first // isotope in the vector having the right name. // Now in this inner loop, continue iterating in the vector, // starting at the present position and go on as long as the // encountered isotopes have the same name. // Set then end iterator to the current position and increment to // the next one, since current position has been iterated into // already (position is stored in "iter") and go on. This way, if // there was a single isotope by given name, "name_iter_end" rightly // positions at the next isotope. If that is not the case, its value // updates and is automatically set to the first isotope that has // not the right name (or will be set to iter_end if that was the // last set of isotopes in the vector). name_iter_end = iter; ++name_iter_end; while(name_iter_end != iter_end) { // qDebug() << "Iterating in: " << (*name_iter_end)->getElement() //<< "with mass:" << (*name_iter_end)->getMass(); if((*name_iter_end)->getElement() == name) { // We can iterate further in the isotopes vector because the // current iterator pointed to an isotope that still had the // right name. // qDebug() << "Going to next iterator position."; ++name_iter_end; } else { // We currently iterate in an isotope that has a name // different from the searched one: the name_iter_end thus // effectively plays the role of the iterator::end() of the // isotopes range having the proper name. break; } } } // qDebug() << "The iter range was found to be:" //<< std::distance(iter, name_iter_end); return IsotopeCstIteratorPair(iter, name_iter_end); } /*! \brief Returns all the unique symbols from the collection as they are stored. */ std::vector IsotopicData::getUniqueSymbolsInOriginalOrder() const { // The way IsoSpec works and the way we configure the // symbol/count/isotopes data depend in some situations on the order in // which the Isotopes were read either from the library tables or from // user-created disk files. // // This function wants to craft a list of unique isotope symbols exactly // as they are found in the member vector. std::set symbol_set; std::vector symbols; for(auto isotope_sp : m_isotopes) { QString symbol = isotope_sp->getSymbol(); auto res = symbol_set.insert(symbol); // res.second is true if the symbol was inserted, which mean it did // not exist in the set. if(res.second) symbols.push_back(symbol); } return symbols; } /*! \brief Returns true if the collection contains at least one isotope of \a symbol, false otherwise. If \a count is not nullptr, its value is set to the count of isotopes of \a symbol. */ bool IsotopicData::containsSymbol(const QString &symbol, int *count) const { IsotopeCstIteratorPair iter_pair = getIsotopesBySymbol(symbol); if(iter_pair.first == m_isotopes.cend()) return false; // Now compute the distance between the two iterators to know how many // isotopes share the same chemical element symbol. if(count != nullptr) *count = std::distance(iter_pair.first, iter_pair.second); return true; } /*! \brief Returns true if the collection contains at least one isotope of element \a name, false otherwise. If \a count is not nullptr, its value is set to the count of isotopes of \a name element. */ bool IsotopicData::containsName(const QString &name, int *count) const { IsotopeCstIteratorPair iter_pair = getIsotopesByName(name); if(iter_pair.first == m_isotopes.cend()) return false; // Now compute the distance between the two iterators to know how many // isotopes share the same chemical element symbol. if(count != nullptr) *count = std::distance(iter_pair.first, iter_pair.second); return true; } /*! \brief Returns a string containing a stanza describing each isotope of \a symbol. \sa Isotope::toString() */ QString IsotopicData::isotopesAsStringBySymbol(const QString &symbol) const { if(symbol.isEmpty()) qFatal("Programming error. The symbol cannot be empty."); QString text; IsotopeCstIteratorPair iter_pair = getIsotopesBySymbol(symbol); if(iter_pair.first == m_isotopes.cend()) { qDebug() << "The symbol was not found in the vector of isotopes."; return text; } while(iter_pair.first != iter_pair.second) { text += (*iter_pair.first)->toString(); text += "\n"; } return text; } /*! \brief Returns true if the \a isotope_csp is isotope of its symbol having the greatest probability, false otherwise. */ bool IsotopicData::isMonoMassIsotope(IsotopeCstSPtr isotope_csp) { // Is the passed isotope the one that has the greatest abundance among // all the isotopes having the same symbol. if(isotope_csp == nullptr) qFatal("Programming error. The isotope pointer cannot be nullptr."); QString symbol(isotope_csp->getSymbol()); double mass = m_symbolMonoMassMap.at(symbol); if(mass == isotope_csp->getMass()) return true; return false; } /*! \brief Returns a constant reference to the container of \l{MsXpS::libXpertMass::Isotope}s. */ const std::vector & IsotopicData::getIsotopes() const { return m_isotopes; } /*! \brief Assigns member data from \a other to this instance's member data. The copying of \a other into this collection is deep, making *this collection essentially identical to \a other. Returns a reference to this collection. */ IsotopicData & IsotopicData::operator=(const IsotopicData &other) { if(&other == this) return *this; m_isotopes = other.m_isotopes; m_symbolMonoMassMap = other.m_symbolMonoMassMap; m_symbolAvgMassMap = other.m_symbolAvgMassMap; return *this; } /*! \brief Returns the count of isotopes in the collection. \note The count that is returned is for \e all isotopes, that is, the count if items in the collection. */ std::size_t IsotopicData::size() const { return m_isotopes.size(); } /*! \brief Returns the count of unique symbols. */ std::size_t IsotopicData::getUniqueSymbolsCount() const { // Go trough the vector of IsotopeSPtr and check how many different // symbols it contains. std::set symbols_set; std::vector::const_iterator iter = m_isotopes.cbegin(); std::vector::const_iterator iter_end = m_isotopes.cend(); while(iter != iter_end) { symbols_set.insert((*iter)->getSymbol()); ++iter; } // Sanity checks: if(symbols_set.size() != m_symbolAvgMassMap.size()) qFatal("Not possible that the sizes do not match"); if(symbols_set.size() != m_symbolMonoMassMap.size()) qFatal("Not possible that the sizes do not match"); return symbols_set.size(); } /*! \brief Validates this Isotope collection. The validation involves iterating in the whole collection and for each item in it invoke its Isotope::validate(). If errors occurred during these validations, they are counted returned. The errors text strings are stored in \a errors. Returns the total count of errors encountered during validation of this collection's \l{Isotope}s. */ int IsotopicData::validate(std::vector &errors) const { int total_error_count = 0; IsotopeVectorCstIterator iter_begin = m_isotopes.cbegin(); IsotopeVectorCstIterator iter_end = m_isotopes.cend(); IsotopeVectorCstIterator iter = iter_begin; while(iter != iter_end) { QString error_text = ""; int error_count = (*iter)->validate(&error_text); if(error_count) { error_text.prepend(QString("Isotope at index %1: ") .arg(std::distance(iter_begin, iter))); errors.push_back(error_text); total_error_count += error_count; } ++iter; } return total_error_count; } /*! \brief Validate the \l{Isotope}s of \a symbol in this collection. This function is more powerful than IsotopicData::validate() because it actually verifies the integrity of the data \e{symbol}-wise. For example, a set of isotopes by the same symbol cannot have a cumulated probability different than 1. If that were the case, the error would be reported. Encountered errors are stored in \a errors. Returns true if validation succeeded, false otherwise. \sa validateAllBySymbol(), getCumulatedProbabilitiesBySymbol() */ bool IsotopicData::validateBySymbol(const QString &symbol, std::vector &errors) const { // This function is more powerful than the other one because it actually // looks the integrity of the data symbol-wise. For example, a set of // isotopes by the same symbol cannot have a cumulated probability greater // than 1. If that were the case, that would be reported. // qDebug() << "symbol: " << symbol; // Validating by symbol means looking into each isotope that has the same // 'symbol' and validating each one separately. However, it also means looking // if all the cumulated isotope probabilities (abundances) for a given symbol // are different than 1. // Record the size of the errors vector so that we can report if we added // errors in this block. std::size_t previous_error_count = errors.size(); // The function below performs validation of the isotopes. double cumulated_probabilities = getCumulatedProbabilitiesBySymbol(symbol, errors); if(cumulated_probabilities != 1) { QString prob_error = QString( "Isotope symbol %1: has cumulated probabilities not equal to 1.\n") .arg(symbol); errors.push_back(prob_error); } return errors.size() > previous_error_count; } /*! \brief Validates all the isotopes of the collection. The validation of the \l{Isotope}s is performed by grouping them by symbol. See validateBySymbol(). Encountered errors are stored in \a errors. Returns true if all the Isotopes validated successfully, false otherwise. \sa validateBySymbol(), validate(), getCumulatedProbabilitiesBySymbol() */ bool IsotopicData::validateAllBySymbol(std::vector &errors) const { // This validation of all the isotopes as grouped by symbol is more // informative than the validation isotope by isotope idependently // because it can spot cumulated probabilities problems. std::size_t previous_error_count = errors.size(); std::vector all_symbols = getUniqueSymbolsInOriginalOrder(); for(auto symbol : all_symbols) { QString error_text = ""; validateBySymbol(symbol, errors); } return errors.size() > previous_error_count; } /*! \brief Replaces \a old_isotope_sp by \a new_isotope_sp in this collection. */ void IsotopicData::replace(IsotopeSPtr old_isotope_sp, IsotopeSPtr new_isotope_sp) { std::replace( m_isotopes.begin(), m_isotopes.end(), old_isotope_sp, new_isotope_sp); } /*! * \brief Clear (empties) all the containers in this collection, essentially resetting it completely. The three containers are cleared: \list \li m_isotopes \li m_symbolMonoMassMap \li m_symbolAvgMassMap \endlist */ void IsotopicData::clear() { m_isotopes.clear(); m_symbolMonoMassMap.clear(); m_symbolAvgMassMap.clear(); } } // namespace libXpertMass } // namespace MsXpS #if 0 Example from IsoSpec. const int elementNumber = 2; const int isotopeNumbers[2] = {2,3}; const int atomCounts[2] = {2,1}; const double hydrogen_masses[2] = {1.00782503207, 2.0141017778}; const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610}; const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses}; const double hydrogen_probs[2] = {0.5, 0.5}; const double oxygen_probs[3] = {0.5, 0.3, 0.2}; const double* probs[2] = {hydrogen_probs, oxygen_probs}; IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts, isotope_masses, probs), 0.99); #endif libxpertmass-1.1.0/src/XpertMass/IsotopicDataBaseHandler.cpp000664 001750 001750 00000013334 14647465366 025364 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include /////////////////////// IsoSpec #include #include /////////////////////// Local includes #include "globals.hpp" #include "PeakCentroid.hpp" #include "IsotopicDataBaseHandler.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::IsotopicDataBaseHandler \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile IsotopicDataBaseHandler.hpp \brief The IsotopicDataBaseHandler class features basic handling of \l{IsotopicData}. The IsotopicDataBaseHandler class provides the skeleton for derived classes to handle \l{IsotopicData}. There are different isotopic data handlers: \list \li The \l{IsotopicDataLibraryHandler} that handles isotopic data from the IsoSpec element data tables directly from the library's data. These are the reference, pristine \e{unmodified} isotopic data. \li The \l{IsotopicDataUserConfigHandler} that handles isotopic data with the exact same format of those from the IsoSpec element data tables. However, these data correspond to user-modified isotopic data, \e{not} the reference, pristine \e{unmodified} isotopic data. \li The \l{IsotopicDataManualConfigHandler} that handles user-defined data according to an entirely different format. These data are typically used when the user defines new chemical elements that cannot fit in the IsoSpec element data tables format. \endlist \sa IsotopicDataLibraryHandler, IsotopicDataUserConfigHandler, IsotopicDataManualConfigHandler */ /*! \variable int MsXpS::libXpertMass::IsotopicDataBaseHandler::msp_isotopicData \brief The msp_isotopicData is a pointer to \l{IsotopicData}. */ /*! \variable int MsXpS::libXpertMass::IsotopicDataBaseHandler::m_fileName \brief The m_fileName is the filename in which to store the \l{IsotopicData} for from with to load the \l{IsotopicData}. */ /*! \brief Constructs the \l{IsotopicDataBaseHandler} with \a file_name. The msp_isotopicData is initialized by creating an empty IsotopicData instance. */ IsotopicDataBaseHandler::IsotopicDataBaseHandler(const QString &file_name) : m_fileName(file_name) { // We cannot allow to have a nullptr msp_isotopicData member. msp_isotopicData = std::make_shared(); } /*! \brief Constructs the \l{IsotopicDataBaseHandler}. \a isotopic_data_sp Isotopic data \a file_name File name */ IsotopicDataBaseHandler::IsotopicDataBaseHandler( IsotopicDataSPtr isotopic_data_sp, const QString &file_name) : msp_isotopicData(isotopic_data_sp), m_fileName(file_name) { // We cannot allow to have a nullptr msp_isotopicData member. if(msp_isotopicData == nullptr) msp_isotopicData = std::make_shared(); } /*! \brief Destructs the \l{IsotopicDataBaseHandler}. */ IsotopicDataBaseHandler::~IsotopicDataBaseHandler() { // qDebug(); } std::size_t IsotopicDataBaseHandler::loadData([[maybe_unused]] const QString &file_name) { qDebug() << "The base class does not load data."; return 0; } std::size_t IsotopicDataBaseHandler::writeData([[maybe_unused]] const QString &file_name) { qDebug() << "The base class does not write data."; return 0; } /*! \brief Sets the IsotopicData to \a isotopic_data_sp. */ void IsotopicDataBaseHandler::setIsotopicData(IsotopicDataSPtr isotopic_data_sp) { msp_isotopicData = isotopic_data_sp; } /*! \brief Returns the IsotopicData */ IsotopicDataSPtr IsotopicDataBaseHandler::getIsotopicData() { return msp_isotopicData; } /*! \brief Sets the file name to \a file_name */ void IsotopicDataBaseHandler::setFileName(const QString &file_name) { m_fileName = file_name; } /*! \brief Returns the file name */ QString IsotopicDataBaseHandler::getFileName() { return m_fileName; } std::size_t IsotopicDataBaseHandler::checkConsistency() { qDebug() << "The base class does not check consistenty."; return 0; } } // namespace libXpertMass } // namespace MsXpS #if 0 Example from IsoSpec. For water H2O const int elementNumber = 2; const int isotopeNumbers[2] = {2,3}; const int atomCounts[2] = {2,1}; const double hydrogen_masses[2] = {1.00782503207, 2.0141017778}; const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610}; const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses}; const double hydrogen_probs[2] = {0.5, 0.5}; const double oxygen_probs[3] = {0.5, 0.3, 0.2}; const double* probs[2] = {hydrogen_probs, oxygen_probs}; IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts, isotope_masses, probs), 0.99); #endif libxpertmass-1.1.0/src/XpertMass/IsotopicDataLibraryHandler.cpp000664 001750 001750 00000033771 14647465366 026125 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include /////////////////////// IsoSpec #include #include /////////////////////// Local includes #include "globals.hpp" #include "PeakCentroid.hpp" #include "IsotopicDataLibraryHandler.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::IsotopicDataLibraryHandler \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile IsotopicDataLibraryHandler.hpp \brief The IsotopicDataLibraryHandler class handles \l{IsotopicData} from the IsoSpec element data tables directly from the library's data. These are the reference, pristine, \e{unmodified}, isotopic data. The IsoSpec element data tables are the following: \list \li elem_table_ID \li elem_table_atomicNo \li elem_table_mass \li elem_table_massNo \li elem_table_extraNeutrons \li elem_table_element \li elem_table_symbol \li elem_table_Radioactive \li elem_table_probability \li elem_table_log_probability \endlist The data tables are all of the same length and the data in each row of a given table matches the contents of that same row in all the other tables. For example, the first two rows of table elem_table_ID are: 1 1 These two rows match the same rows in elem_table_mass: 1.00782503227 2.01410177819 and the the same rows in elem_table_element: "hydrogen" "hydrogen" By reading, row-by-row, the data from the same row number in each one of the tables, one constructs a fully qualified \l{Isotope}. \sa IsotopicDataUserConfigHandler, IsotopicDataManualConfigHandler */ /*! \typedef IsotopicDataLibraryHandlerSPtr \relates IsotopicDataLibraryHandler Synonym for std::shared_ptr. */ /*! \typedef IsotopicDataLibraryHandlerCstSPtr \relates IsotopicDataLibraryHandler Synonym for std::shared_ptr. */ /*! \brief Constructs the \l{IsotopicDataLibraryHandler}. The instance will have empty member data. */ IsotopicDataLibraryHandler::IsotopicDataLibraryHandler() { } /*! \brief Constructs the \l{IsotopicDataLibraryHandler}. The instance will have its isotopic data member pointing to \a isotopic_data_sp. */ IsotopicDataLibraryHandler::IsotopicDataLibraryHandler( IsotopicDataSPtr isotopic_data_sp) : IsotopicDataBaseHandler(isotopic_data_sp) { } // IsotopicDataLibraryHandler::IsotopicDataLibraryHandler(const QString // &file_name) : IsotopicDataBaseHandler(file_name) //{ //} // IsotopicDataLibraryHandler::IsotopicDataLibraryHandler( // IsotopicDataSPtr isotopic_data_sp, const QString &file_name) //: IsotopicDataBaseHandler(isotopic_data_sp, file_name) //{ //} /*! \brief Destructs the \l{IsotopicDataLibraryHandler}. Nothing is explicitely deleted in the destructor. */ IsotopicDataLibraryHandler::~IsotopicDataLibraryHandler() { // qDebug(); } std::size_t IsotopicDataLibraryHandler::loadData([[maybe_unused]] const QString &filename) { return loadData(); } /*! \brief Loads isotopic data directly from IsoSpec library' element data tables. The member isotopic data are cleared before setting new data read from the library's element data tables. The code iterates, row-by-row, in the all the tables and extracts the data to fill in the Isotope data: \code IsotopeSPtr isotope_sp = std::make_shared(IsoSpec::elem_table_ID[iter], QString(IsoSpec::elem_table_element[iter]), QString(IsoSpec::elem_table_symbol[iter]), IsoSpec::elem_table_atomicNo[iter], IsoSpec::elem_table_mass[iter], IsoSpec::elem_table_massNo[iter], IsoSpec::elem_table_extraNeutrons[iter], IsoSpec::elem_table_probability[iter], IsoSpec::elem_table_log_probability[iter], IsoSpec::elem_table_Radioactive[iter]); \endcode Returns the count of \l{Isotope}s that were allocated and stored in the msp_isotopicData member. */ std::size_t IsotopicDataLibraryHandler::loadData() { // We need to allocate one Isotope instance for each element // in the various arrays in the IsoSpec++ source code header file. // extern const int elem_table_ID[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int // elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* // elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const // char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const // bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern // const double // elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // Big sanity check, all the arrays must be the same length! std::size_t array_length = checkConsistency(); if(array_length < 1) return false; // Clear all the data, since this function might be called multiple times. msp_isotopicData->clear(); for(std::size_t iter = 0; iter < array_length; ++iter) { QString elem_element = QString(IsoSpec::elem_table_element[iter]); // These are the last items in the various tables. We do not handle them // at the moment. if(elem_element == "electron" || elem_element == "missing electron" || elem_element == "protonation") continue; IsotopeSPtr isotope_sp = std::make_shared(IsoSpec::elem_table_ID[iter], QString(IsoSpec::elem_table_element[iter]), QString(IsoSpec::elem_table_symbol[iter]), IsoSpec::elem_table_atomicNo[iter], IsoSpec::elem_table_mass[iter], IsoSpec::elem_table_massNo[iter], IsoSpec::elem_table_extraNeutrons[iter], IsoSpec::elem_table_probability[iter], IsoSpec::elem_table_log_probability[iter], IsoSpec::elem_table_Radioactive[iter]); // We do not want to update the mono/avg maps each time we load an // isotope. We'll call the relevant function later. msp_isotopicData->appendNewIsotope(isotope_sp, false); } // Now ask that the mono/avg mass maps be updated. if(!msp_isotopicData->updateMassMaps()) qFatal("Programming error. Failed to update the mass maps."); // qDebug() << "Done loading data with :" << msp_isotopicData->size() //<< "isotopes in the isotopic data."; return msp_isotopicData->size(); } /*! \brief Write all the IsotopicData to \a file_name. If \a file_name is empty, m_fileName is tried. If both are empty, the function returns 0. If any one of the file names are correct (file_name takes precedence over m_fileName), then m_fileName is set to that file name. The format of the file consists in a single line of data per \l{Isotope} as created using the Isotope::toString() function. Each isotope is output to its own line. Returns the count of \l{Isotope}s written to file or 0 if the file does not exist or is not readable. \sa Isotope::Isotope(const QString &text) */ std::size_t IsotopicDataLibraryHandler::writeData(const QString &file_name) { // Although the isotopic data were loaded from the IsoSpec library tables, we // might be willing to store these data to a file. if(file_name.isEmpty() && m_fileName.isEmpty()) return 0; QString temp_file_name; // The passed filename takes precedence over the member datum. So copy // that file name to the member datum. if(!file_name.isEmpty()) temp_file_name = file_name; else temp_file_name = m_fileName; QFile file(temp_file_name); if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug("Failed to open file for writing."); return 0; } QTextStream out(&file); out << "# This file contains isotopic data in a format that can accommodate\n"; out << "# comments in the form of lines beginning with the '#' character.\n\n"; std::size_t isotope_count = 0; for(auto item : msp_isotopicData->m_isotopes) { out << item->toString(); // We need to add it because toString() does not terminate the line with // a new line character. out << "\n"; ++isotope_count; } out.flush(); file.close(); // Now we know that temp_file_name is fine. Store into m_fileName. m_fileName = temp_file_name; return isotope_count; } /*! \brief Checks the consistency in all the IsoSpec library's different isotopic data tables. This function essentially verifies that each table has the same row count as all the other ones. Returns the count of isotopes in the isotopic data. */ std::size_t IsotopicDataLibraryHandler::checkConsistency() { std::size_t array_length = sizeof(IsoSpec::elem_table_atomicNo) / sizeof(IsoSpec::elem_table_atomicNo[0]); // qDebug() << "The array length is:" << array_length; // All the tables in the header file of the IsoSpec library must // have exactly the same size. if(IsoSpec::isospec_number_of_isotopic_entries != array_length) { qFatal("Found corruption: the size of arrays is not like expected."); } // Now test each table one by one. std::size_t tested_length = sizeof(IsoSpec::elem_table_probability) / sizeof(IsoSpec::elem_table_probability[0]); if(tested_length != array_length) { qDebug() << "Found corruption: at least two arrays are not of the same length." << "tested_length:" << tested_length; return 0; } tested_length = sizeof(IsoSpec::elem_table_mass) / sizeof(IsoSpec::elem_table_mass[0]); if(tested_length != array_length) { qDebug() << "Found corruption: at least two arrays are not of the same length." << "tested_length:" << tested_length; return 0; } tested_length = sizeof(IsoSpec::elem_table_massNo) / sizeof(IsoSpec::elem_table_massNo[0]); if(tested_length != array_length) { qDebug() << "Found corruption: at least two arrays are not of the same length." << "tested_length:" << tested_length; return 0; } tested_length = sizeof(IsoSpec::elem_table_extraNeutrons) / sizeof(IsoSpec::elem_table_extraNeutrons[0]); if(tested_length != array_length) { qDebug() << "Found corruption: at least two arrays are not of the same length." << "tested_length:" << tested_length; return 0; } tested_length = sizeof(IsoSpec::elem_table_element) / sizeof(IsoSpec::elem_table_element[0]); if(tested_length != array_length) { qDebug() << "Found corruption: at least two arrays are not of the same length." << "tested_length:" << tested_length; return 0; } tested_length = sizeof(IsoSpec::elem_table_symbol) / sizeof(IsoSpec::elem_table_symbol[0]); if(tested_length != array_length) { qDebug() << "Found corruption: at least two arrays are not of the same length." << "tested_length:" << tested_length; return 0; } tested_length = sizeof(IsoSpec::elem_table_Radioactive) / sizeof(IsoSpec::elem_table_Radioactive[0]); if(tested_length != array_length) { qDebug() << "Found corruption: at least two arrays are not of the same length." << "tested_length:" << tested_length; return 0; } tested_length = sizeof(IsoSpec::elem_table_log_probability) / sizeof(IsoSpec::elem_table_log_probability[0]); if(tested_length != array_length) { qDebug() << "Found corruption: at least two arrays are not of the same length." << "tested_length:" << tested_length; return 0; } return tested_length; } } // namespace libXpertMass } // namespace MsXpS #if 0 Example from IsoSpec. const int elementNumber = 2; const int isotopeNumbers[2] = {2,3}; const int atomCounts[2] = {2,1}; const double hydrogen_masses[2] = {1.00782503207, 2.0141017778}; const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610}; const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses}; const double hydrogen_probs[2] = {0.5, 0.5}; const double oxygen_probs[3] = {0.5, 0.3, 0.2}; const double* probs[2] = {hydrogen_probs, oxygen_probs}; IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts, isotope_masses, probs), 0.99); #endif libxpertmass-1.1.0/src/XpertMass/IsotopicDataManualConfigHandler.cpp000664 001750 001750 00000063511 14647465366 027057 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Std lib includes #include #include /////////////////////// Qt includes #include #include #include /////////////////////// IsoSpec #include #include /////////////////////// Local includes #include "IsotopicDataManualConfigHandler.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::IsotopicDataManualConfigHandler \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile IsotopicDataManualConfigHandler.hpp \brief The IsotopicDataManualConfigHandler class handles a peculiar kind of \l{IsotopicData} that cannot be handled with the other handlers. This kind of IsotopicData handler is typically used when definition of brand new chemical element isotopes are to be performed. For example, if one works with radioactive carbon C14, then that isotope is not available in the IsoSpec's library and cannot be inserted in these data using the \l{IsotopicDataUserConfigHandler}. One interesting feature of this kind of IsotopicData formalism is that these data collectively describe a chemical elemental composition formula, like, for glucose: C6H12O6. Each chemical element is decribed using isotopes with mass/abundance pairs. There is thus virtually no limitation on the complexity of the isotopic distribution to be defined. The format of these data stored on file is nothing like the format used to store Library- or User-Config- isotopic data (\l{IsotopicDataLibraryHandler}, \l{IsotopicDataUserConfigHandler}). In the example below we are defining the isotopic composition of radiolabelled glucose (C6H12O6) where only two of the the six C atoms are 14[C] atoms with a radiolabelling efficiency of 95%: \code [Element] symbol C count 4 # The four atoms that are not labelled appear with natural # abundances for both stable isotopes [Isotopes] 2 mass 12.0 prob 0.989 mass 13.003354 prob 0.010788 [Element] # The two atoms that are 95% labelled with 14[C] need to use # a new artificial symbol symbol Cx count 2 [Isotopes] 3 # 5% of non labelled atoms are of natural 12[C] abundance mass 12.000 prob 0.05*0.989211941850466 # 5% of non labelled atoms are of natural 13[C] abundance mass 13.0033548352 prob 0.05*0.010788058149533084 # The remaining 95% of the atoms are 14[C] atoms with abundance 100% mass 14.003241989 prob 0.95*1.00 [Element] symbol H count 12 [Isotopes] 2 mass 1.0078250 prob 0.99988 mass 2.01410177 prob 0.00011570 [Element] symbol O count 6 [Isotopes] 3 mass 15.9949 prob 0.99756 mass 16.999 prob 0.000380 mass 17.999 prob 0.002051 \endcode Comments are allowed and are on lines that have as their first non-space character the '#' character. These lines are ignored when loading data. The data can be loaded from and written to file. \sa IsotopicDataLibraryHandler, IsotopicDataUserConfigHandler */ /*! \variable IsotopicDataManualConfigHandler::m_symbolCountMap Holds symbol/count pairs to document the count of any symbol in the isotopic data. This symbol count is stored in the file using the following format: \code [Element] symbol C count 6 \endcode The number of carbon atoms in the formula being defined is 6, as in glucose: C6H1206. */ /*! \typedef IsotopicDataManualConfigHandlerSPtr \relates IsotopicDataManualConfigHandler Synonym for std::shared_ptr. */ /*! \typedef IsotopicDataManualConfigHandlerCstSPtr \relates IsotopicDataManualConfigHandler Synonym for std::shared_ptr. */ /*! \brief Constructs the \l{IsotopicDataManualConfigHandler} with \a file_name. */ IsotopicDataManualConfigHandler::IsotopicDataManualConfigHandler( const QString &file_name) : IsotopicDataBaseHandler(file_name) { } /*! \brief Constructs the \l{IsotopicDataManualConfigHandler}. \a isotopic_data_sp Isotopic data \a file_name File name */ IsotopicDataManualConfigHandler::IsotopicDataManualConfigHandler( IsotopicDataSPtr isotopic_data_sp, const QString &file_name) : IsotopicDataBaseHandler(isotopic_data_sp, file_name) { } /*! \brief Destructs the \l{IsotopicDataManualConfigHandler}. */ IsotopicDataManualConfigHandler::~IsotopicDataManualConfigHandler() { // qDebug(); } /*! \brief Assigns \a map to m_symbolCountMap. */ void IsotopicDataManualConfigHandler::setSymbolCountMap(const SymbolCountMap &map) { m_symbolCountMap = map; } /*! \brief Returns a reference to m_symbolCountMap. */ const SymbolCountMap & IsotopicDataManualConfigHandler::getSymbolCountMap() const { return m_symbolCountMap; } /*! \brief Loads isotopic data from \a file_name. Returns the count of \l{Isotope}s that were allocated and stored in the msp_isotopicData member. */ std::size_t IsotopicDataManualConfigHandler::loadData(const QString &file_name) { // File format: // // [Element] // symbol C count 6 // [Isotopes] 2 // mass 12.0 prob 0.989 // mass 13.003354 prob 0.010788 // [Element] // symbol H count 13 // [Isotopes] 2 // mass 1.0078250 prob 0.99988 // mass 2.01410177 prob 0.00011570 // [Element] // symbol O count 6 // [Isotopes] 3 // mass 15.9949 prob 0.99756 // mass 16.999 prob 0.000380 // mass 17.999 prob 0.002051 // if(file_name.isEmpty()) { qDebug("File name is emtpy. Failed to open file for reading."); return 0; } QFile file(file_name); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "Failed to open file for reading."; return 0; } // File-parsing helper variables. bool was_started_one_element = false; bool was_symbol_count_line = false; bool was_isotopes_count_line = false; bool was_mass_prob_line = false; // Instantiate a symbol that we'll use as a place holder for the element name. QString symbol = ""; // Instantiate a count to document the symbol count. std::size_t element_count = 0; // Instantiate a count to document the number of isotopes that were defined // for a given element. std::size_t element_isotope_count = 0; // Increment each time an isotope is parsed and added to the vector *for a // given element stanza*. This helps sanity checking. std::size_t added_isotopes_for_element = 0; // Maintain a counter of the whole count of isotopes for sanity checks. std::size_t all_parsed_isotopes_count = 0; double mass = 0; double prob = 0; bool ok = false; QRegularExpression comment_regexp("^\\s*#"); QRegularExpression symbol_count_regexp( "^\\s*symbol\\s+([A-Z][a-z]?)\\s+count\\s+(\\d+)"); QRegularExpression isotopes_regexp("^\\s*\\[Isotopes\\]\\s(\\d+)"); // QRegularExpression massProbRegexp = QRegularExpression( //"^\\s+mass\\s+(\\d*\\.?\\d*[e]?[-]?[+]?\\d*)\\s+prob\\s+([^\\d^\\.^-]+)(-?" //"\\d*\\.?\\d*[e]?[-]?[+]?\\d*)"); QRegularExpression mass_prob_regexp = QRegularExpression( "^\\s*mass\\s(\\d*\\.?\\d*[e]?[-]?[+]?\\d*)\\sprob\\s(\\d*\\.?\\d*[e]?[" "-]" "?[" "+]?\\d*)"); // qDebug() << "The mass prob regexp is valid?" << // massProbRegexp.isValid(); // mass 13.003354835200 // prob 0.010788058149533083507343178553128382191061973571777343750000 // Make sure we clear the room. msp_isotopicData->clear(); std::vector element_isotopes; // This set is to ensure that we do not have twice the same element frame // (that is, with the same symbol). std::set symbol_set; // File format: // [Element] // symbol C count 6 // [Isotopes] 2 // mass 12.0 prob 0.989 // mass 13.003354 prob 0.010788 QTextStream in(&file); while(!in.atEnd()) { QString line = in.readLine(); // Ignore empty lines if(line.length() < 1) continue; line = line.simplified(); // qDebug() << "Current line:" << line; // Ignore comment lines QRegularExpressionMatch match = comment_regexp.match(line); if(match.hasMatch()) continue; if(line == "[Element]") { // qDebug() << "That's the [Element] stanza opening line."; // We are starting a new Element stanza. It cannot be that we both // have already started one Element stanza and that not a single // mass and probability line had been encountered. Either this is the // very first Element stanza that we read and was_started_one_element // is false or we were reading one Element stanza that has finished // and then was_mass_prob_line has to be true. if(was_started_one_element && !was_mass_prob_line) { qDebug() << "Error: one element is complete but has no isotopes."; return 0; } if(was_started_one_element) { // qDebug() //<< "We had already seen the [Element] stanza opening line."; // We are starting a new Element configuration stanza, but in // fact another was already cooking. We need to terminate it. // Sanity check: the number of purportedly listed isotopes needs // to be identical to the number of isotopes actually added to the // vector of isotopes. if(element_isotope_count != element_isotopes.size() || added_isotopes_for_element != element_isotopes.size()) { qDebug() << "Error. We did not parse the expected number of " "isotopes."; return 0; } // qDebug() << "And there are the right number of isotopes // cooked."; // At this point we have everything we need to add this new // chemical set. // qDebug() << "Creating new chemical set."; if(!newChemicalSet( symbol, element_count, element_isotopes, false)) { qDebug() << "Failed to add new chemical set."; return 0; } // qDebug() << "The completed chemical set: " //"symbol/element_count/isotope_count:" //<< symbol << "/" << element_count << "/" //<< element_isotopes.size(); // Sanity check: the total count of added isotopes for this symbol // needs to correct. if(added_isotopes_for_element != msp_isotopicData->getIsotopeCountBySymbol(symbol)) qFatal("Programming error."); // Now clear for next run. symbol = ""; element_isotope_count = 0; added_isotopes_for_element = 0; element_isotopes.clear(); } // Tell that we actually have entered the first line of an Element // stanza. was_started_one_element = true; // Reset all the other values so that we know we are just at the // beginning of the parsing work. was_symbol_count_line = false; was_isotopes_count_line = false; was_mass_prob_line = false; // Go the next line. continue; } // At this point we are already inside of the [Element] stanza. Parse the // various lines inside it. // File format: // [Element] // symbol C count 6 // [Isotopes] 2 // mass 12.0 prob 0.989 // mass 13.003354 prob 0.010788 match = symbol_count_regexp.match(line); if(match.hasMatch()) { // qDebug() << "Matched the symbol count line."; // If we are parsing "symbol C count 100", then it is not possible // that we did not encounter before [Element] or that we already have // parsed one same line as "symbol C count 100". if(!was_started_one_element || was_symbol_count_line) { qDebug() << "Error encountered in the symbol/count line."; return 0; } symbol = match.captured(1); element_count = match.captured(2).toInt(&ok); if(!ok || !element_count) { qDebug() << "Error encountered in the symbol/count line."; return 0; } // Now check if that symbol was encountered already, which would be an // error. auto res = symbol_set.insert(symbol); if(!res.second) { // We did not insert the symbol because one already existed. That // is an error. qDebug() << "An element by symbol" << symbol << "has already been processed: " "this is not permitted."; return 0; } // qDebug() << "Processed element symbol:" << symbol //<< "with count:" << element_count; // Do not store the element symbol/count pair yet, we'll wait to // encounter a new Element stanza which will close this one. was_symbol_count_line = true; was_isotopes_count_line = false; was_mass_prob_line = false; continue; } // End of // line matched the symbol/count regexp // File format: // [Element] // symbol C count 6 // [Isotopes] 2 // mass 12.0 prob 0.989 // mass 13.003354 prob 0.010788 match = isotopes_regexp.match(line); if(match.hasMatch()) { // qDebug() << "Matched the [Isotopes] count stanza opening line."; // We cannot be parsing the [Isotopes] stanza opening header if we // have not previously parsed the symbol/count line. if(!was_symbol_count_line) { qDebug() << "Error encounteredd in the isotopes lines."; return 0; } // Store the number of isotopes for the current Element. element_isotope_count = match.captured(1).toInt(&ok); if(!ok || !element_isotope_count) { qDebug() << "Error encounteredd in the isotopes lines."; return 0; } // qDebug() << "The isotope count is:" << element_isotope_count; was_isotopes_count_line = true; was_symbol_count_line = false; was_mass_prob_line = false; continue; } // End of // line matched the [Isotopes] count regexp // File format: // [Element] // symbol C count 6 // [Isotopes] 2 // mass 12.0 prob 0.989 // mass 13.003354 prob 0.010788 match = mass_prob_regexp.match(line); if(match.hasMatch()) { // qDebug() << "Matched the mass prob line."; // If we match an isotope's mass/prob line, either --- at previous // line --- we had seen the [Isotopes] stanza opening line or we had // seen another mass prob line. if(!was_isotopes_count_line && !was_mass_prob_line) { qDebug() << "Error encountered in the mass/prob line."; return 0; } mass = match.captured(1).toDouble(&ok); if(!ok) { qDebug() << "Error encountered in the mass/prob line."; return 0; } prob = match.captured(2).toDouble(&ok); if(!ok) { qDebug() << "Error encountered in the mass/prob line."; return 0; } // At this point we have everything we need to actually create the // isotope by symbol and mass and prob. There are a number of fields // that are left to value 0 but this is of no worries. // qDebug() << "Iterated in isotope:" << symbol << ":" << mass << "/" //<< prob; // At this point create a brand new Isotope with the relevant data. // Isotope::Isotope(int id, // QString element, // QString symbol, // int atomicNo, // double mass, // int massNo, // int extraNeutrons, // double probability, // double lnProbability, // bool radioactive) // There are a number of fields that are left to value 0 but this is // of no worries. Store the isotope in the vector of isotopes that // will be added to the isotopic data later when finishing the parsing // of the element frame widget. element_isotopes.push_back(std::make_shared( 0, symbol, symbol, 0, mass, 0, 0, prob, 0, false)); ++added_isotopes_for_element; ++all_parsed_isotopes_count; // qDebug() << "The atom now is:" << atom.asText(); was_mass_prob_line = true; was_isotopes_count_line = false; was_symbol_count_line = false; continue; } // End of // line matched the isotope mass/prob regexp } // We have finished iterating in the file's lines but we were parsing an atom, // append it. // Sanity check if(!was_started_one_element) { qDebug() << "Error: not a single element could be parsed."; return 0; } // Sanity check: the number of purportedly listed isotopes needs // to be identical to the number of isotopes actually added to the // vector of isotopes. if(element_isotope_count != element_isotopes.size() || added_isotopes_for_element != element_isotopes.size()) { qDebug() << "Error. We did not parse the expected number of " "isotopes."; return 0; } // At this point we have everything we need to add this new // chemical set. Do not yet update the mass maps, we'll do at the end of the // process. if(!newChemicalSet(symbol, element_count, element_isotopes, false)) { qDebug() << "Failed to add new chemical set."; return 0; } // qDebug() << "The completed chemical set: " //"symbol/element_count/isotope_count:" //<< symbol << "/" << element_count << "/" << element_isotopes.size(); // Sanity check: the total count of added isotopes for this symbol // needs to correct. if(added_isotopes_for_element != msp_isotopicData->getIsotopeCountBySymbol(symbol)) qFatal("Programming error."); // Sanity check: the total count of isotopes added to the isotopic data member // datum needs to match the total count of parsed isotopes. if(all_parsed_isotopes_count != msp_isotopicData->size()) qFatal("Programming error."); // We have touched the isotopic data, ensure the maps are current. if(!msp_isotopicData->updateMassMaps()) qFatal("Programming error. Failed to update the mass maps."); return msp_isotopicData->size(); } /*! \brief Write all the IsotopicData to \a file_name. If \a file_name is empty, m_fileName is tried. If both are empty, the function returns 0. If any one of the file names are correct (file_name takes precedence over m_fileName), then m_fileName is set to that file name. The format of the file consists in a single line of data per \l{Isotope} as created using the Isotope::toString() function. Each isotope is output to its own line. Returns the count of \l{Isotope}s written to file or 0 if the file does not exist or is not readable. \sa Isotope::Isotope(const QString &text) */ std::size_t IsotopicDataManualConfigHandler::writeData(const QString &file_name) { if(file_name.isEmpty() && m_fileName.isEmpty()) return 0; QString temp_file_name; // The passed filename takes precedence over the member datum. So copy // that file name to the member datum. if(!file_name.isEmpty()) temp_file_name = file_name; else temp_file_name = m_fileName; QFile file(temp_file_name); if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug("Failed to open file for writing."); return false; } QTextStream out(&file); out << "# This file contains isotopic data in a format that can accommodate\n"; out << "# comments in the form of lines beginning with the '#' character.\n"; std::size_t isotope_count = 0; // We want to write the isotopic data exactly in the same order as we might // have loaded them. This is why we need to iterated in the vector of isotopes // and not the in the map that is ordered according to the symbols (the keys). QString last_symbol; for(auto item : msp_isotopicData->m_isotopes) { QString symbol = item->getSymbol(); // We only write the  [Element] and [Isotopes] stanza header once for each // new symbol found in the iterated items. if(symbol != last_symbol) { // This is the first time we encounter an isotope by symbol, we open a // new Element stanza. out << "[Element]\n"; // We now need to write the symbol count line. If the symbol key is // not found, throws an exception. int symbol_count = m_symbolCountMap.at(symbol); out << QString("\tsymbol %1 count %2\n").arg(symbol).arg(symbol_count); // We also need to write the Isotopes stanza: int isotope_count = msp_isotopicData->getIsotopeCountBySymbol(symbol); out << QString("\t[Isotopes] %1\n").arg(isotope_count); } // At this point we can write the currently iterated isotope's mass:prob // pair. out << QString("\t\tmass %1 prob %2\n") .arg(item->getMass(), 0, 'f', 60) .arg(item->getProbability(), 0, 'f', 60); last_symbol = symbol; ++isotope_count; } out.flush(); file.close(); if(isotope_count != msp_isotopicData->size()) qFatal("Programming error. Failed to write all the isotopes to file."); // Now we know that temp_file_name is fine. Store into m_fileName. m_fileName = temp_file_name; return true; } /*! \brief Add a set of \l{Isotope}s belonging to chemical element \a symbol. The count of atoms of \a symbol is defined with \a element_count. The isotopes to be added are provided in \a isotopes. If the new chemical data pertain to a symbol that was already added to the IsotopicData, then that is an error. If \a update_maps is true, the \l{IsotopicData} maps need to be updated. Returns false if the new chemical data pertain to a \a symbol that was already added to the IsotopicData. \sa IsotopicData::updateMonoMassMap(), IsotopicData::updateAvgMassMap() */ bool IsotopicDataManualConfigHandler::newChemicalSet( const QString &symbol, int element_count, const std::vector &isotopes, bool update_maps) { // It is of uttermost importance that we update the symbol/count pair because // that is going to be used when performing the IsoSpec arrays configuration // and later isotopic cluster calculations. std::pair res = m_symbolCountMap.insert(std::pair(symbol, element_count)); if(!res.second) { // Sanity check: it is not possible to add new chemical sets by a given // symbol multiple times. qDebug() << "Error: isotopes by that symbol: " << symbol << "were already found in the isotopic data set."; return false; } std::size_t count_before = msp_isotopicData->size(); // Update the mass maps according to second param below. msp_isotopicData->appendNewIsotopes(isotopes, update_maps); std::size_t count_after = msp_isotopicData->size(); if(count_after - count_before != isotopes.size()) qFatal("Programming error."); return true; } /*! \brief Returns a string containing a formula corresponding to the IsotopicData. The formula is actually computed using the symbol/count pairs stored in m_symbolCountMap. */ QString IsotopicDataManualConfigHandler::craftFormula() const { QString formula; for(auto item : m_symbolCountMap) formula.append(QString("%1%2").arg(item.first).arg(item.second)); return formula; } /*! \brief Returns the count of \l{Isotope}s in this collection. */ std::size_t IsotopicDataManualConfigHandler::checkConsistency() { return msp_isotopicData->size(); } } // namespace libXpertMass } // namespace MsXpS #if 0 Example from IsoSpec. const int elementNumber = 2; const int isotopeNumbers[2] = {2,3}; const int atomCounts[2] = {2,1}; const double hydrogen_masses[2] = {1.00782503207, 2.0141017778}; const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610}; const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses}; const double hydrogen_probs[2] = {0.5, 0.5}; const double oxygen_probs[3] = {0.5, 0.3, 0.2}; const double* probs[2] = {hydrogen_probs, oxygen_probs}; IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts, isotope_masses, probs), 0.99); #endif libxpertmass-1.1.0/src/XpertMass/IsotopicDataUserConfigHandler.cpp000664 001750 001750 00000017657 14647465366 026572 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include /////////////////////// IsoSpec #include #include /////////////////////// Local includes #include "globals.hpp" #include "PeakCentroid.hpp" #include "IsotopicDataUserConfigHandler.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::IsotopicDataUserConfigHandler \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile IsotopicDataUserConfigHandler.hpp \brief The IsotopicDataUserConfigHandler class handles user-defined \l{IsotopicData}. The data can be loaded and written to file. The format of the data is based on the Isotope::toString() implementation that outputs each \l{Isotope}'s data in a separate line. Comments are allowed and are on lines that start with the '#' character. These lines are ignored when loading data. \sa IsotopicDataLibraryHandler, IsotopicDataManualConfigHandler, Isotope::toString() */ /*! \brief Constructs the \l{IsotopicDataUserConfigHandler}. m_fileName is set to \a file_name */ IsotopicDataUserConfigHandler::IsotopicDataUserConfigHandler( const QString &file_name) : IsotopicDataBaseHandler(file_name) { } /*! \brief Constructs the \l{IsotopicDataUserConfigHandler}. The instance will have its isotopic data member pointing to \a isotopic_data_sp. m_fileName is set to \a file_name */ IsotopicDataUserConfigHandler::IsotopicDataUserConfigHandler( IsotopicDataSPtr isotopic_data_sp, const QString &file_name) : IsotopicDataBaseHandler(isotopic_data_sp, file_name) { } /*! \brief Destructs the \l{IsotopicDataUserConfigHandler}. Nothing is explicitely deleted in the destructor. */ IsotopicDataUserConfigHandler::~IsotopicDataUserConfigHandler() { // qDebug(); } /*! \brief Loads isotopic data from \a file_name. The format of the file consists in a single line of data per \l{Isotope} as created using the Isotope::toString() function. Each line is used to create an Isotope with the text-based constructor. Returns the count of \l{Isotope}s created or 0 if the file does not exist or is not readable. \sa Isotope::Isotope(const QString &text) */ std::size_t IsotopicDataUserConfigHandler::loadData(const QString &file_name) { // qDebug() << "Loading isotopic data from file:" << file_name; // See the Isotope::toString() function that is used to write the isotopic // data to file. We thus expect exactly that format from the file. if(file_name.isEmpty()) { qDebug("File name is emtpy. Failed to open file for reading."); return 0; } QFile file(file_name); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug("Failed to open file for reading."); return 0; } // We may have written comment to the file in the form of # lines QRegularExpression commentRegexp("^\\s*#.*$"); msp_isotopicData->clear(); std::size_t isotope_count = 0; QTextStream in(&file); while(!in.atEnd()) { QString line = in.readLine().simplified(); // qDebug() << "simplified line:" << line; // Ignore empty or comment lines if(line.length() < 1 || commentRegexp.match(line).hasMatch()) continue; IsotopeSPtr isotope_sp = std::make_shared(line); // We do not want to update the mono/avg maps each time we load an // isotope. We'll call the relevant function later. msp_isotopicData->appendNewIsotope(isotope_sp, false); ++isotope_count; } // End of // while(!in.atEnd()) // qDebug() << "Finished creating all the Isotope instances."; file.close(); // At this point, it seems that the loading went fine. // Because we have touched the m_isotopes vector, we need to update the // mono/avg masses map. if(!msp_isotopicData->updateMassMaps()) qFatal("Programming error. Failed to update the mass maps."); if(isotope_count != msp_isotopicData->size()) qFatal("Programming error. Failed to load all the isotopes to file."); return msp_isotopicData->size(); } /*! \brief Writes isotopic data to \a file_name. If \a file_name is empty, m_fileName is tried. If both are empty, the function returns 0. If any one of the file names are correct (file_name takes precedence over m_fileName), then m_fileName is set to that file name. The format of the file consists in a single line of data per \l{Isotope} as created using the Isotope::toString() function. Each isotope is output to its own line. Returns the count of \l{Isotope}s written to file or 0 if the file does not exist or is not readable. \sa Isotope::Isotope(const QString &text) */ std::size_t IsotopicDataUserConfigHandler::writeData(const QString &file_name) { // Although the isotopic data were loaded from the IsoSpec library tables, we // might be willing to store these data to a file. if(file_name.isEmpty() && m_fileName.isEmpty()) return 0; QString temp_file_name; // The passed filename takes precedence over the member datum. So copy // that file name to the member datum. if(!file_name.isEmpty()) temp_file_name = file_name; else temp_file_name = m_fileName; QFile file(temp_file_name); if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qDebug("Failed to open file for writing."); return 0; } QTextStream out(&file); out << "# This file contains isotopic data in a format that can accommodate\n"; out << "# comments in the form of lines beginning with the '#' character.\n\n"; std::size_t isotope_count = 0; for(auto item : msp_isotopicData->m_isotopes) { out << item->toString(); // We need to add it because toString() does not terminate the line with // a new line character. out << "\n"; ++isotope_count; } out.flush(); file.close(); if(isotope_count != msp_isotopicData->size()) qFatal("Programming error. Failed to write all the isotopes to file."); // Now we know that temp_file_name is fine. Store into m_fileName. m_fileName = temp_file_name; return isotope_count; } std::size_t IsotopicDataUserConfigHandler::checkConsistency() { return msp_isotopicData->size(); } } // namespace libXpertMass } // namespace MsXpS #if 0 Example from IsoSpec. const int elementNumber = 2; const int isotopeNumbers[2] = {2,3}; const int atomCounts[2] = {2,1}; const double hydrogen_masses[2] = {1.00782503207, 2.0141017778}; const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610}; const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses}; const double hydrogen_probs[2] = {0.5, 0.5}; const double oxygen_probs[3] = {0.5, 0.3, 0.2}; const double* probs[2] = {hydrogen_probs, oxygen_probs}; IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts, isotope_masses, probs), 0.99); #endif libxpertmass-1.1.0/src/XpertMass/MassDataCborBaseHandler.cpp000664 001750 001750 00000015353 14647465366 025307 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Std lib includes /////////////////////// Qt includes #include #include /////////////////////// IsoSpec /////////////////////// Local includes #include "MassDataCborBaseHandler.hpp" namespace MsXpS { namespace libXpertMass { // FIXME TODO DOCUMENTATION MassDataCborBaseHandler::MassDataCborBaseHandler(QObject *parent_p) : QObject(parent_p) { } MassDataCborBaseHandler::~MassDataCborBaseHandler() { } void MassDataCborBaseHandler::setInputFileName(const QString &file_name) { m_inputFileName = file_name; } void MassDataCborBaseHandler::setOutputFileName(const QString &file_name) { m_outputFileName = file_name; } void MassDataCborBaseHandler::setMassDataType(MassDataType mass_data_type) { m_massDataType = mass_data_type; } MassDataType MassDataCborBaseHandler::getMassDataType() const { return m_massDataType; } bool MassDataCborBaseHandler::readContext( [[maybe_unused]] QCborStreamReaderSPtr &reader_sp) { qDebug() << "The base class handler function does nothing."; return false; } MassDataType MassDataCborBaseHandler::readMassDataType(QCborStreamReaderSPtr &reader_sp) { QString current_key; // The file format starts with a quint64 that indicates the type of mass // data (MassDataType enum to quint64). while(!reader_sp->lastError() && reader_sp->hasNext()) { // The first iteration in the CBOR data structure is in a map. QCborStreamReader::Type type = reader_sp->type(); // qDebug() << "Type is:" << type; if(type != QCborStreamReader::Map) qFatal( "The byte array does not have the expected CBOR structure for " "mass data."); reader_sp->enterContainer(); while(reader_sp->lastError() == QCborError::NoError && reader_sp->hasNext()) { QCborStreamReader::Type type = reader_sp->type(); // qDebug() << "Type is:" << type; // The very first item of the map should be a pair // // "DATA_TYPE / quint64 if(type == QCborStreamReader::String) { // Read a CBOR string, concatenating all // the chunks into a single string. QString str; auto chunk = reader_sp->readString(); while(chunk.status == QCborStreamReader::Ok) { str += chunk.data; chunk = reader_sp->readString(); } if(chunk.status == QCborStreamReader::Error) { // handle error condition qDebug() << "There was an error reading string chunk."; str.clear(); } // qDebug() << "The string that was read:" << str; // If the current key is empty, this string value is a new key // or tag. Otherwise, it's a value. if(current_key.isEmpty()) { // qDebug() << "Setting current_key:" << str; current_key = str; } else { // At this point we can reset current_key. current_key = QString(); } } else if(type == QCborStreamReader::UnsignedInteger) { if(current_key != "DATA_TYPE") qFatal( "The byte array does not have the expected CBOR " "structure for " "mass data."); //quint64 data_type = reader_sp->toUnsignedInteger(); //qDebug() << "The mass data type:" << data_type; return static_cast(reader_sp->toUnsignedInteger()); } else qFatal( "The byte array does not have the expected CBOR " "structure for " "mass data."); } } return MassDataType::UNSET; } MassDataType MassDataCborBaseHandler::readMassDataType(const QString &input_file_name) { QFileInfo file_info(input_file_name); if(!file_info.exists()) { qDebug() << "File not found."; return MassDataType::UNSET; } QFile file(input_file_name); bool res = file.open(QIODevice::ReadOnly); if(!res) { qDebug() << "Failed to open the file for read."; return MassDataType::UNSET; } QCborStreamReaderSPtr reader_sp = std::make_shared(&file); return readMassDataType(reader_sp); } MassDataType MassDataCborBaseHandler::readMassDataType(const QByteArray &byte_array) { QCborStreamReaderSPtr reader_sp = std::make_shared(byte_array); return readMassDataType(reader_sp); } bool MassDataCborBaseHandler::readFile( [[maybe_unused]] const QString &input_file_name) { qDebug() << "The base class handler function does nothing."; return false; } bool MassDataCborBaseHandler::readByteArray( [[maybe_unused]] const QByteArray &byte_array) { qDebug() << "The base class handler function does nothing."; return false; } bool MassDataCborBaseHandler::writeFile( [[maybe_unused]] const QString &output_file_name) { qDebug() << "The base class handler function does nothing."; return false; } void MassDataCborBaseHandler::writeByteArray([[maybe_unused]] QByteArray &byte_array) { qDebug() << "The base class handler function does nothing."; } void MassDataCborBaseHandler::setTitle(const QString &title) { m_title = title; } QString MassDataCborBaseHandler::getTitle() const { return m_title; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/MassDataCborMassSpectrumHandler.cpp000664 001750 001750 00000037176 14647465366 027072 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Std lib includes /////////////////////// Qt includes #include #include /////////////////////// IsoSpec /////////////////////// Local includes #include "MassDataCborMassSpectrumHandler.hpp" namespace MsXpS { namespace libXpertMass { // FIXME TODO DOCUMENTATION MassDataCborMassSpectrumHandler::MassDataCborMassSpectrumHandler( QObject *parent_p) : MassDataCborBaseHandler(parent_p) { } // When providing a Trace, it is implicit that this is to write that to file. MassDataCborMassSpectrumHandler::MassDataCborMassSpectrumHandler( QObject *parent_p, const pappso::Trace &trace) : MassDataCborBaseHandler(parent_p), m_trace(trace) { } MassDataCborMassSpectrumHandler::~MassDataCborMassSpectrumHandler() { } bool MassDataCborMassSpectrumHandler::writeFile(const QString &output_file_name) { // The format for this kind of CBOR pappso::Trace data is the following: // First the quint64 that represents MassDataType. In our specific case, that // must be MassDataType::MASS_SPECTRUM. // Then there is the title of data, which is according to the specification: // // Major type 3: a text string, specifically a string of Unicode characters // that is encoded as UTF-8 (that is, not a QByteArray, but a QString). // Then there is a MAP with the following key/value pairs: // start_map // "X_LABEL" / value (text string) // "Y_LABEL" / value (text string) // // "X_DATA" / value (base64 ByteArray) // "Y_DATA" / value (base64 ByteArray) // end_map QString local_file_name = output_file_name; if(local_file_name.isEmpty()) local_file_name = m_outputFileName; QFile file(local_file_name); bool res = file.open(QIODevice::WriteOnly); if(!res) { qDebug() << "Failed to open the file for write."; return false; } msp_writer = std::make_shared(&file); // qDebug() << "Now writing data to file:" << local_file_name; // Start of array containing all mapped items msp_writer->startMap(7); // First the MassDataType: this is the very *first* data bit in the data // stream. msp_writer->append("DATA_TYPE"); msp_writer->append(static_cast(MassDataType::MASS_SPECTRUM)); msp_writer->append("TITLE"); msp_writer->append(m_title); msp_writer->append("TRACE_COLOR"); msp_writer->append(m_colorByteArray); msp_writer->append("X_LABEL"); msp_writer->append("m/z"); msp_writer->append("X_DATA"); msp_writer->append(m_trace.xAsBase64Encoded()); msp_writer->append("Y_LABEL"); msp_writer->append("intensity"); msp_writer->append("Y_DATA"); msp_writer->append(m_trace.yAsBase64Encoded()); msp_writer->endMap(); // Close the map now. file.close(); return res; } void MassDataCborMassSpectrumHandler::writeByteArray(QByteArray &byte_array) { msp_writer = std::make_shared(&byte_array); // qDebug() << "Now writing data byte array."; // Start of array containing all mapped items msp_writer->startMap(7); // First the MassDataType: this is the very *first* data bit in the data // stream. msp_writer->append("DATA_TYPE"); msp_writer->append(static_cast(MassDataType::MASS_SPECTRUM)); msp_writer->append("TITLE"); msp_writer->append(m_title); msp_writer->append("TRACE_COLOR"); msp_writer->append(m_colorByteArray); msp_writer->append("X_LABEL"); msp_writer->append("m/z"); msp_writer->append("X_DATA"); msp_writer->append(m_trace.xAsBase64Encoded()); msp_writer->append("Y_LABEL"); msp_writer->append("intensity"); msp_writer->append("Y_DATA"); msp_writer->append(m_trace.yAsBase64Encoded()); msp_writer->endMap(); // Close the map now. } bool MassDataCborMassSpectrumHandler::readContext(QCborStreamReaderSPtr &reader_sp) { // The format for this kind of CBOR pappso::Trace data is the following: // The whole file is actually a map that contains the key/value // pairs below. // The text string values below are described in this way in the specif: // Major type 3: a text string, specifically a string of Unicode characters // that is encoded as UTF-8 (that is, not a QByteArray, but a QString). // The containers, map, strings and byte arrays dot not need the reader to // explicitely advance to next(). // The simple value, like integers, do need that reader's next() call. // So now, the MAP with the following key/value pairs: // start_map // "DATA_TYPE" / value (quint64) // "TITLE" / value (text string) // "X_LABEL" / value (text string) // "Y_LABEL" / value (text string) // // "X_DATA" / value (base64 ByteArray) // "Y_DATA" / value (base64 ByteArray) // end_map // These are needed to store the x and y data as two different byte arrays. while(reader_sp->lastError() == QCborError::NoError && reader_sp->hasNext()) { QCborStreamReader::Type type = reader_sp->type(); // qDebug() << "Type is:" << type; if(type == QCborStreamReader::UnsignedInteger) { // In this format, the QCborStreamReader::UnsignedInteger datum is // only got as the very first data bit in the file. This bit of // information is a quint64 representing MassDataType::MASS_SPECTRUM. // Now check that the read value actually corresponds to the expected // mass data type for which this object is working! if(m_currentKey == "DATA_TYPE") { // quint64 data_type = 0; // data_type = reader_sp->toUnsignedInteger(); // qDebug() << "The mass data type:" << data_type; if(static_cast(reader_sp->toUnsignedInteger()) != MassDataType::MASS_SPECTRUM) { qDebug() << "The expected DATA_TYPE::MASS_SPECTRUM was not found."; return false; } m_massDataType = MassDataType::MASS_SPECTRUM; m_currentKey = QString(); } // qDebug() << "At this point, check if it has next:" //<< reader_sp->hasNext(); // We had what we wanted, go to next. reader_sp->next(); } else if(type == QCborStreamReader::String) { // Read a CBOR string, concatenating all // the chunks into a single string. QString str; auto chunk = reader_sp->readString(); while(chunk.status == QCborStreamReader::Ok) { str += chunk.data; chunk = reader_sp->readString(); } if(chunk.status == QCborStreamReader::Error) { // handle error condition qDebug() << "There was an error reading string chunk."; str.clear(); } // qDebug() << "The string that was read:" << str; // If the current key is empty, this string value is a new key or tag. // Otherwise, it's a value. if(m_currentKey.isEmpty()) { // qDebug() << "Setting m_currentKey:" << str; m_currentKey = str; } else { // Depending on what we had already, we will set the proper member // datum. if(m_currentKey == "TITLE") { // qDebug() << "Setting m_title:" << str; m_title = str; } if(m_currentKey == "X_LABEL") { // qDebug() << "Setting m_xLabel:" << str; m_xLabel = str; } if(m_currentKey == "Y_LABEL") { // qDebug() << "Setting m_yLabel:" << str; m_yLabel = str; } // At this point we can reset m_currentKey. m_currentKey = QString(); } } // The ByteArray data for X_DATA and Y_DATA. // They are preceded by the keys "X_DATA" and "Y_DATA" respectively. else if(type == QCborStreamReader::ByteArray) { // Read a byte array. That could be either the X_DATA or the Y_DATA. QByteArray array; auto chunk = reader_sp->readByteArray(); while(chunk.status == QCborStreamReader::Ok) { array.append(chunk.data); chunk = reader_sp->readByteArray(); } if(m_currentKey == "X_DATA") { m_xBase64Data = array; } else if(m_currentKey == "Y_DATA") { m_yBase64Data = array; } else if(m_currentKey == "TRACE_COLOR") { m_colorByteArray = array; } else { qDebug() << "Error in the data read from file."; return false; } m_currentKey = QString(); } // The MAP has the following key/value pairs: // start_map // "TITLE" / value (text string) // "X_LABEL" / value (text string) // "Y_LABEL" / value (text string) // // "X_DATA" / value (base64 ByteArray) // "Y_DATA" / value (base64 ByteArray) // end_map else if(type == QCborStreamReader::Map) { reader_sp->enterContainer(); // Read elements until end of map is reached bool res = readContext(reader_sp); if(res) reader_sp->leaveContainer(); else return false; } else { // Ignore all other types, go to the next element reader_sp->next(); } } if(reader_sp->lastError() != QCborError::NoError) qDebug() << "There was an error: " << reader_sp->lastError() << "Returning false."; // Return true if there were no errors // qDebug() << "Returning: " << !reader_sp->lastError(); return !reader_sp->lastError(); } bool MassDataCborMassSpectrumHandler::readByteArray(const QByteArray &byte_array) { msp_reader = std::make_shared(byte_array); bool res = readContext(msp_reader); // qDebug() << "After finishing the read, m_title is:" << m_title //<< "and mass data type is:" << static_cast(m_massDataType); // At this point we need to decode the arrays and with the data initialize the // pappso::Trace. QByteArray x_array; QByteArray y_array; QByteArray::FromBase64Result decoding_result = QByteArray::fromBase64Encoding( m_xBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals); if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok) x_array = decoding_result.decoded; else { qDebug() << "Failed to decode the " << m_xLabel << "data"; return false; } decoding_result = QByteArray::fromBase64Encoding( m_yBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals); if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok) y_array = decoding_result.decoded; else { qDebug() << "Failed to decode the " << m_yLabel << "data"; return false; } m_trace.clear(); m_trace.initialize(QString(x_array), QString(y_array)); return res; } bool MassDataCborMassSpectrumHandler::readFile(const QString &input_file_name) { // The format for this kind of CBOR pappso::Trace data is the following: // First the quint64 that represents MassDataType. In our specific case, that // must be MassDataType::MASS_SPECTRUM. // Then there is the title of data, which is according to the specification: // // Major type 3: a text string, specifically a string of Unicode characters // that is encoded as UTF-8 (that is, not a QByteArray, but a QString). // Then there is a MAP with the following key/value pairs: // start_map // "X_LABEL" / value (text string) // "Y_LABEL" / value (text string) // // "X_DATA" / value (base64 ByteArray) // "Y_DATA" / value (base64 ByteArray) // end_map QString local_file_name = input_file_name; if(local_file_name.isEmpty()) local_file_name = m_inputFileName; QFileInfo file_info(local_file_name); if(!file_info.exists()) { qDebug() << "File not found."; return false; } QFile file(local_file_name); bool res = file.open(QIODevice::ReadOnly); if(!res) { qDebug() << "Failed to open the file for read."; return false; } // qDebug() << "Now starting the CBOR data read."; msp_reader = std::make_shared(&file); res = readContext(msp_reader); file.close(); // qDebug() << "After finishing the read, m_title is:" << m_title //<< "and mass data type is:" << static_cast(m_massDataType); // At this point we need to decode the arrays and with the data initialize the // pappso::Trace. QByteArray x_array; QByteArray y_array; QByteArray::FromBase64Result decoding_result = QByteArray::fromBase64Encoding( m_xBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals); if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok) x_array = decoding_result.decoded; else { qDebug() << "Failed to decode the " << m_xLabel << "data"; return false; } decoding_result = QByteArray::fromBase64Encoding( m_yBase64Data, QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals); if(decoding_result.decodingStatus == QByteArray::Base64DecodingStatus::Ok) y_array = decoding_result.decoded; else { qDebug() << "Failed to decode the " << m_yLabel << "data"; return false; } m_trace.clear(); m_trace.initialize(QString(x_array), QString(y_array)); return res; } void MassDataCborMassSpectrumHandler::setXLabel(const QString &label) { m_xLabel = label; } QString MassDataCborMassSpectrumHandler::getXLabel() const { return m_xLabel; } void MassDataCborMassSpectrumHandler::setYLabel(const QString &label) { m_yLabel = label; } QString MassDataCborMassSpectrumHandler::getYLabel() const { return m_yLabel; } void MassDataCborMassSpectrumHandler::setTrace(const pappso::Trace &trace) { m_trace = trace; } pappso::Trace MassDataCborMassSpectrumHandler::getTrace() const { return m_trace; }; void MassDataCborMassSpectrumHandler::clearTrace() { m_trace.clear(); } void MassDataCborMassSpectrumHandler::setTraceColor( const QByteArray &color_byte_array) { m_colorByteArray = color_byte_array; } QByteArray MassDataCborMassSpectrumHandler::getTraceColor() const { return m_colorByteArray; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/MassDataClient.cpp000664 001750 001750 00000020410 14647465366 023535 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ***************************************************************************** * This specific code is a port to C++ of the Envemind Python code by Radzinski * and colleagues of IsoSpec fame (Lacki, Startek and company :-) * * See https://github.com/PiotrRadzinski/envemind. * ***************************************************************************** * * END software license */ #include #include #include "MassDataClient.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::MassDataClient \inmodule libXpertMass \ingroup XpertMassUtilities \inheaderfile MassDataClient.hpp \brief The MassDataClient class provides a network client. */ /*! \variable MsXpS::libXpertMass::MassDataClient::m_inStream \brief The data stream in input in this MassDataClient instance. */ /*! \variable MsXpS::libXpertMass::MassDataClient::m_data \brief The data being transported. */ /*! \variable MsXpS::libXpertMass::MassDataClient::m_ipAddress \brief The server IP address to which the connection must be made. */ /*! \variable MsXpS::libXpertMass::MassDataClient::m_portNumber \brief The port number to which to attach during the connection.. */ /*! \variable MsXpS::libXpertMass::MassDataClient::m_connectionFrequency \brief The frequency at which the client connects to the server, in times per second. A frequency of 1 means that the connection will be tried once per second. This is empirically correct. */ /*! \variable MsXpS::libXpertMass::MassDataClient::mpa_tcpSocket \brief The socket that is used for the connection. */ /*! \brief Constructs a MassDataClient instance. \list \li \a ip_address: the server IP address at which the client needs to try a connection. \li \a port_number: the port number for the connection. \li \a connection_frequency: the frequency at which the client tries a connection to the server. \li \a parent: the parent QObject. \endlist */ MassDataClient::MassDataClient(const QString &ip_address, int port_number, int connection_frequency, QObject *parent) : QObject(parent), m_ipAddress(ip_address), m_portNumber(port_number), m_connectionFrequency(connection_frequency) { mpa_tcpSocket = new QTcpSocket(parent); if(mpa_tcpSocket == nullptr) qFatal("Failed to allocate a socket."); // QString host_name = QHostInfo::localHostName(); // if(!host_name.isEmpty()) //{ // QString domain_name = QHostInfo::localDomainName(); // if(!domain_name.isEmpty()) // qDebug() << "address:" << host_name + "/" + domain_name; //} // if(host_name != QLatin1String("locahost")) // qDebug() << "host_name is not localhost."; // QList ip_addresses_list = QNetworkInterface::allAddresses(); // for(int iter = 0; iter < ip_addresses_list.size(); ++iter) // qDebug() << "New ip address:" << ip_addresses_list.at(iter) //<< "is loopback:" << ip_addresses_list.at(iter).isLoopback(); connect(mpa_tcpSocket, &QTcpSocket::hostFound, [this]() { emit hostFoundSignal(); }); connect(mpa_tcpSocket, &QTcpSocket::connected, [this]() { emit connectedSignal(); }); connect(mpa_tcpSocket, &QAbstractSocket::errorOccurred, this, &MassDataClient::reportError); m_inStream.setDevice(mpa_tcpSocket); m_inStream.setVersion(QDataStream::Qt_5_0); connect( mpa_tcpSocket, &QIODevice::readyRead, this, &MassDataClient::readData); // A time with a timeout of 0 will trigger as soon as all the other events // have been processed. This is thus not intrusive. QTimer *timer_p = new QTimer(this); // This does not work, the interaval is too short and the server can't serve // in time, it seems. // timer_p->setInterval(0); // 1 second is largely enough, which means a frequency of 1, which is the // default for the connection frequency parameter! timer_p->setInterval(1000 / m_connectionFrequency); connect(timer_p, &QTimer::timeout, this, &MassDataClient::requestNewData); timer_p->start(); } /*! \brief Destructs this MassDataClient instance. */ MassDataClient::~MassDataClient() { if(mpa_tcpSocket != nullptr) { qDebug("Now deleting the socket."); delete mpa_tcpSocket; } } /*! \brief Tries a connection to the server using the member QTcpSocket. */ void MassDataClient::requestNewData() { qDebug() << "Client requests data at" << QDateTime::currentDateTime(); QAbstractSocket::SocketState state = mpa_tcpSocket->state(); if(state != QAbstractSocket::UnconnectedState) mpa_tcpSocket->abort(); mpa_tcpSocket->connectToHost(m_ipAddress, m_portNumber); } /*! \brief Reads the data into the QByteArray m_data member using the QDataStream m_inStream member. Emits the newDataSignal() signal with m_data;. */ void MassDataClient::readData() { // qDebug() << "Reading data."; // We need to have the same version, FIXME, this should go as a macro // somewhere. // qDebug() << "The number of bytes available in this readData call:" //<< mp_tcpSocket->bytesAvailable() << "at" //<< QDateTime::currentDateTime().toString(); // This version uses the readAll() function. // QByteArray byte_array = mp_tcpSocket->readAll(); // QDataStream stream(byte_array); // stream.setVersion(QDataStream::Qt_5_0); // stream.startTransaction(); // stream >> m_data; // if(!stream.commitTransaction()) //{ // qDebug() << "Failed to commit the data read transaction."; // return ; //} // This version uses the stream operator. // Seems to work equivalently to the version above. // stream.setVersion(QDataStream::Qt_5_0); m_inStream.startTransaction(); m_inStream >> m_data; // qDebug() << "Atomically read" << m_data.size(); if(!m_inStream.commitTransaction()) { qDebug() << "Could NOT commit the transaction fine."; return; } // else // qDebug() << "Could commit the transaction fine."; emit newDataSignal(m_data); // qDebug() << "Got these data:" << QString(m_data); } /*! \brief Reports the error \a socket_error. */ void MassDataClient::reportError(QAbstractSocket::SocketError socket_error) { switch(socket_error) { case QAbstractSocket::RemoteHostClosedError: // qDebug() << "Error: QAbstractSocket::RemoteHostClosedError."; // emit reportErrorSignal("RemoteHostClosedError"); break; case QAbstractSocket::HostNotFoundError: // qDebug() << "Error: QAbstractSocket::HostNotFoundError."; // emit reportErrorSignal("HostNotFoundError"); break; case QAbstractSocket::ConnectionRefusedError: qDebug() << "Error: QAbstractSocket::ConnectionRefusedError."; emit reportErrorSignal("ConnectionRefusedError"); break; default: // qDebug() << "Error:" << mp_tcpSocket->errorString(); // emit reportErrorSignal(mp_tcpSocket->errorString()); break; } } /*! \brief Returns a string display the connection details. */ QString MassDataClient::getStatus() { return QString("Doing well, connection should be to IP address: %1, port %2.") .arg(m_ipAddress) .arg(m_portNumber); } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/MassDataServer.cpp000664 001750 001750 00000011172 14647465366 023572 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ***************************************************************************** * This specific code is a port to C++ of the Envemind Python code by Radzinski * and colleagues of IsoSpec fame (Lacki, Startek and company :-) * * See https://github.com/PiotrRadzinski/envemind. * ***************************************************************************** * * END software license */ #include #include #include "MassDataServer.hpp" #include "MassDataServerThread.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::MassDataServer \inmodule libXpertMass \ingroup XpertMassUtilities \inheaderfile MassDataServer.hpp \brief The MassDataServer class provides a network server. */ /*! \variable MsXpS::libXpertMass::MassDataServer::m_data \brief The data to be served. */ /*! \brief Constructs a MassDataServer instance. \list \li \a parent: QObject parent. \endlist */ MassDataServer::MassDataServer(QObject *parent) : QTcpServer(parent) { } /*! \brief Destructs this MassDataServer instance. */ MassDataServer::~MassDataServer() { } /*! \brief Sets to this MassDataServer instance the data to be served in \a byte_array. */ void MassDataServer::serveData(const QByteArray &byte_array) { m_data = byte_array; // qDebug() << "Stored the byte array to be served upon connection. Size:" //<< m_data.size(); } /*! \brief Handles an incoming connection with socket descriptor \a socket_descriptor. If the member m_data are not empty, allocates a MassDataServerThread to serve these data. */ void MassDataServer::incomingConnection(qintptr socket_descriptor) { if(!m_data.size()) { qDebug() << "In this incoming connection we have no data to serve. Returning at:" << QDateTime::currentDateTime().toString(); // It is not because we have not data to use as a response to the caller // that we do not perform the connection closing stuff! Otherwise we // consume a file descriptor (the socket) each time a connection is tried // here from the client.... Bug that has broken my head for weeks... QTcpSocket tcpSocket; if(!tcpSocket.setSocketDescriptor(socket_descriptor)) return; tcpSocket.disconnectFromHost(); tcpSocket.waitForDisconnected(); return; } qDebug() << "In this incoming connection, the data to serve have size:" << m_data.size() << "at:" << QDateTime::currentDateTime().toString(); MassDataServerThread *mass_data_server_thread_p = new MassDataServerThread(socket_descriptor, m_data, this); connect(mass_data_server_thread_p, &MassDataServerThread::finished, mass_data_server_thread_p, &MassDataServerThread::deleteLater); connect(mass_data_server_thread_p, &MassDataServerThread::errorSignal, this, &MassDataServer::error); connect( mass_data_server_thread_p, &MassDataServerThread::writtenDataSignal, [this](std::size_t written_bytes) { qDebug() << "The data were written by the thread-based server socket " "with written_bytes:" << written_bytes << ". Clearing the data now."; m_data = ""; }); mass_data_server_thread_p->start(); } /*! \brief Reports the \a socket_error to the console using qDebug(). */ void MassDataServer::error(QTcpSocket::SocketError socket_error) { qDebug() << "An error occurred in the thread:" << socket_error; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/MassDataServerThread.cpp000664 001750 001750 00000011617 14647465366 024726 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ***************************************************************************** * This specific code is a port to C++ of the Envemind Python code by Radzinski * and colleagues of IsoSpec fame (Lacki, Startek and company :-) * * See https://github.com/PiotrRadzinski/envemind. * ***************************************************************************** * * END software license */ #include #include #include "MassDataServerThread.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::MassDataServerThread \inmodule libXpertMass \ingroup XpertMassUtilities \inheaderfile MassDataServerThread.hpp \brief The MassDataServerThread class provides a server in a QThread instance. */ /*! \variable MsXpS::libXpertMass::MassDataServerThread::m_data \brief The data to be served. */ /*! \variable MsXpS::libXpertMass::MassDataServerThread::m_socketDescriptor \brief The socket descriptor of the connection. */ /*! \variable MsXpS::libXpertMass::MassDataServerThread::mpa_tcpSocket \brief The socket to be used for the connection. */ /*! \brief Constructs a MassDataServerThread instance. \list \li \a socket_descriptor: the socket descriptor of the connection to the client. \li \a data: the data to be served. \li \a parent: the parent QObject. \endlist */ MassDataServerThread::MassDataServerThread(qintptr socket_descriptor, const QByteArray &data, QObject *parent) : QThread(parent), m_socketDescriptor(socket_descriptor), m_data(data) { // qDebug() << "Constructing the server thread with data of size:" //<< m_data.size() << this; } /*! \brief Destructs this MassDataServerThread instance. Deletes mpa_tcpSocket if non-nullptr. */ MassDataServerThread::~MassDataServerThread() { if(mpa_tcpSocket != nullptr) { qDebug("Now deleting the socket."); delete mpa_tcpSocket; } } /*! \brief Runs the task in this thread. Allocates a new QTcpSocket and sets the \c m_socketDescriptor. If the member m_data are not empty, writes the data to the socket and disconnects. */ void MassDataServerThread::run() { // qDebug() << "The server thread has been run at" //<< QDateTime::currentDateTime(); mpa_tcpSocket = new QTcpSocket(); if(!mpa_tcpSocket->setSocketDescriptor(m_socketDescriptor)) { qDebug() << "There was an error initializing the socket descriptor."; emit errorSignal(mpa_tcpSocket->error()); return; } if(m_data.size()) { QByteArray byte_array; QDataStream out_stream(&byte_array, QIODevice::WriteOnly); out_stream.setVersion(QDataStream::Qt_5_0); out_stream << m_data; // qDebug() << "On the verge of writing byte_array of size:" //<< byte_array.size(); int written_bytes = mpa_tcpSocket->write(byte_array); // qDebug() << "Now written " << written_bytes << " bytes to the socket // at" //<< QDateTime::currentDateTime(); if(written_bytes >= byte_array.size()) { // qDebug() << "The data could be written fine. Clearing them now."; emit writtenDataSignal(written_bytes); m_data.clear(); } // else // qDebug() << "The data could not be written fully, not sending " //"successful process signal."; } // else // qDebug() << "There are no data to be written to the socket at" //<< QDateTime::currentDateTime(); qDebug() << "Now trying to disconnect from host."; mpa_tcpSocket->disconnectFromHost(); if (mpa_tcpSocket->state() == QAbstractSocket::UnconnectedState || mpa_tcpSocket->waitForDisconnected(1000)) { qDebug("Succesfully disconnected from the host!"); } } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/MassPeakShaper.cpp000664 001750 001750 00000031553 14647465366 023562 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// StdLib includes #include #include #include #include /////////////////////// Qt includes #include #include /////////////////////// pappsomspp includes #include /////////////////////// Local includes #include "MassPeakShaper.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::MassPeakShaper \inmodule libXpertMass \ingroup XpertMassMassCalculations \inheaderfile MassPeakShaper.hpp \brief The MassPeakShaper class provides the features needed to shape a mass peak. \e{Shaping a peak} means creating a shape around a centroid m/z value such that the m/z peak is represented like it appears in a mass spectrum displayed in "profile" mode. The configuration of the mass peak shaping is held in a specific \l MassPeakShaperConfig class. \sa MassPeakShaperConfig */ /*! \typedef MassPeakShaperSPtr \relates MassPeakShaper Synonym for std::shared_ptr. */ /*! \variable MsXpS::libXpertMass::MassPeakShaper::m_peakCentroid \brief The peak centroid for which a shape is computed. A peak centroid is the center of a mass peak profile and is thus the (m/z, intensity) pair. */ /*! \variable MsXpS::libXpertMass::MassPeakShaper::m_config \brief The configuration needed to drive the mass peak shaping process. */ /*! \variable MsXpS::libXpertMass::MassPeakShaper::m_trace \brief The Trace object that will receive the different points that make the peak shape. */ /*! \brief Constructs a MassPeakShaper instance. */ MassPeakShaper::MassPeakShaper() : m_peakCentroid(0, 0) { } /*! \brief Constructs a MassPeakShaper instance. \list \li \a mz: The peak centroid m/z value. \li \a intensity: The peak centroid intensity value. \li \a config: The configuration driving the mass peak shaping process. \endlist */ MassPeakShaper::MassPeakShaper(double mz, double intensity, const MassPeakShaperConfig &config) : m_peakCentroid(mz, intensity), m_config(config) { } /*! \brief Constructs a MassPeakShaper instance. \list \li \a data_point: The data point representing the peak centroid. \li \a config: The configuration driving the mass peak shaping process. \endlist */ MassPeakShaper::MassPeakShaper(const pappso::DataPoint &data_point, const MassPeakShaperConfig &config) : m_peakCentroid(data_point), m_config(config) { // qDebug()"m_config:" << m_config.asText(800); } /*! \brief Constructs a MassPeakShaper instance as a copy of \a other. */ MassPeakShaper::MassPeakShaper(const MassPeakShaper &other) : m_peakCentroid(other.m_peakCentroid), m_config(other.m_config), m_trace(other.m_trace) { } /*! \brief Destructs this MassPeakShaper instance. */ MassPeakShaper::~MassPeakShaper() { } /*! \brief Sets the \a peak_centroid_data. */ void MassPeakShaper::setPeakCentroid(const pappso::DataPoint &peak_centroid_data) { m_peakCentroid = peak_centroid_data; } /*! \brief Returns the peak centroid data. */ const pappso::DataPoint & MassPeakShaper::getPeakCentroid() const { return m_peakCentroid; } /*! \brief Returns the peak shape as a pappso::Trace. */ const pappso::Trace & MassPeakShaper::getTrace() const { return m_trace; } /* \brief Clears the peak shape. */ void MassPeakShaper::clearTrace() { m_trace.clear(); } /*! \brief Sets the configuration driving the peak shaping process to \a config. */ void MassPeakShaper::setConfig(const MassPeakShaperConfig &config) { m_config = config; } /*! \brief Returns the configuration driving the peak shaping process. */ const MassPeakShaperConfig & MassPeakShaper::getConfig() const { return m_config; } /*! \brief Computes the peak shape of the peak centroid. Returns the count of points in the peak shape. */ int MassPeakShaper::computePeakShape() { if(m_config.getMassPeakShapeType() == MassPeakShapeType::GAUSSIAN) return computeGaussianPeakShape(); else return computeLorentzianPeakShape(); } /*! \brief Computes the peak shape of the peak centroid. \list \li \a mz: the peak centroid's m/z value. \li \a intensity: the peak centroid's intensity value. \li \a config: the configuration driving the peak shaping process. \endlist Returns the pappso::Trace describing the peak shape. */ pappso::Trace MassPeakShaper::computePeakShape(double mz, double intensity, const MassPeakShaperConfig &config) { if(config.getMassPeakShapeType() == MassPeakShapeType::GAUSSIAN) return computeGaussianPeakShape(mz, intensity, config); else return computeLorentzianPeakShape(mz, intensity, config); } /*! \brief Computes the Gaussian peak shape of the peak centroid. */ int MassPeakShaper::computeGaussianPeakShape() { // qDebug(); m_trace.clear(); m_trace = computeGaussianPeakShape(m_peakCentroid.x, m_peakCentroid.y, m_config); return m_trace.size(); } /*! \brief Computes the Gaussian peak shape of the peak centroid. \list \li \a mz: the peak centroid's m/z value. \li \a intensity: the peak centroid's intensity value. \li \a config: the configuration driving the peak shaping process. \endlist Returns the pappso::Trace describing the peak shape. */ pappso::Trace MassPeakShaper::computeGaussianPeakShape(double mz, double intensity, const MassPeakShaperConfig &config) { pappso::Trace trace; // We will use the data in the configuration object. First check that // we can rely on it. This call sets all the proper values to the m_config's // member data after having validate each. MassPeakShaperConfig local_config = config; if(!local_config.resolve()) { qDebug() << "Failed to resolve the MassPeakShaperConfig."; return trace; } // qDebug() << "The peak shaper configuration:" << m_config.toString(); // First off, we need to tell what the height of the gaussian peak should // be. double a; // a = m_config.a(mz); // We actually set a to 1, because it is the intensity above that will // provide the height of the peak, see below where the height of the peak is // set to a * intensity, that is, intensity if a = 1. a = 1; // qDebug() << "a:" << a; bool ok = false; double c = local_config.c(&ok); if(!ok) { return trace; } double c_square = c * c; // qDebug() << "c:" << c << "c²:" << c_square; // Were are the left and right points of the shape ? We have to // determine that using the point count and mz step values. // Compute the mz step that will separate two consecutive points of the // shape. This mzStep is function of the number of points we want for a // given peak shape and the width of the peak shape left and right of the // centroid. double mz_step = local_config.getMzStep(); double left_point = mz - ((double)FWHM_PEAK_SPAN_FACTOR / 2 * local_config.getFwhm()); double right_point = mz + ((double)FWHM_PEAK_SPAN_FACTOR / 2 * local_config.getFwhm()); // qDebug() << "left m/z:" << left_point; // qDebug() << "right m/z:" << right_point; int iterations = (right_point - left_point) / mz_step; double x = left_point; for(int iter = 0; iter < iterations; ++iter) { double y = intensity * a * exp(-1 * (pow((x - mz), 2) / (2 * c_square))); trace.push_back(pappso::DataPoint(x, y)); x += mz_step; } //qDebug() << qSetRealNumberPrecision(15) << "For centroid" << mz //<< "first shape point:" << left_point //<< "with trace:" << trace.toString(); return trace; } /*! \brief Computes the Lorentzian peak shape of the peak centroid. */ int MassPeakShaper::computeLorentzianPeakShape() { // qDebug(); m_trace.clear(); m_trace = computeLorentzianPeakShape(m_peakCentroid.x, m_peakCentroid.y, m_config); return m_trace.size(); } /*! \brief Computes the Lorentzian peak shape of the peak centroid. \list \li \a mz: the peak centroid's m/z value. \li \a intensity: the peak centroid's intensity value. \li \a config: the configuration driving the peak shaping process. \endlist Returns the pappso::Trace describing the peak shape. */ pappso::Trace MassPeakShaper::computeLorentzianPeakShape(double mz, double intensity, const MassPeakShaperConfig &config) { pappso::Trace trace; // We will use the data in the configuration object. First check that // we can rely on it. This call sets all the proper values to the m_config's // member data after having validate each. MassPeakShaperConfig local_config = config; if(!local_config.resolve()) { qDebug() << "Failed to resolve the MassPeakShaperConfig."; return trace; } // qDebug() << "The peak shaper configuration:" << m_config.toString(); // First off, we need to tell what the height of the gaussian peak should // be. double a; // a = local_config.a(mz); // We actually set a to 1, because it is the intensity above that will // provide the height of the peak, see below where the heigh of the peak is // set to a * intensity, that is, intensity if a = 1. a = 1; // qDebug() << "a value:" << a; bool ok = false; // The calls below will trigger the computation of fwhm, if it is // equal to 0 because it was not set manually. double gamma = local_config.gamma(&ok); if(!ok) { return trace; } double gamma_square = gamma * gamma; // qDebug() << "gamma:" << gamma << "gamma²:" << gamma_square; // Were are the left and right points of the shape ? We have to // determine that using the m_points and m_increment values. // Compute the mz step that will separate two consecutive points of the // shape. This mzStep is function of the number of points we want for a // given peak shape and the width of the peak shape left and right of the // centroid. double mz_step = local_config.getMzStep(); double left_point = mz - ((double)FWHM_PEAK_SPAN_FACTOR / 2 * local_config.getFwhm()); double right_point = mz + ((double)FWHM_PEAK_SPAN_FACTOR / 2 * local_config.getFwhm()); // qDebug() << "left m/z:" << left_point; // qDebug() << "right m/z:" << right_point; int iterations = (right_point - left_point) / mz_step; double x = left_point; for(int iter = 0; iter < iterations; ++iter) { double y = intensity * a * (gamma_square / (pow((x - mz), 2) + gamma_square)); trace.push_back(pappso::DataPoint(x, y)); x += mz_step; } // qDebug().noquote() << m_trace.toString(); return trace; } /*! \brief Returns the intensity of a data point in the member peak shape pappso::Trace (m_trace). If the member pappso::Trace contains a data point having its x value equal to \a mz (comparison performed using tolerance \a precision_p), then return the intensity (y member) of that data point and set \a ok to true. Otherwise, return 0 and set \a ok to false. */ double MassPeakShaper::intensityAt(double mz, pappso::PrecisionPtr precision_p, bool &ok) { pappso::DataPoint data_point = m_trace.containsX(mz, precision_p); if(data_point.isValid()) { ok = true; return data_point.y; } ok = false; return 0; } /*! \brief Returns a string with the data in the member pappso::Trace. */ QString MassPeakShaper::shapetoString() { return m_trace.toString(); } /*! \brief Writes to \a file_name a string containing the member pappso::Trace data. Returns true if successful, false otherwise. \sa shapetoString() */ bool MassPeakShaper::shapeToFile(const QString &file_name) { return pappso::Utils::writeToFile(m_trace.toString(), file_name); } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/MassPeakShaperConfig.cpp000664 001750 001750 00000061313 14647465366 024705 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// StdLib includes #include /////////////////////// Qt includes #include /////////////////////// pappsomspp includes /////////////////////// Local includes #include "MassPeakShaperConfig.hpp" namespace MsXpS { namespace libXpertMass { /*! \variable MsXpS::libXpertMass::FWHM_PEAK_SPAN_FACTOR \brief The compounding factor to account for when shaping the sides of the peak. The shape of the peak needs to reflect a real mass peak. In particular, the shape of the peak has to return, \e{on each one of both sides}, to the baseline, thus mimicking the baseline for a m/z distance equivalent to FWHM_PEAK_SPAN_FACTOR times the FWHM m/z range from left to right. That means that the simulated peak region at the left hand side of the centroid value will span (FWHM_PEAK_SPAN_FACTOR/2) times the FWHM and the same one right. Empirically, a good FWHM_PEAK_SPAN_FACTOR is \c 4, meaning that the left half of the peak (that is centered on the centroid) will have a size corresponding to two times the FHWM, and the same for the right half of the peak. This is best exemplified as follows: \code Centroid value ^ | [ m/z - (2 * FWHM) <-|-> m/z + (2 * FWHM) ] <----- width of the whole peak shape -----> \endcode */ int FWHM_PEAK_SPAN_FACTOR = 4; /*! \class MsXpS::libXpertMass::MassPeakShaperConfig \inmodule libXpertMass \ingroup XpertMassMassCalculations \inheaderfile MassPeakShaperConfig.hpp \brief The MassPeakShaperConfig class provides the features required to configure the shaping of a mass peak centroid into a Gaussian or a Lorentzian fully profiled shape. \e{Shaping a peak} means creating a shape around a centroid m/z value such that the m/z peak is represented like it appears in a mass spectrum displayed in "profile" mode. \details{The peak shaping process} The model used to craft the "profiled" mass peak can be of two types: \list \li \l{https://en.wikipedia.org/wiki/Gaussian_function}{Gaussian} \li \l{https://en.wikipedia.org/wiki/Cauchy_distribution}{Lorentzian} \endlist The starting parameters are the (m/z,intensity) values of the peak centroid and the full width at half maximum (FWHM) value that will govern the width of the shape at 50% height of the peak. The width of the peak will be an inverse representation of the resolving power of the instrument: the instrument with the greatest resolving power will yield mass peaks with the smallest width. Conversely, a very old instrument will produce mass spectra where the peaks will be broad. There are thus two possiblities for configuring the peak shape: \list \li Provide the FWHM value itself. \li Provide the resolving power of the instrument that the peak simulation should emulate. \endlist \enddetails */ /*! \enum MsXpS::libXpertMass::MassPeakShapeType This enum specifies the type of mass peak shape to be computed around a m/z peak centroid. \value GAUSSIAN The peak will be shaped using the Gaussian model. \value LORENTZIAN The peak will be shaped using the Lorentizan model. \omitvalue NOT_SET. */ /*! \enum MsXpS::libXpertMass::MassPeakWidthLogic This enum specifies the logic used to computed the width of the mass peak shape around a m/z peak centroid. \value FWHM The full width at half maximum is directly used to compute the peak shape width. \value RESOLUTION The resolving power of the instrument is the starting point to compute the FWHM. \omitvalue NOT_SET. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_resolution \brief Resolving power of the instrument. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_fwhm \brief Full width at half maximum of the shaped peak. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_massPeakWidthLogic \brief Describes the logic used to compute the peak shape width. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_referencePeakMz \brief The m/z value to be used when computing the FWHM value starting from a mass spectrometer resolving power. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_pointCount \brief The count of points used to shape the peak. Typically between 150 and 300. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_withBins \brief Tells if bins are requested for the computation. There is no need in the context of the calculations performed by the MassPeakShaperConfig class to know if bins are required or not. This variable is useful when multiple peak shapes are combined into a more complex trace or mass spectrum, typically when simulating an isotopic cluster. In this case, each cluster peak centroid is shaped in sequence and then they are all merged into a single trace (no bins) or mass spectrum (binning). The width of the bins is defined using this m_withBins variable. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_binSizeDivisor \brief Empirical division factor to apply to \l m_binSize to reduce the size of the bins after the bin size calculation. Empirically, a good value is \c 6. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_binSize \brief The size of the m/z bins. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_isBinSizeFixed \brief Tells if the size of the m/z bins is fixed. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_mzStep \brief The m/z distance (a Delta) between to consecutive data points in the shaped mass peak. */ /*! \variable MsXpS::libXpertMass::MassPeakShaperConfig::m_massPeakShapeType \brief The requested shape of the mass peak. */ /*! \brief Constructs a MassPeakShaperConfig instance. */ MassPeakShaperConfig::MassPeakShaperConfig() { reset(); } /*! \brief Constructs a MassPeakShaperConfig instance as a copy of \a other. */ MassPeakShaperConfig::MassPeakShaperConfig(const MassPeakShaperConfig &other) : m_resolution(other.m_resolution), m_fwhm(other.m_fwhm), m_massPeakWidthLogic(other.m_massPeakWidthLogic), m_referencePeakMz(other.m_referencePeakMz), m_pointCount(other.m_pointCount), m_withBins(other.m_withBins), m_binSizeDivisor(other.m_binSizeDivisor), m_binSize(other.m_binSize), m_isBinSizeFixed(other.m_isBinSizeFixed), m_mzStep(other.m_mzStep), m_massPeakShapeType(other.m_massPeakShapeType) { } /*! \brief Destructs this MassPeakShaperConfig instance. */ MassPeakShaperConfig::~MassPeakShaperConfig() { } /*! \brief Assigns \a other to this MassPeakShaperConfig instance. */ void MassPeakShaperConfig::operator=(const MassPeakShaperConfig &other) { m_resolution = other.m_resolution; m_fwhm = other.m_fwhm; m_massPeakWidthLogic = other.m_massPeakWidthLogic; m_referencePeakMz = other.m_referencePeakMz; m_pointCount = other.m_pointCount; m_withBins = other.m_withBins; m_binSizeDivisor = other.m_binSizeDivisor; m_binSize = other.m_binSize; m_isBinSizeFixed = other.m_isBinSizeFixed; m_mzStep = other.m_mzStep; m_massPeakShapeType = other.m_massPeakShapeType; } /*! \brief Sets the \a resolution (the resolving power of the instrument). */ void MassPeakShaperConfig::setResolution(int resolution) { m_resolution = resolution; } /*! \brief Returns the resolution (the resolving power of the instrument). */ double MassPeakShaperConfig::getResolution() const { return m_resolution; } /*! \brief Calculates the resolution (resolving power of the instrument). The calculation involves using the FWHM (m_fwhm) member datum and the m/z value that is considered to be the reference for the calculation (m_referencePeakMz). If any of these two values is not set, then this function returns 0 and sets \a ok to false. The calculation is that simple: \code m_resolution = m_referencePeakMz / m_fwhm; \endcode Because we used the FWHM to compute the resolution and set the calculated value to the \l m_resolution member datum, we keep a record of this by: \code m_massPeakWidthLogic = MassPeakWidthLogic::FWHM; \endcode Returns the resolving power as set in \l m_resolution and sets \a ok to true. */ int MassPeakShaperConfig::resolution(bool *ok) { if(ok == nullptr) qFatal("The pointer cannot be nullptr."); // If we want to compute the resolution that means that we have to have // m_fwhm. if(!m_fwhm) { *ok = false; return 0; } if(!m_referencePeakMz) { *ok = false; return 0; } m_resolution = m_referencePeakMz / m_fwhm; *ok = true; // We used the FWHM to compute the resolving power. m_massPeakWidthLogic = MassPeakWidthLogic::FWHM; return m_resolution; } /*! \brief Sets the \a fwhm (full width at half maximum). */ void MassPeakShaperConfig::setFwhm(double fwhm) { m_fwhm = fwhm; } /*! \brief Gets the fwhm (full width at half maximum). */ double MassPeakShaperConfig::getFwhm() const { return m_fwhm; } /*! \brief Calculates the fwhm (full width at half maximum). The calculation involves using the instrument's resolving power (m_resolution) member datum and the m/z value that is considered to be the reference for the calculation (m_referencePeakMz). If any of these two values is not set, then this function returns 0 and sets \a ok to false. The calculation is that simple: \code m_fwhm = m_referencePeakMz / m_resolution; \endcode Because we used the resolution to compute the FWHM and set the calculated value to the \l m_fwhm member datum, we keep a record of this by: \code m_massPeakWidthLogic = MassPeakWidthLogic::RESOLUTION; \endcode Returns the FWHM value as set in \l m_fwhm and sets \a ok to true. */ double MassPeakShaperConfig::fwhm(bool *ok) { if(ok == nullptr) qFatal("The pointer cannot be nullptr."); // Or we need to compute it using the mz passed as parameter and the // resolution. if(!m_resolution) { *ok = false; return 0; } if(!m_referencePeakMz) { // qDebug() << "There is no reference peak centroid!"; *ok = false; return 0; } m_fwhm = m_referencePeakMz / m_resolution; *ok = true; // We used the resolving power to compute the FWHM. m_massPeakWidthLogic = MassPeakWidthLogic::RESOLUTION; return m_fwhm; } /*! \brief Returns the half of the FWMH (m_fwhm) for use in the Lorentzian shape calculation. Uses the \l fwhm() function to compute the FWHM value. If the FWHM calculation fails, returns 0 and \a ok is set to false, otherwise returns half of FWHM and \a ok is set to true. \sa fwhm() */ double MassPeakShaperConfig::halfFwhm(bool *ok) { double fwhm_value = fwhm(ok); if(!*ok) return 0; return (fwhm_value / 2); } /*! \brief Sets the reference m/z value required for calculations to \a mz. */ void MassPeakShaperConfig::setReferencePeakMz(double mz) { m_referencePeakMz = mz; } /*! \brief Gets the reference m/z value required for calculations. */ double MassPeakShaperConfig::getReferencePeakMz() const { return m_referencePeakMz; } /*! \brief Sets the requirement to prepare m/z bins at a later computing stage to \a with_bins. \sa IsotopicClusterShaper::run() */ void MassPeakShaperConfig::setWithBins(bool with_bins) { m_withBins = with_bins; } /*! \brief Returns the requirement to prepare m/z bins at a later computing stage. \sa IsotopicClusterShaper::run() */ bool MassPeakShaperConfig::withBins() const { return m_withBins; } /*! \brief Sets the \a bin_size. */ void MassPeakShaperConfig::setBinSize(double bin_size) { m_binSize = bin_size; } /*! \brief Computes the bin size. If there is no requirement for bins (m_withBins is false), returns 0 and set \a ok to true. In order to compute the bin size we need that at least the FWHM (m_fwhm) or the resolving power (m_resolution) be set. If none of these values are set, this function returns 0 and sets \a ok to false. If the bin size was fixed (m_isBinSizeFixed is true), then the bin size (m_binSize) must be set. If that is not the case, the function returns 0 and sets \a ok to false. If the FWHM value (m_fwhm) is set, then it is used right away. Otherwise, the FWHM is computed starting from the resolving power (m_resolution). If that computation fails, the function returns 0 and sets \a ok to false. Starting from the FWHM value (m_fwhm), the bin size calculation requires that the m_binSizeDivisor be > 1. The bin size is thus computed as the ratio FWHM / m_binSizeDivisor, and the obtained value is set to m_binSize. Empirically, a value of 6 for m_binSizeDivisor, yields bin sizes that allow combining correctly multiple peak shapes into a mass spectrum. Essentially, that means that 6 individual m/z bins are required to combine nicely all the peaks shapes obtained for all the peak centroids in a given isotopic cluster, for example. At this point, the function returns the bin size and sets \a ok to true. \sa setBinSizeDivisor() */ double MassPeakShaperConfig::binSize(bool *ok) { if(ok == nullptr) qFatal("The pointer cannot be nullptr."); // If the bin size was set before, then, just return it. // if(m_binSize) //{ //*ok = true; // return m_binSize; //} if(!m_withBins) { // qDebug() << "Bins are not requested, just return 0 and set true."; *ok = true; return 0; } // In order to compute the bin Size, we need the FWHM and the number of // points. if(!m_resolution && !m_fwhm) { // qDebug() << "That's an error when neither resolution nor FWHM is set."; *ok = false; return 0; } if(m_fwhm) { // FWHM is fine, we can use that immediately. // qDebug() << "FWHM:" << m_fwhm; if(!m_pointCount) { // qDebug() << "That's an error that the point count is 0."; *ok = false; return 0; } } else { // We have to work with the resolution. // qDebug() << "Resolution:" << m_resolution; fwhm(ok); if(!*ok) { // qDebug() //<< "Could not compute FWHM on the basis of the resolution."; if(!m_pointCount) { // qDebug() << "That's an error that the point count is 0."; *ok = false; return 0; } } } if(m_isBinSizeFixed) { // The bin size has to be set and must not be changed, as the user has // set it manually. if(!m_binSize) { // qDebug() << "The bin size should be set manually but is not set."; *ok = false; return 0; } } else { m_binSize = m_fwhm / m_binSizeDivisor; // qDebug() << "The bin size was computed:" << m_binSize; } *ok = true; return m_binSize; } /*! \brief Returns (no computation) the bin size. */ double MassPeakShaperConfig::getBinSize() const { return m_binSize; } /*! \brief Sets the requirement that the bin size be set and fixed (not modifiable by computations) to \a is_fixed. When \a is_fixed is true, the \l binSize() function returns the bin size without trying to compute it. \sa binSize() */ void MassPeakShaperConfig::setBinSizeFixed(bool is_fixed) { m_isBinSizeFixed = is_fixed; } /* \brief Gets the requirement that the bin size be set and fixed (not modifiable by computations). */ bool MassPeakShaperConfig::getBinSizeFixed() { return m_isBinSizeFixed; } /*! \brief Sets the bin size computation dividing \a factor. When the bin size is computed (m_isBinSizeFixed is false), that value is determined by dividing the FWHM value by this \a factor (m_binSizeDivisor). Empirically, a value of 6 for m_binSizeDivisor, yields bin sizes that allow combining correctly multiple peak shapes into a mass spectrum. Essentially, that means that 6 individual m/z bins are required to combine nicely all the peaks shapes obtained for all the peak centroids in a given isotopic cluster, for example. */ void MassPeakShaperConfig::setBinSizeDivisor(int factor) { if(std::abs(factor) < 1) qFatal("Programming error."); m_binSizeDivisor = std::abs(factor); } /*! \brief Returns the bin size computation dividing factor. \sa setBinSizeDivisor() */ int MassPeakShaperConfig::getBinSizeDivisor() const { return m_binSizeDivisor; } /*! \brief Sets the \a point_count to be used to shape the mass peak around the m/z centroid value. Typically, a value between 150 and 200 points is fine to nicely shape a m/z peak centroid. */ void MassPeakShaperConfig::setPointCount(int point_count) { m_pointCount = point_count; } /*! \brief Gets the count of points to be used to shape the mass peak around the m/z centroid value. */ int MassPeakShaperConfig::getPointCount() const { return m_pointCount; } /*! \brief Sets the type of mass peak shape to \a mass_peak_shape_type. \sa MsXpS::libXpertMass::MassPeakShapeType */ void MassPeakShaperConfig::setMassPeakShapeType( MassPeakShapeType mass_peak_shape_type) { m_massPeakShapeType = mass_peak_shape_type; } /*! \brief Gets the type of mass peak shape. \sa MsXpS::libXpertMass::MassPeakShapeType */ MassPeakShapeType MassPeakShaperConfig::getMassPeakShapeType() const { return m_massPeakShapeType; } /*! \brief Sets the mass peak width calculation \a logic. The full mass peak width at half peak maximum (FWHM) can either be set manually or be computed from a reference m/z value and the instrument's resolving power. \sa MsXpS::libXpertMass::MassPeakWidthLogic */ void MassPeakShaperConfig::setMassPeakWidthLogic(MassPeakWidthLogic logic) { m_massPeakWidthLogic = logic; } /*! \brief Gets the mass peak width calculation logic. \sa MsXpS::libXpertMass::MassPeakWidthLogic */ MassPeakWidthLogic MassPeakShaperConfig::getMassPeakWidthLogic() const { return m_massPeakWidthLogic; } double MassPeakShaperConfig::c(bool *ok) { // c in the Gaussian curve is related to the fwhm value: fwhm(ok); if(!*ok) { return 0; } double c = m_fwhm / (2 * sqrt(2 * log(2))); // qDebug() << "c:" << c; *ok = true; return c; } double MassPeakShaperConfig::a(bool *ok) { // double pi = 3.1415926535897932384626433832795029; double c_value = c(ok); if(!*ok) { return 0; } double a = (1 / (c_value * sqrt(2 * M_PI))); // qDebug() << "a:" << a; *ok = true; return a; } double MassPeakShaperConfig::gamma(bool *ok) { fwhm(ok); if(!*ok) { return 0; } double gamma = m_fwhm / 2; // qDebug() << "gamma:" << gamma; *ok = true; return gamma; } /*! \brief Sets the \a mz_step. The m/z step value is the distance between two consecutive points in the peak shape. */ void MassPeakShaperConfig::setMzStep(double mz_step) { m_mzStep = mz_step; } /*! \brief Gets the m/z step. The m/z step value is the distance between two consecutive points in the peak shape. */ double MassPeakShaperConfig::getMzStep() const { return m_mzStep; } /*! \brief Calculates the m/z step. The m/z step value is the distance between two consecutive points in the peak shape. To compute that value, the full m/z width of the calculated shape (not only the width at half maximum, FWHM) is divided by the total number of data points that make the final shape of the m/z peak. The first variable is FWHM_PEAK_SPAN_FACTOR * m_fwhm, and the second variable is m_pointCount. The calculation is thus: \code m_mzStep = (FWHM_PEAK_SPAN_FACTOR * m_fwhm) / m_pointCount; \endcode The first step is to compute the FWHM and then to check the value of m_pointCount (that cannot be 0). If any of these fails, the function returns 0 and set \a ok to false, otherwise the computed value is set to m_mzStep and returned, while \a ok is set to true. \sa FWHM_PEAK_SPAN_FACTOR */ double MassPeakShaperConfig::mzStep(bool *ok) { // But what is the mz step ? // // We want the shape to be able to go down to baseline. Thus we want that // the shape to have a "basis" (or, better, a "ground") corresponding to // twice the FWHM on the left of the centroid and to twice the FWHM on the // right (that makes in total FWHM_PEAK_SPAN_FACTOR * FWHM, that is, // FWHM_PEAK_SPAN_FACTOR = 4). fwhm(ok); if(!*ok) { return 0; } if(!m_pointCount) { *ok = false; return 0; } m_mzStep = (FWHM_PEAK_SPAN_FACTOR * m_fwhm) / m_pointCount; return m_mzStep; } /*! \brief Resets this MassPeakShaperConfig instance to default values like at construction time. */ void MassPeakShaperConfig::reset() { // Values to start over. m_resolution = 0; m_fwhm = 0; m_referencePeakMz = 0; m_pointCount = 0; m_withBins = false; m_binSizeDivisor = 6; m_binSize = 0; m_isBinSizeFixed = false; m_mzStep = 0; m_massPeakShapeType = MassPeakShapeType::NOT_SET; } /*! \brief Calculates the various parameters of the peak shape using member data. This function first checks that all the required data are set. Then this functions computes the FWHM value required to shape the peak centroid. Once the basic data have been computed or set, the parameters of the shape are computed: c, a, gamma. Returns true if the computation was successful, false otherwise. */ bool MassPeakShaperConfig::resolve() { // We need to try to set all the relevant parameters by calculation. bool ok = false; // These are the essential parameters: if(!m_referencePeakMz) return false; if(!m_fwhm && !m_resolution) return false; if(!m_pointCount) return false; // At this point we should be able to compute the relevant data. // The FWHM is the *leading* value for the determination of the peak shape's // width at half maximum. If that FWHM value is 0, then we resort to the // resolution. Both R and FWHM cannot be 0! if(m_fwhm) { // If we have FWHM, immediately try to compute the resolution as we'll // need it later. FWHM takes precedence over resolution! resolution(&ok); if(!ok) return false; } else { // We should be able to compute FWHM by resorting to the resolution. fwhm(&ok); if(!ok) return false; } // Now check if we have and can compute the bins. // But we do this only if the user has not stated that the bin size has to // be set manually. // qDebug() << "In the resolve, check the bin size"; binSize(&ok); if(!ok) return false; mzStep(&ok); if(!ok) return false; // Now the other parameters for the shape. c(&ok); if(!ok) return false; a(&ok); if(!ok) return false; gamma(&ok); if(!ok) return false; return true; } /*! \brief Returns a string representing this MassPeakShaperConfig instance. No verification whatsoever is performed. */ QString MassPeakShaperConfig::toString() { QString string; bool ok = resolve(); if(!ok) return QString(); QString peak_shape_text; if(m_massPeakShapeType == MassPeakShapeType::GAUSSIAN) peak_shape_text = "Gaussian"; if(m_massPeakShapeType == MassPeakShapeType::LORENTZIAN) peak_shape_text = "Lorentzian"; QString with_bins_text; if(m_withBins) with_bins_text += QString("With bins of size: %1 m/z.\n").arg(m_binSize, 0, 'f', 10); else with_bins_text = "Without bins.\n"; string = QString( "%1 peak shaping:\n" "Configuration for reference m/z value: %2\n" "Resolution: %3\n" "FWHM: %4\n" "%5\n" "Number of points to shape the peak: %6\n" "c: %7\n" "c^2: %8\n" "mz step: %9\n\n") .arg(peak_shape_text) .arg(m_referencePeakMz, 0, 'f', 5) .arg(m_resolution) .arg(m_fwhm, 0, 'f', 5) .arg(with_bins_text) .arg(m_pointCount) .arg(c(&ok), 0, 'f', 5) .arg(c(&ok) * c(&ok), 0, 'f', 5) .arg(m_mzStep, 0, 'f', 5); return string; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Modif.cpp000664 001750 001750 00000062731 14647465366 021753 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "globals.hpp" #include "Modif.hpp" #include "PolChemDef.hpp" #include "Monomer.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::Modif \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile Modif.hpp \brief The Modif class provides abstractions to work with chemical modifications. The Modif class provides a chemical modification that can be set to any monomer in a polymer sequence or to any one of the polymer sequence ends.In the protein world, chemical modifications of proteins that occur in the living cell are called post-translational modifications.This class aims at modelling, among others, such modifications. The chemical reaction described by the Modif class is encoded as an actionformula (see \l{Formula}). */ /*! \variable int MsXpS::libXpertMass::Modif::m_targets \brief String that holds a list of all the target monomers of this modification. If there are more than one target, the targets (monomer codes) must be separated by ';' characters. If any monomer in the polymer chemistry definition might be modified by this Modif object, then, the "*" string can be used to indicate so. */ /*! \variable int MsXpS::libXpertMass::Modif::m_maxCount \brief Value indicating the maximum number of times this modification can be set to a target entity (monomer or polymer). */ /*! \brief Constructs a modification. A Modif instance cannot be of any use if it is not associated logically to a polymer chemistry definition (\a pol_chem_def_csp). The formula is defined by its \a name and its \a formula. The \a formula might be a simple formula ("O", for an oxydation) or an actionformula, if that is required to best characterize the modification ("-H20+CH3COOH", for example, for an acetylation). The Ponderable base class' mono and avg masses are initialized to (0,0). The targets (m_targets) is set to "all", that is \c "*" and the maximum count of this modification that can set set to a target is set to \c 1 by default. */ Modif::Modif(PolChemDefCstSPtr pol_chem_def_csp, QString name, QString formula) : PolChemDefEntity(pol_chem_def_csp, name), Formula(formula), Ponderable(0, 0), m_targets("*"), m_maxCount(1) { // By default a modification will target any monomer. By default // the modification will be settable only once at maximum to a // given monomer entity. } /*! \brief Constructs a Modif object as a copy of \a other. */ Modif::Modif(const Modif &other) : PolChemDefEntity(other), Formula(other), Ponderable(other), PropListHolder(other), m_targets(other.m_targets), m_maxCount(other.m_maxCount) { } /*! \brief Destructs this Modif. */ Modif::~Modif() { } /*! \brief Assigns \a other to this modification. Returns a reference to this modification. */ Modif & Modif::operator=(const Modif &other) { if(&other == this) return *this; PolChemDefEntity::operator=(other); Formula::operator=(other); Ponderable::operator=(other); PropListHolder::operator=(other); return *this; } /*! Resets this modification to an empty object. */ void Modif::reset() { m_name = "NOT_SET"; m_formula.clear(); m_plusFormula.clear(); m_minusFormula.clear(); // When m_targets is empty, the modification cannot modify // anything. Useful for testing purposes. m_targets.clear(); m_maxCount = 1; m_mono = 0; m_avg = 0; while(!m_propList.isEmpty()) delete m_propList.takeFirst(); } /*! \brief Sets the \a targets for this modification. Setting \e{targets} means specifying which target might be modified using this modification. For example, for \c Phosphorylation, in protein chemistry, one would define targets as \c Serine, \c Threonine, \c Tyrosine (there are other targets, but very rarely encountered). Multiple targets are separated using ';'. */ QString & Modif::setTargets(QString targets) { if(targets.isEmpty()) { m_targets.clear(); return m_targets; } // qDebug() << __FILE__ << __LINE__ // << "Before unspacification" << targets; // Remove any space from 'targets'. m_targets = unspacifyString(targets); // qDebug() << __FILE__ << __LINE__ // << "After unspacification" << m_targets; // Validate and simplify: true is by default the bool param. if(!validateTargets()) m_targets = QString(); return m_targets; } /*! \brief Returns the tagets of this modification. */ QString Modif::targets() const { return m_targets; } /*! \brief Returns the tagets of this modification in the form a string list. The member m_targets string is split using ';' as a delimitor, the obtained list of strings is set to \a string_list and the size of the list is returned. */ int Modif::targets(QStringList &string_list) const { // Return a string list after splitting at ';'. string_list.clear(); string_list = m_targets.split(';', Qt::SkipEmptyParts, Qt::CaseSensitive); return string_list.size(); } /*! \brief Returns true if monomer \a code is found among the targets of this modifications, false otherwise. */ bool Modif::hasMonomerTarget(QString code) const { if(m_targets == "*") return true; if(m_targets == "!") return false; QString delimitedCode = QString(";%1;").arg(code); // The m_targets string is in the form ";code;code;code;". return m_targets.contains(delimitedCode, Qt::CaseSensitive); } /*! \brief Validates the target of this modification. The target list is split using ';' as a delimiter. If '*' is found and \a simplify is true, the function returns true immediately (as all the monomers in the polymer chemistry definition might be a target of this modification). If at least one target is found, then each target is a monomer code and that code is looked for in the member polymer chemistry definition list of monomers. If the code is found, that code is added to a temporary string. If the code is not found, m_targets is set to that temporary string (only having in it monomer codes found in the polymer chemistry definition's list of monomers) and the function returns false. Returns true if the targets of this modification all validated successfully. */ bool Modif::validateTargets(bool simplify) { // A targets string cannot contain both a '*' and a '!' // character. We check that immediately. if(m_targets.contains('*') && m_targets.contains('!')) return false; QList monomerList = mcsp_polChemDef->monomerList(); QString result; // If the m_targets is empty, this is an error, because we cannot // know what's to be done with the modification. if(m_targets.isEmpty()) { return false; } // A targets string looks like "Ser ; Thr ; Tyr". QStringList stringList = m_targets.split(';', Qt::SkipEmptyParts, Qt::CaseSensitive); for(int iter = 0; iter < stringList.size(); ++iter) { QString currentString = stringList.at(iter); // There are two character that might be encountered: '*' is the // equivalent of "all the monomers in the definition"; '!' is // equivalent to "none of the monomers in the definition". But // it is not possible that both * and ! be present in the same // targets string. if(currentString == "*" || currentString == "!") { // Simplification asked: if '*' is found then it can be // there alone. Same for '!'. '*' means that any monomer in // the definition might be modified with this modification, // '!' means that none of the monomers might be modified. if(simplify) { m_targets = currentString; return true; } else { result.append(QString("%1;").arg(currentString)); } continue; } // At this point, we have something to check as a monomer code: if(Monomer::isCodeInList(currentString, monomerList) == -1) { qDebug() << "Monomer code is not known:" << currentString; m_targets = result; return false; } else { // Want the string to be ;code;code;code;(notice the first // and last ';'), so that we can later ask easily if ;Lys; // is found in the targets string, without risking to also // match ;Ly;. if(!result.size()) result.append(QString(";%1;").arg(currentString)); else result.append(QString("%1;").arg(currentString)); } } if(result.isEmpty()) return false; m_targets = result; return true; } /*! \brief Set the maximum count of times that this modification might be set to a target to \a value. */ void Modif::setMaxCount(int value) { Q_ASSERT(value > 0); m_maxCount = value; } /*! \brief Returns the maximum count of times that this modification might be set to a target. */ int Modif::maxCount() { return m_maxCount; } /* \brief Returns the formula describing this modification. */ QString Modif::formula() const { return Formula::toString(); } /*! \brief Returns true if this and the \a other modifications are identical, false otherwise. */ bool Modif::operator==(const Modif &other) { int tests = 0; tests += (m_targets == other.m_targets); tests += PolChemDefEntity::operator==(other); tests += Formula::operator==(other); tests += Ponderable::operator==(other); if(tests < 4) return false; return true; } /*! \brief Returns true if this and the \a other modifications differ, false otherwise. */ bool Modif::operator!=(const Modif &other) { int tests = 0; tests += (m_targets != other.m_targets); tests += PolChemDefEntity::operator!=(other); tests += Formula::operator!=(other); tests += Ponderable::operator!=(other); if(tests > 0) return true; return false; } /*! \brief Returns true if this modification's name is found in the member polymer chemistry definition's list of modifications, false otherwise */ int Modif::isNameKnown() { const QList &refList = mcsp_polChemDef->modifList(); if(m_name.isEmpty()) return -1; for(int iter = 0; iter < refList.size(); ++iter) { if(refList.at(iter)->m_name == m_name) return iter; } return -1; } /*! \brief Searches for a modification by name \a name in the reference list \a refList. If a modification is found, and \a other is non-nullptr, the found modif is copied to \a other. Returns true if the modification was found, false otherwise. */ int Modif::isNameInList(const QString &name, const QList &refList, Modif *other) { Modif *iter_modif_p = 0; if(name.isEmpty()) return -1; // qDebug() << "Looking for modif by name" << name; for(int iter = 0; iter < refList.size(); ++iter) { iter_modif_p = refList.at(iter); Q_ASSERT(iter_modif_p); if(iter_modif_p->m_name == name) { Modif test_modif(*iter_modif_p); if(other) { *other = *iter_modif_p; } return iter; } } return -1; } /*! \brief Validates this modification. The modification validates successfully if: \list \li The member polymer chemistry definition must be set \li The name is not empty \li The formula validates successfully \li The targets validate successfully \li The m_maxCount member is greater than 0 \endlist Returns true if the modification validates successfully, false otherwise. */ bool Modif::validate() { if(mcsp_polChemDef == nullptr) return false; if(m_name.isEmpty()) return false; Formula formula(m_formula); IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); if(!formula.validate(isotopic_data_csp)) { qDebug() << "The formula failed to validate."; return false; } if(!validateTargets()) { qDebug() << "The targets failed to validate."; return false; } if(m_maxCount <= 0) return false; return true; } /*! \brief Calculates the net masses of this modification. The masses of the modification are the masses (monoisotopic and average) that are added to the target as a result of that target being modified with this modification. The calculated masses are set to the Ponderable base class' m_mono and m_avg members. Returns true if the mass calculations were successful, false otherwise. \sa Formula::accountMasses() */ bool Modif::calculateMasses() { IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); m_mono = 0; m_avg = 0; qDebug() << "Right before accounting modif formula masses for formula:" << m_formula; if(!Formula::accountMasses(isotopic_data_csp, &m_mono, &m_avg)) { qDebug() << "Failed accounting masses for modif:" << m_name << "and formula:" << m_formula; return false; } qDebug() << "Right after accounting modif formula masses for formula:" << m_formula << m_mono << "-" << m_avg; return true; } /*! \brief Adds to \a mono and \a avg the corresponding mass of this modification. The m_mono and m_avg masses are added to the arguments. The masses are compounded by factor \a times before the addition. Returns true. */ bool Modif::accountMasses(double *mono, double *avg, int times) { if(mono) *mono += m_mono * times; if(avg) *avg += m_avg * times; return true; } /*! \brief Parses the modification XML \a element specifically for \a version. Parses the modif \c mdf XML element passed as argument and for each encountered data will set the data to this modif (this is called XML rendering).The parsing is delegated to a function that is specific for \a version of the polymer chemistry definition. The \c mdf XML element is found in the polymer chemistry definition and has the following form: \code Acetylation C2H2O1 ;K; 1 AmidationAsp H1N1-O1 ;D; 1 \endcode After setting all the data, this modification calculates it masses and validates itself. If any of these steps fails, the error is reported by returning false. Returns true if parsing was successful, false otherwise. */ bool Modif::renderXmlMdfElement(const QDomElement &element, int version) { if(element.tagName() != "mdf") return false; if(version == 1) { // no-op version = 1; } QDomElement child; if(element.tagName() != "mdf") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); if(m_name.isEmpty()) return false; child = child.nextSiblingElement("formula"); if(child.isNull()) return false; if(!Formula::renderXmlFormulaElement(child)) return false; if(!calculateMasses()) { qDebug() << "Failed accounting masses for modif: " << m_name << "with formula:" << m_formula; return false; } child = child.nextSiblingElement("targets"); if(child.isNull()) return false; m_targets = child.text(); child = child.nextSiblingElement("maxcount"); if(child.isNull()) return false; bool ok = false; m_maxCount = child.text().toInt(&ok); if(!m_maxCount && !ok) return false; // The validation will take care of checking that the // element did have correct text inside and that be // correct also. if(!validate()) return false; return true; } /*! \brief Formats this modification's data as a string suitable to be used as a \c mdf XML element in the polymer chemistry definition. The typical modification element that is generated in this function looks like this: \code Acetylation C2H2O1 ;K; 1 AmidationAsp H1N1-O1 ;D; 1 \endcode The formatting of the XML element takes into account \a offset and \a indent by prepending the string with \a offset * \a indent character substring. \a indent defaults to two spaces. Returns a dynamically allocated string that needs to be freed after use. */ QString * Modif::formatXmlMdfElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } /* We are willing to create an node that should look like this: * Phosphorylation -H+H2PO3 S;T;Y 1 * */ *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); *string += QString("%1%2\n").arg(lead).arg(m_formula); *string += QString("%1%2\n").arg(lead).arg(m_targets); *string += QString("%1%2\n").arg(lead).arg(m_maxCount); // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } /*! \brief Outputs a string representing this modification using qDebug(). */ void Modif::debugPutStdErr() { qDebug() << m_name << m_formula << m_targets; } //////////////////////// ModifProp //////////////////////// //////////////////////// ModifProp //////////////////////// /*! \class MsXpS::libXpertMass::ModifProp \inmodule libXpertMass \ingroup ThePropSystem \brief The ModifProp class provides a Prop instance of which the member data points to a dynamically allocated \l Modif instance. The member datum \l m_name is set to "MODIF". */ /*! \brief Constructs a ModifProp instance using \a modif. The \a modif pointer is set to the \l mpa_data member. */ ModifProp::ModifProp(Modif *modif) : Prop("MODIF") { mpa_data = static_cast(modif); } /*! \brief Constructs a ModifProp instance as a copy of \a other. */ ModifProp::ModifProp(const ModifProp &other) : Prop(other) { if(other.mpa_data != nullptr) { Modif *modif = static_cast(other.mpa_data); mpa_data = static_cast(new Modif(*modif)); } else mpa_data = nullptr; } /*! \brief Destructs this ModifProp instance. The deletion of the data are delegated to \l deleteData(). */ ModifProp::~ModifProp() { deleteData(); } /*! \brief Deletes the member data. */ void ModifProp::deleteData() { if(mpa_data != nullptr) { delete static_cast(mpa_data); mpa_data = nullptr; } } /*! \brief Assigns \a other to this ModifProp instance. The member data are first deleted and then set to a copy of those in \a other. */ ModifProp & ModifProp::operator=(const ModifProp &other) { if(&other == this) return *this; Prop::operator=(other); if(mpa_data != nullptr) deleteData(); if(other.mpa_data) { Modif *modif = static_cast(other.mpa_data); mpa_data = static_cast(new Modif(*modif)); } else mpa_data = nullptr; return *this; } /*! \brief Duplicates this ModifProp instance and returns its pointer. */ ModifProp * ModifProp::cloneOut() const { ModifProp *new_p = new ModifProp(*this); return new_p; } /*! \brief Parses the property XML \a element using a \a{version}ed function. The element looks like this: \code MODIF Phosphorylation // That is the Modif name -H+H2PO3 // -------------------- formula S;T;Y // -------------------- targets \endcode As the data in \a element are parsed they are set to the member data, thus essentially initializing the Modif object pointed to by the member data. Returns true if parsing was successful, false otherwise. */ bool ModifProp::renderXmlElement(const QDomElement &element, int version) { if(element.tagName() != "prop") return false; if(version == 1) { // no-op version = 1; } QDomElement child; // The element looks like this: // // // MODIF // Phosphorylation // That is the Modif name // -H+H2PO3 // -------------------- formula // S;T;Y // -------------------- targets // if(element.tagName() != "prop") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); if(m_name != "MODIF") return false; // And now we have to manage the prop's data elements. When this // ModifProp object was allocated, one Modif was allocated and // set as the data of the Prop. Get to it. Modif *modif = static_cast(mpa_data); // Next sibling is the modif's name data. child = child.nextSiblingElement("data"); if(child.isNull()) return false; modif->setName(child.text()); // Next sibling is the modif's formula data. child = child.nextSiblingElement("data"); if(child.isNull()) return false; modif->setFormula(child.text()); if(!modif->calculateMasses()) { qDebug() << __FILE__ << __LINE__ << "Failed to calculate masses for modification:" << modif->name(); return false; } // Next sibling is the modif's targets data. child = child.nextSiblingElement("data"); if(child.isNull()) return false; modif->setTargets(child.text()); // The validation will take care of checking that the // element did have correct text inside. if(!modif->validate()) return false; return true; } /*! \brief Formats a string suitable to use as an XML element. Formats a string suitable to be used as an XML element in an XML file (a polymer sequence file, for example). Typical ModifProp elements that might be generated in this function look like this: \code MODIF Phosphorylation // That is the Modif name -H+H2PO3 // -------------------- formula S;T;Y // -------------------- targets \endcode \a offset times the \a indent string must be used as a lead in the formatting of elements. Returns a dynamically allocated string that needs to be freed after use. */ QString * ModifProp::formatXmlElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } // The property has its data member that points to a Modif. Thus // the formatting of the element should produce something like this: // // // MODIF // Phosphorylation // That is the Modif name // -H+H2PO3 // -------------------- formula // S;T;Y // -------------------- targets // *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); *string += QString("%1%2\n") .arg(lead) .arg(static_cast(mpa_data)->name()); *string += QString("%1%2\n") .arg(lead) .arg(static_cast(mpa_data)->formula()); *string += QString("%1%2\n") .arg(lead) .arg(static_cast(mpa_data)->targets()); // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Monomer.cpp000664 001750 001750 00000071476 14647465366 022337 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "Monomer.hpp" #include "PolChemDef.hpp" #include "CrossLink.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::Monomer \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile Monomer.hpp \brief The Monomer class provides abstractions to work with monomers. In libmass, a momomer is an entity that is part of a polymer chemistry definition. A monomer models a chemical entity that is part of a polymer. In protein chemistry, that would be a \e{residue}, that is, an amino-acid that has been polymerized into a residue chain (that is, a protein, or a peptide). The chemical reaction that polymerizez an amino acid into an elongating protein structure is a condensation, with loss of H2O from the amino acid to actually lead to a what is called a \e{residue} of a monomer, or for short a \e{residue}. \note The monomer, that is partly defined by its formula, has the formula of the \e{residue}, not of the amino acid. This is always true, whatever the polymer chemistry definition at hand: protein, saccharide, nucleic acid. */ /*! \variable int MsXpS::libXpertMass::Monomer::m_code \brief The code of the monomer, like K for lysine, A for adenine. */ /*! \variable int MsXpS::libXpertMass::Monomer::mpa_modifList \brief Allocated list of MsXpS::libXpertMass::Modif objects. */ /*! \brief Constructs a monomer with its member data set to \a name, \a code, \a formula. The \a pol_chem_def_csp polymer chemistry definition is set to the PolChemDefEntity base class' member. */ Monomer::Monomer(PolChemDefCstSPtr pol_chem_def_csp, QString name, QString code, QString formula) : PolChemDefEntity(pol_chem_def_csp, name), Formula(formula), Ponderable(0, 0), m_code(code) { mpa_modifList = 0; } /*! \brief Constructs a monomer as a copy of \a other. */ Monomer::Monomer(const Monomer &other) : PolChemDefEntity(other), Formula(other.m_formula), Ponderable(other), PropListHolder(other) { } /*! \brief Destroys the monomer. */ Monomer::~Monomer() { } /*! \brief Assigns \a other's member data to this monomer. The copy is deep, in particular with the mpa_modifList being copied. Returns a reference to this monomer. */ Monomer & Monomer::operator=(const Monomer &other) { if(&other == this) return *this; PolChemDefEntity::operator=(other); Formula::operator=(other); Ponderable::operator=(other); PropListHolder::operator=(other); m_code = other.m_code; if(mpa_modifList != nullptr) qDeleteAll(*mpa_modifList); if(other.mpa_modifList != nullptr) { if(mpa_modifList == nullptr) mpa_modifList = new QList(); for(int iter = 0; iter < other.mpa_modifList->size(); ++iter) { Modif *modif = new Modif(*other.mpa_modifList->at(iter)); mpa_modifList->append(modif); } } else { delete(mpa_modifList); mpa_modifList = nullptr; } return *this; } /*! \brief Sets the code to \a code */ void Monomer::setCode(const QString &code) { m_code = code; } /*! \brief Returns the code */ QString Monomer::code() const { return m_code; } /*! \brief Returns true if this monomer and \a other are identical, false otherwise. The comparison involves also the comparison of the Modif objects in mpa_modifList. */ bool Monomer::operator==(const Monomer &other) const { int tests = 0; tests += PolChemDefEntity::operator==(other); tests += Formula::operator==(other); tests += Ponderable::operator==(other); tests += (m_code == other.m_code); if(mpa_modifList->size() != other.mpa_modifList->size()) return false; for(int iter = 0; iter < mpa_modifList->size(); ++iter) { if(mpa_modifList->at(iter) != other.mpa_modifList->at(iter)) return false; } if(tests < 4) return false; return true; } /*! \brief Returns true if this monomer and \a other differ, false otherwise. The comparison involves also the comparison of the Modif objects in mpa_modifList. */ bool Monomer::operator!=(const Monomer &other) const { int tests = 0; tests += PolChemDefEntity::operator!=(other); tests += Formula::operator!=(other); tests += Ponderable::operator!=(other); tests += (m_code != other.m_code); if(mpa_modifList->size() != other.mpa_modifList->size()) return true; for(int iter = 0; iter < mpa_modifList->size(); ++iter) { if(mpa_modifList->at(iter) != other.mpa_modifList->at(iter)) return true; } if(tests > 0) return true; return false; } /*! \brief Checks the code's syntactic validity. The monomer code is verified and has to verify these criteria: \list \li It must be non-empty \li Its character length has to be less or equal to the code length parameter in the polymer chemistry definition (see PolChemDef::m_codeLength) \li The first character is uppercase \li All the remaining characters are lowercase \endlist Returns true if the code syntax checked successfully, false otherwise. \sa validate() */ bool Monomer::checkCodeSyntax() const { // The code has to be at least one character long. // The first letter in the code has to be uppercase. // All the remaining authorized characters have to be // lowercase. int codeLength = mcsp_polChemDef->codeLength(); if(m_code.length() < 1 || m_code.length() > codeLength) return false; // Note that the actual monomer code length might be less than the // codeLength member datum in the polymer chemistry // definition. Which is why we have to make sure we test that before // risking to access a character ouf of bonds of the m_code string. for(int iter = 0; iter < m_code.size(); ++iter) { // Test that the m_code length is not greater than codeLength. if(iter + 1 > codeLength) return false; // And now check the character syntax. QChar curChar = m_code.at(iter); if(iter == 0) { if(curChar.category() != QChar::Letter_Uppercase) return false; } else if(curChar.category() == QChar::Letter_Uppercase) return false; } return true; } /*! \brief Tells if a monomer by this monomer's code is known in the polymer chemistry definition. The monomers in the list of monomers of the polymer chemistry definition (mcsp_polChemDef) are searched through for this monomer's code (m_code). Returns the index of the found monomer or -1 either if m_code is empty or if the monomer was not found. */ int Monomer::isCodeKnown() const { const QList &refList = mcsp_polChemDef->monomerList(); if(m_code.isEmpty()) return -1; for(int iter = 0; iter < refList.size(); ++iter) { if(refList.at(iter)->m_code == m_code) return iter; } return -1; } /*! \brief Tells if a monomer by the monomer \a code is in \a refList. The monomers in \a refList are searched through for the monomer \a code. If a monomer is found and \a monomer_p is non-nullptr, the monomer pointed to by \a monomer_p is set to the contents of the found monomer. Returns the index of the found monomer or -1 either if m_code is empty or if the monomer was not found. */ int Monomer::isCodeInList(const QString &code, const QList &refList, Monomer *monomer_p) { Monomer *iter_monomer_p = nullptr; if(code.isEmpty()) return -1; for(int iter = 0; iter < refList.size(); ++iter) { iter_monomer_p = refList.at(iter); Q_ASSERT(iter_monomer_p); if(iter_monomer_p->m_code == code) { // qDebug() << "Found the monomer in the reference list by code:" << // str; if(monomer_p) *monomer_p = *iter_monomer_p; return iter; } } return -1; } /*! \brief Tells if a monomer by this monomer's name is known in the polymer chemistry definition. The monomers in the list of monomers of the polymer chemistry definition (mcsp_polChemDef) are searched through for this monomer's name (m_name). Returns the index of the found monomer or -1 either if m_name is empty or if the monomer was not found. */ int Monomer::isNameKnown() const { const QList &refList = mcsp_polChemDef->monomerList(); if(m_name.isEmpty()) return -1; for(int iter = 0; iter < refList.size(); ++iter) { if(refList.at(iter)->m_name == m_name) return iter; } return -1; } /*! \brief Tells if a monomer by the monomer \a name is in \a refList. The monomers in \a refList are searched through for the monomer \a name. If a monomer is found and \a monomer_p is non-nullptr, the monomer pointed to by \a monomer_p is set to the contents of the found monomer. Returns the index of the found monomer or -1 either if m_name is empty or if the monomer was not found. */ int Monomer::isNameInList(const QString &name, const QList &refList, Monomer *monomer_p) { Monomer *monomer = 0; if(name.isEmpty()) return -1; for(int iter = 0; iter < refList.size(); ++iter) { monomer = refList.at(iter); Q_ASSERT(monomer != 0); if(monomer->m_name == name) { if(monomer_p != 0) *monomer_p = *monomer; return iter; } } return -1; } /*! \brief Returns the modification list */ QList * Monomer::modifList() const { return mpa_modifList; } /* \brief Return the formula of this monomer as a string. */ QString Monomer::formula() const { return Formula::toString(); } /* \brief Returns true if this monomer is a target of Modif \a modif, false otherwise. */ bool Monomer::isModifTarget(const Modif &modif) const { // Pure convenience function. return modif.hasMonomerTarget(m_code); } /*! \brief Modifies this monomer using \a modif. The two verifications that are done: \list \li This monomer must be a \a modif target; \li The count of \a modif modifications in this monomer must be at most the authorized count - 1, to accomodate this new modification [see Modif::maxCount()]. \endlist The two restriction above can be overridden by setting \a override to true. If errors are encountered, these are reported as strings in \a errorList. The \a{modif}'s ownership is taken by this monomer. Returns true on success, false otherwise. */ bool Monomer::modify(Modif *modif, bool override, QStringList &errorList) { // Will take ownership of the modif. // We have to check two things: // 1. That *this monomer is actually a target of the modification // at hand(or that we can override limitations); // 2. That *this monomer can still accommodate one such 'modif' // more (that is the count of 'modif' for *this mononomer is // correct for adding one more. Q_ASSERT(modif); if(!isModifTarget(*modif) && !override) { // This monomer is not a target for the modif, and no override // is allowed. errorList << QObject::tr( "\t%1 not target of modif %2 " "(no overriding allowed)") .arg(m_name) .arg(modif->name()); return false; } int count = modifCount(modif->name()); if(count >= modif->maxCount() && !override) { // This monomer has already the maximum count of 'modif' objects. errorList << QObject::tr( "\t%1 already modified %2 times " "(no overriding allowed)") .arg(m_name) .arg(count); return false; } // We are going to add one modification to the list of // modifications. Note however, that if the monomer had never been // modified(or all of its modifications had been removed), then its // modifList should be 0. We must allocate it. if(!mpa_modifList) mpa_modifList = new QList(); mpa_modifList->append(modif); return true; } /*! \brief Removes \a modif from this monomer. The member list of modifications must exist (it is a heap-allocated list that is allocated upon the first modification of the monomer) and must be non-empty. Returns true. */ bool Monomer::unmodify(Modif *modif) { // The unmodification pertains to the single 'modif' object. // We are given the address of a specific modif to remove, thus it // cannot be that the list of modifs be 0 or empty. Q_ASSERT(mpa_modifList); Q_ASSERT(mpa_modifList->size()); // Will remove only one item, even if we call removeAll() because // there is only one 'modif' pointer to Modif. int ret = mpa_modifList->removeAll(modif); // Only one item should have been found in the list. if(ret != 1) qFatal("Programming error"); // If we removed the last item, free the list. if(!mpa_modifList->size()) { delete mpa_modifList; mpa_modifList = 0; } return true; } /*! \brief Removes \e{all} the modification from this monomer. Returns true. */ bool Monomer::unmodify() { if(mpa_modifList) { qDeleteAll(*mpa_modifList); delete mpa_modifList; } return true; } /*! \brief Returns true if this monomer has at least one modification, false otherwise. */ bool Monomer::isModified() const { if(mpa_modifList && mpa_modifList->size()) return true; return false; } /*! \brief Returns the count of modifications by name \a modif_name in this monomer. */ int Monomer::modifCount(const QString &modif_name) { int count = 0; if(!mpa_modifList) return 0; for(int iter = 0; iter < mpa_modifList->size(); ++iter) { Modif *modif = mpa_modifList->at(iter); if(modif_name == modif->name()) ++count; } return count; } /*! \brief Returns true if this monomer is valid, false otherwise. Validation of the monomer occurs if: \list \li Its name is not empty \li Its code is not empty and its syntax is correct \li Its formula validates \li Its modifications (if any) validate \endlist \sa checkCodeSyntax() */ bool Monomer::validate() { IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); if(m_name.isEmpty()) return false; if(!checkCodeSyntax()) return false; if(!Formula::validate(isotopic_data_csp)) return false; if(mpa_modifList) { for(int iter = 0; iter < mpa_modifList->size(); ++iter) { if(!mpa_modifList->at(iter)->validate()) return false; } } return true; } /*! \brief Calculates this monomer's monoisotopic and avg masses. The calculation is performed by computing the masses of this monomer's formula. If \a monomer_chemical_entities & MONOMER_CHEMENT_MODIF is true, then the masses are updated to account for the mass of modifications. Returns true if the calculations were successful, false otherwise. \sa Formula::accountMasses() */ bool Monomer::calculateMasses(int monomer_chemical_entities) { IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); m_mono = 0; m_avg = 0; // qDebug() << "Accounting masses for monomer: " << m_name; if(!Formula::accountMasses(isotopic_data_csp, &m_mono, &m_avg)) return false; if(monomer_chemical_entities & MONOMER_CHEMENT_MODIF) { if(mpa_modifList) { for(int iter = 0; iter < mpa_modifList->size(); ++iter) { Modif *modif = mpa_modifList->at(iter); modif->accountMasses(&m_mono, &m_avg); } } } return true; } /* \brief Calculates a Formula representing this monomer . The calculated formula accounts for this monomer's formula and for its modifications formulas if any and if \a (monomer_chemical_entities & MONOMER_CHEMENT_MODIF) is true. This monomer's formula must validate using Modif::validate. Returns the Formula. \sa Modif::accountFormula() */ Formula Monomer::calculateFormula(int monomer_chemical_entities) const { // We want to return the formula of this monomer that accounts for its // modifications if so is requested. IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); // qDebug() << "Calculating formula for monomer: " << m_name //<< "with chemical entities:" << monomer_chemical_entities; Formula formula(m_formula); // qDebug() << "Basic formula:" << formula.toString(); // The Formula object above is only a text string. We need to convert that // into the symbol/count pair by validating it with true true params. // The formula is asked to validate with storage of the found symbol/count // pairs and with resetting of the previous contents of the symbol/count map. // We need to seed the symbol/count pairs because the following call // (accountFormula()) will update the pairs' values. if(!formula.validate(isotopic_data_csp, true, true)) { qDebug() << "Formula:" << formula.toString() << "failed to validate."; return Formula(); } if(monomer_chemical_entities & MONOMER_CHEMENT_MODIF) { if(mpa_modifList) { for(int iter = 0; iter < mpa_modifList->size(); ++iter) { // qDebug() << "Before accounting modif:" //<< mpa_modifList->at(iter)->name() //<< "with formula:" << mpa_modifList->at(iter)->formula() //<< "the global formula:" << formula.toString(); formula.accountFormula(mpa_modifList->at(iter)->formula(), mcsp_polChemDef->getIsotopicDataCstSPtr()); // qDebug() << "After accounting modif:" //<< mpa_modifList->at(iter)->name() //<< "with formula:" << mpa_modifList->at(iter)->formula() //<< "the new global formula:" << formula.toString(); } } } // qDebug() << "Returning the formula as string:" << formula.toString(); return formula; } /*! \brief Accounts this monomer's masses in \a mono and \a avg. The \a mono and \a avg masses are updated only if they are non-nullptr. This monomer's masses are stored in member data \c m_mono and \c m_avg. This monomer's masses multiplied by \a times before setting the values to \a mono and \a avg. Returns true. */ bool Monomer::accountMasses(double *mono, double *avg, int times) const { if(mono) *mono += m_mono * times; if(avg) *avg += m_avg * times; return true; } /*! \brief Accounts this monomer's masses in \a ponderable's mono and avg masses. This monomer's masses are stored in member data \c m_mono and \c m_avg. These masses are multiplied by \a times before setting the values to \a ponderable. \a ponderable cannot be nullptr. Returns true. */ bool Monomer::accountMasses(Ponderable *ponderable, int times) const { Q_ASSERT(ponderable != nullptr); ponderable->rmono() += m_mono * times; ponderable->ravg() += m_avg * times; return true; } /*! \brief Parses the monomer XML \a element specifically for \a version. Parses the monomer XML element passed as argument and for each encountered data will set the data to this monomer (this is called XML rendering).The parsing is delegated to a function that is specific for for \a version of the polymer chemistry definition. The XML element is found in the polymer chemistry definition and has the following form: \code Glycine G C2H3N1O1 Alanine A C3H5N1O1 \endcode After setting all the data, this monomer calculates it masses and validates itself. If any of these steps fails, the error is reported by returning false. \sa validate() */ bool Monomer::renderXmlMnmElement(const QDomElement &element, int version) { if(version == 1) { // NoOp version = 1; } QDomElement child; /* In a polymer chemistry definition, the xml node we are in is * structured this way: * * * Glycine * G * C2H3N1O1 * * * And the element parameter points to the * * element tag: * ^ * | * +----- here we are right now. * * Which means that element.tagName() == "mnm" and that we'll have * to go one step down to the first child of the current node in * order to get to the element. * */ if(element.tagName() != "mnm") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); child = child.nextSiblingElement("code"); if(child.isNull()) return false; m_code = child.text(); child = child.nextSiblingElement("formula"); if(child.isNull()) return false; if(!Formula::renderXmlFormulaElement(child)) return false; if(!this->calculateMasses(MONOMER_CHEMENT_NONE)) { qDebug() << "Failed to calculate masses for monomer" << m_name; return false; } if(!validate()) return false; return true; } /*! \brief Formats this monomer's data as a string suitable to be used as an XML element in the polymer chemistry definition. The typical monomer element that is generated in this function looks like this: \code Glycine G C2H3N1O1 \endcode The formatting of the XML element takes into account \a offset and \a indent by prepending the string with \a offset * \a indent character substring. \a indent defaults to two spaces. Returns a dynamically allocated string that needs to be freed after use. */ QString * Monomer::formatXmlMnmElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); *string += QString("%1%2\n").arg(lead).arg(m_code); *string += QString("%1%2\n").arg(lead).arg(m_formula); // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } /*! \brief Parses into this monomer the XML monomer \a element passed as argument. The XML element comes from a polymer sequence file, where the monomer is singled out (not in a sequence string) because it might be modified. \a version indicates the format version of this XML \a element. As soon as the monomer code is known, while parsing the \a element, the corresponding monomer is searched in the list of monomers in the member polymer chemistry definition (\c mcsp_polChemDef). Then, the found monomer is copied into \c this monomer so that both monomers are identical, effectively initializing this monomer to the monomer described by the \a element. If the \a element contains one or more \c mdf modifications, these modifications are allocated as \l{Modif}'s and validated. If these modifications validate successfully, they are appended to this monomer's list of modifications. Returns true if initializationt of his monomer with the contents of \a element succeeded, false otherwise. \sa formatXmlMonomerElement(int offset, const QString &indent) */ bool Monomer::renderXmlMonomerElement(const QDomElement &element, int version) { if(version == 1) { // no-op version = 1; } QDomElement child; QDomElement indentedChild; if(element.tagName() != "monomer") return false; child = element.firstChildElement("code"); if(child.isNull()) return false; QString code = child.text(); Monomer other(mcsp_polChemDef, "NOT_SET"); const QList &refList = mcsp_polChemDef->monomerList(); int index = -1; index = isCodeInList(code, refList, &other); if(index == -1) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); *this = other; // Sanity check if(m_code != code) qFatal("Programming error. Both codes should be identical."); // And now we have to manage the prop objects. child = child.nextSiblingElement(); while(!child.isNull()) { if(child.tagName() != "mdf") return false; // Allocate the modification that will be set to the monomer // element. Modif *modif = new Modif(mcsp_polChemDef, "NOT_SET", "NOT_SET"); if(!modif->renderXmlMdfElement(child, version)) { delete modif; return false; } if(!modif->calculateMasses()) { qDebug() << __FILE__ << __LINE__ << "Failed to calculate masses for modification" << modif->name(); delete modif; return false; } // The validation will take care of checking that the // element did have correct text inside. if(!modif->validate()) { delete modif; return false; } // OK, at this point we can add the new modif to the list of // modifs. if(!mpa_modifList) mpa_modifList = new QList(); mpa_modifList->append(modif); child = child.nextSiblingElement(); } return true; } /*! \brief Formats a string suitable to be used as an XML element in a polymer sequence file. The typical monomer element that is generated in this function looks like this: \code S MODIF Phosphorylation COMMENT Phosphorylation is only partial \endcode The formatting of the XML element takes into account \a offset and \a indent by prepending the string with \a offset * \a indent character substring. \a indent defaults to two spaces. Returns a dynamically allocated string that needs to be freed after use. */ QString * Monomer::formatXmlMonomerElement(int offset, const QString &indent) const { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_code); // The monomer may have any number of modif objects, which we have // to document here. if(mpa_modifList && mpa_modifList->size()) { for(iter = 0; iter < mpa_modifList->size(); ++iter) { Modif *modif = mpa_modifList->at(iter); QString *modifString = modif->formatXmlMdfElement(newOffset); Q_ASSERT(modifString); *string += *modifString; delete modifString; } } // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } /*! \brief Outputs a string representing this monomer using qDebug(). */ void Monomer::debugPutStdErr() const { qDebug() << m_name << m_code << m_formula; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/MonomerDictionary.cpp000664 001750 001750 00000030075 14647465366 024353 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "MonomerDictionary.hpp" #include "Sequence.hpp" namespace MsXpS { namespace libXpertMass { MonomerDictionary::MonomerDictionary(QString file_path, const QStringList &input_chain_string_list, int input_code_length, int output_code_length) : m_filePath(file_path), m_inputChainStringList(input_chain_string_list), m_inputCodeLength(input_code_length), m_outputCodeLength(output_code_length) { } /*! \brief Destructs this MonomerDictionary instance. */ MonomerDictionary::~MonomerDictionary() { } void MonomerDictionary::setFilePath(QString &file_path) { m_filePath = file_path; } void MonomerDictionary::setInputChainStringList( const QStringList &input_chain_string_list) { m_inputChainStringList = input_chain_string_list; } void MonomerDictionary::setInputCodeLength(int code_length) { m_inputCodeLength = code_length; } void MonomerDictionary::setOutputCodeLength(int code_length) { m_outputCodeLength = code_length; } bool MonomerDictionary::isLineProperSectionDivider(const QString &line) { // Section dividers in the monomer dictionary file format are // lines containing the following syntax: X>Y, that is for example // 3>1. This means that the following conversion rules (like // ILE>I) should convert 3-letter codes into 1-letter codes. // However, this line should only be considered proper if X is // actually the value of m_inputCodeLength and Y the value of // m_outputCodeLength. // qDebug() << __FILE__ << __LINE__ // << "Checking if line is proper section divider :" << line; if(line.contains(QRegularExpression("[0-9]+>[0-9]+"))) { // We are opening a new section, get the input/output code // lengths and if they math what we expect, then set the // current stream position and call the section parser. int greaterThanIndex = line.indexOf('>'); QString codeLengthString = line.left(greaterThanIndex); // qDebug() << __FILE__ << __LINE__ // << "Left codeLengthString:" << codeLengthString // << "m_inputCodeLength:" << m_inputCodeLength; bool ok = false; int codeLength = codeLengthString.toInt(&ok, 10); if(!codeLength && !ok) { qDebug() << __FILE__ << __LINE__ << "Monomer dictionary" << "Failed to parse file " << m_filePath << "at line " << line; return false; } if(codeLength != m_inputCodeLength) { return false; } codeLengthString = line.mid(greaterThanIndex + 1, -1); // qDebug() << __FILE__ << __LINE__ // << "Right codeLengthString:" << codeLengthString // << "m_outputCodeLength:" << m_outputCodeLength; ok = false; codeLength = codeLengthString.toInt(&ok, 10); if(!codeLength && !ok) { qDebug() << __FILE__ << __LINE__ << "Monomer dictionary" << "Failed to parse file " << m_filePath << "at line " << line; return false; } if(codeLength != m_outputCodeLength) { return false; } // At this point, it seems we are in the proper // section. return true; } // If we are here, that means that the section is not for us. // qDebug() << __FILE__ << __LINE__ // << "Line is no proper section divider."; return false; } void MonomerDictionary::skipSection(QTextStream *stream) { // We have entered a section, all we have to do is go through it // and return when we have found either the end of the stream or // the {END} marker. qint64 lineLength = 1024; QString line; while(!stream->atEnd()) { line = stream->readLine(lineLength); if(!line.contains("{END}")) { line = stream->readLine(lineLength); } else return; } } int MonomerDictionary::parseSection(QTextStream *stream) { Q_ASSERT(stream); qint64 lineLength = 1024; QString line; // Iterate in the file using the stream and for each line create // an item to insert into the dictionary hash. while(!stream->atEnd()) { line = stream->readLine(lineLength); // We might encounter the end of the section, that is a line // having {END} as its sole content. if(line.contains("{END}")) break; QStringList stringList = line.split('>'); QString inputCode = stringList.first(); QString outputCode = stringList.last(); // Check that the monomer codes have the proper length. if(inputCode.length() != m_inputCodeLength || outputCode.length() != m_outputCodeLength) { qDebug() << __FILE__ << __LINE__ << QObject::tr("Monomer dictionary:") << QObject::tr("Failed to load dictionary.") << QObject::tr("Monomer code lengths do not match:") << QObject::tr("inputCode:") << inputCode << QObject::tr("outputCode:") << outputCode; // We have to empty the hash m_dictionaryHash.clear(); break; } m_dictionaryHash.insert(inputCode, outputCode); // qDebug() << __FILE__ << __LINE__ // << stringList.first () << stringList.last (); } // At this point the parsing is finished, either because we // encountered the {END} section-ending delimiter, or because we // reached the en of file. int hashSize = m_dictionaryHash.size(); if(hashSize) m_dictionaryLoaded = true; else { qDebug() << __FILE__ << __LINE__ << QObject::tr("Monomer dictionary:") << QObject::tr("Failed to load dictionary."); m_dictionaryLoaded = false; } return hashSize; } bool MonomerDictionary::loadDictionary() { // Load the file and for each line deconstruct the item into two // QString objects that are used to make a QHash entry in // QHash m_dictionaryHash. bool success = true; qint64 lineLength = 1024; QString line; QFile file(m_filePath); if(!file.open(QIODevice::ReadOnly)) { m_dictionaryLoaded = false; qDebug() << __FILE__ << __LINE__ << "Monomer dictionary:" << "Failed to open file" << m_filePath << "for writing."; return false; } if(m_inputCodeLength < 1 || m_outputCodeLength < 1) { qDebug() << __FILE__ << __LINE__ << "Monomer dictionary:" << "Failed to parse file " << m_filePath << "Please, set the m_inputCodeLength and " "m_ouputCodeLength variables first."; return false; } QTextStream *stream = new QTextStream(&file); stream->setEncoding(QStringConverter::Utf8); while(!stream->atEnd()) { line = stream->readLine(lineLength); // qDebug() << __FILE__ << __LINE__ // << "line: " << line; // Remove spaces from start and end of line. line = line.simplified(); if(line.startsWith('#') || line.isEmpty()) { line = stream->readLine(lineLength); continue; } // There might be any number of sections in the file, all // delimited with a X>Y directive, indicating how many // characters are allowed for the input code and for the // output code. if(!isLineProperSectionDivider(line)) { // qDebug() << __FILE__ << __LINE__ // << "skipping line:" << line; line = stream->readLine(lineLength); continue; } else { // qDebug() << __FILE__ << __LINE__ // << "parsing section: " << line; if(parseSection(stream) < 1) { qDebug() << __FILE__ << __LINE__ << "Monomer dictionary:" << "Failed to parse file " << m_filePath; success = false; break; } else { // We successfully parsed the section. Our work is done. success = true; break; } } } delete stream; return success; } QStringList * MonomerDictionary::translate(const QStringList &chain_string_list) { // The string in sequence is a space-separated list of monomer // codes in the original monomer code format. We have to translate // that to the proper monomer code format using the hash in this // dictionary. QStringList *outputChainStringList = new QStringList(); if(!chain_string_list.isEmpty()) m_inputChainStringList = chain_string_list; // If there is nothing to do return an empty string list so that // caller knows nothing is actually wrong, only there is no // sequence to translate. if(m_inputChainStringList.isEmpty()) return outputChainStringList; // Iterate in each chain string of the list and perform the // translation. for(int iter = 0; iter < m_inputChainStringList.size(); ++iter) { QString iterString = chain_string_list.at(iter); // qDebug() << __FILE__ << __LINE__ // << "translating sequence:" << iterString; QStringList codeList = iterString.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); // qDebug() << __FILE__ << __LINE__ // << "codeList:" << codeList; // qDebug() << __FILE__ << __LINE__ // << "hash:" // << m_dictionaryHash; for(int jter = 0; jter < codeList.size(); ++jter) { QString code = codeList.at(jter); QHash::const_iterator hashIter = m_dictionaryHash.find(code); if(hashIter != m_dictionaryHash.end()) codeList.replace(jter, hashIter.value()); else { // Delete the string list, set the pointer to 0 and // return that pointer so that caller knows something // has gone wrong. qDebug() << __FILE__ << __LINE__ << "Monomer dictionary:" << "Failed to convert monomer code " << code; outputChainStringList->clear(); delete outputChainStringList; outputChainStringList = 0; return outputChainStringList; } } // At this point the sequence codes have been translated. Join all // the item of the codeList into one single string. outputChainStringList->append(codeList.join(QString(""))); } // End of // for (int iter = 0; iter < chainStringList.size(); ++iter) // If no translation could be performed, return a n if(!outputChainStringList->size()) { outputChainStringList->clear(); delete outputChainStringList; outputChainStringList = 0; } return outputChainStringList; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/MonomerSpec.cpp000664 001750 001750 00000016323 14647465366 023140 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include #include #include #include /////////////////////// Local includes #include "MonomerSpec.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::MonomerSpec \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile MonomerSpec.hpp \brief The MonomerSpec class provides the specification about how \l Monomer objects are represented. The MonomerSpec class specifies how a \l Monomer object is represented graphically, mainly by connecting its monomer code to a graphics SVG file that is located in the Polymer chemistry definition directory. That connection is performed in the "monomer_dictionary" dictionary file itself also located in the polymer chemistry definition directory. Its contents look like this: \code A%alanine.svg C%cysteine.svg D%aspartate.svg E%glutamate.svg F%phenylalanine.svg G%glycine.svg H%histidine.svg" \endcode The \c{A%alanine.svg} line indicates that, when a Monomer object by code 'A' is to be rendered graphically, the corresponding vignette to be used is in the file named "alanine.svg" in the polymer chemistry definition directory. */ /*! \variable int MsXpS::libXpertMass::MonomerSpec::m_name \brief The name of the monomer. */ /*! \variable int MsXpS::libXpertMass::MonomerSpec::m_code \brief The code of the monomer. */ /*! \variable int MsXpS::libXpertMass::MonomerSpec::m_raster \brief The file name of the raster representation of the monomer. */ /*! \variable int MsXpS::libXpertMass::MonomerSpec::m_vector \brief The filename of the vector representation of the monomer. */ /*! \variable int MsXpS::libXpertMass::MonomerSpec::m_nameSound \brief The file name of the sound for the name of the monomer. */ /*! \variable int MsXpS::libXpertMass::MonomerSpec::m_codeSound \brief The file name of the sound for the code of the monomer. */ /*! \brief Constructs a MonomerSpec instance. */ MonomerSpec::MonomerSpec() { } /*! \brief Destructs this MonomerSpec instance. */ MonomerSpec::~MonomerSpec() { } /*! \brief Sets the monomer \a name. */ void MonomerSpec::setName(const QString &name) { m_name = name; } /*! \brief Returns the monomer name. */ const QString & MonomerSpec::name() { return m_name; } /*! \brief Sets the monomer \a code. */ void MonomerSpec::setCode(const QString &code) { m_code = code; } /*! \brief Returns the monomer code. */ const QString & MonomerSpec::code() { return m_code; } /*! \brief Sets the raster image file name to \a raster. */ void MonomerSpec::setRaster(const QString &raster) { m_raster = raster; } /*! \brief Returns the raster image file name. */ const QString & MonomerSpec::raster() { return m_raster; } /*! \brief Sets the vector image file name to \a vector. */ void MonomerSpec::setVector(const QString &vector) { m_vector = vector; } /*! \brief Returns the vector image file name. */ const QString & MonomerSpec::vector() { return m_vector; } /*! \brief Sets the file name of the Monomer's name \a sound file. */ void MonomerSpec::setNameSound(const QString &sound) { m_nameSound = sound; } /*! \brief Returns the file name of the Monomer's name sound file. */ const QString & MonomerSpec::nameSound() { return m_nameSound; } /*! \brief Sets the file name of the Monomer's code \a sound file. */ void MonomerSpec::setCodeSound(const QString &sound) { m_codeSound = sound; } /*! \brief Returns the file name of the Monomer's code sound file. */ const QString & MonomerSpec::codeSound() { return m_codeSound; } /*! \brief Parses the \a file_path dictionary containing the Monomer specifications. At the moment the file has this format: \code A%alanine.svg C%cysteine.svg D%aspartate.svg E%glutamate.svg \endcode Upon parsing, the \a monomer_spec_list of MonomerSpec instances will be filled with instances created on the basis of each parsed line in the file. Returns true if the parsing was successful, false otherwise. */ bool MonomerSpec::parseFile(QString &file_path, QList *monomer_spec_list) { MonomerSpec *monomerSpec = 0; qint64 lineLength; QString line; QString temp; char buffer[1024]; Q_ASSERT(monomer_spec_list != 0); if(file_path.isEmpty()) return false; QFile file(file_path); if(!file.open(QFile::ReadOnly)) return false; // The lines we have to parse are of the following type: // A%alanine.svg|alanine.png // Any line starting with '#' are not parsed. // Get the first line of the file. Next we enter in to a // while loop. lineLength = file.readLine(buffer, sizeof(buffer)); while(lineLength != -1) { // The line is now in buffer, and we want to convert // it to Unicode by setting it in a QString. line = buffer; // The line that is in line should contain something like: // A%alanine.svg // Remove all the spaces from the borders: whitespace means any // character for which QChar::isSpace() returns true. This // includes the ASCII characters '\t', '\n', '\v', '\f', '\r', // and ' '. line = line.trimmed(); if(line.isEmpty() || line.startsWith('#', Qt::CaseInsensitive)) { lineLength = file.readLine(buffer, sizeof(buffer)); continue; } // Now some other checks. Remember the format of the line: // A%alanine.svg QString code; QString file_name; QRegularExpression reg_exp(QString("^([A-Z][a-z]*)%(.*)$")); QRegularExpressionMatch match = reg_exp.match(line); if(match.hasMatch()) { code = match.captured(1); file_name = match.captured(2); } // qDebug() << "code:" << code << "file_name:" << file_name; // OK, we finally can allocate a new MonomerSpec *. monomerSpec = new MonomerSpec(); monomerSpec->m_code = code; monomerSpec->m_vector = file_name; monomer_spec_list->append(monomerSpec); lineLength = file.readLine(buffer, sizeof(buffer)); } return true; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Oligomer.cpp000664 001750 001750 00000075260 14647465366 022473 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "Oligomer.hpp" #include "Polymer.hpp" #include "IonizeRule.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::Oligomer \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile Oligomer.hpp \brief The Oligomer class provides abstractions to work with an oligomer molecule (peptide , for example). The notion of an oligomer is that it is part of a \l Polymer and is thus defined by a range of \l Monomer indices in this Polymer (the \l Coordinates). The start index cannot be less than 0 nor greater than the size of the polymer minus one, and the end index follows the same rule. Derived from \l Ionizable (itself derived from \l Ponderable), an oligomer is also characterized by a monoisotopic mass and an average mass. All computations about an oligomer (fragmentation, composition, for example, isoelectric point, ...) can only be performed by referring to the sequence of its "enclosing" \l Polymer. Therefore, an Oligomer should never exist after the destruction of its "enclosing" polymer. */ /*! \variable MsXpS::libXpertMass::Oligomer::mp_polymer \brief The \l Polymer instance of which this Oligomer is part. */ /*! \variable MsXpS::libXpertMass::Oligomer::m_description \brief The description of the Oligomer. */ /*! \variable MsXpS::libXpertMass::Oligomer::m_isModified \brief Tell if the Oligomer is modified. */ /*! \variable MsXpS::libXpertMass::Oligomer::m_crossLinkList \brief The list of CrossLink events in the oligomer sequence. */ /*! \variable MsXpS::libXpertMass::Oligomer::m_calcOptions \brief The calculation options definining how calculations are performed. */ /*! \typedef OligomerSPtr \relates Oligomer Synonym for std::shared_ptr OligomerSPtr. */ /*! \typedef OligomerCstSPtr \relates Oligomer Synonym for std::shared_ptr OligomerSPtr. */ /*! \brief Constructs an oligomer. The Oligomer instance is constructed with these arguments: \a polymer: The polymer instance that encloses this Oligomer. Used to intialize the member m_polymer and the Ionizable base class' polymer chemistry definition \a name: The name of this Oligomer, used to intialize the Ionizable base class \a description: The description of this Oligomer (m_description) \a modified: Tells if the Oligomer is modified \a ponderable: Used to initialize the Ionizable base class \a ionizeRule: Used to initialize the Ionizable base class \a calcOptions: Used to initialize the m_calcOptions member \a isIonized: Tells if this Oligomer instance is ionized \a startIndex: The oligomer's start index coordinate in the enclosing Polymer \a endIndex: The oligomer's end index coordinate in the enclosing Polymer */ Oligomer::Oligomer(Polymer *polymer, const QString &name, const QString &description, bool modified, const Ponderable &ponderable, const IonizeRule &ionizeRule, const CalcOptions &calcOptions, bool isIonized, int startIndex, int endIndex) : Ionizable( polymer->getPolChemDefCstSPtr(), name, ponderable, ionizeRule, isIonized), mp_polymer(polymer), m_description(description), m_isModified{modified}, m_calcOptions(calcOptions) { setStartEndIndices(startIndex, endIndex); } /*! \brief Constructs an oligomer. The Oligomer instance is constructed with these arguments: \a pol_chem_def_csp: The polymer chemistry definition used to initialize the Ionizable base class \a name: The name of this Oligomer, used to intialize the Ionizable base class \a description: The description of this Oligomer (m_description) \a modified: Tells if the Oligomer is modified \a ponderable: Used to initialize the Ionizable base class \a ionizeRule: Used to initialize the Ionizable base class \a calcOptions: Used to initialize the m_calcOptions member \a isIonized: Tells if this Oligomer instance is ionized \a startIndex: The oligomer's start index coordinate in the enclosing Polymer \a endIndex: The oligomer's end index coordinate in the enclosing Polymer */ Oligomer::Oligomer(PolChemDefCstSPtr pol_chem_def_csp, const QString &name, const QString &description, bool modified, const Ponderable &ponderable, const IonizeRule &ionizeRule, const CalcOptions &calcOptions, bool isIonized, int startIndex, int endIndex) : Ionizable(pol_chem_def_csp, name, ponderable, ionizeRule, isIonized), mp_polymer(nullptr), m_description(description), m_isModified{modified}, m_calcOptions(calcOptions) { setStartEndIndices(startIndex, endIndex); // if (startIndex < 0) // qDebug() << __FILE__ << __LINE__ // << "Construct with startIndex:" << startIndex; } /*! \brief Constructs an oligomer. The Oligomer instance is constructed with these arguments: \a polymer: The polymer instance that encloses this Oligomer. Used to intialize the member m_polymer and the Ionizable base class' polymer \a name: The name of this Oligomer, used to intialize the Ionizable base class \a description: The description of this Oligomer (m_description) \a modified: Tells if the Oligomer is modified \a ponderable: Used to initialize the Ionizable base class \a calcOptions: Used to initialize the m_calcOptions member \a startIndex: The oligomer's start index coordinate in the enclosing Polymer \a endIndex: The oligomer's end index coordinate in the enclosing Polymer */ Oligomer::Oligomer(Polymer *polymer, const QString &name, const QString &description, bool modified, const Ponderable &ponderable, int startIndex, int endIndex, const CalcOptions &calcOptions) : Ionizable(polymer->getPolChemDefCstSPtr(), name, ponderable), mp_polymer(polymer), m_description(description), m_isModified(modified), m_calcOptions(calcOptions) { setStartEndIndices(startIndex, endIndex); if(startIndex < 0) qDebug() << __FILE__ << __LINE__ << "Construct with startIndex:" << startIndex; } /*! \brief Constructs an oligomer. The Oligomer instance is constructed with these arguments: \a pol_chem_def_csp: The polymer chemistry definition used to initialize the Ionizable base class \a name: The name of this Oligomer, used to intialize the Ionizable base class \a description: The description of this Oligomer (m_description) \a modified: Tells if the Oligomer is modified \a ponderable: Used to initialize the Ionizable base class \a calcOptions: Used to initialize the m_calcOptions member \a startIndex: The oligomer's start index coordinate in the enclosing Polymer \a endIndex: The oligomer's end index coordinate in the enclosing Polymer */ Oligomer::Oligomer(PolChemDefCstSPtr pol_chem_def_csp, const QString &name, const QString &description, bool modified, const Ponderable &ponderable, const CalcOptions &calcOptions, int startIndex, int endIndex) : Ionizable(pol_chem_def_csp, name, ponderable), mp_polymer(nullptr), m_description(description), m_isModified{modified}, m_calcOptions(calcOptions) { setStartEndIndices(startIndex, endIndex); if(startIndex < 0) qDebug() << __FILE__ << __LINE__ << "Construct with startIndex:" << startIndex; } /*! \brief Constructs an oligomer. The Oligomer instance is constructed with these arguments: \a ionizable: Used to initialize the Ionizable base class \a calcOptions: Used to initialize the m_calcOptions member \a startIndex: The oligomer's start index coordinate in the enclosing Polymer \a endIndex: The oligomer's end index coordinate in the enclosing Polymer */ Oligomer::Oligomer(const Ionizable &ionizable, const CalcOptions &calcOptions, int startIndex, int endIndex) : Ionizable(ionizable), mp_polymer(nullptr), m_description("NOT_SET"), m_calcOptions(calcOptions) { setStartEndIndices(startIndex, endIndex); if(startIndex < 0) qDebug() << __FILE__ << __LINE__ << "Construct with startIndex:" << startIndex; } /*! \brief Constructs an oligomer. The Oligomer instance is constructed with these arguments: \a polymer: Use to initialize the member mp_polymer and also to initialize the Ionizable base class (by using its polymer chemistry definition member). \a name: The name of this Oligomer, used to intialize the Ionizable base class \a description: The description of this Oligomer (m_description) \a modified: Tells if the Oligomer is modified \a mono and \a avg: Used to initialize the Ionizable::Ponderable base class \a calcOptions: Used to initialize the m_calcOptions member \a startIndex: The oligomer's start index coordinate in the enclosing Polymer \a endIndex: The oligomer's end index coordinate in the enclosing Polymer */ Oligomer::Oligomer(Polymer *polymer, const QString &name, const QString &description, bool modified, double mono, double avg, int startIndex, int endIndex, const CalcOptions &calcOptions) : Ionizable(polymer->getPolChemDefCstSPtr(), name, Ponderable(mono, avg)), mp_polymer(polymer), m_description(description), m_isModified{modified}, m_calcOptions(calcOptions) { setStartEndIndices(startIndex, endIndex); if(startIndex < 0) qDebug() << __FILE__ << __LINE__ << "Construct with startIndex:" << startIndex; } /*! \brief Constructs an oligomer. The Oligomer instance is constructed with these arguments: \a pol_chem_def_csp: The polymer chemistry definition used to initialize the Ionizable base class \a name: The name of this Oligomer, used to intialize the Ionizable base class \a description: The description of this Oligomer (m_description) \a modified: Tells if the Oligomer is modified \a calcOptions: Used to initialize the m_calcOptions member \a mono and \a avg: Used to initialize the Ionizable::Ponderable base class \a startIndex: The oligomer's start index coordinate in the enclosing Polymer \a endIndex: The oligomer's end index coordinate in the enclosing Polymer */ Oligomer::Oligomer(PolChemDefCstSPtr pol_chem_def_csp, const QString &name, const QString &description, bool modified, const CalcOptions &calcOptions, double mono, double avg, int startIndex, int endIndex) : Ionizable(pol_chem_def_csp, name, Ponderable(mono, avg)), mp_polymer(0), m_description(description), m_isModified{modified}, m_calcOptions(calcOptions) { setStartEndIndices(startIndex, endIndex); } /*! \brief Constructs the Oligomer as a copy of \a other. */ Oligomer::Oligomer(const Oligomer &other) : Sequence(other), CoordinateList(other), Ionizable(other), PropListHolder(other), mp_polymer(other.mp_polymer), m_description(other.m_description), m_isModified{other.m_isModified}, m_calcOptions(other.m_calcOptions) { } /*! \brief Destructs this Oligomer. */ Oligomer::~Oligomer() { // qDebug() << "~Oligomer()"; } /*! \brief Returns the polymer. */ const Polymer * Oligomer::polymer() const { return mp_polymer; } /*! \brief Sets the start and end indices to \a value1 and \a value2 respectively. The values are used to construct a Coordinates instance that is allocated on the heap and added to member CoordinateList if that list is empty. If the CoordinateList is not empty, then the values are set to the first Coordinates instance in that list. \sa setStartIndex(), setEndIndex() */ void Oligomer::setStartEndIndices(int value1, int value2) { if(!CoordinateList::size()) { Coordinates *coordinates = new Coordinates(value1, value2); append(coordinates); // qDebug() << __FILE__ << __LINE__ // << "[start--end]:" << startIndex() << endIndex(); } else { Coordinates *coordinates = first(); coordinates->setStart(value1); coordinates->setEnd(value2); // qDebug() << __FILE__ << __LINE__ // << "[start--end]:" << startIndex() << endIndex(); } } /*! \brief Sets the start index to \a value. The value is used to construct a Coordinates instance that is allocated on the heap and added to member CoordinateList if that list is empty. If the CoordinateList is not empty, then the value is set to the first Coordinates instance in that list. */ void Oligomer::setStartIndex(int value) { if(value < 0) qDebug() << __FILE__ << __LINE__ << "setStartIndex with value:" << value; if(!CoordinateList::size()) { Coordinates *coordinates = new Coordinates(); coordinates->setStart(value); append(coordinates); } else { Coordinates *coordinates = first(); coordinates->setStart(value); } } /*! \brief Returns the start index, or -1 if no Coordinates instance is found in the CoordinateList member. The start index that is returned is the start index of the first Coordinates instance of the CoordinateList member. */ int Oligomer::startIndex() const { if(!CoordinateList::size()) return -1; Coordinates *coordinates = first(); return coordinates->start(); } /*! \brief Sets the end index to \a value. The value is used to construct a Coordinates instance that is allocated on the heap and added to member CoordinateList if that list is empty. If the CoordinateList is not empty, then the value is set to the first Coordinates instance in that list. */ void Oligomer::setEndIndex(int value) { if(!CoordinateList::size()) { Coordinates *coordinates = new Coordinates(); coordinates->setEnd(value); append(coordinates); } else { Coordinates *coordinates = first(); coordinates->setEnd(value); } } /*! \brief Returns the end index. The end index that is returned is the end index of the first Coordinates instance of the CoordinateList member. */ int Oligomer::endIndex() const { if(!CoordinateList::size()) return -1; Coordinates *coordinates = first(); return coordinates->end(); } /*! \brief Set the description to \a description. */ void Oligomer::setDescription(const QString &description) { m_description = description; } /*! \brief Returns the description. */ QString Oligomer::description() const { return m_description; } /*! \brief Add to the member CoordinateList all the Coordinates instances found in \a list. The added Coordinates instances are copies of the instances found in \a list. Returns the count of added Coordinates instances allocated on the heap. */ int Oligomer::appendCoordinates(CoordinateList *list) { Q_ASSERT(list); int count = 0; for(int iter = 0; iter < list->size(); ++iter) { Coordinates *iterCoordinates = list->at(iter); Coordinates *coordinates = new Coordinates(*iterCoordinates); append(coordinates); ++count; } return count; } /*! \brief Set the IonizeRule member to \a ionize_rule. \sa MsXpS::libXpertMass::Ionizable::m_ionizeRule */ void Oligomer::setIonizeRule(IonizeRule &ionize_rule) { m_ionizeRule = ionize_rule; } /*! \brief Returns a reference to the IonizeRule member. */ IonizeRule & Oligomer::ionizeRule() { return m_ionizeRule; } /*! \brief Set the calculation options to \a calc_options. \sa m_calcOptions */ void Oligomer::setCalcOptions(const CalcOptions &calc_options) { m_calcOptions = calc_options; } /*! \brief Returns the calculation options. */ const CalcOptions & Oligomer::calcOptions() const { return m_calcOptions; } /*! \brief Updates the member calculation options with this Oligomer's CoordinateList. The data in m_calcOptions that need updating are the CoordinateList elements. */ void Oligomer::updateCalcOptions() { // The data that need update are the CoordinateList elements // depending on the internal ::OligomerList data. m_calcOptions.setCoordinateList(*(dynamic_cast(this))); } /*! \brief Returns the Monomer in the enclosing Polymer that is located at the start index of this oligomer. \sa atRightEnd(), startIndex() */ const Monomer & Oligomer::atLeftEnd() const { // qDebug() << __FILE__ << __LINE__ << "Going to call at() with value" // << startIndex(); return *(mp_polymer->at(startIndex())); } /*! \brief Returns the Monomer in the enclosing Polymer that is located at the end index of this oligomer. \sa atLeftEnd(), endIndex() */ const Monomer & Oligomer::atRightEnd() const { // qDebug() << __FILE__ << __LINE__ << "Going to call at() with value" // << endIndex(); return *(mp_polymer->at(endIndex())); } /*! \brief Return the Monomer that is located in the enclosing Polymer at \a index. */ const Monomer * Oligomer::monomerAt(int index) const { Q_ASSERT(index >= 0); Q_ASSERT(index < mp_polymer->size() - 1); // qDebug() << __FILE__ << __LINE__ << "Going to call at() with value" // << index; return mp_polymer->at(index); } /*! \brief Returns the member crossLinkList. */ QList * Oligomer::crossLinkList() { return &m_crossLinkList; } /*! \brief Add the \a cross_link CrossLink to this Oligomer's list of \l{CrossLink}s Returns true if the cross-link was added succesfully, or false if \a cross_link was already in the list. */ bool Oligomer::addCrossLink(CrossLink *cross_link) { // Add the cross-link only if it does not exist already. Return true // only if the crossLink has been added. if(!m_crossLinkList.contains(cross_link)) { m_crossLinkList.append(cross_link); return true; } return false; } // ELEMENTAL CALCULATION FUNCTION ///////////////////////////////// /*! \brief Returns the elemental composition of this Oligomer instance. */ QString Oligomer::elementalComposition() { return elementalComposition(m_calcOptions, m_ionizeRule); } /*! \brief Returns the elemental composition of this Oligomer instance. The computation of the elemental composition is performed as configured in \a calc_options and using the ionization described in \a ionize_rule. */ QString Oligomer::elementalComposition(const CalcOptions &calc_options, const IonizeRule &ionize_rule) { int times = 0; if(calc_options.selectionType() == SELECTION_TYPE_RESIDUAL_CHAINS) { times = 1; // qDebug() << __FILE__ << __LINE__ // << "SELECTION_TYPE_RESIDUAL_CHAINS ; times:" << times; } else { // times = CoordinateList::size(); // Use the version whereby we can perform a sanity check that // calc_options was updated with the proper CoordinateList // data. times = calc_options.coordinateList().size(); if(times != CoordinateList::size()) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); // qDebug() << __FILE__ << __LINE__ // << "SELECTION_TYPE_OLIGOMERS ; times:" << times; } return mp_polymer->elementalComposition( ionize_rule, *(static_cast(this)), calc_options); } ///////////////////////////////// // ELEMENTAL CALCULATION FUNCTION /*! \brief Calculates the sequence of this Oligomer and sets it to the Sequence base class member m_monomerText. Returns the size of m_monomerText. If m_polymer is nullptr, return 0. \sa Sequence */ int Oligomer::makeMonomerText() { // Prepare the text version of the oligomer's sequence by basing // it on the coordinates of *this oligomer and set that text to // m_monomerText. if(mp_polymer == nullptr) return 0; QString *text = monomerText(); m_monomerText = *text; delete(text); return m_monomerText.size(); } /*! \brief Returns a string with the sequence of this Oligomer. If mp_polymer is non-nullptr, the sequence is calculated by looking into the Polymer using member Coordinates. If mp_polymer is nullptr and if Sequence::m_monomerText is not empty, that is returned. If mp_polymer is nullptr and if Sequence::m_monomerText is emtpy, then the sequence is crafted by looking into Sequence::m_monomerList. \sa makeMonomerText() */ QString * Oligomer::monomerText() { // Allocate a new string to hold the text version of *this // oligomer's sequence. // There are two situations: // 1. The mp_polymer member is non-0, we can get access to it. Ask // the polymer to do the work. This is the most faithful // situation. But the caller must first ensure that the polymer // actually exists. // 2. The mp_polymer member is 0, we may have the oligomer // sequence stored in *this oligomer. Test that. QString *text = new QString(); if(mp_polymer) { // For each oligomer(if more than one, which is the case when the // oligomer is actually a cross-linked oligomer), create a string... int oligomerCount = CoordinateList::size(); for(int iter = 0; iter < oligomerCount; ++iter) { Coordinates *coordinates = CoordinateList::at(iter); QString *local = mp_polymer->monomerText( coordinates->start(), coordinates->end(), true); text->append(*local); // If this is a cross-linked oligomer and we are not appending // text for the __last__ oligomer, then append "~" to let the // user know that the sequences are cross-linked. if(oligomerCount > 1 && iter < oligomerCount - 1) text->append("~"); delete(local); } } else { if(m_monomerText.size()) { *text = m_monomerText; return text; } else { if(m_monomerList.size()) return Sequence::monomerText(0, m_monomerList.size(), true); } } return text; } /*! \brief Calculates the monoisotopic and average masses. The calculation is performed by computing the mono and avg masses of the sequence stretch as described by the start and end indices in the polymer sequence. Default calculation options are used. Returns true if calculations were successful, false otherwise. */ bool Oligomer::calculateMasses() { CalcOptions calcOptions; calcOptions.setCapping(CAP_NONE); IonizeRule rule; return calculateMasses(&calcOptions, &rule); } /*! \brief Calculates the monoisotopic and average masses. The calculation is performed by computing the mono and avg masses of the sequence stretch as described by the start and end indices in the polymer sequence. The calculations are configured by \a calc_options and the ionization is defined in \a ionize_rule. Returns true if calculations were successful, false otherwise. */ bool Oligomer::calculateMasses(const CalcOptions *calc_options, const IonizeRule *ionize_rule) { Q_ASSERT(calc_options); CalcOptions localOptions(*calc_options); // The coordinates of the oligomer are the following: // MAMISGMSGRKAS // For a tryptic peptide obtained from protein above, we'd have // MAMISGMSGR, that is in the oligomer coordinates: // [0] MAMISGMSGR [9] // When computing the mass of the oligomer, we have to do a // for (iter == [0] ; iter < [9 + 1]; ++iter) // Which is why we increment add 1 to m_endIndex in the function below. // A polymer might be something made of more than one residual chain // in case it is a cross-linked oligomer. Compute the mass fore each // residual chain, without accounting for the cross-links... m_mono = 0; m_avg = 0; // An oligomer _is_ a CoordinateList, and we need that list in the // calcOptions so that we can call the Polymer::accountMasses // function. localOptions.setCoordinateList(*this); // We do not want to take into account the cross-links because // we'll be doing this here and because it cannot work if the // cross-links are taken into account from the polymer. int flags = localOptions.monomerEntities(); flags &= ~MONOMER_CHEMENT_CROSS_LINK; localOptions.setMonomerEntities(flags); Polymer::accountMasses(mp_polymer, localOptions, &m_mono, &m_avg); // qDebug() << __FILE__ << __LINE__ // << "After accounting masses(prior to cross-links):" // << "mono mass is:" << m_mono; // At this point, we have added the mass of each constituent // oligomer's residual chain. Let's deal with the cross-links. for(int iter = 0; iter < m_crossLinkList.size(); ++iter) { CrossLink *crossLink = m_crossLinkList.at(iter); if(!crossLink->accountMasses(&m_mono, &m_avg)) return false; // qDebug() << __FILE__ << __LINE__ // << "After accounting cross-link:" // << crossLink->name() // << "mono mass is:" << m_mono; } // If an ionizeRule is provided, use it. Otherwise, ionize // automatically using the ::Ionizable IonizeRule. if(ionize_rule) { // Note that if ionizeRule is invalid, then the ionization is // not performed. if(ionize_rule->isValid()) { /* if (ionize(mp_polymer, *ionizeRule) == -1) The line above is a huge bug. While we should be ionizing this oligomer, we end up ionizing the polymer ! */ if(ionize(*ionize_rule) == -1) return false; } } else { if(ionize() == -1) return false; } // qDebug() << __FILE__ << __LINE__ // << "Coming out from the calculateMasses function:" // << "mono mass is:" << m_mono; return true; } /*! \brief Set the m_isModified status of this Oligomer to \a modified. */ void Oligomer::setModified(bool modified) { m_isModified = modified; } /*! \brief Returns the chemical modification status of this Oligomer. If \a deep is true, the enclosing Polymer must exist (m_polymer cannot be nullptr) because each monomer of the Polymer is probed for a potential modification. This function returns true as soon as such a modified monomer is encountered. If \a deep is false, the value of m_isModified is returned. \sa m_isModified, Monomer::isModified() */ bool Oligomer::isModified(bool deep /*false*/) { // Either we truly go to the polymer instance and check if the oligomer is // modified or we just ask for the member datum, that might have been set, // for example, during creation of the oligomer in the Cleaver::cleave() // function. We need the possibility to ask for the member datum because // there are circumstances where the oligomer exists and not the original // polymer (for example if the polymer sequence is edited while a set of // cleavage oligomers is displayed in the cleavage dialog. When the // tableviewmodel needs to refresh the contents of the cells, it crashes // because the polymer has been edited and one monomer is missing from the // sequence of the oligomer as it had been configured in the first place. if(deep) { if(mp_polymer == nullptr) qFatal("Programming error."); int oligomerCount = CoordinateList::size(); for(int iter = 0; iter < oligomerCount; ++iter) { Coordinates *coordinates = CoordinateList::at(iter); for(int jter = coordinates->start(); jter < coordinates->end() + 1; ++jter) { // qDebug() << __FILE__ << __LINE__ << "Going to call at() // with value" // << iter; const Monomer *monomer = mp_polymer->at(jter); if(monomer->isModified()) return true; } } return false; } else { return m_isModified; } } /*! \brief Returns the size of this Oligomer. The size is computed by adding the length of all the regions of the enclosing Polymer as documented in the Coordinates instances in the member coordinateList. */ int Oligomer::size() { int sum = 0; int oligomerCount = CoordinateList::size(); // The size of an oligomer is the sum of all its oligomeric // components as described by the various coordinates. for(int iter = 0; iter < oligomerCount; ++iter) { Coordinates *coordinates = CoordinateList::at(iter); sum += coordinates->length(); } return sum; } /*! \brief Returns true if this Oligomer spans at least one region of the enclosing polymer that contains a Monomer at \a index, false otherwise. */ bool Oligomer::encompasses(int index) const { int oligomerCount = CoordinateList::size(); for(int iter = 0; iter < oligomerCount; ++iter) { Coordinates *coordinates = CoordinateList::at(iter); if(index <= coordinates->start() && index >= coordinates->end()) return true; } return false; } /*! \brief Returns true if this Oligomer spans at least one region of the enclosing polymer that contains the Monomer \a monomer, false otherwise. The search is performed by comparing pointers, thus the Monomer to be search \e is \a monomer. */ bool Oligomer::encompasses(const Monomer *monomer) const { int oligomerCount = CoordinateList::size(); for(int iter = 0; iter < oligomerCount; ++iter) { Coordinates *coordinates = CoordinateList::at(iter); for(int jter = coordinates->start(); jter < coordinates->end() + 1; ++jter) { // qDebug() << __FILE__ << __LINE__ << "Going to call at() with // value" // << jter; if(mp_polymer->at(jter) == monomer) return true; } } return false; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/PeakCentroid.cpp000664 001750 001750 00000006036 14647465366 023261 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "PeakCentroid.hpp" #include namespace MsXpS { namespace libXpertMass { PeakCentroid::PeakCentroid(double mz, double intensity) { Q_ASSERT(mz >= 0); Q_ASSERT(intensity >= 0); m_mz = mz; m_intensity = intensity; } PeakCentroid::PeakCentroid() : m_mz(0), m_intensity(0) { } PeakCentroid::PeakCentroid(const PeakCentroid &other) : m_mz(other.m_mz), m_intensity(other.m_intensity) { } PeakCentroid::~PeakCentroid() { } PeakCentroid & PeakCentroid::operator=(const PeakCentroid &other) { if(&other == this) return *this; m_mz = other.m_mz; m_intensity = other.m_intensity; return *this; } bool PeakCentroid::operator==(const PeakCentroid &other) { return (m_mz == other.m_mz && m_intensity == other.m_intensity); } bool PeakCentroid::operator!=(const PeakCentroid &other) { return (m_mz != other.m_mz || m_intensity != other.m_intensity); } void PeakCentroid::setMz(double value) { m_mz = value; } double PeakCentroid::mz() const { return m_mz; } void PeakCentroid::setIntensity(double value) { Q_ASSERT(value >= 0); m_intensity = value; } void PeakCentroid::incrementIntensity(double value) { Q_ASSERT(value >= 0); m_intensity += value; } double PeakCentroid::intensity() const { return m_intensity; } QString PeakCentroid::intensity(int decimalPlaces) const { if(decimalPlaces < 0) decimalPlaces = 0; QString prob; prob.setNum(m_intensity, 'f', decimalPlaces); return prob; } QString PeakCentroid::toString() const { QString text = QString("m/z: %1, int: %2)\n").arg(m_mz).arg(m_intensity); return text; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/PkaPhPi.cpp000664 001750 001750 00000077544 14647465366 022221 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Std includes #include /////////////////////// Local includes #include "PkaPhPi.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::PkaPhPi \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile PkaPhPi.hpp \brief The PkaPhPi class provides a model for specifying the acido-basic properties of a chemical entity. */ /*! \variable MsXpS::libXpertMass::PkaPhPi::m_ph \brief The pH of the environment. This pH value is required to compute the number of charges of a given chemical entity (a \l Polymer) sequence, for example. */ /*! \variable MsXpS::libXpertMass::PkaPhPi::m_pi \brief The pI of the chemical entity. */ /*! \variable MsXpS::libXpertMass::PkaPhPi::m_polymer \brief The polymer of which the acidobasic properties are computed. */ /*! \variable MsXpS::libXpertMass::PkaPhPi::m_calcOptions \brief The \l CalcOptions that configure the way the computations are to be carried out. */ /*! \variable MsXpS::libXpertMass::PkaPhPi::m_positiveCharges \brief The count of positive charges. */ /*! \variable MsXpS::libXpertMass::PkaPhPi::m_negativeCharges \brief The count of negative charges. */ /*! \variable MsXpS::libXpertMass::PkaPhPi::mpa_monomerList \brief The list of \l Monomer instances as read from the pka_ph_pi.xml file. The PkaPhPi instance takes ownership of the items and of the list itself. */ /*! \variable MsXpS::libXpertMass::PkaPhPi::mpa_modifList \brief The list of \l Modif instances as read from the pka_ph_pi.xml file. The PkaPhPi instance takes ownership of the items and of the list itself. */ /*! \brief Constructs a PkaPhPi instance. \list \li \a polymer: .The polymer within the context of which the calculations are performed. \li \a calc_options: The options driving the calculations. \li \a monomer_list_p: .The list of \l Monomer instances. \li \a modif_list_p: .The list of \l Modif instances. \endlist */ PkaPhPi::PkaPhPi(libXpertMass::Polymer &polymer, libXpertMass::CalcOptions &calc_options, QList *monomer_list_p, QList *modif_list_p) : m_polymer(polymer), m_calcOptions(calc_options), mpa_monomerList(monomer_list_p), mpa_modifList(modif_list_p) { } /*! \brief Destructs this PkaPhPi instance. */ PkaPhPi::~PkaPhPi() { if(mpa_monomerList) { while(!mpa_monomerList->isEmpty()) delete mpa_monomerList->takeFirst(); delete mpa_monomerList; mpa_monomerList = 0; } if(mpa_modifList) { while(!mpa_modifList->isEmpty()) delete mpa_modifList->takeFirst(); delete mpa_modifList; mpa_modifList = 0; } } /*! \brief Sets the pH to \a ph. */ void PkaPhPi::setPh(double ph) { Q_ASSERT(ph > 0 && ph < 14); m_ph = ph; } /*! \brief Returns the pH. */ double PkaPhPi::ph() { return m_ph; } /*! \brief Returns the pI. */ double PkaPhPi::pi() { return m_pi; } /*! \brief Returns the positive charges. */ double PkaPhPi::positiveCharges() { return m_positiveCharges; } /*! \brief Returns the negative charges. */ double PkaPhPi::negativeCharges() { return m_negativeCharges; } /*! \brief Sets the calculation options to \a calc_options. */ void PkaPhPi::setCalcOptions(const libXpertMass::CalcOptions &calc_options) { m_calcOptions = calc_options; } /*! \brief Sets the monomer list to \a monomer_list_p. The list and its contents are now owned by this PkaPhPi instance. */ void PkaPhPi::setMonomerList(QList *monomer_list_p) { Q_ASSERT(monomer_list_p); mpa_monomerList = monomer_list_p; } /*! \brief Sets the modification list to \a modif_list_p. The list and its contents are now owned by this PkaPhPi instance. */ void PkaPhPi::setModifList(QList *modif_list_p) { Q_ASSERT(modif_list_p); mpa_modifList = modif_list_p; } /*! \brief Calculates the charges (positive and negative). The general scheme is : Get the list of the coordinates of the different \l Polymer region selections. For each first monomer and end monomer of a given region selection, check if the the region is an oligomer or a residual chain (m_selectionType of libXpertMass::CalcOptions); act accordingly. Also, check for each selection region if it encompasses the polymer left/right end. If the left/right end modifications are to be taken into account, act accordingly. The positive and negative charges are stored in the member \l m_positiveCharges and \l m_negativeCharges variables. Returns the count of chemical groups that have been processed. \sa calculatePi() */ int PkaPhPi::calculateCharges() { int processedChemicalGroups = 0; m_positiveCharges = 0; m_negativeCharges = 0; // We of course need monomers ! Instead, we may not need modifs. if(!mpa_monomerList) return -1; int polymerSize = m_polymer.size(); const libXpertMass::CoordinateList &coordinateList = m_calcOptions.coordinateList(); for(int iter = 0; iter < coordinateList.size(); ++iter) { libXpertMass::Coordinates *coordinates = coordinateList.at(iter); int startIndex = coordinates->start(); int endIndex = coordinates->end(); bool leftMostCoordinates = coordinateList.isLeftMostCoordinates(coordinates); bool rightMostCoordinates = coordinateList.isRightMostCoordinates(coordinates); for(int jter = startIndex; jter < endIndex + 1; ++jter) { const libXpertMass::Monomer *seqMonomer = m_polymer.at(jter); // qDebug() << __FILE__ << __LINE__ // << "-- libXpertMass::Monomer:" << seqMonomer->name() // << "position:" << jter + 1; // Find a monomer by the same code in our list of monomers // that have been fed with chemical group data. Note that // all the monomers in a given sequence must not // necessarily have a counterpart in the local list of // monoemers. For example, there might be cases in which a // given monomer might not bring any charge whatsoever. int index = libXpertMass::Monomer::isCodeInList(seqMonomer->code(), *mpa_monomerList); if(index == -1) continue; const libXpertMass::Monomer *monomer = mpa_monomerList->at(index); Q_ASSERT(monomer); // A monomer can have multiple such "CHEMICAL_GROUP" // properties. Indeed, for example for proteins, a monomer // might have three such chemical groups(and thus three // libXpertMass::Prop objects): one for the alpha NH2, one for the alpha // COOH and one for a residual chain chemical group, like // epsilon NH2 for lysine. for(int kter = 0; kter < monomer->propList().size(); ++kter) { libXpertMass::Prop *prop = monomer->propList().at(kter); if(prop->name() != "CHEMICAL_GROUP") continue; // qDebug() << __FILE__ << __LINE__ // << "Monomer has property CHEMICAL_GROUP..."; // Get the chemical group out of the property. libXpertMass::ChemicalGroup *chemicalGroup = static_cast(prop->data()); if(chemicalGroup->polRule() & ChemicalGroupTrapping::LEFT_TRAPPED) { // qDebug() << __FILE__ << __LINE__ // << "... that is CHEMGROUP_LEFT_TRAPPED"; // The chemical group we are dealing with is trapped // when the monomer is polymerized on the left end, that // is if the monomer is not the left end monomer of the // sequence being analyzed. // Thus we only can take it into account if one of // two conditions are met: // 1. The monomer is the left end monomer of the // whole polymer sequence. // 2. The monomer is the left end monomer of the // region selection AND the selection type is // oligomers(thus it does not get polymerized to // the previous selection region). if(jter > 0) { // Clearly we are not dealing with the left // end of the polymer, so check if we have to // account for this chemical group or not. if(!leftMostCoordinates) { // The current libXpertMass::Coordinates is not the // left-most libXpertMass::Coordinates in the // libXpertMass::CoordinateList, thus we cannot consider // it to be the "left end coordinates" of // the libXpertMass::CoordinateList. Just continue // without exploring any more. continue; } if(jter == startIndex) { // The current monomer is the first // monomer of libXpertMass::Coordinates. We only take // into account the chemical group if each // libXpertMass::Coordinates is to be considered an // oligomer. if(m_calcOptions.selectionType() != libXpertMass::SELECTION_TYPE_OLIGOMERS) continue; } } } if(chemicalGroup->polRule() & ChemicalGroupTrapping::RIGHT_TRAPPED) { // qDebug() << __FILE__ << __LINE__ // << "... that is CHEMGROUP_RIGHT_TRAPPED"; // See explanations above. if(jter < polymerSize - 1) { // Clearly, we are not dealing with the right // end of the polymer. if(!rightMostCoordinates) { // The current libXpertMass::Coordinates is not the // right-most libXpertMass::Coordinates of the // libXpertMass::CoordinateList, thus we cannot consider // it to be the "right end coordinates" of // the libXpertMass::CoordinateList. Just continue // without exploring anymore. continue; } if(jter == endIndex) { // The current monomer is the last monomer // of libXpertMass::Coordinates. We only take into // account the chemical group if each // libXpertMass::Coordinates is to be considered an // oligomer(and not a residual chain). if(m_calcOptions.selectionType() != libXpertMass::SELECTION_TYPE_OLIGOMERS) continue; } } } if(iter == 0 && m_calcOptions.polymerEntities() & libXpertMass::POLYMER_CHEMENT_LEFT_END_MODIF) { // We are iterating in the monomer that is at the // beginning of the polymer sequence. We should // test if the chemical group we are dealing with // right now has a special rule for the left end // of the polymer sequence region. int ret = accountPolymerEndModif( libXpertMass::POLYMER_CHEMENT_LEFT_END_MODIF, *chemicalGroup); if(ret >= 0) { // qDebug() << __FILE__ << __LINE__ // << "Accounted for left end modif."; processedChemicalGroups += ret; continue; } } if(iter == polymerSize - 1 && m_calcOptions.polymerEntities() & libXpertMass::POLYMER_CHEMENT_RIGHT_END_MODIF) { int ret = accountPolymerEndModif( libXpertMass::POLYMER_CHEMENT_RIGHT_END_MODIF, *chemicalGroup); if(ret >= 0) { // qDebug() << __FILE__ << __LINE__ // << "Accounted for right end modif."; processedChemicalGroups += ret; continue; } } if(m_calcOptions.monomerEntities() & libXpertMass::MONOMER_CHEMENT_MODIF && seqMonomer->isModified()) { int ret = accountMonomerModif(*seqMonomer, *chemicalGroup); if(ret >= 0) { // qDebug() << __FILE__ << __LINE__ // << "Accounted for monomer modif."; processedChemicalGroups += ret; continue; } } double charge = calculateChargeRatio( chemicalGroup->pka(), chemicalGroup->isAcidCharged()); // qDebug() << __FILE__ << __LINE__ // << "Charge:" << charge; if(charge < 0) m_negativeCharges += charge; else if(charge > 0) m_positiveCharges += charge; // qDebug() << __FILE__ << __LINE__ // << "Pos =" << m_positiveCharges // << "Neg = " << m_negativeCharges; ++processedChemicalGroups; } // End of // for (int kter = 0; kter < monomer->propList().size(); ++kter) // qDebug() << __FILE__ << __LINE__ // << "End dealing with libXpertMass::Monomer:" << // seqMonomer->name() // << "position:" << jter + 1; } // End of // for (int jter = startIndex; jter < endIndex + 1; ++jter) // qDebug() << __FILE__ << __LINE__ // << "End dealing with libXpertMass::Coordinates"; } // End of // for (int iter = 0; iter < coordinateList.size(); ++iter) // We have finished processing all the libXpertMass::Coordinates in the list. return processedChemicalGroups; } /*! \brief Calculates the isoelectric point. The isoelectric point is the pH at which a given analyte will have a net charge of 0, that is, when the count of negative charges will be equal to the count of positive charges. The pI will be stored in the \l m_pi member variable. Returns the count of chemical groups that have been processed. \sa calculateCharges() */ int PkaPhPi::calculatePi() { int processedChemicalGroups = 0; int iteration = 0; double netCharge = 0; double firstCharge = 0; double thirdCharge = 0; // We of course need monomers ! Instead, we may not need modifs. if(!mpa_monomerList) { m_pi = 0; m_ph = 0; return -1; } m_ph = 0; while(true) { // qDebug() << "Current pH being tested:" << m_ph; processedChemicalGroups = calculateCharges(); if(processedChemicalGroups == -1) { qDebug() << "Failed to calculate net charge for pH" << m_ph; m_pi = 0; m_ph = 0; return -1; } netCharge = m_positiveCharges + m_negativeCharges; // Note that if the 0.01 tested_ph step is enough to switch the // net charge from one excess value to another excess value in // the opposite direction, we'll enter an infinite loop. // // The evidence for such loop is that every other two measures, // the net charge of the polymer sequence will be the same. // // Here we test this so that we can break the loop. ++iteration; if(iteration == 1) { firstCharge = netCharge; } else if(iteration == 3) { thirdCharge = netCharge; if(firstCharge == thirdCharge) break; iteration = 0; firstCharge = netCharge; } // At this point we have to test the net charge: if(netCharge >= -0.1 && netCharge <= 0.1) { // qDebug() << "Breaking loop with netCharge:" << netCharge; break; } if(netCharge > 0) { // The test ph is too low. m_ph += 0.01; // qDebug() << "Set new pH m_ph += 0.01:" << m_ph // << "netCharge:" << netCharge; continue; } if(netCharge < 0) { // The test ph is too high. m_ph -= 0.01; // qDebug() << "Set new pH m_ph -= 0.01:" << m_ph // << "netCharge:" << netCharge; continue; } } // End of // while(true) // At this point m_pi is m_ph. m_pi = m_ph; // qDebug() << "pi is:" << m_pi; return processedChemicalGroups; } /*! \brief Returns the ratio between the charged and the uncharged forms of the chemical entity using \a pka and \a is_acid_charged. If the charge is negative, the returned ratio is negative, positive otherwise. The charged and uncharged species are the AH an A- species of the acido-basic theory. The calculation is based on the use of the m_ph member variable value. */ double PkaPhPi::calculateChargeRatio(double pka, bool is_acid_charged) { double aOverAh = 0; if(pka < 0) return 0; if(pka > 14) return 0; if(m_ph < 0) return 0; if(m_ph > 14) return 0; // Example with pKa = 4.25(Glu) ; pH = 4.16, thus we are more // acidic than pKa, we expect AH to be greater than A by a small // margin. aOverAh = (double)pow(10, (m_ph - pka)); // aOverAh = 0.81283051616409951 (confirmed manually) if(aOverAh < 1) { /* The solution contains more acid forms(AH) than basic forms (A). */ if(is_acid_charged) return (1 - aOverAh); else // The acid is not charged, that is, it is a COOH. // AH = 1 - A // A = aOverAh.AH // A = aOverAh.(1-A) // A = aOverAh - aOverAh.A // A(1+aOverAh) = aOverAh // A = aOverAh /(1+aOverAh), for us this is // A = 0.81283 / 1.81283 = 0.448 // And not - aOverAh, that is - aOverAh. // Below seems faulty(20071204) // return(- aOverAh); // Tentative correction(20071204) return (-(aOverAh / (1 + aOverAh))); } else if(aOverAh > 1) { /* The solution contains more basic forms(A) than acid forms (AH). */ if(is_acid_charged) return (1 / aOverAh); else return (-(1 - (1 / aOverAh))); } else if(aOverAh == 1) { /* The solution contains as many acid forms(AH) as basic forms (H). */ if(is_acid_charged) return (aOverAh / 2); else return (-aOverAh / 2); } else qFatal("Programming error."); return 0; } /*! \brief Accounts for the \a chemical_group in the context of the \l Polymer \a end_modif. A chemical group is described as follows: \code C N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped Lateral SH2 8.3 FALSE never_trapped \endcode Returns the count of rules that were accounted for, or -1 if none was. */ int PkaPhPi::accountPolymerEndModif(PolymerChemEnt end_modif, const libXpertMass::ChemicalGroup &chemical_group) { QString modifName; libXpertMass::ChemicalGroupRule *rule = nullptr; int count = 0; // Get the name of the modification of the polymer (if any) and get // the rule dealing with that polymer modification (if any). if(end_modif & libXpertMass::POLYMER_CHEMENT_LEFT_END_MODIF) { modifName = m_polymer.leftEndModif().name(); rule = chemical_group.findRule("LE_PLM_MODIF", modifName); } else if(end_modif & libXpertMass::POLYMER_CHEMENT_RIGHT_END_MODIF) { modifName = m_polymer.rightEndModif().name(); rule = chemical_group.findRule("RE_PLM_MODIF", modifName); } else qFatal("Programming erro."); // The polymer might not be modified, and also the chemical group // passed as parameter might not contain any rule about any polymer // modification. In that case we just have nothing to do. if(modifName.isEmpty()) { if(rule) { double charge = calculateChargeRatio(chemical_group.pka(), chemical_group.isAcidCharged()); if(charge < 0) m_negativeCharges += charge; else if(charge > 0) m_positiveCharges += charge; return ++count; } else { // The polymer end was NOT modified and the chemical group // was NOT eligible for a polymer end modification. This // means that we do not have to process it, and we return -1 // so that the caller function knows we did not do anything // and that this chemical group should continue to undergo // analysis without skipping it. return -1; } } // End of // if (modifName.isEmpty()) if(!rule) { // This chemical group was not "designed" to receive any polymer // end modification, so we have nothing to do with it and we // return -1 so that the caller function knows we did not do // anything and that this chemical group should continue to // undergo analysis without skipping it. return -1; } // At this point we know that the chemical group 'group' we are // analyzing is eligible for a polymer left end modification and // that it is indeed modified with a correcct modification. So we // have a rule for it. Let's continue the analysis. // Apparently the rule has data matching the ones we are looking // for. At this point we should now what action to take for this // group. if(rule->fate() == ChemicalGroupRuleFate::LOST) { // We do not use the current chemical group 'group' because the // polymer end's modification has abolished it. } else if(rule->fate() == ChemicalGroupRuleFate::PRESERVED) { double charge = calculateChargeRatio(chemical_group.pka(), chemical_group.isAcidCharged()); if(charge < 0) m_negativeCharges += charge; else if(charge > 0) m_positiveCharges += charge; return ++count; } else qFatal("Programming error."); // Whatever we should do with the left/right end monomer's chemgroup, // we should take into account the modification itself that might // have brought chemgroup(s) worth calculating their intrinsic // charges! // Find a modif object in the local list of modif objects, that has // the same name as the modification with which the left/right end // of the polymer is modified. We'll see what chemgroup(s) this // modification brings to the polymer sequence. int index = libXpertMass::Modif::isNameInList(modifName, *mpa_modifList); if(index == -1) { // qDebug() << __FILE__ << __LINE__ // << "Information: following modif not in local list:" // << modifName; return count; } const libXpertMass::Modif *modif = mpa_modifList->at(index); Q_ASSERT(modif); for(int jter = 0; jter < modif->propList().size(); ++jter) { libXpertMass::Prop *prop = modif->propList().at(jter); if(prop->name() != "CHEMICAL_GROUP") continue; // Get the chemical group out of the property. const libXpertMass::ChemicalGroup *chemicalGroup = static_cast(prop->data()); double charge = calculateChargeRatio(chemicalGroup->pka(), chemicalGroup->isAcidCharged()); if(charge < 0) m_negativeCharges += charge; else if(charge > 0) m_positiveCharges += charge; ++count; } return count; } /*! \brief Accounts for the \a chemical_group in the context of the \a monomer. A chemical group is described as follows: \code C N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped Lateral SH2 8.3 FALSE never_trapped \endcode Returns the count of rules that were accounted for, or -1 if none was. */ int PkaPhPi::accountMonomerModif(const libXpertMass::Monomer &monomer, libXpertMass::ChemicalGroup &chemical_group) { QString modifName; libXpertMass::ChemicalGroupRule *rule = 0; int count = 0; // For each modification in the monomer, make the accounting work. Q_ASSERT(mpa_modifList); Q_ASSERT(mpa_modifList->size()); for(int iter = 0; iter < monomer.modifList()->size(); ++iter) { libXpertMass::Modif *iterModif = monomer.modifList()->at(iter); // Get the name of the modification of the monomer(if any) and get // the rule dealing with that monomer modification(if any). modifName = iterModif->name(); rule = chemical_group.findRule("MONOMER_MODIF", modifName); if(modifName.isEmpty()) { // The monomer does not seem to be modified. However, we still // have to make sure that the chemgroup that we were parsing is // actually a chemgroup suitable for a modif. If this chemgroup // was actually suitable for a monomer modif, but it is not // effectively modified, that means that we have to calculate // the charge for the non-modified chemgroup... if(rule) { double charge = calculateChargeRatio( chemical_group.pka(), chemical_group.isAcidCharged()); if(charge < 0) m_negativeCharges += charge; else if(charge > 0) m_positiveCharges += charge; return ++count; } else { // The current monomer was NOT modified, and the chemgroup // was NOT eligible for a monomer modification. This means // that we do not have to process it, and we return -1 so // that the caller function knows we did not do anything and // that this chemgroup should continue to undergo analysis // without skipping it. return -1; } } // End of // if (modifName.isEmpty()) if(!rule) { // This chemgroup was not "designed" to receive any // modification, so we have nothing to do with it, and we return // -1 to let the caller know that its processing should be // continued in the caller's function space. return -1; } // At this point, we know that the chemgroup we are analyzing is // eligible for a modification and that we have a rule for it. Let's // continue the analysis: // Apparently, a rule object has member data matching the ones we // were looking for. At this point we should know what action to // take for this chemgroup. if(rule->fate() == ChemicalGroupRuleFate::LOST) { // We do not use the current chemical group 'group' because the // monomer modification has abolished it. } else if(rule->fate() == ChemicalGroupRuleFate::PRESERVED) { double charge = calculateChargeRatio(chemical_group.pka(), chemical_group.isAcidCharged()); if(charge < 0) m_negativeCharges += charge; else if(charge > 0) m_positiveCharges += charge; return ++count; } else Q_ASSERT(0); // Whatever we should do with this monomer's chemgroup, we should // take into account the modification itself that might have brought // chemgroup(s) worth calculating their intrinsic charges! // Find a modif object in the local list of modif objects, that has // the same name as the modification with which the monomer is // modified. We'll see what chemgroup(s) this modification brings to // the polymer sequence. int index = libXpertMass::Modif::isNameInList(modifName, *mpa_modifList); if(index == -1) { // qDebug() << __FILE__ << __LINE__ // << "Information: following modif not in local list:" // << modifName; return count; } libXpertMass::Modif *modif = mpa_modifList->at(index); Q_ASSERT(modif); for(int jter = 0; jter < modif->propList().size(); ++jter) { libXpertMass::Prop *prop = modif->propList().at(jter); if(prop->name() != "CHEMICAL_GROUP") continue; // Get the chemical group out of the property. const libXpertMass::ChemicalGroup *chemicalGroup = static_cast(prop->data()); double charge = calculateChargeRatio(chemicalGroup->pka(), chemicalGroup->isAcidCharged()); if(charge < 0) m_negativeCharges += charge; else if(charge > 0) m_positiveCharges += charge; ++count; } } return count; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/PkaPhPiDataParser.cpp000664 001750 001750 00000031172 14647465366 024153 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #include #include /////////////////////// Local includes #include "PkaPhPiDataParser.hpp" #include "ChemicalGroup.hpp" #include "PolChemDefEntity.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::PkaPhPiDataParser \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile PkaPhPiDataParser.hpp \brief The PkaPhPiDataParser class provides a file reader for the pKa, pH, pI data XML file.. The format is the following: \code A N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped [...] C N-term NH2 9.6 TRUE left_trapped LE_PLM_MODIF Acetylation LOST C-term COOH 2.35 FALSE right_trapped Lateral SH2 8.3 FALSE never_trapped Phosphorylation none_set 1.2 FALSE none_set 6.5 FALSE \endcode */ /*! \variable MsXpS::libXpertMass::PkaPhPiDataParser::mcsp_polChemDef \brief The polymer chemistry definition. */ /*! \variable MsXpS::libXpertMass::PkaPhPiDataParser::m_filePath \brief The path the file that contains all the specifications for chemical groups and chemical rules. */ /*! \brief Constructs a PkaPhPiDataParser instance. \list \li \a pol_chem_def_csp: The polymer chemistry definition (cannot be nullptr). \li \a file_path: the specification file path. \endlist */ PkaPhPiDataParser::PkaPhPiDataParser( const libXpertMass::PolChemDefCstSPtr pol_chem_def_csp, QString file_path) : mcsp_polChemDef(pol_chem_def_csp), m_filePath(file_path) { Q_ASSERT(mcsp_polChemDef); } /*! \brief Destructs this PkaPhPiDataParser instance */ PkaPhPiDataParser::~PkaPhPiDataParser() { } /*! \brief Sets the \a file_path. */ void PkaPhPiDataParser::setFilePath(const QString &file_path) { m_filePath = file_path; } /*! \brief Returns the file path. */ const QString & PkaPhPiDataParser::filePath() { return m_filePath; } /*! \brief Parses the file and fills-in the \a monomer_list_p: the list of \l Monomer instances documented in the file. \a modif_list_p: the list of \l Modif instances documented in the file. Returns true upon success, false otherwise. */ bool PkaPhPiDataParser::renderXmlFile(QList *monomer_list_p, QList *modif_list_p) { Q_ASSERT(monomer_list_p); Q_ASSERT(modif_list_p); // // // // A // // N-term NH2 // 9.6 // TRUE // left_trapped // // LE_PLM_MODIF // Acetylation // LOST // // // // C-term COOH // 2.35 // FALSE // right_trapped // // // [...] // // C // // N-term NH2 // 9.6 // TRUE // left_trapped // // LE_PLM_MODIF // Acetylation // LOST // // // // C-term COOH // 2.35 // FALSE // right_trapped // // // Lateral SH2 // 8.3 // FALSE // never_trapped // // // // // // Phosphorylation // // none_set // 1.2 // FALSE // // // none_set // 6.5 // FALSE // // // // // // The DTD stipulates that: // // // // // // QDomDocument doc("pkaPhPiData"); QDomElement element; QDomElement child; QDomElement indentedChild; QFile file(m_filePath); if(!file.open(QIODevice::ReadOnly)) return false; if(!doc.setContent(&file)) { file.close(); return false; } file.close(); element = doc.documentElement(); if(element.tagName() != "pkaphpidata") { qDebug() << __FILE__ << __LINE__ << "pKa-pH-pI data file is erroneous\n"; return false; } // The first child element must be . child = element.firstChildElement(); if(child.tagName() != "monomers") { qDebug() << __FILE__ << __LINE__ << "pKa-pH-pI data file is erroneous\n"; return false; } // Parse the elements. indentedChild = child.firstChildElement(); while(!indentedChild.isNull()) { if(indentedChild.tagName() != "monomer") return false; QDomElement superIndentedElement = indentedChild.firstChildElement(); if(superIndentedElement.tagName() != "code") return false; libXpertMass::Monomer *monomer = new libXpertMass::Monomer( mcsp_polChemDef, "NOT_SET", superIndentedElement.text()); // All the elements, if any. superIndentedElement = superIndentedElement.nextSiblingElement(); while(!superIndentedElement.isNull()) { if(superIndentedElement.tagName() != "mnmchemgroup") { delete monomer; return false; } libXpertMass::ChemicalGroup *chemGroup = new libXpertMass::ChemicalGroup("NOT_SET"); if(!chemGroup->renderXmlMnmElement(superIndentedElement)) { delete monomer; delete chemGroup; return false; } libXpertMass::ChemicalGroupProp *prop = new libXpertMass::ChemicalGroupProp("CHEMICAL_GROUP", chemGroup); monomer->appendProp(prop); superIndentedElement = superIndentedElement.nextSiblingElement(); } monomer_list_p->append(monomer); indentedChild = indentedChild.nextSiblingElement(); } #if 0 qDebug() << __FILE__ << __LINE__ << "Debug output of all the monomers parsed:"; for (int iter = 0; iter < monomerList->size(); ++iter) { Monomer *monomer = monomerList->at(iter); qDebug() << __FILE__ << __LINE__ << "Monomer:" << monomer->name(); for(int jter = 0; jter < monomer->propList()->size(); ++jter) { libXpertMass::Prop *prop = monomer->propList()->at(jter); if (prop->name() == "CHEMICAL_GROUP") { const libXpertMass::ChemicalGroup *chemGroup = static_cast(prop->data()); qDebug() << __FILE__ << __LINE__ << "Chemical group:" << chemGroup->name() << chemGroup->pka(); } } } #endif // And now parse the elements, if any, this time, as // this element is not compulsory. child = child.nextSiblingElement(); if(child.isNull()) return true; if(child.tagName() != "modifs") { qDebug() << __FILE__ << __LINE__ << "pKa-pH-pI data file is erroneous\n"; return false; } // Parse the elements. indentedChild = child.firstChildElement(); while(!indentedChild.isNull()) { if(indentedChild.tagName() != "modif") return false; QDomElement superIndentedElement = indentedChild.firstChildElement(); if(superIndentedElement.tagName() != "name") return false; libXpertMass::Modif *modif = new libXpertMass::Modif(mcsp_polChemDef, superIndentedElement.text(), "H0"); // All the elements, if any. superIndentedElement = superIndentedElement.nextSiblingElement(); while(!superIndentedElement.isNull()) { if(superIndentedElement.tagName() != "mdfchemgroup") { delete modif; return false; } libXpertMass::ChemicalGroup *chemGroup = new libXpertMass::ChemicalGroup("NOT_SET"); if(!chemGroup->renderXmlMdfElement(superIndentedElement)) { delete modif; delete chemGroup; return false; } libXpertMass::ChemicalGroupProp *prop = new libXpertMass::ChemicalGroupProp("CHEMICAL_GROUP", chemGroup); modif->appendProp(prop); superIndentedElement = superIndentedElement.nextSiblingElement(); } modif_list_p->append(modif); indentedChild = indentedChild.nextSiblingElement(); } #if 0 qDebug() << __FILE__ << __LINE__ << "Debug output of all the modifs parsed:"; for (int iter = 0; iter < modifList->size(); ++iter) { Modif *modif = modifList->at(iter); // qDebug() << __FILE__ << __LINE__ // << "Modif:" << modif->name(); for(int jter = 0; jter < modif->propList()->size(); ++jter) { libXpertMass::Prop *prop = modif->propList()->at(jter); if (prop->name() == "CHEMICAL_GROUP") { const libXpertMass::ChemicalGroup *chemGroup = static_cast(prop->data()); qDebug() << __FILE__ << __LINE__ << "Chemical group:" << chemGroup->name() << chemGroup->pka(); } } } #endif return true; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/PolChemDef.cpp000664 001750 001750 00000123402 14647465366 022654 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include #include #include /////////////////////// Local includes #include "PolChemDef.hpp" #include "Monomer.hpp" #include "Polymer.hpp" #include "IsotopicDataLibraryHandler.hpp" #include "IsotopicDataUserConfigHandler.hpp" int polChemDefSPtrMetaTypeId = qRegisterMetaType( "MsXpS::libXpertMass::PolChemDefSPtr"); int polChemDefCstSPtrMetaTypeId = qRegisterMetaType( "MsXpS::libXpertMass::PolChemDefCstSPtr"); namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::PolChemDef \inmodule libXpertMass \ingroup PolChemDef \inheaderfile PolChemDef.hpp \brief The PolChemDef class provides a complete set of chemical entities fully qualifying a polymer chemistry definition, like Proteins, Saccharides or Nucleic acids. The PolChemDef class provides a full set of chemical entity definitions (\l{Isotope}s, \l{Monomer}s, chemical \l{Modif}ications, \l{CrossLink}s), chemical reaction models (in the liquid or gas phase, like \l Polymer cleavage: \l CleaveSpec or \l Oligomer fragmentation: \l FragSpec)\dots */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_name \brief The name of the polymer chemistry definition, like \e{protein-1-letter} or \e{nucac}, for example. This name is typically identical to both the name of the directory where all the data defining this \c PolChemDef is stored and the name of the XML file that contains the definition itself. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_xmlDataFilePath \brief The path to the XML data file that contains the description of this polymer chemistry definition.. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_isotopicDataFilePath \brief The path to the file that contains this polymer chemistry definition's isotopic data. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_leftCap \brief The \l Formula that defines how of the left end of a polymer sequence of this \l PolChemDef needs to be capped in order to finalize the polymerization state of the \l Polymer sequence. \sa PolChemDef::m_rightCap */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_rightCap \brief The \l Formula that defines how of the right end of a polymer sequence of this \l PolChemDef needs to be capped in order to finalize the polymerization state of the \l Polymer sequence. \sa PolChemDef::m_leftCap */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_codeLength \brief The maximum length of a \l Monomer code in this defintion. The valid syntax of a Monomer code is that the first character of the code is uppercase and all the remaining ones are lowercase. The total number of characters cannot exceed m_codeLength. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_delimitedCodes \brief The set of \l Monomer codes separated by '@' characters, like "@Ala@Tyr@Phe@". */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_ionizeRule \brief The \l{IonizeRule}{ionization rule} that governs the manner the \l Polymer sequences of this polymer chemistry definition are ionized by default. */ /*! \variable MsXpS::libXpertMass::PolChemDef::msp_isotopicData \brief The isotopic data defining the fundamentals of this \l PolChemDef instance. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_monomerList \brief The list of \l{Monomer}s defined to be part of this \l PolChemDef instance. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_modifList \brief The list of \l{Modif}s defined to be part of this \l PolChemDef instance. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_crossLinkerList The list of \l{CrossLinker}s defined to be part of this \l PolChemDef instance. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_cleaveSpecList \brief The list of \l{CleaveSpec}s defining the various ways to cleave \l Polymer sequences of this \l PolChemDef instance. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_fragSpecList \brief The list of \l{FragSpec}s defining the various ways to fragment \l Oligomer sequences of this \l PolChemDef instance. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_monomerSpecList \brief The list of \l{MonomerSpec}s defining how \l{Monomer}s are represented in the graphical user interface. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_modifSpecList \brief The list of \l{ModifSpec}s defining how \l{Modif}s are represented in the graphical user interface. */ /*! \variable MsXpS::libXpertMass::PolChemDef::m_crossLinkerSpecList \brief The list of \l{CrossLinkerSpec}s defining how \l{CrossLinker}s are represented in the graphical user interface. */ /*! \variable MsXpS::libXpertMass::PolChemDef::mp_repositoryList \brief The list of \l PolChemDef instances available in the repositories. */ /*! \variable MsXpS::libXpertMass::POL_CHEM_DEF_FILE_FORMAT_VERSION \brief The latest version of the format of the file containing the polymer chemistry definition. Brought to 1 20230130 for massXpert2 */ const int POL_CHEM_DEF_FILE_FORMAT_VERSION = 1; /*! \brief Constructs a polymer chemistry definition. */ PolChemDef::PolChemDef() { // qDebug() << "Constructing PolChemDef *:" << this; // We have to set the m_codeLength member to 1. It would make no // sense to have monomers described with codes of 0-character // length. m_codeLength = 1; return; } /*! \brief Constructs a polymer chemistry definition on the basis of \a pol_chem_def_spec. The \a pol_chem_def_spec polymer chemistry definition specification provides the name of the polymer chemistry definition and of the file that contains it. */ PolChemDef::PolChemDef(const PolChemDefSpec &pol_chem_def_spec) { m_name = pol_chem_def_spec.name(); // qDebug() << "Constructing PolChemDef *:" << this << "with name:" << // m_name; m_xmlDataFilePath = pol_chem_def_spec.getFilePath(); m_codeLength = 1; return; } /*! \brief Destroys the polymer chemistry definition */ PolChemDef::~PolChemDef() { // qDebug() << "Entering ~PolChemDef *:" << this << "with name:" << m_name; // We have to free all the allocated stuff in the QList members ! while(!m_monomerList.isEmpty()) delete m_monomerList.takeFirst(); while(!m_modifList.isEmpty()) delete m_modifList.takeFirst(); while(!m_crossLinkerList.isEmpty()) delete m_crossLinkerList.takeFirst(); while(!m_cleaveSpecList.isEmpty()) delete m_cleaveSpecList.takeFirst(); while(!m_fragSpecList.isEmpty()) delete m_fragSpecList.takeFirst(); while(!m_monomerSpecList.isEmpty()) delete m_monomerSpecList.takeFirst(); while(!m_modifSpecList.isEmpty()) delete m_modifSpecList.takeFirst(); // qDebug() // << "Leaving ~PolChemDef():" << this; } /*! \brief Sets the \a name of this PolChemDef instance. */ void PolChemDef::setName(const QString &name) { m_name = name; } /*! \brief Returns the name of this PolChemDef instance. */ QString PolChemDef::name() const { return m_name; } /*! \brief Sets the \a file_path of this PolChemDef instance. */ void PolChemDef::setXmlDataFilePath(const QString &file_path) { m_xmlDataFilePath = file_path; } /*! \brief Returns the file path of this PolChemDef instance. */ QString PolChemDef::getXmlDataFilePath() const { return m_xmlDataFilePath; } /*! \brief Returns the absolute directory path of this PolChemDef instance. */ QString PolChemDef::getXmlDataDirPath() const { // qDebug() << "The xml data file path:" << m_xmlDataFilePath; QFileInfo fileInfo(m_xmlDataFilePath); QDir dir(fileInfo.dir()); // qDebug() << "Returning the pol chem def data dir path:" << dir.absolutePath(); return dir.absolutePath(); } /*! \brief Sets the path of the isotopic data file to \a file_path. */ void PolChemDef::setIsotopicDataFilePath(const QString &file_path) { m_isotopicDataFilePath = file_path; } /*! \brief Returns the path of the isotopic data file. */ QString PolChemDef::getIsotopicDataFilePath() const { return m_isotopicDataFilePath; } /*! \brief Returns the path of the isotopic data file. The deduction is based on the fact that the file should have "isotopic-data.dat" as its name. */ QString PolChemDef::deduceIsotopicDataFilePath() const { // From the xml data file path, deduce the file name of the isotopic data. QFileInfo file_info(m_xmlDataFilePath); if(!file_info.exists()) qFatal( "Programming error. At this stage the name of the polymer chemistry " "definition file should be known."); QDir dir(file_info.dir()); QString isotopic_data_file_path = dir.absolutePath() + QDir::separator() + "isotopic-data.dat"; return isotopic_data_file_path; } /*! \brief Sets the left cap \a formula. */ void PolChemDef::setLeftCap(const Formula &formula) { m_leftCap = formula; } /*! \brief Returns the left cap formula. */ const Formula & PolChemDef::leftCap() const { return m_leftCap; } /*! \brief Sets the right cap \a formula. */ void PolChemDef::setRightCap(const Formula &formula) { m_rightCap = formula; } /*! \brief Returns the right cap formula. */ const Formula & PolChemDef::rightCap() const { return m_rightCap; } /*! \brief Sets the \a code_length. */ void PolChemDef::setCodeLength(int code_length) { m_codeLength = code_length; } /*! \brief Returns the code length. */ int PolChemDef::codeLength() const { return m_codeLength; } /*! \brief Constructs a string with the codes of all the \l{Monomer}s in this PolChemDef instance. The codes are delimited by the '@' character. Returns true. */ bool PolChemDef::calculateDelimitedCodes() { // We have to produce a QString containing all the codes from the // monomers known to this polymer chemistry definition. m_delimitedCodes.clear(); m_delimitedCodes.append('@'); for(int iter = 0; iter < m_monomerList.size(); ++iter) { Monomer *monomer = m_monomerList.at(iter); Q_ASSERT(monomer); m_delimitedCodes.append(monomer->code()); m_delimitedCodes.append('@'); } // Close the string with a delim char: m_delimitedCodes.append('@'); return true; } /*! \brief Returns a string with the codes of all the \l{Monomer}s in this PolChemDef instance. The codes are delimited by the '@' character. */ const QString & PolChemDef::delimitedCodes() { if(m_delimitedCodes.isEmpty()) calculateDelimitedCodes(); return m_delimitedCodes; } /*! \brief Sets the ionization rule to \a ionize_rule. */ void PolChemDef::setIonizeRule(const IonizeRule &ionize_rule) { m_ionizeRule = ionize_rule; } /*! \brief Returns a const reference to the ionization rule. */ const IonizeRule & PolChemDef::ionizeRule() const { return m_ionizeRule; } /*! \brief Returns a pointer to the ionization rule. */ IonizeRule * PolChemDef::ionizeRulePtr() { return &m_ionizeRule; } /*! \brief Sets the isotopic data to \a isotopic_data_sp. */ void PolChemDef::setIsotopicDataSPtr(IsotopicDataSPtr isotopic_data_sp) { msp_isotopicData = isotopic_data_sp; } /*! \brief Returns the isotopic data as const shared pointer.. */ IsotopicDataCstSPtr PolChemDef::getIsotopicDataCstSPtr() const { return msp_isotopicData; } /*! \brief Returns the isotopic data as non-const shared pointer.. */ IsotopicDataSPtr PolChemDef::getIsotopicDataSPtr() { return msp_isotopicData; } /*! \brief Returns a const reference to the list of monomers. */ const QList & PolChemDef::monomerList() const { return m_monomerList; } /*! \brief Returns a pointer to the list of monomers. */ QList * PolChemDef::monomerListPtr() { return &m_monomerList; } /*! \brief Returns a const reference to the list of modifications. */ const QList & PolChemDef::modifList() const { return m_modifList; } /*! \brief Returns a pointer to the list of modifications. */ QList * PolChemDef::modifListPtr() { return &m_modifList; } /*! \brief Returns a const reference to the list of cross-linkers. */ const QList & PolChemDef::crossLinkerList() const { return m_crossLinkerList; } /*! \brief Returns a pointer to the list of cross-linkers. */ QList * PolChemDef::crossLinkerListPtr() { return &m_crossLinkerList; } /*! \brief Returns a const reference to the list of cleavage specifications. */ const QList & PolChemDef::cleaveSpecList() const { return m_cleaveSpecList; } /*! \brief Returns a pointer to the list of cleavage specifications. */ QList * PolChemDef::cleaveSpecListPtr() { return &m_cleaveSpecList; } /*! \brief Returns a const reference to the list of fragmentation specifications. */ const QList & PolChemDef::fragSpecList() const { return m_fragSpecList; } /*! \brief Returns a pointer to the list of fragmentation specifications. */ QList * PolChemDef::fragSpecListPtr() { return &m_fragSpecList; } /*! \brief Searches in the member list of modifications a modification by \a name. If the modification object is found, and \a modification_p is non-nullptr, it is copied in \a modification_p. Returns true if the modification was found, false otherwise. */ bool PolChemDef::referenceModifByName(const QString &name, Modif *modification_p) const { if(name.isEmpty()) return false; for(int iter = 0; iter < m_modifList.size(); ++iter) { Modif *modif = m_modifList.at(iter); if(modif->name() == name) { if(modification_p) { *modification_p = *modif; return true; } return true; } } return false; } /*! \brief Searches in the member list of cross-linkers a cross-linker by \a name. If the cross-linker object is found, and \a cross_linker_p is non-nullptr, it is copied in \a cross_linker_p. Returns true if the cross-linker was found, false otherwise. */ bool PolChemDef::referenceCrossLinkerByName(const QString &name, CrossLinker *cross_linker_p) const { if(name.isEmpty()) return false; for(int iter = 0; iter < m_crossLinkerList.size(); ++iter) { CrossLinker *localCrossLinker = m_crossLinkerList.at(iter); if(localCrossLinker->name() == name) { if(cross_linker_p) { *cross_linker_p = *localCrossLinker; return true; } return true; } } return false; } /*! \brief Returns a const reference to the list of monomer specifications. */ QList & PolChemDef::monomerSpecList() const { return m_monomerSpecList; } /*! \brief Returns a pointer to the list of monomer specifications. */ QList * PolChemDef::monomerSpecListPtr() { return &m_monomerSpecList; } /*! \brief Returns a const reference to the list of modification specifications. */ QList & PolChemDef::modifSpecList() const { return m_modifSpecList; } /*! \brief Returns a pointer to the list of modification specifications. */ QList * PolChemDef::modifSpecListPtr() { return &m_modifSpecList; } /*! \brief Returns a const reference to the list of cross-linker specifications. */ QList & PolChemDef::crossLinkerSpecList() const { return m_crossLinkerSpecList; } /*! \brief Returns a pointer to the list of cross-linker specifications. */ QList * PolChemDef::crossLinkerSpecListPtr() { return &m_crossLinkerSpecList; } /*! \brief Parses the polymer chemistry definition file and updates \a pol_chem_def_sp accordingly. Upon parsing of the file and validation of the data, the \a pol_chem_def_sp is updated, essentially initializing it with the data from the file. Note that the \a pol_chem_def_sp should have a working set of isotopic data. Returns true if the parsing was successful and false otherwise. */ bool PolChemDef::renderXmlPolChemDefFile(PolChemDefSPtr pol_chem_def_sp) { Q_ASSERT(pol_chem_def_sp != nullptr && pol_chem_def_sp.get() != nullptr); // qDebug() << "The PolChemDef *:" << pol_chem_def_sp.get() //<< "and usage:" << pol_chem_def_sp.use_count(); ///////////////////// ATTENTION /////////////////// // Before reading the polymer chemistry data file, we need to make sure we // actually have read the isotopic data! // qDebug() << "First check if we have isotopic data ready."; if(pol_chem_def_sp->getIsotopicDataSPtr() == nullptr) { // qDebug() << "No isotopic data found in the polymer chemistry " // "definition, need to load the data."; // First read the data! std::size_t count = pol_chem_def_sp->loadIsotopicData(pol_chem_def_sp); if(!count) qFatal("Programming error. The isotopic data could not be loaded."); // qDebug() << "At this point the isotopic data were loaded fine with" //<< count << "isotopes loaded."; } QDomDocument doc("polChemDefData"); QDomElement element; QDomElement child; QDomElement indentedChild; QFile file(pol_chem_def_sp->m_xmlDataFilePath); Monomer *monomer = 0; Modif *modif = 0; CrossLinker *crossLinker = 0; CleaveSpec *cleaveSpec = 0; FragSpec *fragSpec = 0; // The general structure of the file we are reading is this: // // // // protein // +H // +OH // 1 // // +H // 1 // 1 // // // // Glycine // G // C2H3NO // // // // // Phosphorylation // -H+H2PO3 // // // // // CyanogenBromide // M/ // // M // -CH2S+O // // // // // // a // LE // -C1O1 // // a-fgr-1 // +H200 // E // D // F // comment here! // // // // // if(!file.open(QIODevice::ReadOnly)) return false; if(!doc.setContent(&file)) { qDebug() << "Failed to set doc content."; file.close(); return false; } file.close(); // qDebug() << "Closed the polymer chemistry definition file."; element = doc.documentElement(); if(element.tagName() != "polchemdefinition") { qDebug() << "Polymer chemistry definition file is erroneous\n"; return false; } /////////////////////////////////////////////// // Check the version of the document. QString text; if(!element.hasAttribute("version")) text = "1"; else text = element.attribute("version"); // qDebug() << "The format of the definition:" << text; bool ok = false; int version = text.toInt(&ok, 10); if(version < 1 || !ok) { qDebug() << "Polymer chemistry definition file has bad " "version number:" << version; return false; } ////////////////////////////////////////////// // child = element.firstChildElement(); if(child.tagName() != "polchemdefdata") { qDebug() << "Polymer chemistry definition file is erroneous\n"; return false; } // child = child.firstChildElement(); if(child.tagName() != "name") return false; pol_chem_def_sp->m_name = child.text(); // child = child.nextSiblingElement(); if(child.tagName() != "leftcap") return false; pol_chem_def_sp->m_leftCap.setFormula(child.text()); // child = child.nextSiblingElement(); if(child.tagName() != "rightcap") return false; pol_chem_def_sp->m_rightCap.setFormula(child.text()); // child = child.nextSiblingElement(); if(child.tagName() != "codelen") return false; ok = false; pol_chem_def_sp->m_codeLength = child.text().toInt(&ok); if(pol_chem_def_sp->m_codeLength == 0 && !ok) return false; // child = child.nextSiblingElement(); if(child.tagName() != "ionizerule") return false; if(!pol_chem_def_sp->m_ionizeRule.renderXmlIonizeRuleElement(child)) return false; // We have to ascertain that the IonizeRule is valid. IsotopicDataCstSPtr isotopic_data_csp = pol_chem_def_sp->getIsotopicDataCstSPtr(); if(!pol_chem_def_sp->m_ionizeRule.IonizeRule::validate(isotopic_data_csp)) return false; // child = child.nextSiblingElement(); if(child.tagName() != "monomers") return false; indentedChild = child.firstChildElement(); while(!indentedChild.isNull()) { // qDebug() << "Now rendering one of all the mnm elements."; if(indentedChild.tagName() != "mnm") return false; monomer = new Monomer(pol_chem_def_sp, "NOT_SET"); if(!monomer->renderXmlMnmElement(indentedChild, version)) { qDebug() << "Failed to render mnm element."; delete monomer; return false; } pol_chem_def_sp->m_monomerList.append(monomer); indentedChild = indentedChild.nextSiblingElement(); } // qDebug() << "Size of MonomerList:" << // pol_chem_def_sp->m_monomerList.size() //<< "pol chem def usage:" << pol_chem_def_sp.use_count(); // child = child.nextSiblingElement(); if(child.tagName() != "modifs") return false; indentedChild = child.firstChildElement(); while(!indentedChild.isNull()) { if(indentedChild.tagName() != "mdf") return false; modif = new Modif(pol_chem_def_sp, "NOT_SET"); if(!modif->renderXmlMdfElement(indentedChild, version)) { delete modif; return false; } pol_chem_def_sp->m_modifList.append(modif); indentedChild = indentedChild.nextSiblingElement(); } // qDebug() << "Size of ModifList:" << pol_chem_def_sp->m_modifList.size() //<< "pol chem def usage:" << pol_chem_def_sp.use_count(); // // Note that crosslinkers have appeared since version 3. child = child.nextSiblingElement(); if(child.tagName() != "crosslinkers") return false; indentedChild = child.firstChildElement(); while(!indentedChild.isNull()) { if(indentedChild.tagName() != "clk") return false; // We set the formula to nothing below because that formula might be empty // in the polymer chemistry definition. crossLinker = new CrossLinker(pol_chem_def_sp, "NOT_SET", ""); if(!crossLinker->renderXmlClkElement(indentedChild, version)) { delete crossLinker; return false; } // qDebug() << "Rendered CrossLinker: " << crossLinker->name() //<< "with masses:" << crossLinker->mono() << "/" //<< crossLinker->avg(); pol_chem_def_sp->m_crossLinkerList.append(crossLinker); indentedChild = indentedChild.nextSiblingElement(); } // qDebug() << "Size of CrossLinkerList:" //<< pol_chem_def_sp->m_crossLinkerList.size() //<< "pol chem def usage:" << pol_chem_def_sp.use_count(); // child = child.nextSiblingElement(); if(child.tagName() != "cleavespecs") return false; indentedChild = child.firstChildElement(); while(!indentedChild.isNull()) { if(indentedChild.tagName() != "cls") return false; cleaveSpec = new CleaveSpec(pol_chem_def_sp, "NOT_SET"); if(!cleaveSpec->renderXmlClsElement(indentedChild, version)) { delete cleaveSpec; return false; } pol_chem_def_sp->m_cleaveSpecList.append(cleaveSpec); indentedChild = indentedChild.nextSiblingElement(); } // qDebug() << "Size of CleaveSpecList:" //<< pol_chem_def_sp->m_cleaveSpecList.size() //<< "pol chem def usage:" << pol_chem_def_sp.use_count(); // child = child.nextSiblingElement(); if(child.tagName() != "fragspecs") return false; indentedChild = child.firstChildElement(); while(!indentedChild.isNull()) { if(indentedChild.tagName() != "fgs") return false; fragSpec = new FragSpec(pol_chem_def_sp, "NOT_SET"); if(!fragSpec->renderXmlFgsElement(indentedChild, version)) { delete fragSpec; return false; } pol_chem_def_sp->m_fragSpecList.append(fragSpec); indentedChild = indentedChild.nextSiblingElement(); } // qDebug() << "Size of FragSpecList:" << // pol_chem_def_sp->m_fragSpecList.size() //<< "pol chem def usage:" << pol_chem_def_sp.use_count(); // qDebug() << "Returning true" << "pol chem def usage:" << // pol_chem_def_sp.use_count(); return true; } /*! \brief Loads the isotopic data stored in the file \a file_path. The data loaded from file are parsed and validated. Upon parsing of the file, the data are stored in the \a pol_chem_def_sp instance's isotopic data member. If \a file_path is empty, it is reconstructed using the directory of the polymer chemistry definition and the fact that the isotopic data file name is "isotopic-data.dat". Returns the count of parsed isotopes. */ std::size_t PolChemDef::loadIsotopicData(PolChemDefSPtr pol_chem_def_sp, const QString &file_path) { // There are three situations: // // 1. The file_path exists and the data are loaded from there. // // 2. A file named isotopic-data.dat is found in the polymer chemistry // definition directory and it is loaded. // // 3. No file named isotopic-data.dat is found in the polymer chemistry // defintion directory and then data are loaded from the IsoSpec tables. // If the provided argument is not empty and actually describes an existing // file, then save the data there. QString local_file_path(file_path); QFileInfo local_file_info(local_file_path); if(local_file_path.isEmpty() || !local_file_info.exists()) { // qDebug() << "The provided file name" << file_path << "does not exist."; // Then try the member datum that provides the name of the file from which // to load the isotopic data. local_file_path = pol_chem_def_sp->getIsotopicDataFilePath(); local_file_info.setFile(local_file_path); if(local_file_path.isEmpty() || !local_file_info.exists()) { // qDebug() << "The member datum file name " << local_file_path //<< "does not exist."; // Last resort: deduce the isotopic data file name from the directory // of the polymer chemistry definition. local_file_path = pol_chem_def_sp->getXmlDataDirPath(); // qDebug() << "The XML data dir path:" << local_file_path; local_file_info.setFile(local_file_path); if(!local_file_info.exists() || !local_file_info.isDir()) qFatal( "Programming error. At this stage the name of the polymer " "chemistry definition directory should be known and should be a " "directory."); local_file_path = local_file_info.absoluteFilePath() + QDir::separator() + "isotopic-data.dat"; // qDebug() << "Crafted the isotopic-data.dat file path:" //<< local_file_path; // Finally for a later last check: local_file_info.setFile(local_file_path); } } // qDebug() << "Allocating brand new isotopic data instance."; // At this point we can delete the pre-exsting isotopic data. pol_chem_def_sp->msp_isotopicData = std::make_shared(); // Allocate a specific handler to the kind of isotopic data (library tables or // user file). std::size_t count = 0; if(local_file_path.isEmpty() || !local_file_info.exists()) { // qDebug() << "Loading the isotopic data from the library."; IsotopicDataLibraryHandler isotopic_data_handler( pol_chem_def_sp->msp_isotopicData); count = isotopic_data_handler.loadData(); } else { // qDebug() << "Loading the isotopic data from file:" << local_file_path; IsotopicDataUserConfigHandler isotopic_data_handler( pol_chem_def_sp->msp_isotopicData); count = isotopic_data_handler.loadData(local_file_path); if(!count) { qDebug() << "Failed to load any isotopic data."; return false; } // Now set the file path to the pol chem def. pol_chem_def_sp->setIsotopicDataFilePath(local_file_path); } if(!count) { qDebug() << "Failed to load any isotopic data."; return false; } return count; } /*! \brief Returns a string with the XML DTD for a polymer chemistry definition file. */ QString * PolChemDef::formatXmlDtd() { QString *string = new QString( "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "]>\n"); return string; } /*! \brief Writes the polymer chemistry definition to file. The file's name is from m_xmlDataFilePath. Returns true if successful, false otherwise. */ bool PolChemDef::writeXmlFile() { QString *string = 0; QString indent(" "); QString lead; int offset = 0; int iter = 0; // We are asked to send an xml description of the polymer chemistry // definition. QFile file(m_xmlDataFilePath); if(!file.open(QIODevice::WriteOnly)) { qDebug() << "Failed to open file" << m_xmlDataFilePath << "for writing."; return false; } QTextStream stream(&file); stream.setEncoding(QStringConverter::Utf8); // The DTD string = formatXmlDtd(); stream << *string; delete string; // Open the element. stream << QString("\n") .arg(POL_CHEM_DEF_FILE_FORMAT_VERSION); // Prepare the lead. ++offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } // Open the element. stream << QString("%1\n").arg(lead); // Prepare the lead. ++offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } stream << QString("%1%2\n").arg(lead).arg(m_name); stream << QString("%1%2\n").arg(lead).arg(m_leftCap.toString()); stream << QString("%1%2\n") .arg(lead) .arg(m_rightCap.toString()); stream << QString("%1%2\n").arg(lead).arg(m_codeLength); // Before writing the ionization rule, set the level to 1. This // member datum is set to 0 in the constructor. m_ionizeRule.setLevel(1); string = m_ionizeRule.formatXmlIonizeRuleElement(offset); stream << *string; delete string; stream << QString("%1\n").arg(lead); // Prepare the lead. ++offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } for(iter = 0; iter < m_monomerList.size(); ++iter) { QString *newString = m_monomerList.at(iter)->formatXmlMnmElement(offset); stream << *newString; delete newString; } // Prepare the lead. --offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } stream << QString("%1\n").arg(lead); stream << QString("%1\n").arg(lead); // Prepare the lead. ++offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } for(iter = 0; iter < m_modifList.size(); ++iter) { QString *newString = m_modifList.at(iter)->formatXmlMdfElement(offset); stream << *newString; delete newString; } // Prepare the lead. --offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } stream << QString("%1\n").arg(lead); stream << QString("%1\n").arg(lead); // Prepare the lead. ++offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } for(iter = 0; iter < m_crossLinkerList.size(); ++iter) { QString *newString = m_crossLinkerList.at(iter)->formatXmlClkElement(offset); stream << *newString; delete newString; } // Prepare the lead. --offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } stream << QString("%1\n").arg(lead); stream << QString("%1\n").arg(lead); // Prepare the lead. ++offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } for(int iter = 0; iter < m_cleaveSpecList.size(); ++iter) { QString *newString = m_cleaveSpecList.at(iter)->formatXmlClsElement(offset); stream << *newString; delete newString; } // Prepare the lead. --offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } stream << QString("%1\n").arg(lead); stream << QString("%1\n").arg(lead); // Prepare the lead. ++offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } for(int iter = 0; iter < m_fragSpecList.size(); ++iter) { QString *newString = m_fragSpecList.at(iter)->formatXmlFgsElement(offset); stream << *newString; delete newString; } // Prepare the lead. --offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } stream << QString("%1\n").arg(lead); // Prepare the lead. --offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } stream << QString("%1\n").arg(lead); // Prepare the lead. --offset; lead.clear(); iter = 0; while(iter < offset) { lead += indent; ++iter; } stream << QString("%1\n").arg(lead); file.close(); return true; } /*! \brief Writes the isotopic data in the \a pol_chem_def_sp PolChemDef instance to file \a file_path. Returns the count of isotopes written to file. */ std::size_t PolChemDef::writeIsotopicData(PolChemDefSPtr pol_chem_def_sp, const QString &file_path) { // There are three situations: // // 1. The file_path is not empty and the data are saved there. // // 2. The file_path is empty and the data are stored in the member file path // name (if not empty). // // 3. The file path is crafted from the directory of the polymer chemistry // definition. // Whatever the situation, the isotopic data handler we need here is this one: IsotopicDataUserConfigHandler isotopic_data_handler( pol_chem_def_sp->msp_isotopicData); // We'll instantiate the proper isotopic data handler depending on the // situation. if(!file_path.isEmpty()) { // We have a file name, store in there. pol_chem_def_sp->setIsotopicDataFilePath(file_path); return isotopic_data_handler.writeData(file_path); } // Check the member datum. if(!pol_chem_def_sp->m_isotopicDataFilePath.isEmpty()) { // We have a file name, store in there. return isotopic_data_handler.writeData( pol_chem_def_sp->m_isotopicDataFilePath); } // Last resort: deduce the isotopic data file name from the directory // of the polymer chemistry definition. QString local_file_path = pol_chem_def_sp->getXmlDataDirPath(); QFileInfo local_file_info(local_file_path); if(!local_file_info.exists()) qFatal( "Programming error. At this stage the name of the polymer " "chemistry definition file should be known."); QDir dir(local_file_info.dir()); local_file_path = dir.absolutePath() + QDir::separator() + "isotopic-data.dat"; pol_chem_def_sp->setIsotopicDataFilePath(local_file_path); return isotopic_data_handler.writeData(local_file_path); } /*! \brief Returns a string list with the mass difference between all the \l{Monomer}s in this PolChemDef instace. If the difference is below the \a threshold, the difference is added to the string list, otherwise it is skipped. The masses that are compared between every two monomers is of \a mass_type. */ QStringList * PolChemDef::differenceBetweenMonomers(double threshold, MassType mass_type) { // qDebug() // << "threshold" << threshold; QStringList *list = new QStringList(); ; // We iterate in the list of monomers and compute a difference of // mass for each monomer with respect to all the other ones. If // the mass difference is less or equal to the threshold, then the // pair is returned. for(int iter = 0; iter < m_monomerList.size(); ++iter) { Monomer *monomer1 = m_monomerList.at(iter); monomer1->calculateMasses(); for(int jter = 0; jter < m_monomerList.size(); ++jter) { // We are not going to explain that the same monomer has the // same mass ! if(iter == jter) continue; Monomer *monomer2 = m_monomerList.at(jter); monomer2->calculateMasses(); // At this point we have the masses for both monomers. if(mass_type != MassType::MASS_MONO && mass_type != MassType::MASS_AVG) { qDebug() << "Please set the mass type " "to either MassType::MASS_MONO or " "MassType::MASS_AVG"; return list; } double diff = 0; if(mass_type == MassType::MASS_MONO) { diff = monomer2->mass(MassType::MASS_MONO) - monomer1->mass(MassType::MASS_MONO); } else //(monoOrAvg == MassType::MASS_AVG) { diff = monomer2->mass(MassType::MASS_AVG) - monomer1->mass(MassType::MASS_AVG); } // At this point, make sure that diff is within the // threshold. Note that to avoid duplicates, we remove all // values that are negative. if(diff >= 0 && diff <= threshold) { QString line = QString("%1 - %2 = %3") .arg(monomer2->name()) .arg(monomer1->name()) .arg(diff); list->append(line); } } // End of // for (int jter = 0; jter < m_monomerList.size(); ++jter) } // End of // for (int iter = 0; iter < m_monomerList.size(); ++iter) // Only return a list if it contains at least one item. if(!list->size()) { delete list; list = 0; } return list; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/PolChemDefEntity.cpp000664 001750 001750 00000012317 14647465366 024053 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "PolChemDefEntity.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::PolChemDefEntity \inmodule libXpertMass \ingroup PolChemDef \inheaderfile PolChemDefEntity.hpp \brief The PolChemDefEntity class describes a chemical entity that belongs to a specific \l PolChemDef polymer chemistry definition. */ /*! \variable MsXpS::libXpertMass::PolChemDefEntity::m_name \brief The name of the chemical entity. */ /*! \variable MsXpS::libXpertMass::PolChemDefEntity::mcsp_polChemDef \brief The polymer chemistry definition that is the context of this chemical entity. */ /*! Constructs a polymer chemistry definition entity. \a pol_chem_def_csp reference polymer chemistry definition. This pointer cannot be nullptr. \a name name of the entity. */ PolChemDefEntity::PolChemDefEntity(PolChemDefCstSPtr pol_chem_def_csp, const QString &name) { Q_ASSERT(pol_chem_def_csp); Q_ASSERT(pol_chem_def_csp.get()); mcsp_polChemDef = pol_chem_def_csp; Q_ASSERT(!name.isEmpty()); m_name = name; } /*! Constructs a PolChemDefEntity instance as a copy of \a other. */ PolChemDefEntity::PolChemDefEntity(const PolChemDefEntity &other) : mcsp_polChemDef(other.mcsp_polChemDef), m_name(other.m_name) { if(mcsp_polChemDef == nullptr) qFatal("Programming error. The pointer cannot be nullptr."); if(m_name.isEmpty()) qFatal( "Programming error. The polymer chemistry entity cannot have an empty " "name."); } /*! \brief Destructs this PolChemDefEntity instance. */ PolChemDefEntity::~PolChemDefEntity() { } /*! \brief Assigns \a other to this PolChemDef instance. Returns a reference to this PolChemDef instance */ PolChemDefEntity & PolChemDefEntity::operator=(const PolChemDefEntity &other) { if(&other == this) return *this; mcsp_polChemDef = other.mcsp_polChemDef; m_name = other.m_name; return *this; } /*! \brief Sets the \a name. */ void PolChemDefEntity::setName(const QString &name) { m_name = name; } /*! \brief Returns the name. */ QString PolChemDefEntity::name() const { Q_ASSERT(!m_name.isEmpty()); return m_name; } /*! \brief Sets this PolChemDefEntity's \a pol_chem_def_csp. */ void PolChemDefEntity::setPolChemDefCstSPtr(PolChemDefCstSPtr pol_chem_def_csp) { mcsp_polChemDef = pol_chem_def_csp; } /*! \brief Returns the polymer chemistry definition. */ PolChemDefCstSPtr PolChemDefEntity::getPolChemDefCstSPtr() const { Q_ASSERT(mcsp_polChemDef); Q_ASSERT(mcsp_polChemDef.get()); return mcsp_polChemDef; } /*! \brief Returns true if this PolChemDefEntity instance and \a other are identical, false otherwise. */ bool PolChemDefEntity::operator==(const PolChemDefEntity &other) const { int tests = 0; Q_ASSERT(mcsp_polChemDef && other.mcsp_polChemDef); Q_ASSERT(mcsp_polChemDef.get() && other.mcsp_polChemDef.get()); tests += (mcsp_polChemDef == other.mcsp_polChemDef); Q_ASSERT(!m_name.isEmpty() && !other.m_name.isEmpty()); tests += (m_name == other.m_name); if(tests < 2) return false; return true; } /*! Returns true if this PolChemDefEntity instance and \a other differ, false otherwise. */ bool PolChemDefEntity::operator!=(const PolChemDefEntity &other) const { int tests = 0; Q_ASSERT(mcsp_polChemDef && other.mcsp_polChemDef); Q_ASSERT(mcsp_polChemDef.get() && other.mcsp_polChemDef.get()); tests += (mcsp_polChemDef != other.mcsp_polChemDef); Q_ASSERT(!m_name.isEmpty() && !other.m_name.isEmpty()); tests += (m_name != other.m_name); if(tests > 0) return true; return false; } /*! \brief Returns true if this PolChemDef instance validates successfully, false otherwise. The name cannot be empty and the polymer chemistry definition cannot be nullptr. */ bool PolChemDefEntity::validate() const { int tests = 0; tests += (m_name.isNull() || m_name.isEmpty()); tests += (!mcsp_polChemDef); tests += (!mcsp_polChemDef.get()); if(tests) return false; return true; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/PolChemDefSpec.cpp000664 001750 001750 00000004130 14647465366 023463 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #include "PolChemDefSpec.hpp" /////////////////////// Qt includes #include namespace MsXpS { namespace libXpertMass { // FIXME TODO DOCUMENTATION PolChemDefSpec::PolChemDefSpec() { } PolChemDefSpec::~PolChemDefSpec() { } void PolChemDefSpec::setName(const QString &name) { m_name = name; } const QString & PolChemDefSpec::name() const { return m_name; } void PolChemDefSpec::setFilePath(const QString &file_path) { m_filePath = file_path; } QString PolChemDefSpec::getFilePath() const { return m_filePath; } QString PolChemDefSpec::dirPath() { // if m_filePath is "protein-1-letter/protein-1-letter.xml", // returns "/protein-1-letter" QFileInfo fileInfo(m_filePath); QDir dir(fileInfo.dir()); return dir.absolutePath(); } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Polymer.cpp000664 001750 001750 00000210262 14647465366 022336 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #include #include #include /////////////////////// Local includes #include "Polymer.hpp" #include "Modif.hpp" #include "FragSpec.hpp" #include "CleaveSpec.hpp" #include "PolChemDef.hpp" #include "CrossLink.hpp" int polymerMetaTypeId = qRegisterMetaType("MsXpS::libXpertMass::Polymer"); int polymerSPtrMetaTypeId = qRegisterMetaType("MsXpS::libXpertMass::PolymerSPtr"); int polymerCstSPtrMetaTypeId = qRegisterMetaType( "MsXpS::libXpertMass::PolymerCstSPtr"); namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::Polymer \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile Polymer.hpp \brief The Polymer class provides abstractions to work with a polymer molecule (protein or saccharide , for example). The Polymer class provides a polymer sequence. In the protein world, a polymer sequence is a protein. From a computing standpoint, that sequence is first created by chaining amino acid residues (the residue chain). In a second step, the entity is set to a finished polymer state by adding the N-terminal proton and the C-terminal hydroxyl residue. It might happen also that the ends of a polymer be chemically modified (acetylation of the N-terminal residue, for example). The Polymer class allows modelling a polymer sequence in its finest details. */ /*! \variable int MsXpS::libXpertMass::POL_SEQ_FILE_FORMAT_VERSION \brief Version number of the polymer sequence file format. */ /*! \variable int MsXpS::libXpertMass::Polymer::m_name \brief The name of the polymer, for example, "Apomyoglobin". */ /*! \variable int MsXpS::libXpertMass::Polymer::m_code \brief The code of the polymer, for example, the accession number in SwissProt. */ /*! \variable int MsXpS::libXpertMass::Polymer::m_author \brief The author or creator of the file. */ /*! \variable int MsXpS::libXpertMass::Polymer::m_dateTime \brief The date and time of last modification. */ /*! \variable int MsXpS::libXpertMass::Polymer::m_leftEndModif \brief The left end modification. */ /*! \variable int MsXpS::libXpertMass::Polymer::m_rightEndModif \brief The right end modification. */ /*! \variable int MsXpS::libXpertMass::Polymer::mpa_crossLinkList \brief The list of CrossLink events in the polymer sequence. */ /*! \brief Constructs a polymer with \a name, \a code, \a author and the \a pol_chem_def_csp polymer chemistry definition. */ Polymer::Polymer(PolChemDefCstSPtr pol_chem_def_csp, const QString &name, const QString &code, const QString &author) : Ionizable(pol_chem_def_csp, name), m_code(code), m_author(author), m_leftEndModif(pol_chem_def_csp, "NOT_SET"), m_rightEndModif(pol_chem_def_csp, "NOT_SET") { mpa_crossLinkList = new CrossLinkList(); } /*! \brief Destructs the polymer. */ Polymer::~Polymer() { delete mpa_crossLinkList; } /*! \brief Sets the \a name. */ void Polymer::setName(const QString &name) { m_name = name; } /*! \brief Returns the name. */ QString Polymer::name() const { return m_name; } /*! \brief Sets the \a code. */ void Polymer::setCode(const QString &code) { m_code = code; } /*! \brief Returns the code. */ QString Polymer::code() const { return m_code; } /*! \brief Sets the \a author. */ void Polymer::setAuthor(const QString &author) { m_author = author; } /*! \brief Returns the author. */ QString Polymer::author() const { return m_author; } /*! \brief Sets the file path to \a file_path. */ void Polymer::setFilePath(const QString &file_path) { m_filePath = file_path; } /*! \brief Returns the file path. */ QString Polymer::filePath() const { return m_filePath; } /*! \brief Set the date and time to \a date_time. */ void Polymer::setDateTime(const QString &date_time) { m_dateTime = QDateTime::fromString((QString)date_time, "yyyy-MM-dd:mm:ss"); } /*! \brief Returns the date and time. */ QString Polymer::dateTime() const { return m_dateTime.toString("yyyy-MM-dd:mm:ss"); } /*! \brief Returns true if a least one monomer in the sequence between residues \a left_index and \a right_index is modified. */ bool Polymer::hasModifiedMonomer(int left_index, int right_index) const { if(left_index >= size() || right_index >= size()) qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__); if(left_index > right_index) qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__); int startIndex = left_index; int endIndex = right_index; if(left_index == -1) startIndex = 0; if(right_index == -1) endIndex = size() - 1; for(int iter = startIndex; iter <= endIndex; ++iter) { if(at(iter)->isModified()) return true; } return false; } /*! \brief Sets this polymer's left end modification to \a name. Returns true if a modification by \a name was found in the member polymer chemistry definition's list of modifications and if that modification could be set and its masses could be calculated successfully, otherwise returns false. */ bool Polymer::setLeftEndModif(const QString &name) { const QList &refList = getPolChemDefCstSPtr()->modifList(); if(name.isNull() || name.isEmpty()) { // Reset the modif to nothing. m_leftEndModif.reset(); } if(Modif::isNameInList(name, refList, &m_leftEndModif) != -1) { if(!m_leftEndModif.calculateMasses()) return false; else return true; } else return false; } /*! \brief Sets this polymer's left end modification to \a modif. Returns true if the modification could be set and its masses could be calculated successfully, otherwise returns false. */ bool Polymer::setLeftEndModif(const Modif &modif) { m_leftEndModif = modif; if(!m_leftEndModif.calculateMasses()) return false; else return true; } /*! \brief Returns this polymer's left end modif. */ const Modif & Polymer::leftEndModif() const { return m_leftEndModif; } /*! \brief Returns true if this polymer is modified at its left end. */ bool Polymer::isLeftEndModified() const { return m_leftEndModif.checkSyntax(); } /*! \brief Sets this polymer's right end modification to \a name. Returns true if a modification by \a name was found in the member polymer chemistry definition's list of modifications and if that modification could be set and its masses could be calculated successfully, otherwise returns false. */ bool Polymer::setRightEndModif(const QString &name) { const QList &refList = getPolChemDefCstSPtr()->modifList(); if(name.isNull() || name.isEmpty()) { // Reset the modif to nothing. m_rightEndModif.reset(); } if(Modif::isNameInList(name, refList, &m_rightEndModif) != -1) { if(!m_rightEndModif.calculateMasses()) return false; else return true; } else return false; } /*! \brief Sets this polymer's right end modification to \a modif. Returns true if the modification could be set and its masses could be calculated successfully, otherwise returns false. */ bool Polymer::setRightEndModif(const Modif &modif) { m_rightEndModif = modif; if(!m_rightEndModif.calculateMasses()) return false; else return true; } /*! \brief Returns this polymer's right end modif. */ const Modif & Polymer::rightEndModif() const { return m_rightEndModif; } /*! \brief Returns true if this polymer is modified at its right end. */ bool Polymer::isRightEndModified() const { return m_rightEndModif.checkSyntax(); } /*! \brief Returns a reference to the list of CrossLink entities associated to this polymer. */ const CrossLinkList & Polymer::crossLinkList() const { return *mpa_crossLinkList; } /*! \brief Returns the list of CrossLink entities associated to this polymer. */ CrossLinkList * Polymer::crossLinkListPtr() { return mpa_crossLinkList; } /*! \brief Fills-in the \a index_list_p with indices of monomers involved in cross-links. This function iterates in this polymer's list of cross-links and checks for each cross-link the monomers that are involved in that cross-link have indices: \list \li Fully contained in the indices range [\a start_index, \a end_index] \li Only partially contained in the range \li Not contained at all in the range. \endlist The \a index_list_p is filled only with indices of monomers involved in cross-links of which the monomers are fully contained in range [\a start_index, \a end_index]. If \a partials is not nullptr, then, it is set to the count of cross-links involving monomers only partially contained in range [\a start_index, \a end_index]. Returns true if at least one cross-link was found to be fully encompassed by the range, false otherwise. */ bool Polymer::crossLinkedMonomerIndexList(int start_index, int end_index, QList *index_list_p, int *partials) { if(index_list_p == nullptr) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); // We are asked to return a list of all the indices of the // monomers involved in cross-links that link any monomer in the // region delimited by startIndex and endIndex. bool oneFound = false; // Iterate in the list of cross-links set to *this polymer. For // each iterated cross-link, check if it is fully encompasse by // the region delimited by [startIndex--endIndex]. If so, get the // indices of the monomers involved in that cross-link and append // these (no duplicates) to the indexList passed as param to the // function. for(int jter = 0; jter < mpa_crossLinkList->size(); ++jter) { CrossLink *crossLink = mpa_crossLinkList->at(jter); // qDebug() << __FILE__ << __LINE__ // << "Cross-link:" << crossLink; int ret = crossLink->encompassedBy(start_index, end_index); if(ret == CROSS_LINK_ENCOMPASSED_FULL) { // qDebug() << __FILE__ << __LINE__ // << "Cross-link:" << crossLink // << "is encompassed by region:" // << startIndex << "-" << endIndex; QList localIndexList; crossLink->monomerIndexList(&localIndexList); // qDebug() << __FILE__ << __LINE__ // << "Index list:" << localIndexList; // Avoid duplicating indices in the target index // list. This will have the excellent side effect of // condensating into one single region a number of // contained cross-linked regions. For example, from the // Kunitz inhibitor, there are the following cross-links: // 90 -- 187 // 230 -- 280 // 239 -- 263 // 255 -- 276 // 286 -- 336 // 295 -- 319 // 311 -- 332 // Thanks to our strategy below, the cross-links // 230 -- 280 // 239 -- 263 // 255 -- 276 // become contained in one single region: // 230--276. // Same for the cross-links // 286 -- 336 // 295 -- 319 // 311 -- 332 // Which become contained in 286--336 for(int iter = 0, size = localIndexList.size(); iter < size; ++iter) { int index = localIndexList.at(iter); if(!index_list_p->contains(index)) index_list_p->append(index); } oneFound = true; } else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL) { if(partials) ++(*partials); } } return oneFound; } /*! \brief Fills-in the \a crosslink_list_p with cross-links. This function iterates in this polymer's list of cross-links and checks for each cross-link the monomers that are involved in that cross-link have indices: \list \li Fully contained in the indices range [\a start_index, \a end_index] \li Only partially contained in the range \li Not contained at all in the range. \endlist The \a crosslink_list_p is filled only with cross-links found to be fully encompassed by the range. If \a partials is not nullptr, then, it is set to the count of cross-links involving monomers only partially contained in range [\a start_index, \a end_index]. Returns true if at least one cross-link was found to be fully encompassed by the range, false otherwise. */ bool Polymer::crossLinkList(int start_index, int end_index, QList *crosslink_list_p, int *partials) { if(crosslink_list_p == nullptr) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); // We are asked to return a list of all the cross-links that are // fully encompassed by the [startIndex--endIndex] region. bool oneFound = false; // Iterate in the list of cross-links set to *this polymer. For // each iterated cross-link, check if it is fully encompassed by // the [startIndex--endIndex] region. If so simply add it to the // list of cross-links. for(int jter = 0; jter < mpa_crossLinkList->size(); ++jter) { CrossLink *crossLink = mpa_crossLinkList->at(jter); // qDebug() << __FILE__ << __LINE__ // << "Cross-link:" << crossLink; int ret = crossLink->encompassedBy(start_index, end_index); if(ret == CROSS_LINK_ENCOMPASSED_FULL) { // qDebug() << __FILE__ << __LINE__ // << "Cross-link:" << crossLink // << "is encompassed by region:" // << startIndex << "-" << endIndex; crosslink_list_p->append(crossLink); oneFound = true; } else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL) { if(partials) ++(*partials); } } return oneFound; } /*! \brief Prepares for the removal of \a monomer from this polymer's sequence. The challenge is that monomers might be involved in cross-links. In that case, removing a monomer that was involved in a cross-link need preparation: it needs to first be uncross-linked. Returns true; \sa uncrossLink() */ bool Polymer::prepareMonomerRemoval(Monomer *monomer) { Q_ASSERT(monomer); // We are asked to destroy **all** the crossLinks that involve the // 'monomer'. // Iterate in the list of crossLinks, and for each crossLink check // if it involves 'monomer'. if(!mpa_crossLinkList->size()) return true; int iter = mpa_crossLinkList->size(); while(iter) { CrossLink *crossLink = mpa_crossLinkList->at(iter - 1); if(crossLink->involvesMonomer(monomer) != -1) { // The current crossLink involves the monomer for which all // the crossLinks should be destroyed. uncrossLink(crossLink); // The uncrossLinking takes care of removing the crossLink // from the list. } --iter; } return true; } /*! \brief Removes the monomer at \a index. The monomer is first uncross-linked (if it was). Returns false if the uncross-linking of the monomer failed, true otherwise. */ bool Polymer::removeMonomerAt(int index) { Q_ASSERT(index > -1); Q_ASSERT(index < size()); Monomer *monomer = const_cast(at(index)); if(!prepareMonomerRemoval(monomer)) return false; m_monomerList.removeAt(index); delete monomer; return true; } /*! \brief Computes a MD5SUM has with the data described in \a hash_data_specifs. If hash_data_specifs & HASH_ACCOUNT_SEQUENCE, the sequence is included in the hash computation. If hash_data_specifs & HASH_ACCOUNT_MONOMER_MODIF, the monomer modifications are included in the hash computation. If hash_data_specifs & HASH_ACCOUNT_POLYMER_MODIF, the polymer modifications are include in the hash computation. Returns the hash. */ QByteArray Polymer::md5Sum(int hash_data_specifs) const { // We first need to craft a complete string that encapsulates the // maximum number of information from the polymer sequence (sequence, // modifications in the monomers...) depending on the parameter passed // to the function. QString *dataString = new QString; if(hash_data_specifs & HASH_ACCOUNT_SEQUENCE) { for(int iter = 0; iter < size(); ++iter) { const Monomer *monomer = at(iter); dataString->append(monomer->code()); if(hash_data_specifs & HASH_ACCOUNT_MONOMER_MODIF) { if(monomer->isModified()) { QList *modifList = monomer->modifList(); for(int jter = 0; jter < modifList->size(); ++jter) { Modif *modif = modifList->at(jter); dataString->append(modif->name()); } } } } } if(hash_data_specifs & HASH_ACCOUNT_POLYMER_MODIF) { if(isLeftEndModified()) { dataString->append(leftEndModif().formula()); } if(isRightEndModified()) { dataString->append(rightEndModif().formula()); } } // Now that we have the data string, we can craft the hash itself: QByteArray hash = QCryptographicHash::hash(dataString->toUtf8(), QCryptographicHash::Md5); delete dataString; return hash; } // MASS CALCULATION FUNCTIONS /*! \brief Accounts the masses of this polymer. Accounting masses means calculating masses and adding the results to objects. Here the masses of this polymer are calculated and added to those of this polymer. The calculation of the masses (monoisotopic and average) is configured in \a calc_options. Returns true upon success, false otherwise. \sa CalcOptions */ bool Polymer::accountMasses(const CalcOptions &calc_options) { // We do not want to reset masses prior to calculating the masses // because we are accounting them in the polymer itself. return calculateMasses(calc_options, false); } /*! \brief Accounts the masses of this polymer. Accounting masses means calculating masses and adding the results to objects. Here the masses of this polymer are calculated and set to \a mono and \a avg. The calculation of the masses (monoisotopic and average) is configured in \a calc_options. \sa CalcOptions */ bool Polymer::accountMasses(Polymer *polymer, const CalcOptions &calc_options, double *mono, double *avg) { // We do not want to reset masses prior to calculating the masses // because we are accounting them in the polymer itself. return calculateMasses(polymer, calc_options, mono, avg, false); } /*! \brief Calculates the masses of this polymer. The calculated masses are set to this polymer. If \a reset is true, the masses of this polymer are first reset, otherwise this polymer's masses are incremented with those obtained from the calculation. The calculation of the masses (monoisotopic and average) is configured in \a calc_options. Returns true. \sa CalcOptions */ bool Polymer::calculateMasses(const CalcOptions &calc_options, bool reset) { return calculateMasses(this, calc_options, &m_mono, &m_avg, reset); } /*! \brief Calculates the masses of \a polymer_p. The calculated masses are set to \a mono and \a avg (that cannot be nullptr). If \a reset is true, \a mono and \a avg are reset, otherwise they are incremented with the masses obtained from the calculation. The calculation of the masses (monoisotopic and average) is configured in \a calc_options. Returns true. \sa CalcOptions */ bool Polymer::calculateMasses(Polymer *polymer_p, const CalcOptions &calc_options, double *mono, double *avg, bool reset) { Q_ASSERT(polymer_p != nullptr); Q_ASSERT(mono != nullptr && avg != nullptr); int ret = 0; if(reset) { // Reset the masses to 0. *mono = 0; *avg = 0; } // The calc_options parameter holds a CoordinateList instance // listing all the coordinates of the different(if any) region // selections of the polymer sequence. This CoordinateList is // never empty, as it should at least contain the pseudo-selection // of the sequence, that is [start of sequence, cursor index] or // the [-1, -1] values for whole sequence mass // calculation. Iterate in this CoordinateList and for each item // call this function. Q_ASSERT(calc_options.coordinateList().size()); // For each Coordinates item in the calc_options.coordinateList() // list of such items, perform the mass calculation. for(int iter = 0; iter < calc_options.coordinateList().size(); ++iter) { // New coordinates instance we are iterating into. Coordinates coordinates(*(calc_options.coordinateList().at(iter))); qDebug() << "Iterating in Coordinates:" << coordinates.indicesAsText(); // If the start value is less than 0(typically it is set to // -1) then set it to 0(the first monomer of the sequence. if(coordinates.start() < 0) coordinates.setStart(0); // If the end value is less than 0(typically it is set to -1) // then set it to be the size of the polymer so that all the // protein is taken into account in the calculation. if(coordinates.end() < 0) coordinates.setEnd(polymer_p->size() - 1); // If the end value is greater than the polymer size, set it // to the polymer size. if(coordinates.end() >= polymer_p->size()) coordinates.setEnd(polymer_p->size() - 1); // First account for the residual chain masses. qDebug() << "calculateMasses: accounting for residual chain indices" << "[" << coordinates.start() << "--" << coordinates.end() << "]"; for(int jter = coordinates.start(); jter <= coordinates.end(); ++jter) { qDebug() << "Going to call at() with value" << jter; Monomer *monomer = const_cast(polymer_p->at(jter)); if(calc_options.isDeepCalculation()) monomer->calculateMasses(calc_options.monomerEntities()); monomer->accountMasses(mono, avg); } } // Even if we are not in the residual chain loop, we have to account // for the crossLinks, if so requires it. The crossLinks are a // monomer chemical entity, but because it is unpractical to // calculate their ponderable contribution in the loop above, we // deal with them here. This is difficult stuff. In fact, the // crossLinks, which in reality belong to at least two monomers //(monomers can be engaged in more than a crossLink), are not // stored as properties in the monomers(contrary to monomer // modifications, for example). The crossLinks are stored in a list // of such instances in the polymer(m_crossLinkList of CrossLink // pointers). Now, the point is: if one of the monomers of a // crossLink is selected but not the other partners, then what // should be do about that crossLink accounting ? if(calc_options.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK) { // qDebug() << "Cross-links are to be accounted for."; // We have to take into account the crossLinks. Hmmm... hard // task. The calculation is to be performed for the sequence // stretch from localStart to localEnd. We can iterate in the // crossLink list and for each crossLink check if it involves // monomers that *all* are contained in the sequence stretch //(or sequence stretches, that is a number of Coordinates // items in the calc_options.coordinateList()) we're // calculating the mass of. If at least one monomer of any // crossLink is not contained in the [localStart--localEnd] // sequence stretch, than increment a count variable and do // not account the mass. const CrossLinkList &crossLinkList = polymer_p->crossLinkList(); int crossLinkPartial = 0; for(int jter = 0; jter < crossLinkList.size(); ++jter) { CrossLink *crossLink = crossLinkList.at(jter); ret = crossLink->encompassedBy(calc_options.coordinateList()); if(ret == CROSS_LINK_ENCOMPASSED_FULL) { // qDebug() << "CrossLink at iter:" << jter //<< "is fully encompassed: accounting its masses."; // The crossLink is fully encompassed by our monomer // stretch, so we should take it into account. ret = crossLink->accountMasses(mono, avg); Q_ASSERT(ret); } else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL) { // qDebug() //<< "CrossLink at iter:" << jter //<< "is only partially encompassed: not accounting its" // "masses."; ++crossLinkPartial; } else { // qDebug() //<< "CrossLink at iter:" << jter //<< "is not encompassed at all: not accounting its masses."; } } emit(polymer_p->crossLinksPartiallyEncompassedSignal(crossLinkPartial)); } // We now have to account for the left/right cappings. However, // when there are multiple region selections(that is multiple // Coordinate elements in the calc_options.coordinateList()) it is // necessary to know if the user wants each of these Coordinates // to be considered real oligomers(each one with its left/right // caps) or as residual chains. Thus there are two cases: // 1. Each Coordinates item should be considered an oligomer //(SelectionType is SELECTION_TYPE_OLIGOMERS), thus for each item // the left and right caps should be accounted for. This is // typically the case when the user selects multiple regions to // compute the mass of cross-linked oligomers. // 2. Each Coordinates item should be considered a residual chain //(SelectionType is SELECTION_TYPE_RESIDUAL_CHAINS), thus only // one item should see its left and right caps accounted for. This // is typically the case when the user selects multiple regions // like it would select repeated sequence elements in a polymer // sequence: all the regions selected are treated as a single // oligomer. // Holds the number of times the chemical entities are to be // accounted for. int times = 0; if(calc_options.selectionType() == SELECTION_TYPE_RESIDUAL_CHAINS) { // qDebug() << __FILE__ << __LINE__ // << "SELECTION_TYPE_RESIDUAL_CHAINS"; times = 1; } else { // qDebug() << __FILE__ << __LINE__ // << "SELECTION_TYPE_OLIGOMERS"; times = calc_options.coordinateList().size(); } // Account for the left and right cap masses, if so required. if(calc_options.capping() & CAP_LEFT) { ret = Polymer::accountCappingMasses(polymer_p, CAP_LEFT, mono, avg, times); Q_ASSERT(ret); } if(calc_options.capping() & CAP_RIGHT) { ret = Polymer::accountCappingMasses(polymer_p, CAP_RIGHT, mono, avg, times); Q_ASSERT(ret); } // Account for the left and right modification masses, if so // required and the region(s) require(s) it: we have to make it // clear if the selection encompasses indices 0(left end) and/or // polymerSize-1(right end). // Note that if we are force to take into account either or both // the left/right end modif, then even if the selected region does // not encompass the end(s), their modif(s) must be taken into // account. if(calc_options.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF) { if(calc_options.polymerEntities() & POLYMER_CHEMENT_FORCE_LEFT_END_MODIF) { ret = Polymer::accountEndModifMasses( polymer_p, POLYMER_CHEMENT_LEFT_END_MODIF, mono, avg); Q_ASSERT(ret); } else { if(calc_options.coordinateList().encompassIndex(0)) { ret = Polymer::accountEndModifMasses( polymer_p, POLYMER_CHEMENT_LEFT_END_MODIF, mono, avg); Q_ASSERT(ret); } } } if(calc_options.polymerEntities() & POLYMER_CHEMENT_RIGHT_END_MODIF) { if(calc_options.polymerEntities() & POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF) { ret = Polymer::accountEndModifMasses( polymer_p, POLYMER_CHEMENT_RIGHT_END_MODIF, mono, avg); Q_ASSERT(ret); } else { if(calc_options.coordinateList().encompassIndex(polymer_p->size() - 1)) { ret = Polymer::accountEndModifMasses( polymer_p, POLYMER_CHEMENT_RIGHT_END_MODIF, mono, avg); Q_ASSERT(ret); } } } // qDebug() <<__FILE__ << __LINE__ // << "CalculateMasses Mono:" // << polymer->mono() // << "Avg:" // << polymer->avg(); return true; } /*! \brief Accounts for the mass of the end caps. The polymer sequence is actually a chain of monomers (that is, residues). In order to compute the mass of the polymer in its finished state, it is necessary to add the masses of its end caps (typically, a proton and a hydroxyl group in protein chemistry, respectively capping the N-terminus and the C-terminus). The mass of the the left end is added to the monoisotopic and average masses of this polymer if (\a how & CAP_LEFT). The mass of the the right end is added to the monoisotopic and average masses of this polymer if (\a how & CAP_RIGHT). The masses of the caps are multiplied by \a times before accounting them to this polymer's masses. Returns true. */ bool Polymer::accountCappingMasses(int how, int times) { return accountCappingMasses(this, how, &m_mono, &m_avg, times); } /*! \brief Accounts for the \a{polymer}'s masses of its end caps to \a mono and \a avg. The polymer sequence is actually a chain of monomers (that is, residues). In order to compute the mass of the polymer in its finished state, it is necessary to add the masses of its end caps (typically, a proton and a hydroxyl group in protein chemistry, respectively capping the N-terminus and the C-terminus). The mass of the the left end is added to the \a mono and \a avg masses if (\a how & CAP_LEFT). The mass of the the right end is added to the \a mono and \a avg masses if (\a how & CAP_RIGHT). The masses of the caps are multiplied by \a times before accounting them to \a mono and \a avg \a mono and \a avg cannot be nullptr. \a polymer cannot be nullptr. Returns true. */ bool Polymer::accountCappingMasses( Polymer *polymer, int how, double *mono, double *avg, int times) { Q_ASSERT(polymer); Q_ASSERT(mono && avg); PolChemDefCstSPtr polChemDef = polymer->getPolChemDefCstSPtr(); IsotopicDataCstSPtr isotopic_data_csp = polChemDef->getIsotopicDataCstSPtr(); Formula formula; if(how & CAP_LEFT) { formula = polChemDef->leftCap(); } else if(how & CAP_RIGHT) { formula = polChemDef->rightCap(); } else if(how & CAP_NONE) return true; else Q_ASSERT(0); if(!formula.accountMasses(isotopic_data_csp, mono, avg, times)) return false; return true; } /*! \brief Accounts for this polymer's end modifications masses as defined by \a how. The left end's modification masses are accounted for in this polymer if (how & POLYMER_CHEMENT_LEFT_END_MODIF), and the right end's are if (how & POLYMER_CHEMENT_RIGHT_END_MODIF). Returns true upon success, false otherwise. */ bool Polymer::accountEndModifMasses(int how) { return accountEndModifMasses(this, how, &m_mono, &m_avg); } /*! \brief Accounts for the \a{polymer}'s end modifications masses as defined by \a how. The masses are accounted for into \a ponderable without resetting its masses. If (how & POLYMER_CHEMENT_LEFT_END_MODIF), the left end modification masses are accounted for and the right end's are if (how & POLYMER_CHEMENT_RIGHT_END_MODIF). */ bool Polymer::accountEndModifMasses(Polymer *polymer, int how, Ponderable *ponderable) { Q_ASSERT(polymer); Q_ASSERT(ponderable); return accountEndModifMasses( polymer, how, &ponderable->rmono(), &ponderable->ravg()); } /*! \brief Accounts for the \a{polymer}'s end modifications masses as defined by \a how. The masses are accounted for into \a mono and \a avg without resetting these masses. If \c{(how & POLYMER_CHEMENT_LEFT_END_MODIF)}, the left end modification masses are accounted for and the right end's are if \c{(how& POLYMER_CHEMENT_RIGHT_END_MODIF)}. Returns true upon success and false otherwise. */ bool Polymer::accountEndModifMasses(Polymer *polymer, int how, double *mono, double *avg) { Q_ASSERT(polymer); Q_ASSERT(mono && avg); // Make a safe copy of the polymer's left/right end modif and use it // for doing the calculation INTO the 'mono' and 'avg' variables. if(how & POLYMER_CHEMENT_LEFT_END_MODIF) { Modif modif(polymer->leftEndModif()); if(!modif.accountMasses(mono, avg)) return false; } if(how & POLYMER_CHEMENT_RIGHT_END_MODIF) { Modif modif(polymer->rightEndModif()); if(!modif.accountMasses(mono, avg)) return false; } return true; } /*! \brief Performs the actual cross-linking as described in \a cross_link_p. The chemical representation of the cross-link must have been performed in \a cross_link_p. Returns true upon success or false if the CrossLink does not validate successfully. */ bool Polymer::crossLink(CrossLink *cross_link_p) { Q_ASSERT(cross_link_p); // This function must be called once all the members taking part // into the crossLink have been set. if(!cross_link_p->validate()) return false; // OK, from the perspective of the chemical modification of the // monomers involved in the crosslink, everything is fine. // Now is the moment that we actually perform the crossLink : this // is done simply by adding *this crossLink to the list of // crossLinks that belongs to the polymer. mpa_crossLinkList->append(cross_link_p); // If the crossLink dialog is open, inform it that it can refresh // the data. emit(crossLinkChangedSignal(this)); return true; } /*! \brief Undoes the cross-link \a cross_link_p. Returns true upon success or false if the CrossLink does not validate successfully. */ bool Polymer::uncrossLink(CrossLink *cross_link_p) { Q_ASSERT(cross_link_p); if(!cross_link_p->validate()) return false; mpa_crossLinkList->removeAt(mpa_crossLinkList->indexOf(cross_link_p)); delete cross_link_p; cross_link_p = 0; // If the crossLink dialog is open, inform it that it can refresh // the data. emit(crossLinkChangedSignal(this)); return true; } /*! \brief Determines the element composition of this polymer. The elemental composition is performed by looking into the core chemical entities of the polymer, like the monomers, the modifications, but also by accounting for the IonizeRule \a ionize_rule, and the CalcOptions \a calc_options. The polymer sequence is accounted for by looking at the \a coordinate_list list of Coordinates. Returns the elemental composition. \sa Coordinates, CoordinateList, IonizeRule */ QString Polymer::elementalComposition(const IonizeRule &ionize_rule, const CoordinateList &coordinate_list, const CalcOptions &calc_options) { Formula formula; IsotopicDataCstSPtr isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr(); // Iterate in all the oligomers that are encompassed in the // selection. for(int iter = 0; iter < coordinate_list.size(); ++iter) { // New coordinates instance we are iterating into. Coordinates *coordinates = coordinate_list.at(iter); for(int jter = coordinates->start(); jter < coordinates->end() + 1; ++jter) { const Monomer *iterMonomer = at(jter); Q_ASSERT(iterMonomer); // Set the formula of the new monomer in the same formula // instance. formula.setFormula(iterMonomer->formula()); // Incrementally account for the new formula in the same // atomcount list in the formula. if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); if(calc_options.monomerEntities() & MONOMER_CHEMENT_MODIF && iterMonomer->isModified()) { for(int jter = 0; jter < iterMonomer->modifList()->size(); ++jter) { Modif *modif = iterMonomer->modifList()->at(jter); formula.setFormula(modif->formula()); // Incrementally account for the new formula in the same // atomcount list in the formula. if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false) qFatal( "Fatal error at %s@%d. " "Aborting.", __FILE__, __LINE__); } } } // End of for (int jter = m_startIndex ; jter < m_endIndex + 1; // ++jter) } // qDebug() << __FILE__ << __LINE__ // << "Formula after accounting for all the residual chains:" // << formula.elementalComposition(); // We now have to account for the left/right cappings. However, // when there are multiple region selections(that is multiple // Coordinate elements in the calc_options.coordinateList()) it is // necessary to know if the user wants each of these Coordinates // to be considered real oligomers(each one with its left/right // caps) or as residual chains. Thus there are two cases: // 1. Each Coordinates item should be considered an oligomer //(SelectionType is SELECTION_TYPE_OLIGOMERS), thus for each item // the left and right caps should be accounted for. This is // typically the case when the user selects multiple regions to // compute the mass of cross-linked oligomers. // 2. Each Coordinates item should be considered a residual chain //(SelectionType is SELECTION_TYPE_RESIDUAL_CHAINS), thus only // one item should see its left and right caps accounted for. This // is typically the case when the user selects multiple regions // like it would select repeated sequence elements in a polymer // sequence: all the regions selected are treated as a single // oligomer. // Holds the number of times the chemical entities are to be // accounted for. int times = 0; if(calc_options.selectionType() == SELECTION_TYPE_RESIDUAL_CHAINS) { times = 1; // qDebug() << __FILE__ << __LINE__ // << "SELECTION_TYPE_RESIDUAL_CHAINS ; times:" << times; } else { times = calc_options.coordinateList().size(); // qDebug() << __FILE__ << __LINE__ // << "SELECTION_TYPE_OLIGOMERS ; times:" << times; } // Account for the left and right cap masses, if so required. if(calc_options.capping() & CAP_LEFT) { formula.setFormula(mcsp_polChemDef->leftCap()); // Incrementally account for the new formula in the same // atomcount list in the formula. if(formula.accountSymbolCounts(isotopic_data_csp, times) == false) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); // qDebug() << __FILE__ << __LINE__ // << "Formula after accounting left cap:" // << formula.elementalComposition(); } if(calc_options.capping() & CAP_RIGHT) { formula.setFormula(mcsp_polChemDef->rightCap()); // Incrementally account for the new formula in the same // atomcount list in the formula. if(formula.accountSymbolCounts(isotopic_data_csp, times) == false) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); // qDebug() << __FILE__ << __LINE__ // << "Formula after accounting right cap:" // << formula.elementalComposition(); } // Account for the left and right modification masses, if so // required and the region(s) require(s) it: we have to make it // clear if the selection encompasses indices 0(left end) and/or // polymerSize-1(right end). if(calc_options.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF) { if(coordinate_list.encompassIndex(0)) { Modif modif = leftEndModif(); formula.setFormula(modif.formula()); // qDebug() << __FILE__ << __LINE__ // << "Accounting for left end modif:" // << modif.name(); // Incrementally account for the new formula in the same // atomcount list in the formula. if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); // qDebug() << __FILE__ << __LINE__ // << "Formula after accounting left end modif:" // << formula.elementalComposition(); } } if(calc_options.polymerEntities() & POLYMER_CHEMENT_RIGHT_END_MODIF) { if(coordinate_list.encompassIndex(size() - 1)) { Modif modif = rightEndModif(); formula.setFormula(modif.formula()); // qDebug() << __FILE__ << __LINE__ // << "Accounting for right end modif:" // << modif.name(); // Incrementally account for the new formula in the same // atomcount list in the formula. if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); // qDebug() << __FILE__ << __LINE__ // << "Formula after accounting right end modif:" // << formula.elementalComposition(); } } // At this point we should not forget if the user asks to take into // account the cross-links... However, BE CAREFUL that cross-links // can only be taken into account if all the partners of a given // cross-link are actually encompassed into the selection. if(calc_options.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK) { for(int iter = 0; iter < crossLinkList().size(); ++iter) { CrossLink *crossLink = crossLinkList().at(iter); if(crossLink->encompassedBy(coordinate_list) == CROSS_LINK_ENCOMPASSED_FULL) { // The crossLink is fully encompassed by our monomer // stretch, so we should take it into account. // qDebug() << __FILE__ << __LINE__ // << "Accounting for fully encompassed cross-link:" // << crossLink->name(); if(!crossLink->formula().isEmpty()) { formula.setFormula(crossLink->formula()); // qDebug() << __FILE__ << __LINE__ // << "Cross-link formula:" << // crossLink->formula(); // Incrementally account for the new formula in the same // atomcount list in the formula. if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false) qFatal( "Fatal error at %s@%d. " "Aborting.", __FILE__, __LINE__); } // And now each modification that belongs to the // crosslinker. for(int jter = 0; jter < crossLink->modifList().size(); ++jter) { QString iterFormulaString = crossLink->modifList().at(jter)->formula(); // qDebug() << __FILE__ << __LINE__ // << "Cross-link's modif formula:" // << iterFormulaString; formula.setFormula(iterFormulaString); // Incrementally account for the new formula in the same // atomcount list in the formula. if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false) qFatal( "Fatal error at %s@%d. " "Aborting.", __FILE__, __LINE__); } } // End of // if (ret == CROSS_LINK_ENCOMPASSED_FULL) } // End of // for (int iter = 0; iter < crossLinkList->size(); ++iter) } // End of // if (calc_options.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK) // The ionization rule. Do not forget to take into account the // level! formula.setFormula(ionize_rule.formula()); // Incrementally account for the new formula in the same // atomcount list in the formula. if(formula.accountSymbolCounts(isotopic_data_csp, ionize_rule.level()) == false) qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__); // qDebug() << __FILE__ << __LINE__ // << "Formula after accounting ionization: " // << formula.elementalComposition(); return formula.elementalComposition(); } /*! \brief Parses the XML \a element representing a sequence of monomer codes. We are getting this: \c{MEFEEGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT} about which we need to create \l{Monomer}s and add them to this polymer's \l{Sequence}. Returns true if parsing and conversion of the text to a monomer list were successful, false otherwise. \sa Sequence::makeMonomerList() */ bool Polymer::renderXmlCodesElement(const QDomElement &element) { QString sequence; // We are getting this: // MEFEEDWYGEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT // We have to make monomers and add them to the list of monomers. if(element.tagName() != "codes") { qDebug() << "Expected codes element not found."; return false; } m_monomerText = element.text(); // qDebug() << "Now rendering the element, that is the sequence:" //<< m_monomerText; if(makeMonomerList(mcsp_polChemDef, false) == -1) { qDebug() << "Failed to make the monomer list."; return false; } else return true; } /*! \brief Extracts the name of the polymer chemistry definition from the \a file_path polymer sequence file. Returns the polymer chemistry definition name. */ QString Polymer::xmlPolymerFileGetPolChemDefName(const QString &file_path) { QDomDocument doc("polSeqData"); QDomElement element; QDomElement child; QDomElement indentedChild; QFile file(file_path); /* protein ... */ if(!file.open(QIODevice::ReadOnly)) return QString(""); if(!doc.setContent(&file)) { file.close(); return QString(""); } file.close(); element = doc.documentElement(); if(element.tagName() != "polseqdata") { qDebug() << "Polymer sequence file is erroneous\n"; return QString(""); } // child = element.firstChildElement(); if(child.tagName() != "polchemdef_name") return QString(""); return child.text(); } /*! \brief Extracts from \a element, using the proper function (\a version), the polymer end modification. The \a element tag is found in the polymer sequence XML file. If the \a element tag name is \c{le_modif}, the modification name is set to the left end modification of this polymer sequence; if the tag name is \c{re_modif}, the right end of this polymer is modifified. The modifications are then rendered in place. Returns true if no error was encountered, false otherwise. \sa Modif::renderXmlMdfElement() */ bool Polymer::renderXmlPolymerModifElement(const QDomElement &element, int version) { if(element.tagName() != "le_modif" && element.tagName() != "re_modif") return false; if(version == 1) { // no-op version = 1; } QDomElement child; if(element.tagName() == "le_modif") { // Go down to the element. child = element.firstChildElement(); if(child.isNull()) return true; if(!m_leftEndModif.renderXmlMdfElement(child, version)) return false; else return true; } else if(element.tagName() == "re_modif") { // Go down to the element. child = element.firstChildElement(); if(child.isNull()) return true; if(!m_rightEndModif.renderXmlMdfElement(child, version)) return false; else return true; } return false; } /*! \brief Extracts from \a element, using the proper function (\a version), all the \l{CrossLink}s contained in it. Each cross-link is rendered apart and applied to this polymer. Returns true if no error was encountered, false otherwise. \sa crossLink() */ bool Polymer::renderXmlCrossLinksElement(const QDomElement &element, int version) { if(version == 1) { // no-op version = 1; } QDomElement child; QDomElement indentedChild; // element is // // // DisulfideBond // ;2;6; // // if(element.tagName() != "crosslinks") return false; child = element.firstChildElement(); // There can be any number of elements. while(!child.isNull()) { if(child.tagName() != "crosslink") return false; indentedChild = child.firstChildElement(); if(indentedChild.tagName() != "name") return false; // We actually do have a element, so we can allocate // one now. // qDebug() << "Rendering a polymer sequence CrossLink by name:" //<< indentedChild.text(); CrossLink *aCrossLink = new CrossLink( mcsp_polChemDef, this, indentedChild.text(), "NOT_SET", "NOT_SET"); // And now find in the polymer chemistry definition the right // crossLinker and copy it into our newly allocated one. if(!mcsp_polChemDef->referenceCrossLinkerByName( indentedChild.text(), static_cast(aCrossLink))) { delete aCrossLink; return false; } // At this point the crossLinker superclass of crossLink is // updated with the ref one. indentedChild = indentedChild.nextSiblingElement(); if(indentedChild.tagName() != "targets") { delete aCrossLink; return false; } if(aCrossLink->populateMonomerList(indentedChild.text()) == -1) { delete aCrossLink; return false; } indentedChild = indentedChild.nextSiblingElement(); if(!indentedChild.isNull()) { if(indentedChild.tagName() != "comment") { delete aCrossLink; return false; } } // At this point the crossLink element is finished rendering, // all we have to do is perform the crossLink proper. if(!crossLink(aCrossLink)) { delete aCrossLink; return false; } child = child.nextSiblingElement(); } return true; } /*! \brief Parses the \a file_path polymer sequence file. During parsing, the encountered data are set to this polymer. This parsing is called "rendering". Returns true if parsing succeeded, false otherwise. */ bool Polymer::renderXmlPolymerFile(QString file_path) { QString localFilePath; QDomDocument doc("polSeqData"); QDomElement element; QDomElement child; QDomElement indentedChild; Monomer *monomer = nullptr; /* protein Sample SP2003 rusconi 1967-09-224:09:23 */ if(file_path.isEmpty()) localFilePath = m_filePath; else localFilePath = file_path; QFile file(localFilePath); if(!file.open(QIODevice::ReadOnly)) { qDebug() << "Could not open file."; return false; } if(!doc.setContent(&file)) { qDebug() << "Failed to set file contents to doc object."; file.close(); return false; } file.close(); element = doc.documentElement(); if(element.tagName() != "polseqdata") { qDebug() << "Polymer sequence file is erroneous\n"; return false; } /////////////////////////////////////////////// // Check the version of the document. QString text; if(!element.hasAttribute("version")) text = "1"; else text = element.attribute("version"); bool ok = false; int version = text.toInt(&ok, 10); if(version < 1 || !ok) { qDebug() << "Polymer sequence file has bad version number: " << version; return false; } // child = element.firstChildElement(); if(child.tagName() != "polchemdef_name") return false; // mcsp_polChemDef->setName(child.text()); // child = child.nextSiblingElement(); if(child.tagName() != "name") return false; m_name = child.text(); // child = child.nextSiblingElement(); if(child.tagName() != "code") return false; m_code = child.text(); // child = child.nextSiblingElement(); if(child.tagName() != "author") return false; m_author = child.text(); // child = child.nextSiblingElement(); if(child.tagName() != "datetime") return false; m_dateTime = QDateTime::fromString(child.text(), "yyyy-MM-dd:mm:ss"); // child = child.nextSiblingElement(); if(child.tagName() != "polseq") return false; /* MEFEEDF S MODIF Phosphorylation GRKDKNFLKMGRK Acetylation -H+C2H3O * 1 Phosphorylation -H+H2PO3 * 1 */ // There can be any number of and elements, in // whatever order. indentedChild = child.firstChildElement(); while(!indentedChild.isNull()) { if(indentedChild.tagName() == "codes") { if(!renderXmlCodesElement(indentedChild)) { qDebug() << "Failed to render the XML codes element."; return false; } } else if(indentedChild.tagName() == "monomer") { monomer = new Monomer(mcsp_polChemDef, "NOT_SET"); if(!monomer->renderXmlMonomerElement(indentedChild, version)) { qDebug() << "Failed to render the XML monomer element."; delete monomer; return false; } m_monomerList.append(monomer); } else return false; indentedChild = indentedChild.nextSiblingElement(); } // Go on to the next element(has to be . QString error; child = child.nextSiblingElement(); if(child.tagName() != "le_modif") error = "Expected le_modif element not found."; if(!renderXmlPolymerModifElement(child, version)) error = "Failed to render the left end modif element."; // Go on to the next element(has to be . child = child.nextSiblingElement(); if(child.tagName() != "re_modif") error = "Expected re_modif element not found."; if(!renderXmlPolymerModifElement(child, version)) error = "Failed to render the right end modif element."; // Go on to the next element(has to be . child = child.nextSiblingElement(); if(child.tagName() != "crosslinks") error = "Expected crosslinks element not found."; if(!renderXmlCrossLinksElement(child, version)) error = "Failed to render the crosslinks element."; if(!error.isEmpty()) { qDebug() << "Rendering of XML file failed with error:" << error; return false; } setFilePath(localFilePath); // qDebug() << "Finished rendering the XML file, returning true."; return true; } /*! \brief Creates the XML DTD for a polymer sequence file. Returns The DTD in a dynamically allocated string. */ QString * Polymer::formatXmlDtd() { QString *string = new QString( "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "]>\n"); return string; } /*! \brief Writes this polymer to file. Returns true if successful, false otherwise. */ bool Polymer::writeXmlFile() { QString *string = 0; QString indent(" "); // We are asked to send an xml description of the polymer sequence. QFile file(m_filePath); if(!file.open(QIODevice::WriteOnly)) { qDebug() << "Failed to open file" << m_filePath << "for writing."; return false; } QTextStream stream(&file); stream.setEncoding(QStringConverter::Utf8); // The DTD string = formatXmlDtd(); stream << *string; delete string; // Open the element. //"\n" stream << QString("\n") .arg(POL_SEQ_FILE_FORMAT_VERSION); Q_ASSERT(!mcsp_polChemDef->name().isEmpty()); stream << QString("%1%2\n") .arg(indent) .arg(mcsp_polChemDef->name()); stream << QString("%1%2\n") .arg(indent) .arg(m_name.isEmpty() ? "Not Set" : m_name); stream << QString("%1%2\n") .arg(indent) .arg(m_code.isEmpty() ? "Not Set" : m_code); Q_ASSERT(!m_author.isEmpty()); stream << QString("%1%2\n").arg(indent).arg(m_author); m_dateTime = QDateTime::currentDateTime(); stream << QString("%1%2\n").arg(indent).arg(dateTime()); string = formatXmlPolSeqElement(POL_SEQ_FILE_FORMAT_VERSION); if(string == 0) { qDebug() << "Failed to produce the element string."; return false; } stream << *string; delete string; // Now deal with the polymer modifications. These are represented as // elements. // Left end modif Q_ASSERT(!m_leftEndModif.name().isEmpty()); stream << QString("%1\n").arg(indent); if(m_leftEndModif.name() != "NOT_SET") { string = m_leftEndModif.formatXmlMdfElement(POL_SEQ_FILE_FORMAT_VERSION); stream << *string; delete string; } stream << QString("%1\n").arg(indent); // Right end modif Q_ASSERT(!m_rightEndModif.name().isEmpty()); stream << QString("%1\n").arg(indent); if(m_rightEndModif.name() != "NOT_SET") { string = m_rightEndModif.formatXmlMdfElement(POL_SEQ_FILE_FORMAT_VERSION); stream << *string; delete string; } stream << QString("%1\n").arg(indent); string = 0; string = formatXmlCrossLinksElement(POL_SEQ_FILE_FORMAT_VERSION); if(!string) { qDebug() << "Failed to produce the element string."; return false; } stream << *string; delete string; // Note that at some point, there might be any number of polymer // elements at this place... // Finally close the polseqdata. stream << QString("\n"); return true; } /*! \brief Formats this polymer's sequence as a string suitable to use as an XML element. This function generates a string holding all the elements pertaining to this polymer' \e sequence (the list of monomers, potentially modified, \e not all the other data). The typical element that is generated in this function looks like this: \code MEFEEDWYGEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT S Phosphorylation * \endcode \a offset times the \a indent string must be used as a lead in the formatting of elements. Returns a dynamically allocated string that needs to be freed after use. \sa writeXmlFile() */ QString * Polymer::formatXmlPolSeqElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString codesString(""); QString *monomerString = 0; QString *string = new QString(); const Monomer *monomer = 0; // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } // At this point, we have to iterate in the sequence. If the // monomers are not modified, then put their codes in a raw, like // "ETGSH", in a element. As soon as a monomer is modified, // whatever the modification --that is, it has a prop object in its // --m_propList, it and its contents should be listed in a detailed // element. *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Iterate in the polymer sequence. for(int iter = 0; iter < m_monomerList.size(); ++iter) { monomer = m_monomerList.at(iter); Q_ASSERT(monomer); // Check if the monomer is modified. If not, we just append its // code to the elongating codesString, else we use a more // thorough monomer element-parsing function. if(!monomer->isModified()) { codesString += monomer->code(); continue; } else { // If something was baking in codesString, then we have to // create the element right now, fill the data in it and // close it before opening one element below. if(!codesString.isEmpty()) { *string += QString("%1%2%3") .arg(lead) .arg(codesString) .arg("\n"); codesString.clear(); } monomerString = monomer->formatXmlMonomerElement(newOffset); if(!monomerString) { delete string; return 0; } else { *string += *monomerString; } } } // If something was baking in codesString, then we have to // create the element right now, fill the data in it and // close it before opening one element below. if(!codesString.isEmpty()) { *string += QString("%1%2%3").arg(lead).arg(codesString).arg("\n"); codesString.clear(); } // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } /*! \brief Formats an XML element suitable to describe the \c element. Iterates in the cross-link list of this polymer and crafts XML elements describing them. The XML element looks like this: \code DisulfideBond ;2;6; \endcode \a offset times the \a indent string must be used as a lead in the formatting of elements. Returns the XML element as a dynamically allocated string. */ QString * Polymer::formatXmlCrossLinksElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } // This is the kind of string we have to generate. // // // DisulfideBond // ;2;6; // // // At this point, we have to iterate in the list of crosslinks and // for each crosslink determine what's the crosslinker and which // monomer are actually crosslinked together. *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } for(int jter = 0; jter < mpa_crossLinkList->size(); ++jter) { CrossLink *crossLink = mpa_crossLinkList->at(jter); Q_ASSERT(crossLink); *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1%2\n").arg(lead).arg(crossLink->name()); // Create the string with all the monomer indices(which are the // targets of the crossLink). *string += QString("%1%2\n") .arg(lead) .arg(crossLink->monomerIndexText()); *string += QString("%1%2\n") .arg(lead) .arg(crossLink->comment()); // Prepare the lead. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); } // Prepare the lead. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } /*! \brief Validates the Sequence of this polymer. Returns true if validation was successful, false, otherwise. \sa Sequence::validate() */ bool Polymer::validate() { return Sequence::validate(mcsp_polChemDef); } /*! Outputs a string describing the polymer using qDebug(). Used for debugging purposes. */ void Polymer::debugPutStdErr() { qDebug() << m_name << m_code << mcsp_polChemDef->name() << m_author << m_filePath << m_leftEndModif.name() << m_rightEndModif.name(); for(int iter = 0; iter < m_monomerList.size(); ++iter) { qDebug() << m_monomerList.at(iter)->code(); } } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Ponderable.cpp000664 001750 001750 00000020317 14647465366 022762 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "Ponderable.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::Ponderable \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile Ponderable.hpp \brief The Ponderable class provides an abstraction for any chemical entity having a monoisotopic and an average mass. Functions are provided to modify the member masses. \sa Modif, Monomer */ /*! \variable MsXpS::libXpertMass::Ponderable::m_mono \brief The monoisotopic mass. */ /*! \variable MsXpS::libXpertMass::Ponderable::m_avg \brief The average mass. */ /*! \brief Constructs a ponderable initializing its masses with \a mono and \a avg. */ Ponderable::Ponderable(double mono, double avg) { m_mono = mono; m_avg = avg; } /*! \brief Constructs a ponderable initializing it using \a other. */ Ponderable::Ponderable(const Ponderable &other) : m_mono(other.m_mono), m_avg(other.m_avg) { } /*! \brief Destructs the ponderable. */ Ponderable::~Ponderable() { } /*! \brief Assigns to this Ponderable' member data the values in \a other. Returns a reference to this Ponderable */ Ponderable & Ponderable::operator=(const Ponderable &other) { if(&other == this) return *this; m_mono = other.m_mono; m_avg = other.m_avg; return *this; } /*! \brief Initializes the member data using \a mono and \a avg. */ void Ponderable::setMasses(double mono, double avg) { m_mono = mono; m_avg = avg; } /*! \brief Initializes the member data using \a mass. The member datum that is initialized depends on the value of \a type. */ void Ponderable::setMass(double mass, MassType type) { if(type & MassType::MASS_MONO) m_mono = mass; else if(type & MassType::MASS_AVG) m_avg = mass; return; } /*! \brief Add to the member data the mass value in \a mass. The member datum that is modified depends on the value of \a type. */ void Ponderable::incrementMass(double mass, MassType type) { if(type & MassType::MASS_MONO) m_mono += mass; else if(type & MassType::MASS_AVG) m_avg += mass; return; } /*! \brief Sets the \a mono and \a avg to the corresponding member data values. The pointer arguments cannot be nullptr. */ void Ponderable::masses(double *mono, double *avg) const { Q_ASSERT(mono != nullptr && avg != nullptr); *mono = m_mono; *avg = m_avg; } /*! \brief Returns the mass depending on the \a type. */ double Ponderable::mass(MassType type) const { if(type == MassType::MASS_MONO) return m_mono; return m_avg; } /*! \brief Sets the member data masses to 0.0. */ void Ponderable::clearMasses() { m_mono = 0.0; m_avg = 0.0; } /*! \brief Sets the monoisotopic mass to \a mass. */ void Ponderable::setMono(double mass) { m_mono = mass; } /*! \brief Sets the monoisotopic mass to \a mass. The \a mass string is converted to double. Returns true if the conversion succceeded, false otherwise. */ bool Ponderable::setMono(const QString &mass) { bool ok = false; double value = mass.toDouble(&ok); if(!ok) return false; m_mono = value; return true; } /*! \brief Increments the monoisotopic mass by adding \a mass. */ void Ponderable::incrementMono(double mass) { m_mono += mass; } /*! \brief Decrements the monoisotopic mass by subtracting \a mass. */ void Ponderable::decrementMono(double mass) { m_mono -= mass; } /*! \brief Returns the monoisotopic mass. */ double Ponderable::mono() const { return m_mono; } /*! \brief Returns a reference to the monoisotopic mass. */ double & Ponderable::rmono() { return m_mono; } /*! \brief Returns a string representing the monoisotopic mass using \a decimalPlaces for the precision. */ QString Ponderable::monoString(int decimalPlaces) const { if(decimalPlaces < 0) decimalPlaces = 0; QString mass; mass.setNum(m_mono, 'f', decimalPlaces); return mass; } /*! \brief Sets the average mass to \a mass. */ void Ponderable::setAvg(double mass) { m_avg = mass; } /*! \brief Sets the average mass to \a mass. The \a mass string is converted to double. Returns true if the conversion succceeded, false otherwise. */ bool Ponderable::setAvg(const QString &mass) { bool ok = false; double value = mass.toDouble(&ok); if(!ok) return false; m_avg = value; return true; } /*! \brief Increments the average mass by adding \a mass. */ void Ponderable::incrementAvg(double mass) { m_avg += mass; } /*! \brief Decrements the average mass by subtracting \a mass. */ void Ponderable::decrementAvg(double mass) { m_avg -= mass; } /*! \brief Returns the average mass. */ double Ponderable::avg() const { return m_avg; } /*! \brief Returns a reference to the average mass. */ double & Ponderable::ravg() { return m_avg; } /*! \brief Returns a string representing the average mass using \a decimalPlaces for the precision. */ QString Ponderable::avgString(int decimalPlaces) const { if(decimalPlaces < 0) decimalPlaces = 0; QString mass; mass.setNum(m_avg, 'f', decimalPlaces); return mass; } /*! Returns true if \a other and this ponderable have the same masses, false otherwise. */ bool Ponderable::operator==(const Ponderable &other) const { if(m_mono == other.m_mono && m_avg == other.m_avg) return true; return false; } /*! Returns true if \a other and this ponderable have at least one differing mass, false otherwise. */ bool Ponderable::operator!=(const Ponderable &other) const { if(m_mono != other.m_mono || m_avg != other.m_avg) { // qDebug() << __FILE__ << __LINE__ // << "m_mono:" << m_mono // << "/" // << "other.m_mono:" << other.m_mono // << "--" // << "m_avg:" << m_avg // << "/" // << "other.m_avg:" << other.m_avg; return true; } return false; } bool Ponderable::calculateMasses() { return true; } /*! \brief Increase the member masses by adding either \a mono and or \a avg compounded by \a times. The monoisotopic mass is increased if \a mono is not nullptr. The \a mono value is compounded by \a times and the result is added to the member monoisotopic datum. The average mass is increased if \a avg is not nullptr. The \a avg value is compounded by \a times and the result is added to the member average datum. Always returns true. */ bool Ponderable::accountMasses(double *mono, double *avg, int times) const { if(mono != nullptr) *mono += m_mono * times; if(avg != nullptr) *avg += m_avg * times; return true; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Prop.cpp000664 001750 001750 00000055262 14647465366 021636 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Local includes #include "Prop.hpp" #include "ChemicalGroup.hpp" #include "CrossLink.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::Prop \inmodule libXpertMass \ingroup ThePropSystem \inheaderfile Prop.hpp \brief The Prop class is the abstract base class for a number of specialized properties. Properties are libmass' way of extending the capabilities of objects. A property is merely an encapsulation of: \list \li a name(a QString); \li a pointer to data specifically allocated for this property to become the owner of these data (the member data pointer is a void *); \endlist In order to perform tasks in the derived classes using dynamic binding, virtual functions are available to derived classes to perform: \list \li XML string formatting (formatXmlElement()); \li XML element rendering (renderXmlElement()); \li data destruction (deleteData()). \endlist Derived classes should be named according the following scheme: XxxYyyZzzzProp, like StringProp or MonomerProp or ModifProp. \note When a derived class is created, it should register itself to the system by registering its name in the propAllocator(const QString &) function. This function will be able to allocate a property using the proper constructor based on the property name that is passed as argument. It returns a pointer to the newly allocated instance. The classes that benefit from this Property-based extension mechanism all derive from \l PropListHolder. \sa StringProp, PropListHolder */ /*! \variable int MsXpS::libXpertMass::Prop::m_name \brief The name of the property. Initialized to "NOT_SET". */ /*! \variable int MsXpS::libXpertMass::Prop::mpa_data \brief The allocated data belonging to this Prop instance. Initialized to nullptr. */ /*! \brief Constructs a Prop instance. */ Prop::Prop() { } /*! \brief Constructs a Prop instance with \a name. */ Prop::Prop(const QString &name) : m_name(name) { } /*! \brief Constructs a Prop instance as a copy of \a other. \note The data are not duplicated. */ Prop::Prop(const Prop &other) : m_name(other.m_name) { // Now check how to duplicate the data. if(other.mpa_data == nullptr) return; // We cannot duplicate the data because we do not know their type. } /*! \brief Destructs this Prop instance. */ Prop::~Prop() { // Do nothing here. } /*! \brief Sets the \a name. */ void Prop::setName(QString &name) { m_name = name; } /*! \brief Returns the name. */ const QString & Prop::name() { return m_name; } /*! \brief Sets the \a data */ void Prop::setData(void *data) { if(mpa_data != 0) deleteData(); mpa_data = data; } /*! \brief Returns the data. */ void * Prop::data() const { return mpa_data; } void Prop::deleteData() { // Do nothing here. } /*! \brief Assigns \a other to this Prop instance. */ Prop & Prop::operator=(const Prop &other) { if(&other == this) return *this; m_name = other.m_name; // We cannot duplicate the data because we do not know their type. return *this; } bool Prop::renderXmlElement([[maybe_unused]] const QDomElement &element, [[maybe_unused]] int version) { // Do nothing here. return false; } QString * Prop::formatXmlElement([[maybe_unused]] int offset, [[maybe_unused]] const QString &indent) { // Do nothing here. return nullptr; } //////////////////////// StringProp //////////////////////// //////////////////////// StringProp //////////////////////// /*! \class MsXpS::libXpertMass::StringProp \inmodule libXpertMass \ingroup ThePropSystem \brief The StringProp class is the specialized class for properties that hold data in the form of string objects. A StringProp property is a simple property in which the data is a pointer to an allocated QString. */ /*! \brief Constructs a StringProp instance. \list \li \a name: The name of the property. \li \a data: The data of the property. \endlist The string passed as \a data is used to dynamically allocate a new string with the same contents and then is set to the member mpa_data pointer. */ StringProp::StringProp(const QString &name, const QString &data) { if(!name.isEmpty()) m_name = name; else m_name = QString(); if(!data.isEmpty()) mpa_data = static_cast(new QString(data)); else mpa_data = nullptr; } /*! \brief Constructs a StringProp instance. \list \li \a name: The name of the property. \li \a data: The data of the property. \endlist The string passed as \a data is used to dynamically allocate a new string with the same contents and then is set to the member mpa_data pointer. */ StringProp::StringProp(const QString &name, QString *data) { if(!name.isEmpty()) m_name = name; else m_name = QString(); if(data) mpa_data = new QString(*data); else mpa_data = nullptr; } /*! \brief Constructs a StringProp instance as a copy of \a other. */ StringProp::StringProp(const StringProp &other) : Prop(other) { if(mpa_data != nullptr) deleteData(); if(other.mpa_data != nullptr) { QString *text = static_cast(other.mpa_data); mpa_data = static_cast(new QString(*text)); } else mpa_data = nullptr; } /*! \brief Destructs this StringProp instance. Deletion of the data is delegated to \l deleteData(). */ StringProp::~StringProp() { deleteData(); } /*! \brief Deletes the member data. */ void StringProp::deleteData() { if(mpa_data != nullptr && !static_cast(mpa_data)->isNull()) { delete static_cast(mpa_data); mpa_data = nullptr; } } /*! \brief Assigns \a other to this StringProp instance. Returns a reference to this StringProp instance. */ StringProp & StringProp::operator=(const StringProp &other) { if(&other == this) return *this; Prop::operator=(other); if(mpa_data != nullptr) deleteData(); if(other.mpa_data != nullptr) { QString *text = static_cast(other.mpa_data); mpa_data = static_cast(new QString(*text)); } else mpa_data = nullptr; return *this; } /*! \brief Duplicates this StringProp instance and returns a pointer to it. */ StringProp * StringProp::cloneOut() const { StringProp *new_p = new StringProp(*this); return new_p; } /*! \brief Parses the string-only property XML \a element using a \a{version}ed function. Parses the string-only property XML element passed as argument and for each encountered data(name and data) will set the data to this string-only property (this is called XML rendering). Returns true if parsing was successful, false otherwise. */ bool StringProp::renderXmlElement(const QDomElement &element, [[maybe_unused]] int version) { QDomElement child; /* This is what we expect. * * MODIF * acetylation * */ if(element.tagName() != "prop") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); // And now we have to manage the prop objects. child = child.nextSiblingElement(); if(child.isNull()) return false; mpa_data = static_cast(new QString(child.text())); return true; } /*! \brief Formats a string suitable to use as an XML element. Formats a string suitable to be used as an XML element in a polymer sequence file. Typical string-only property elements that might be generated in this function look like this: \code MODIF Phosphorylation COMMENT Phosphorylation is only partial \endcode \a offset times the \a indent string must be used as a lead in the formatting of elements. Returns a dynamically allocated string that needs to be freed after use. */ QString * StringProp::formatXmlElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } /* We are willing to create an node that should look like this: * * MODIF * Phosphorylation * * * COMMENT * Phosphorylation is only partial * * * As shown, all the member data of the prop object are simple * strings. The name string is never dynamically allocated, while * the data string is always dynamically allocated. */ *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); *string += QString("%1%2\n") .arg(lead) .arg(*static_cast(mpa_data)); // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } //////////////////////// IntProp //////////////////////// //////////////////////// IntProp //////////////////////// /*! \class MsXpS::libXpertMass::IntProp \inmodule libXpertMass \ingroup ThePropSystem \brief The IntProp class is the specialized class for properties that hold data in the form of integer values. A IntProp property is a simple property in which the data is a pointer to an allocated integer. */ /*! \brief Constructs a IntProp instance. \list \li \a name: The name of the property. \li \a data: The data of the property. \endlist The integer passed as \a data is used to dynamically allocate a new integer with the same contents and then is set to the member mpa_data pointer. */ IntProp::IntProp(const QString &name, int data) : Prop(name) { mpa_data = static_cast(new int(data)); } /*! \brief Constructs a IntProp instance as a copy of \a other. */ IntProp::IntProp(const IntProp &other) : Prop(other) { if(other.mpa_data != nullptr) { int *value = static_cast(other.mpa_data); mpa_data = static_cast(new int(*value)); } } /*! \brief Destructs this IntProp instance. Deletion of the data is delegated to \l deleteData(). */ IntProp::~IntProp() { deleteData(); } /*! \brief Deletes the member data. */ void IntProp::deleteData() { if(mpa_data != nullptr) { delete static_cast(mpa_data); mpa_data = 0; } } /*! \brief Assigns \a other to this IntProp instance. Returns a reference to this IntProp instance. */ IntProp & IntProp::operator=(const IntProp &other) { if(&other == this) return *this; Prop::operator=(other); if(mpa_data != nullptr) deleteData(); if(other.mpa_data != nullptr) { int *value = static_cast(other.mpa_data); mpa_data = static_cast(new int(*value)); } else mpa_data = nullptr; return *this; } /*! \brief Duplicates this IntProp instance and returns a pointer to it. */ IntProp * IntProp::cloneOut() const { IntProp *new_p = new IntProp(*this); return new_p; } /*! \brief Parses a integer property XML \a element using a \a{version}ed function. Parses the integer property XML element passed as argument and for each encountered data (name and data) will set the data to this IntProp instance (this is called XML rendering). Returns true if parsing was successful, false otherwise.) */ bool IntProp::renderXmlElement(const QDomElement &element, [[maybe_unused]] int version) { QDomElement child; /* This is what we expect. * IONIZATION_LEVEL 5 * */ if(element.tagName() != "prop") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); // And now we have to manage the prop objects. child = child.nextSiblingElement(); if(child.isNull()) return false; mpa_data = static_cast(new int(child.text().toInt())); return true; } /*! \brief Formats a string suitable to use as an XML element. Formats a string suitable to be used as an XML element. Typical integer property elements that might be generated in this function look like this: \code IONIZATION_LEVEL 5 \endcode \a offset times the \a indent string must be used as a lead in the formatting of elements. Returns a dynamically allocated string that needs to be freed after use. */ QString * IntProp::formatXmlElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } /* We are willing to create an node that should look like this: * * * SEARCHED_MASS * 1000.234 * * */ *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); QString value; value = QString::number(*static_cast(mpa_data), 'g', 10); *string += QString("%1%2\n").arg(lead).arg(value); // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } //////////////////////// DoubleProp //////////////////////// //////////////////////// DoubleProp //////////////////////// /*! \class MsXpS::libXpertMass::DoubleProp \inmodule libXpertMass \ingroup ThePropSystem \brief The DoubleProp class is the specialized class for properties that hold data in the form of double values. A DoubleProp property is a simple property in which the data is a pointer to an allocated double. */ /*! \brief Constructs a DoubleProp instance. \list \li \a name: The name of the property. \li \a data: The data of the property. \endlist The integer passed as \a data is used to dynamically allocate a new double with the same contents and then is set to the member mpa_data pointer. */ DoubleProp::DoubleProp(const QString &name, double data) : Prop(name) { mpa_data = static_cast(new double(data)); } /*! \brief Constructs a DoubleProp instance as a copy of \a other. */ DoubleProp::DoubleProp(const DoubleProp &other) : Prop(other) { if(other.mpa_data != nullptr) { double *value = static_cast(other.mpa_data); mpa_data = static_cast(new double(*value)); } } /*! \brief Destructs this DoubleProp instance. Deletion of the data is delegated to \l deleteData(). */ DoubleProp::~DoubleProp() { deleteData(); } /*! \brief Deletes the member data. */ void DoubleProp::deleteData() { if(mpa_data) { delete static_cast(mpa_data); mpa_data = 0; } } /*! \brief Assigns \a other to this DoubleProp instance. Returns a reference to this DoubleProp instance. */ DoubleProp & DoubleProp::operator=(const DoubleProp &other) { if(&other == this) return *this; Prop::operator=(other); if(mpa_data != nullptr) deleteData(); if(other.mpa_data != nullptr) { double *value = static_cast(other.mpa_data); mpa_data = static_cast(new double(*value)); } else mpa_data = nullptr; return *this; } /*! \brief Duplicates this DoubleProp instance and returns a pointer to it. */ DoubleProp * DoubleProp::cloneOut() const { DoubleProp *new_p = new DoubleProp(*this); return new_p; } /*! \brief Parses a double property XML \a element using a \a{version}ed function. Parses the double property XML element passed as argument and for each encountered data (name and data) will set the data to this DoubleProp instance (this is called XML rendering). Returns true if parsing was successful, false otherwise.) */ bool DoubleProp::renderXmlElement(const QDomElement &element, [[maybe_unused]] int version) { QDomElement child; /* This is what we expect. * * SEARCHED_MASS * 1000.234 * */ if(element.tagName() != "prop") return false; child = element.firstChildElement("name"); if(child.isNull()) return false; m_name = child.text(); // And now we have to manage the prop objects. child = child.nextSiblingElement(); if(child.isNull()) return false; mpa_data = static_cast(new double(child.text().toDouble())); return true; } /*! \brief Formats a string suitable to use as an XML element. Formats a string suitable to be used as an XML element. Typical double property elements that might be generated in this function look like this: \code SEARCHED_MASS 1000.234 \endcode \a offset times the \a indent string must be used as a lead in the formatting of elements. Returns a dynamically allocated string that needs to be freed after use. */ QString * DoubleProp::formatXmlElement(int offset, const QString &indent) { int newOffset; int iter = 0; QString lead(""); QString *string = new QString(); // Prepare the lead. newOffset = offset; while(iter < newOffset) { lead += indent; ++iter; } /* We are willing to create an node that should look like this: * * * SEARCHED_MASS * 1000.234 * * */ *string += QString("%1\n").arg(lead); // Prepare the lead. ++newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } // Continue with indented elements. *string += QString("%1%2\n").arg(lead).arg(m_name); QString value; value = QString::number(*static_cast(mpa_data), 'g', 10); *string += QString("%1%2\n").arg(lead).arg(value); // Prepare the lead for the closing element. --newOffset; lead.clear(); iter = 0; while(iter < newOffset) { lead += indent; ++iter; } *string += QString("%1\n").arg(lead); return string; } /////////////////// NoDeletePointerProp /////////////////// /////////////////// NoDeletePointerProp /////////////////// /*! \class MsXpS::libXpertMass::NoDeletePointerProp \inmodule libXpertMass \ingroup ThePropSystem \brief The NoDeletePointerProp class provides a pointer property. A NoDeletePointerProp property is a simple property in which the data is a pointer to an allocated instance, but which may never be destroyed by the property itself. This property is regarded as a simple "message-containing property". The message is nothing but the name of the property. */ /*! \brief Constructs a NoDeletePointerProp instance. \list \li \a name: The name of the property. \li \a no_delete_data_p: The data of the property. \endlist The member data pointer (\l mpa_data) is assigned \a no_delete_data_p and is not going to be deleted upon destruction of this NoDeletePointerProp instance. */ NoDeletePointerProp::NoDeletePointerProp(const QString &name, void *no_delete_data_p) : Prop(name) { mpa_data = no_delete_data_p; } /*! \brief Constructs a NoDeletePointerProp instance as a copy of \a other. */ NoDeletePointerProp::NoDeletePointerProp(const NoDeletePointerProp &other) : Prop(other) { mpa_data = static_cast(other.mpa_data); } /*! \brief Destructs this NoDeletePointerProp instance. Deletion of the data is delegated to \l deleteData(), that won't delete the data. */ NoDeletePointerProp::~NoDeletePointerProp() { deleteData(); } /*! \brief Does not delete the member data. */ void NoDeletePointerProp::deleteData() { // We do not do anything here. } /*! \brief Assigns \a other to this NoDeletePointerProp instance. Returns a reference to this NoDeletePointerProp instance. */ NoDeletePointerProp & NoDeletePointerProp::operator=(const NoDeletePointerProp &other) { if(&other == this) return *this; Prop::operator=(other); mpa_data = static_cast(other.mpa_data); return *this; } /*! \brief Duplicates this NoDeletePointerProp instance and returns a pointer to it. */ NoDeletePointerProp * NoDeletePointerProp::cloneOut() const { NoDeletePointerProp *new_p = new NoDeletePointerProp(*this); return new_p; } /*! \brief This function is a no-op. The \a element and the \a version parameters are not used. The NoDeletePointerProp class is not used to store or read data to or from files. Returns false if the \a element tag name is not "prop", true otherwise. */ bool NoDeletePointerProp::renderXmlElement(const QDomElement &element, [[maybe_unused]] int version) { if(element.tagName() != "prop") return false; return true; } /*! \brief This function is a no-op. The \a offset and the \a indent parameters are not used. The NoDeletePointerProp class is not used to store or read data to or from files. */ QString * NoDeletePointerProp::formatXmlElement(int offset, const QString &indent) { QString *string = new QString(QObject::tr("%1-This function does not return anything " "interesting-%2") .arg(offset) .arg(indent)); return string; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/PropListHolder.cpp000664 001750 001750 00000013165 14647465366 023624 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include /////////////////////// Local includes #include "PropListHolder.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::PropListHolder \inmodule libXpertMass \ingroup ThePropSystem \inheaderfile PropListHolder.hpp \brief The PropListHolder class is the base class for a number of classes that need storing Prop instances. \sa Modif, Monomer, Oligomer, PkaPhPi, */ /*! \variable int MsXpS::libXpertMass::PropListHolder::m_propList \brief The m_propList stores pointer to \l Prop instances. */ /*! \brief Constructs a PropListHolder instance. */ PropListHolder::PropListHolder() { } /*! \brief Constructs a PropListHolder instance as a copy of \a other. The Prop instances in the \a other PropListHolder instance's member list of Prop instances are duplicated and stored in this instance using their own copy constructor. */ PropListHolder::PropListHolder(const PropListHolder &other) { for(int iter = 0; iter < other.m_propList.size(); ++iter) { Prop *prop = other.m_propList.at(iter); Q_ASSERT(prop); //qDebug() << "Prop name:" << prop->name(); // Each Prop-derived class produces a derived Prop class. m_propList.append(prop->cloneOut()); } } /*! \brief Destructs this PropListHolder instance. */ PropListHolder::~PropListHolder() { while(!m_propList.isEmpty()) delete(m_propList.takeFirst()); } /*! \brief Assigns \a other to this PropListHolder instance. The Prop instances in the \a other PropListHolder instance's member list of Prop instances are duplicated and stored in this instance using their own copy constructor. Returns a reference to this PropListHolder instance. */ PropListHolder & PropListHolder::operator=(const PropListHolder &other) { if(&other == this) return *this; for(int iter = 0; iter < other.m_propList.size(); ++iter) { Prop *prop = other.m_propList.at(iter); Q_ASSERT(prop); m_propList.append(prop->cloneOut()); } return *this; } /*! \brief Returns a const reference to the member list of Prop instances. */ const QList & PropListHolder::propList() const { return m_propList; } /*! \brief Returns a non-const reference to the member list of Prop instances. */ QList & PropListHolder::propList() { return m_propList; } /*! \brief Searches in the member list of Prop instances a Prop having \a name. If the Prop instance was found and \a index is non-nullptr, its index in the list is set to this parameter. Returns the found Prop instance. */ Prop * PropListHolder::prop(const QString &name, int *index) { for(int iter = 0; iter < m_propList.size(); ++iter) { Prop *localProp = m_propList.at(iter); Q_ASSERT(localProp); if(localProp->name() == name) { if(index) *index = iter; return localProp; } } return 0; } /*! \brief Searches in the member list of Prop instances a Prop having \a name. If the Prop instance was found and \a prop is non-nullptr, its pointer is set to this parameter. Returns the index of the found Prop instance. */ int PropListHolder::propIndex(const QString &name, Prop *prop) { for(int iter = 0; iter < m_propList.size(); ++iter) { Prop *localProp = m_propList.at(iter); Q_ASSERT(localProp); if(localProp->name() == name) { if(prop) prop = localProp; return iter; } } return -1; } /*! \brief Adds \a prop to the member list of Prop instances. Returns true. */ bool PropListHolder::appendProp(Prop *prop) { m_propList.append(prop); return true; } /*! \brief Removes \a prop from the member list of Prop instances. Returns true if \a prop was removed, false otherwise. */ bool PropListHolder::removeProp(Prop *prop) { if(m_propList.removeAll(prop)) return true; return false; } /*! \brief Removes the Prop instance having \a name from the member list of Prop instances. If more than one Prop instance by the \a name are in the list, only the first encountered Prop instance is removed. Returns true if a Prop instance was removed, false otherwise. */ bool PropListHolder::removeProp(const QString &name) { for(int iter = 0; iter < m_propList.size(); ++iter) { if(m_propList.at(iter)->name() == name) { m_propList.removeAt(iter); return true; } } return false; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/Sequence.cpp000664 001750 001750 00000064205 14647465366 022463 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #include /////////////////////// Local includes #include "Sequence.hpp" #include "PolChemDef.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::Sequence \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile Sequence.hpp \brief The Sequence class provides abstractions to work with a simple sequence of \l{Monomer}s. A sequence of monomer can be represented in two ways: \list \li A string of monomer codes concatenated one to the other with no delimitation(like "ATGC" or "AlaThrGlyCys"); \li A list of fully qualified \l{Monomer} instances allocate on the heap. \endlist \note The reference status of a sequence is in the form of a list of allocated Monomer instances. The conversion to the string of monomer codes is only a utility. When a sequence is created (with an argument that is a string of monomer codes) the caller should ensure that the text sequence is converted into a list of monomers prior to starting using its methods extensively (see makeMonomerList()). Functions size() and removeMonomerAt()) only work on a sequence in the form of a list of \l{Monomer} instances. Methods are provided to convert from one sequence kind (concatenated codes) to the other sequence kind(list of Monomer instances). Equally interesting is the ability of the methods in this class to be able to: - parse the monomer sequence and to extract monomer codes one after the other; - remove monomers from the sequence at specified indexes; - add monomers to the sequence at specified indexes. However, for this rather basic class to be able to perform interesting tasks it has to be able to know where to find polymer chemistry definition \l{PolChemDef} data. This is possible only when a pointer to a polymer chemistry definition is passed to the used functions. */ /*! \variable MsXpS::libXpertMass::Sequence::m_monomerText \brief String holding the sequence of monomer codes. */ /*! \variable MsXpS::libXpertMass::Sequence::m_monomerList \brief List of allocated \l Monomer instances that should match the sequence of codes string (m_monomerText). */ /*! \brief Construct a Sequence using \a text. The sequence is in the form of a string of concatenated monomer codes. No quality check is performed. */ Sequence::Sequence(const QString &text) : m_monomerText(text) { } /*! \brief Construct this Sequence as a copy of \a other. The copying is deep with the list of Monomer instances copied to this Sequence. */ Sequence::Sequence(const Sequence &other) : m_monomerText(other.m_monomerText) { for(int iter = 0; iter < other.m_monomerList.size(); ++iter) m_monomerList.append(new Monomer(*other.m_monomerList.at(iter))); } /*! \brief Destructs this sequence. The \l Monomer instances are deleted. */ Sequence::~Sequence() { while(!m_monomerList.isEmpty()) delete m_monomerList.takeFirst(); } /*! \brief Assigns \a other to this Sequence Returns a reference to this Sequence. */ Sequence & Sequence::operator=(const Sequence &other) { if(&other == this) return *this; m_monomerText = other.m_monomerText; for(int iter = 0; iter < other.m_monomerList.size(); ++iter) m_monomerList.append(new Monomer(*other.m_monomerList.at(iter))); return *this; } /*! \brief Set this Sequence' string of monomer codes to \a text. */ void Sequence::setMonomerText(const QString &text) { m_monomerText = text; } /*! \brief Appends the \a text sequence of monomer codes to this Sequence. No verification is performed on \a text. */ void Sequence::appendMonomerText(const QString &text) { if(text.isEmpty()) return; m_monomerText += text; } /*! \brief Returns this Sequence's string of monomer codes. */ const QString * Sequence::monomerText() { return &m_monomerText; } /*! \brief Returns a reference to this Sequence's list of \l Monomer instances. */ const QList & Sequence::monomerList() const { return m_monomerList; } /*! \brief Returns this Sequence's list of \l Monomer instances. */ QList * Sequence::monomerListPtr() { return &m_monomerList; } /*! \brief Returns the size of this Sequence as the size of the list of Monomers instances. */ int Sequence::size() const { return m_monomerList.size(); } /*! \brief Returns true if \a index is valid as an index of a Monomer instance in this Sequence's list of \l{Monomer}s, false otherwise. */ bool Sequence::isInBound(int index) { if(index >= 0 && index < size()) return true; return false; } /*! \brief Removes all spaces, carriage returns and linefeeds from this Sequence's monomer codes string. */ void Sequence::unspacifyMonomerText() { // Removal of all spaces, carriage returns and linefeeds: for(int iter = m_monomerText.length() - 1; iter >= 0; --iter) { QChar curChar = m_monomerText.at(iter); QChar::Category category = curChar.category(); if(category == QChar::Separator_Space) m_monomerText.remove(iter, 1); else if(curChar == '\n') m_monomerText.remove(iter, 1); else if(curChar == '\r') m_monomerText.remove(iter, 1); } } /*! \brief Creates the monomer codes string version of this Sequence's list of \l{Monomer} instances and set it to this Sequence The function essentially writes to the m_monomerText string the code of each Monomer instance in m_monomerList. m_monomerText is cleared before writing the codes to it. Returns the count of monomer codes added to m_monomerText. \sa makeMonomerList() */ int Sequence::makeMonomerText() { int iter = 0; m_monomerText.clear(); for(iter = 0; iter < m_monomerList.size(); ++iter) m_monomerText.append(m_monomerList.at(iter)->code()); return iter; } /*! \brief Returns an allocated string with the monomer codes. The returned string only contains the sequence of monomer codes for Monomer instances between indices [\a start -- \a end] in this Sequence's list of Monomer instances (m_monomerList). If \a with_modif is true, the modification(s) associated to \l{Monomer}s are also output to the string. The form of the string is, in this case, \code Thr \endcode */ QString * Sequence::monomerText(int start, int end, bool with_modif) const { int localStart = 0; int localEnd = 0; QString *p_text = new QString(); if(size() == 0) return p_text; if(start > end) { localStart = end; localEnd = start; } else { localStart = start; localEnd = end; } if(localStart < 0) localStart = 0; if(localEnd < 0 || localEnd >= size()) localEnd = size() - 1; QString text; for(int iter = localStart; iter < localEnd + 1; ++iter) { const Monomer *monomer = m_monomerList.at(iter); // FIXME Error, the code below does not seem to work. if(with_modif) { if(monomer->isModified()) { for(int iter = 0; iter < monomer->modifList()->size(); ++iter) { text = QString("%1<%2>") .arg(monomer->code()) .arg(monomer->modifList()->at(iter)->name()); } } else text = monomer->code(); } else text = monomer->code(); p_text->append(text); } return p_text; } /*! \brief Returns an allocated string with the monomer codes. The returned string only contains the sequence of monomer codes for Monomer instances contained in the regions (\l Coordinates) described in \a coordinate_list. If \a with_modif is true, the modification(s) associated to \l{Monomer}s are also output to the string. The form of the string is, in this case, \code Thr \endcode If \a delimited_regions, the sequence of Monomer codes beloging to each will be delimited using the Coordinates positions. \sa Coordinates::positionsAsText() */ QString * Sequence::monomerText(const CoordinateList &coordinate_list, bool with_modif, bool delimited_regions) const { QString *p_text = new QString(); for(int iter = 0; iter < coordinate_list.size(); ++iter) { // New coordinates instance we are iterating into. Coordinates *coordinates = coordinate_list.at(iter); QString *tempString = monomerText(coordinates->start(), coordinates->end(), with_modif); if(delimited_regions) *p_text += QString("Region %1: %2\n") .arg(coordinates->positionsAsText()) .arg(*tempString); else *p_text += *tempString; delete(tempString); } *p_text += QString("\n"); return p_text; } /*! \brief Allocates all the Monomer instances to describe this Sequence's string representation of monomer codes. This function parses the member Monomer codes string (m_monomerText) and, for each encountered code, creates a \l Monomer instance and add it to the member list of Monomer instances (m_monomerList). If \a reset is true, the member list of Monomer instances is reset before the work is done. If \a errorList is non-nullptr, errors are stored in this list in the form of the indices of failing monomer codes in the string. The allocation of each Monomer instance based on its code is performed by looking at the reference Monomer list in \a pol_chem_def_csp. Because the m_monomerText member string of Monomer codes does not document any monomer modification, no modifications are handled in this function. Returns the size of the Monomer instances list or -1 if an error occurred. */ int Sequence::makeMonomerList(PolChemDefCstSPtr pol_chem_def_csp, bool reset, QList *errorList) { if(!pol_chem_def_csp) { qDebug() << "The PolChemDef pointer is nullptr!"; return -1; } // If error indices are to be stored, the list MUST be empty. if(errorList) Q_ASSERT(errorList->size() == 0); if(reset) { while(!m_monomerList.isEmpty()) delete m_monomerList.takeFirst(); } unspacifyMonomerText(); // qDebug() << "Sequence:" << m_monomerText; int index = 0; int ret = -1; QString err; QString code; ret = nextCode(&code, &index, &err, pol_chem_def_csp->codeLength()); const QList &refList = pol_chem_def_csp->monomerList(); while(1) { if(ret < 0) { // There was an error in the parsed code. Store the index. if(errorList) { errorList->append(index); ++index; ret = nextCode(&code, &index, &err, pol_chem_def_csp->codeLength()); continue; } else { break; } } if(ret == 0) break; Monomer *monomer = new Monomer(pol_chem_def_csp, "NOT_SET"); if(Monomer::isCodeInList(code, refList, monomer) == -1) { qDebug() << "Monomer:" << monomer->name() << "was not found in the monomer reference list."; delete monomer; if(errorList) { errorList->append(index); ++index; ret = nextCode(&code, &index, &err, pol_chem_def_csp->codeLength()); continue; } else { return -1; } } else { // qDebug() << "Monomer:" << monomer->name() //<< "with code:" << monomer->code() //<< "was indeed found in the monomer reference list."; } m_monomerList.append(monomer); // qDebug() << "New monomer:" << monomer->name(); ++index; // qDebug() << "index:" << index; ret = nextCode(&code, &index, &err, pol_chem_def_csp->codeLength()); } // End of // while(1) if(errorList) { if(errorList->size()) return -1; } if(ret == -1) return -1; return m_monomerList.size(); } /*! \brief Seeks the next code occurring in this Sequence's string of Monomer codes. This function starts looking in this Sequence's string of Monomer codes (m_monomerText) at \a index. The next found Monomer code is stored in \a code. If the text is not a monomer code, it is set to \a err. The parsing of this Sequence's string of Monomer codes takes into account the \a code_length. Returns the count of characters that make \a code. This count can be used to search for the next code by setting its value incremented by 1 to \a index for a next function call. */ int Sequence::nextCode(QString *code, int *index, QString *err, int code_length) { QString newCode; int iter = 0; // We get a sequence of monomer codes(like "LysArgGlu" for example) // and we have to return the next code starting from *index. Note // that the sequence must not contain invalid characters. The // invalid characters might be placed in err for further scrutiny by // the caller. // Returns the count of actually parsed characters in the string // newCode(copied to 'code' param). If an error occurs -1 is // returned and the faulty character is copied in 'err'. 'index' is // updated with the index of the last valid character parsed for // current code. Q_ASSERT(code); Q_ASSERT(index); Q_ASSERT(err); code->clear(); err->clear(); int codes_string_length = m_monomerText.length(); while(1) { if(iter >= code_length) { // Because we have progressed farther than authorized by // the number of characters allowed in the monomer codes // of this polymer chemistry definition, we decrement iter // and break the loop... Later in this function, we'll set // the proper index in the sequence where next parsing run // should occurs (the calling function will increment // *index by one). --iter; break; } if(iter + *index >= codes_string_length) break; QChar curChar = m_monomerText.at(iter + *index); if(!curChar.isLetter()) { // qDebug() << __FILE__ << __LINE__ // << "The character is not a letter:" // << curChar; *err = curChar; // The non-Letter character might be '/', which would be // perfectly fine, as we use it to symbolize the actual // cleavage site. Which means that we will continue // parsing the rest of the string : we have to give the // current position back to the caller in the *index // variable for the next call to this function to start at // next character (not falling back to '/', which would // make us enter in an infinite loop). *index = *index + iter; return -1; } bool isLower = (curChar.category() == QChar::Letter_Lowercase); if(iter == 0) { if(isLower) { // qDebug() << __FILE__ << __LINE__ // << "First character of monomer code might not be" // << "lower case; sequence is" // << m_monomerText; *err = curChar; return -1; } else { // Good, first char is uppercase. newCode += curChar; } } else //(iter != 0) { // We are not in our first iteration. So either the current // character is lowercase and we are just continuing to // iterate into a multi-char monomer code, or the current // character is uppercase, in which case we are starting to // iterate in a new monomer code. if(isLower) newCode += curChar; else { // Decrement iter, because this round was for nothing: // we had "invaded" the next monomer code in sequence, // which we must not do. --iter; break; } } ++iter; } // We finished parsing at most codeLength characters out of // 'm_monomerText', so we have a valid code in the 'code' variable. We // can also compute a new index position in the sequence and return // the number of characters that we effectively parsed. Note that // the caller will be responsible for incrementing the 'index' value // by one character unit so as not to reparse the last characters of // the sent 'code' object. *index = *index + iter; *code = newCode; err->clear(); return code->length(); } /*! \brief Searches in for a Sequence textual \a motif in this Sequence's list of Monomer instances starting at \a index. \a motif, a text string is first converted to a list of Monomer instances (using the reference list of monomers in \a pol_chem_def_csp). Then, this Sequence's monomer instances list is searched for a monomer stretch matching that created for \a motif. As soon as a monomer code stretch is found, the index in this Sequence's list of Monomer instances is set to \a index. Returns 1 if \a motif was found in this Sequence, 0 otherwise. */ int Sequence::findForwardMotif(Sequence *motif, PolChemDefCstSPtr pol_chem_def_csp, int *index) { Q_ASSERT(motif); Q_ASSERT(pol_chem_def_csp); Q_ASSERT(index); if(*index < 0) return -1; if(*index >= size()) return -1; int motifSize = motif->size(); // If motif's length is 0, then nothing to search for, return // unmodified 'index'. if(!motifSize) return 0; // Simple optimization, if index + size of motif is greater then // size of sequence, return right away. if(*index + motifSize >= size()) return 0; // First, make a monomerList. if(motif->makeMonomerList(pol_chem_def_csp) == -1) return -1; // Compare *this sequence with the one in 'motif', starting at index // 'index' in *this sequence and 0 in 'motif'. bool matched = false; int matchIndex = 0; for(int iter = *index; iter < size(); ++iter) { matched = false; int jter = 0; const Monomer *monomer = at(iter); const Monomer *motifMonomer = motif->at(jter); // We do not compare with operator == because that comparison // would involve the comparison of modifications inside the // monomers, which would not work here. if(monomer->code() != motifMonomer->code()) continue; // An easy check is to see if the number of remaining monomers // in the polymer sequence is compatible with the number of // monomers still to be matched in the find array. Imagine the // sequence of the polymer ends like this: ==========JTOUTVU and // the sequence to be searched for is : TVUL What we see is that // the T of the TVU of the sequence matches; however we can stop // the search right away because there is a 'L' in the search // pattern that is not present in the end part of the // sequence. This is exactly what is checked below. Note that // this check makes SURE that at the end of the second inner // loop, when we get out of it, the sole reason we may not // consider that the match did not occur is because actually two // monomers differred and not because anybody came out of the // borders of the sequence in neither the array of the sequence // to be searched, nor the array of the polymer sequence. This // makes it very easy to assess if a match occurred or not. if(size() - iter < motif->size() - jter) { // Note that if it were ==, then it would have been possible // that the sequence "just-in-time" match prior to ending of // the polymer sequence array. Do not forget that we are in // forward mode, thus we can break immediately, because we // are certain that we won't have any chance to find the // sequence downstream of current index. matched = false; break; } matchIndex = iter; // We have to set the matched boolean to true, because if the // motif to find is one monomer-long, then the loop below will // not be entered, and we'll fail to know that the match // occurred later on. matched = true; // Now that we have our anchoring point in the *this sequence, // let's iterate in the motif, and check if the identity in // sequence goes along. for(int kter = jter + 1; kter < motif->size(); ++kter) { // At first run in this loop, we are in the second cell of // the find list, which means that we should have jter == // 1. And we should compare its contents with those of the // cell in the sequence list at index(iter + jter). monomer = at(iter + kter); motifMonomer = motif->at(kter); // We do not compare with operator == because that // comparison would involve the comparison of modifications // inside the monomers, which would not work here. if(monomer->code() == motifMonomer->code()) { // The monomers still match. matched = true; continue; } else { matched = false; break; } } // End of // for (int kter = jter + 1 ; kter < motif->size() ; ++kter) // At this point, we either have normally extinguished the run // in the inner loop, or we have gone out of it before its // normal termination. In either case, we have to test if the // match occurred or not. // Check if the match did NOT occur: if(!matched) { // We just continue with the outer loop, that is, we continue // searching in this sequence for a match with the // first monomer in the motif. continue; } else { // The match indeed occurred. *index = matchIndex; return 1; } } // End of // for (int iter = *index; iter < size(); ++iter) // No match could be achieved, we have to let the caller function // know this in a durable manner : returning 0. return 0; } /*! \brief Returns the Monomer instance at index \a index in this Sequence's monomer instance list. */ const Monomer * Sequence::at(int index) const { qDebug() << "In call at() with value:" << index; if(index < 0) qFatal("%s@%d -- Index cannot be less than 0.", __FILE__, __LINE__); if(index > m_monomerList.size()) qFatal("%s@%d -- Index cannot be greater than polymer size.", __FILE__, __LINE__); return m_monomerList.at(index); } /*! \brief Returns the \index of \a monomer in this Sequence's list of Monomer instances. The search is based on comparison of the pointers, that is, the returned index is for the \e same monomer object. Returns -1 if \a monomer is not found. */ int Sequence::monomerIndex(const Monomer *monomer) { for(int iter = 0; iter < m_monomerList.size(); ++iter) { if(m_monomerList.at(iter) == monomer) return iter; } return -1; } /*! \brief Insert \a monomer at index \a index. Returns true. */ bool Sequence::insertMonomerAt(const Monomer *monomer, int index) { Q_ASSERT(monomer); Q_ASSERT(index > -1 && index <= size()); m_monomerList.insert(index, monomer); return true; } bool Sequence::prepareMonomerRemoval([[maybe_unused]] const Monomer *monomer) { return true; } /*! \brief Removes the monomer instance at index \a index from this Sequence's list of Monomer instances. Returns true. */ bool Sequence::removeMonomerAt(int index) { Q_ASSERT(index > -1); Q_ASSERT(index < size()); const Monomer *monomer = at(index); if(!prepareMonomerRemoval(monomer)) return false; m_monomerList.removeAt(index); delete monomer; return true; } /*! \brief Validates this Sequence using \a pol_chem_def_csp as the reference polymer chemistry definition. Returns true if all the monomers in textual representation of the sequence (m_monomerText) could be converted into Monomer instances. This conversion actually fills in m_monomerList. If an error occurred, returns false. \sa makeMonomerList() */ bool Sequence::validate(PolChemDefCstSPtr pol_chem_def_csp) { Q_ASSERT(pol_chem_def_csp); if(makeMonomerList(pol_chem_def_csp) > -1) return true; return false; } /*! \brief Returns a checksum calculated on this Sequence's portion contained in [\a index_start -- \a index_end]. The sequence matching the [\a index_start -- \a index_end] range is extracted from m_monomerText, with (\a with_modifs is true) or without (\a with_modifs is false) the monomer modifications. The checksum is computed on that extracted string. Returns the checksum. */ quint16 Sequence::checksum(int index_start, int index_end, bool with_modifs) const { if(!size()) return 0; QString *text = monomerText(index_start, index_end, with_modifs); QByteArray bytes = text->toUtf8(); quint16 checksum = qChecksum(QByteArrayView(bytes)); // qDebug() << __FILE__ << __LINE__ // << "checksum:" << checksum; return checksum; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/globals.cpp000664 001750 001750 00000047453 14650444310 022320 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// StdLib includes #include /////////////////////// Qt includes #include #include #include /////////////////////// pappsomspp includes /////////////////////// Local includes #include "globals.hpp" namespace MsXpS { namespace libXpertMass { /*! \enum MsXpS::libXpertMass::MassType \ingroup Globals This enum type specifies the type of mass: \value MASS_NONE The mass type is not defined \value MASS_MONO The mass is monoisotopic \value MASS_AVG The mass is average \value MASS_BOTH (MASS_MONO | MASS_AVG) */ /*! \enum MsXpS::libXpertMass::MassToleranceType \ingroup Globals This enum type specifies the kind of mass tolerance to use for a mass calculation or a mass comparison. \value MASS_TOLERANCE_NONE The tolerance is not specified \value MASS_TOLERANCE_PPM The tolerance is based on parts per million \value MASS_TOLERANCE_MZ The tolerance is based on an absolute m/z value \value MASS_TOLERANCE_AMU The tolerance is based on an absolute mass value \value MASS_TOLERANCE_RES The tolerance is based on resolution \omitvalue MASS_TOLERANCE_LAST */ /*! \brief Map relating the \l{MassToleranceType} to a textual representation \ingroup Globals */ QMap massToleranceTypeMap{ {MassToleranceType::MASS_TOLERANCE_NONE, "NONE"}, {MassToleranceType::MASS_TOLERANCE_PPM, "PPM"}, {MassToleranceType::MASS_TOLERANCE_MZ, "MZ"}, {MassToleranceType::MASS_TOLERANCE_AMU, "AMU"}, {MassToleranceType::MASS_TOLERANCE_RES, "RES"}}; /*! \enum MsXpS::libXpertMass::PolymerEnd \ingroup Globals This enum specifies the polymer end. \value END_NONE Not defined \value END_LEFT The left end \value END_RIGHT The right end \value END_BOTH (END_LEFT | END_RIGHT) */ /*! \enum MsXpS::libXpertMass::SelectionType \ingroup Globals This enum specifies the selection type in a polymer sequence. \value SELECTION_TYPE_RESIDUAL_CHAINS The selection comprises only residues \value SELECTION_TYPE_OLIGOMERS The selection comprises oligomers, that is, residual chains capped with the left and right caps. */ /*! \enum MsXpS::libXpertMass::CapType \ingroup Globals This enum specifies the type of cap (the chemical entities that are set to the polymer ends so as to finish its polymerization state from a chain of residues to an actual polymer molecule. \value CAP_NONE The cap is not defined \value CAP_LEFT The left cap \value CAP_RIGHT The right cap \value CAP_BOTH (CAP_LEFT | CAP_RIGHT) */ /*! \enum MsXpS::libXpertMass::MonomerChemEnt \ingroup Globals This enum specifies the monomer chemical entities to account for in a calculation. This enum is typically used when mass calculations need to account or not for the various chemical entities that are attached to a given monomer. \value MONOMER_CHEMENT_NONE The monomer chemical entity is not defined. \value MONOMER_CHEMENT_MODIF The monomer modifications \value MONOMER_CHEMENT_CROSS_LINK The monomer cross-links */ /*! \enum MsXpS::libXpertMass::PolymerChemEnt \ingroup Globals This enum specifies the polymer sequence chemical entities to account for in a calculation. This enum is typically used when mass calculations need to account or not for the various chemical entities that are attached to a given polymer. \value POLYMER_CHEMENT_NONE The polymer chemical entity is not defined. \value POLYMER_CHEMENT_LEFT_END_MODIF The left end modification \value POLYMER_CHEMENT_FORCE_LEFT_END_MODIF The left end modification, even if that polymer's end is not selected \value POLYMER_CHEMENT_RIGHT_END_MODIF The right end modification \value POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF The right end modification, even if that polymer's end is not selected \value POLYMER_CHEMENT_BOTH_END_MODIF (POLYMER_CHEMENT_LEFT_END_MODIF | POLYMER_CHEMENT_RIGHT_END_MODIF) \value POLYMER_CHEMENT_FORCE_BOTH_END_MODIF (POLYMER_CHEMENT_FORCE_LEFT_END_MODIF | POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF) */ /*! \enum MsXpS::libXpertMass::HashAccountData \ingroup Globals This enum specifies the chemical entites to account for when calculating a hash. \value HASH_ACCOUNT_SEQUENCE The sequence \value HASH_ACCOUNT_MONOMER_MODIF Monomer modifications \value HASH_ACCOUNT_POLYMER_MODIF Polymer modifications */ /*! \variable MsXpS::libXpertMass::subFormulaRegExp \brief Regular expression used to deconstruct the main formula into minus and plus component subformulas. \code "([+-]?)([A-Z][a-z]*)(\\d*[\\.]?\\d*)" \endcode \sa Formula::splitActionParts() */ QRegularExpression subFormulaRegExp = QRegularExpression(QString("([+-]?)([A-Z][a-z]*)(\\d*[\\.]?\\d*)")); /*! \brief Regular expression that matches the end of line in text files. \ingroup Globals */ QRegularExpression gEndOfLineRegExp = QRegularExpression("^\\s+$"); /*! \brief Regular expression that matches the m/z,i pairs in text files. \ingroup Globals */ QRegularExpression gXyFormatMassDataRegExp = QRegularExpression("^(\\d*\\.?\\d+)([^\\d^\\.^-]+)(-?\\d*\\.?\\d*[e-]?\\d*)"); /*! \brief Number of decimal places after the decimal symbol for atom masses. \ingroup Globals */ int ATOM_DEC_PLACES = 10; /*! \brief Number of decimal places after the decimal symbol for oligomer masses. \ingroup Globals */ int OLIGOMER_DEC_PLACES = 5; /*! \brief Number of decimal places after the decimal symbol for polymer masses. \ingroup Globals */ int POLYMER_DEC_PLACES = 3; /*! \brief Number of decimal places after the decimal symbol for pH/pKa values. \ingroup Globals */ int PH_PKA_DEC_PLACES = 2; /*! \brief Returns the average of the \a list of \c double values. \ingroup Globals All the values in list are process in order to compute the following: \list \li \a sum \li \a average \li \a variance \li \a stdDeviation \li \a nonZeroSmallest \li \a smallest \li \a smallestMedian \li \a greatest \endlist statistical values if the corresponding parameters are non-nullptr. \sa doubleVectorStatistics() */ void doubleListStatistics(QList list, double *sum, double *average, double *variance, double *stdDeviation, double *nonZeroSmallest, double *smallest, double *smallestMedian, double *greatest) { if(sum == Q_NULLPTR || average == Q_NULLPTR || variance == Q_NULLPTR || stdDeviation == Q_NULLPTR || nonZeroSmallest == Q_NULLPTR || smallest == Q_NULLPTR || greatest == Q_NULLPTR || smallestMedian == Q_NULLPTR) qFatal( "Fatal error at %s@%d -- %s(). " "Pointers cannot be nullptr." "Program aborted.", __FILE__, __LINE__, __FUNCTION__); // Sort the list, we'll need it sorted to compute the median value. std::sort(list.begin(), list.end()); int count = list.size(); if(count == 0) return; // Sorted list, the smallest is the first. *smallest = list.first(); // The greatest is the last. *greatest = list.last(); *sum = 0; *nonZeroSmallest = std::numeric_limits::max(); for(int iter = 0; iter < count; ++iter) { double current = list.at(iter); *sum += current; // If current is non-zero, then take its value into account. if(current > 0 && current < *nonZeroSmallest) *nonZeroSmallest = current; } *average = *sum / count; double varN = 0; for(int iter = 0; iter < count; ++iter) { varN += (list.at(iter) - *average) * (list.at(iter) - *average); } *variance = varN / count; *stdDeviation = std::sqrt(*variance); // Now the median value // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()" //<< "count:" << count; if(count == 1) { *smallestMedian = list.first(); } else { if(count % 2 == 0) *smallestMedian = (list.at(count / 2 - 1) + list.at(count / 2)) / 2; else *smallestMedian = list.at(count / 2 + 1); } } /*! \brief Returns the average of the \a vector of \c double values. \ingroup Globals All the values in list are process in order to compute the following: \list \li \a sum \li \a average \li \a variance \li \a stdDeviation \li \a nonZeroSmallest \li \a smallest \li \a smallestMedian \li \a greatest \endlist statistical values if the corresponding parameters are non-nullptr. \sa doubleListStatistics() */ void doubleVectorStatistics(std::vector &vector, double *sum, double *average, double *variance, double *stdDeviation, double *nonZeroSmallest, double *smallest, double *smallestMedian, double *greatest) { if(sum == Q_NULLPTR || average == Q_NULLPTR || variance == Q_NULLPTR || stdDeviation == Q_NULLPTR || nonZeroSmallest == Q_NULLPTR || smallest == Q_NULLPTR || greatest == Q_NULLPTR || smallestMedian == Q_NULLPTR) qFatal("Programming error."); // Sort the vector, we'll need it sorted to compute the median value. std::sort(vector.begin(), vector.end()); int count = vector.size(); if(!count) return; // Sorted vector, the smallest is the first. *smallest = vector.front(); // The greatest is the last. *greatest = vector.back(); *sum = 0; *nonZeroSmallest = std::numeric_limits::max(); for(auto &&value : vector) { double current = value; *sum += current; // If current is non-zero, then take its value into account. if(current > 0 && current < *nonZeroSmallest) *nonZeroSmallest = current; } *average = *sum / count; double varN = 0; for(auto &&value : vector) { varN += (value - *average) * (value - *average); } *variance = varN / count; *stdDeviation = sqrt(*variance); // Now the median value // qDebug() << "count:" << count; if(count == 1) { *smallestMedian = vector.front(); } else { if(count % 2 == 0) *smallestMedian = (vector.at(count / 2 - 1) + vector.at(count / 2)) / 2; else *smallestMedian = vector.at(count / 2 + 1); } } /*! \brief Returns true if both double values \a value1 and \a value2, are equal within the double representation capabilities of the platform, false otherwise. \ingroup Globals In the comparison, the \a decimal_places are taken into account. */ bool almostEqual(double value1, double value2, int decimal_places) { // QString value1String = QString("%1").arg(value1, // 0, 'f', 60); // QString value2String = QString("%1").arg(value2, // 0, 'f', 60); // qWarning() << __FILE__ << __LINE__ << __FUNCTION__ //<< "value1:" << value1String << "value2:" << value2String; // The machine epsilon has to be scaled to the magnitude of the values used // and multiplied by the desired precision in ULPs (units in the last place) // (decimal places). double valueSum = std::abs(value1 + value2); // QString valueSumString = QString("%1").arg(valueSum, // 0, 'f', 60); double valueDiff = std::abs(value1 - value2); // QString valueDiffString = QString("%1").arg(valueDiff, // 0, 'f', 60); double epsilon = std::numeric_limits::epsilon(); // QString epsilonString = QString("%1").arg(epsilon, // 0, 'f', 60); double scaleFactor = epsilon * valueSum * decimal_places; // QString scaleFactorString = QString("%1").arg(scaleFactor, // 0, 'f', 60); // qWarning() << "valueDiff:" << valueDiffString << "valueSum:" << // valueSumString << "epsilon:" << epsilonString << "scaleFactor:" << // scaleFactorString; bool res = valueDiff < scaleFactor // unless the result is subnormal: || valueDiff < std::numeric_limits::min(); // qWarning() << __FILE__ << __LINE__ << __FUNCTION__ //<< "returning res:" << res; return res; } /*! \brief Provides a text stream handle to the standard output. \ingroup Globals Use: \code qStdOut() << __FILE__ << __LINE__ << "text to the standard output," << "not the error standard output." \endcode Returns a reference to a static QTextStream that directs to the standard output. */ QTextStream & qStdOut() { static QTextStream ts(stdout); return ts; } /*! \brief Removes all space characters from the \a text string (the removal is in-place). \ingroup Globals Returns a reference to the in-place-modified string. */ QString & unspacifyString(QString &text) { if(text.isEmpty()) { return text; } text.remove(QRegularExpression("\\s+")); return text; } /*! \brief Returns a string with a binary representation of the \a value integer. \ingroup Globals */ QString binaryRepresentation(int value) { QString string; string = QString("%1").arg(value, 32, 2); return string; } /*! \brief Returns a shortened (elided) version of the \a text string. \ingroup Globals It is sometimes necessary to display, in a graphical user interface a very long string, that cannot fit in the provided widget. This function returns a shortened version of the input string. For example, "Interesting bits of information are often lost where there are too many details", becomes "Interes...details". \a chars_left: Count of characters to be kept on the left side of the string. \a chars_right: Count of characters to be kept on the right side of the string. \a delimiter string to use as the elision delimiter (in the above example, that is '.'). */ QString elideText(const QString &text, int chars_left, int chars_right, const QString &delimiter) { // We want to elide text. For example, imagine we have text = "that // is beautiful stuff", with charsLeft 4 and charsRight 4 and // delimitor "...". Then the result would be "that...tuff" if(chars_left < 1 || chars_right < 1) { return text; } int size = text.size(); // If the text string is already too short, no need to elide // anything. if((chars_left + chars_right + delimiter.size()) >= size) { return text; } QString result = text.left(chars_left); result.append(delimiter); result.append(text.right(chars_right)); return result; } /*! \brief Returns a string containing a paragraph-based version of the very long single-line \a text. \ingroup Globals When a text string is too long to be displayed in a line of reasonable length, inserts newline characters at positions calculated to yield a paragraph of the given \a width. \sa stanzifyParagraphs() */ QString stanzify(const QString &text, int width) { QString result = text; // First, replace all the existing newline chars with spaces. result = result.replace("\n", " "); int iter = width; // Then, iterate in the obtained string and every width characters try to // insert a newline character by iterating back to the left and searching // for a space. for(; iter < result.size(); iter += width) { // Now iterate in reverse and search for a space where to insert a // newline int jter = iter; for(; jter >= 0; --jter) { if(result.at(jter) == ' ') { result[jter] = '\n'; break; } } } return result; } /*! \brief Returns a string containing a series of paragraph-based versions of the very long single-line-containing paragraphs in \a text. \ingroup Globals \a text is a string with newline characters that delimit paragraph that thelmselves are made of a very long single line. This function splits \a text along the \c '\n' character and each obtained very long single-line string is reduced to a set of lines to make a pararagraph (see \l{stanzify}). The with of the line in that generated paragraph is \a width. \sa stanzify() */ QString stanzifyParagraphs(const QString &text, int width) { QString result; QStringList paragraphList = text.split("\n"); for(int iter = 0; iter < paragraphList.size(); ++iter) { QString line = paragraphList.at(iter); QString stanzifiedLine = stanzify(line, width); result.append(stanzifiedLine); result.append("\n"); } return result; } /*! \brief Returns the number of zero decimals in \a value that are found between the decimal point and the first non-zero decimal. \ingroup Globals For example, 0.11 would return 0 (no empty decimal) 2.001 would return 2 1000.0001254 would return 3 */ int countZeroDecimals(double value) { int intPart = static_cast(value); double decimalPart = value - intPart; int count = -1; while(1) { ++count; decimalPart *= 10; if(decimalPart > 1) return count; } return count; } /*! \brief Returns the delta value corresponding to \a value and \a ppm part-per-million. \ingroup Globals */ double ppm(double value, double ppm) { return (value * ppm) / 1000000; } /*! \brief Returns \a value incremented by the matching \a ppm part. \ingroup Globals */ double addPpm(double value, double ppm) { return value + (value * ppm) / 1000000; } /*! \brief Returns \a value decremented by the matching \a ppm part. \ingroup Globals */ double removePpm(double value, double ppm) { return value - (value * ppm) / 1000000; } /*! \brief Return the delta corresponding to \a value and resolution \a res. \ingroup Globals */ double res(double value, double res) { return value / res; } /*! \brief Returns \a value incremented by the matching \a res part. \ingroup Globals */ double addRes(double value, double res) { return value + (value / res); } /*! \brief Returns \a value decremented by the matching \a res part. \ingroup Globals */ double removeRes(double value, double res) { return value - (value / res); } /*! \brief Returns a string representing the \a pointer address location (for debugging). \ingroup Globals */ QString pointerAsString(void *pointer) { return QString("%1").arg( (quintptr)pointer, QT_POINTER_SIZE * 2, 16, QChar('0')); } /*! \brief Returns a string holding the the file path to the configuration settings for application \a module_name. \ingroup Globals */ QString configSettingsFilePath(const QString &module_name) { // The configuration settings file for all the settings of the program QString file_path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); if(file_path.isEmpty()) file_path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); file_path = file_path + QDir::separator() + module_name; file_path = file_path + QDir::separator() + "configSettings.ini"; return file_path; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/000775 001750 001750 00000000000 14651507337 021774 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/000775 001750 001750 00000000000 14651507337 024411 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Averagine.hpp000664 001750 001750 00000004535 14647465366 027044 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "Formula.hpp" #include "IsotopicData.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC Averagine { public: Averagine(IsotopicDataCstSPtr isotopic_data_csp); Averagine(const Averagine &other); virtual ~Averagine(); Averagine &operator=(const Averagine &other); void setIsotopicData(IsotopicDataCstSPtr isotopic_data_csp); void setContent(QString &symbol, double content); double getContent(QString &symbol) const; void setFormula(const QString formula_string); double getAvgMass() const; double computeFormulaAveragineEquivalents(const QString &formula_string = QString()); double computeFormulaMonoMass(const QString &formula_string = QString()); bool validate(); protected: IsotopicDataCstSPtr mcsp_isotopicData; double m_avg; Formula m_formula; std::map m_symbolContentMap; void setupDefaultConfiguration(); double computeAvgMass(); }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/CalcOptions.hpp000664 001750 001750 00000007107 14647465366 027357 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" #include "globals.hpp" #include "Coordinates.hpp" namespace MsXpS { namespace libXpertMass { //! The CalcOptions class provides calculation options. /*! Calculations can be performed in two manners : * * - by taking into account the whole sequence; * * - by taking into account the selected region of the sequence; * * In the first case the computation is for the \em polymer, while in * the second case the computation is for an \em oligomer. * * Options involve also the way the computations are taking into * account the left/end caps of the sequence or the monomer * modifications, or the polymer modifications... */ class DECLSPEC CalcOptions { public: CalcOptions(); CalcOptions(bool, int = MassType::MASS_BOTH, int = CAP_BOTH, int = MONOMER_CHEMENT_NONE, int = POLYMER_CHEMENT_NONE); CalcOptions(const CalcOptions &other); ~CalcOptions(); CalcOptions &operator=(const CalcOptions &other); void setDeepCalculation(bool); bool isDeepCalculation() const; void setStartIndex(int); int startIndex() const; void setEndIndex(int); int endIndex() const; void setCoordinateList(const Coordinates & = Coordinates()); void setCoordinateList(const CoordinateList &); const CoordinateList &coordinateList() const; void setMassType(int); int massType() const; void setSelectionType(SelectionType); SelectionType selectionType() const; void setCapping(int); int capping() const; void setMonomerEntities(int); int monomerEntities() const; void setPolymerEntities(int); int polymerEntities() const; void debugPutStdErr() const; protected: //! Tells if the calculation should involve the recalculation of /*! all the masses of the monomers of the sequence. */ bool m_deepCalculation; CoordinateList m_coordinateList; //! MASS_TYPE. int m_massType; //! CAP_TYPE. int m_capping; //! MONOMER_CHEMENT. int m_monomerEntities; //! POLYMER_CHEMENT. int m_polymerEntities; SelectionType m_selectionType; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/ChemicalGroup.hpp000664 001750 001750 00000006325 14647465366 027664 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "ChemicalGroupRule.hpp" #include "Prop.hpp" namespace MsXpS { namespace libXpertMass { enum ChemicalGroupTrapping { NOT_SET = 0x000, NEVER_TRAPPED = 1 << 0, LEFT_TRAPPED = 1 << 1, RIGHT_TRAPPED = 1 << 2, }; class DECLSPEC ChemicalGroup { public: ChemicalGroup(QString = QString(), float = 7.0, bool = true, ChemicalGroupTrapping = ChemicalGroupTrapping::NEVER_TRAPPED); ChemicalGroup(const ChemicalGroup &other); ~ChemicalGroup(); ChemicalGroup &operator=(const ChemicalGroup &other); void setName(QString); QString name() const; void setPka(float); float pka() const; void setAcidCharged(bool); bool isAcidCharged() const; void setPolRule(ChemicalGroupTrapping); ChemicalGroupTrapping polRule() const; QList &ruleList(); ChemicalGroupRule *findRuleEntity(QString, int * = 0) const; ChemicalGroupRule *findRuleName(QString, int * = 0) const; ChemicalGroupRule *findRule(QString, QString, int * = 0) const; bool renderXmlMnmElement(const QDomElement &); bool renderXmlMdfElement(const QDomElement &); protected: QString m_name; float m_pka; bool m_acidCharged; ChemicalGroupTrapping m_polymerizationRule; QList m_ruleList; }; class ChemicalGroupProp : public Prop { public: ChemicalGroupProp(const QString & = QString(), ChemicalGroup * = 0); ChemicalGroupProp(const ChemicalGroupProp &other); ~ChemicalGroupProp(); virtual void deleteData(); using Prop::operator=; ChemicalGroupProp &operator=(const ChemicalGroupProp &other); ChemicalGroupProp *cloneOut() const; bool renderXmlElement(const QDomElement &, int = 1); QString *formatXmlElement(int, const QString & = QString(" ")); }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/ChemicalGroupRule.hpp000664 001750 001750 00000004160 14647465366 030507 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include #include #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { enum ChemicalGroupRuleFate { LOST = 0, PRESERVED = 1, }; class DECLSPEC ChemicalGroupRule { public: ChemicalGroupRule(QString = QString(), QString = QString(), ChemicalGroupRuleFate = ChemicalGroupRuleFate::LOST); ~ChemicalGroupRule() { } void setEntity(QString); QString entity(); void setName(QString); QString name(); void setFate(ChemicalGroupRuleFate); ChemicalGroupRuleFate fate(); bool renderXmlElement(const QDomElement &element); protected: QString m_name; QString m_entity; ChemicalGroupRuleFate m_chemicalGroupFate; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/CleaveMotif.hpp000664 001750 001750 00000005025 14647465366 027334 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef CLEAVE_MOTIF_HPP #define CLEAVE_MOTIF_HPP /////////////////////// Qt includes #include #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "PolChemDefEntity.hpp" #include "Monomer.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC CleaveMotif : public PolChemDefEntity { public: CleaveMotif(PolChemDefCstSPtr, QString, const QString & = QString(), int = 0, bool = false); CleaveMotif(const CleaveMotif &other); ~CleaveMotif(); using PolChemDefEntity::operator=; CleaveMotif &operator=(const CleaveMotif &other); void setMotif(const QString &); const QString &motif(); const QStringList &codeList() const; void setOffset(int); int offset(); void setForCleave(bool); bool isForCleave(); int parse(const QString &); using PolChemDefEntity::validate; bool validate(); protected: //! Motif. QString m_motif; //! List of codes in motif. QStringList m_codeList; //! Offset of the cleavage. int m_offset; //! Tells if motif is for cleavage. bool m_isForCleave; }; } // namespace libXpertMass } // namespace MsXpS #endif // CLEAVE_MOTIF_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/CleaveRule.hpp000664 001750 001750 00000007062 14647465366 027170 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef CLEAVE_RULE_HPP #define CLEAVE_RULE_HPP /////////////////////// Qt includes #include /////////////////////// Local includesDECLSPEC #include "PolChemDefEntity.hpp" #include "Formula.hpp" #include "Monomer.hpp" namespace MsXpS { namespace libXpertMass { //! The CleaveRule class provides a cleavage rule. /*! Cleavage rules help refine the description of the chemical reaction that is the basis of a cleavage(either enzymatic or chemical). While a number of cleavage agent(like a number of enzymes) do not make unexpected reactions upon cleavage(enzyme usually hydrolyze their substrates), chemical agents sometimes make a cleavage, and in the process of the cleavage reaction modify chemically the ends of the generaed oligomers. One notorious example is the case of cyanogen bromide, that cleaves proteins right of methionyl residues. Upon such cleavage, the monomer at the right side of the generated oligomer (methionyl residue) get modified according to this formula: "-CH2S+O". Cleavage rules are designed to be able to model such complex reactions. */ class DECLSPEC CleaveRule : public PolChemDefEntity { public: CleaveRule(PolChemDefCstSPtr, QString, QString = QString(), QString = QString(), QString = QString(), QString = QString()); CleaveRule(const CleaveRule &other); ~CleaveRule(); void setLeftCode(const QString &); const QString &leftCode(); void setRightCode(const QString &); const QString &rightCode(); void setLeftFormula(const Formula &); const Formula &leftFormula(); void setRightFormula(const Formula &); const Formula &rightFormula(); static int isNameInList(const QString &, const QList &, CleaveRule *other = nullptr); using PolChemDefEntity::operator=; CleaveRule &operator=(const CleaveRule &other); using PolChemDefEntity::validate; bool validate(); bool renderXmlClrElement(const QDomElement &, int); QString *formatXmlClrElement(int, const QString & = QString(" ")); protected: //! Left code. QString m_leftCode; //! Left formula. Formula m_leftFormula; //! Right code. QString m_rightCode; //! Right formula. Formula m_rightFormula; }; } // namespace libXpertMass } // namespace MsXpS #endif // CLEAVE_RULE_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/CleaveSpec.hpp000664 001750 001750 00000005064 14647465366 027153 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef CLEAVE_SPEC_HPP #define CLEAVE_SPEC_HPP /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "CleaveMotif.hpp" #include "CleaveRule.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC CleaveSpec : public PolChemDefEntity { public: CleaveSpec(PolChemDefCstSPtr, QString, QString = QString()); CleaveSpec(const CleaveSpec &other); ~CleaveSpec(); using PolChemDefEntity::operator=; CleaveSpec &operator=(const CleaveSpec &other); void setPattern(const QString &); const QString &pattern(); QList *motifList(); QList *ruleList(); static int isNameInList(const QString &, const QList &, CleaveSpec *other = nullptr); bool parse(); using PolChemDefEntity::validate; bool validate(); bool renderXmlClsElement(const QDomElement &, int); QString *formatXmlClsElement(int, const QString & = QString(" ")); protected: //! Pattern. QString m_pattern; //! List of cleavage motifs. QList m_motifList; // "Lys/" and -Lys/Pro and... //! List of cleavage rules. QList m_ruleList; // Cleavage conditions... }; } // namespace libXpertMass } // namespace MsXpS #endif // CLEAVE_SPEC_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Coordinates.hpp000664 001750 001750 00000005642 14647465366 027415 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef COORDINATES_HPP #define COORDINATES_HPP /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { class DECLSPEC Coordinates { public: Coordinates(int = -1, int = -1); Coordinates(const Coordinates &); ~Coordinates(); void setStart(int); int start() const; void setEnd(int); void incrementEnd(); int end() const; int length() const; QString indicesAsText() const; QString positionsAsText() const; bool setFromText(QString); void reset(); protected: int m_start; int m_end; }; class DECLSPEC CoordinateList : public QList { public: CoordinateList(QString = QString(), QList * = 0); CoordinateList(const CoordinateList &); ~CoordinateList(); CoordinateList &operator=(const CoordinateList &other); void setCoordinates(const Coordinates &); void setCoordinates(const CoordinateList &); void appendCoordinates(const Coordinates &); int setCoordinates(const QString &); void setComment(QString comment); QString comment() const; int leftMostCoordinates(QList &) const; bool isLeftMostCoordinates(Coordinates *) const; int rightMostCoordinates(QList &) const; bool isRightMostCoordinates(Coordinates *) const; bool encompassIndex(int) const; bool overlap() const; QString indicesAsText() const; QString positionsAsText() const; void empty(); void debugPutStdErr(); protected: QString m_comment; }; } // namespace libXpertMass } // namespace MsXpS #endif // COORDINATES_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/CrossLink.hpp000664 001750 001750 00000010720 14647465366 027043 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once //#warning "Entering CROSS_LINK_HPP" /////////////////////// Local includes #include "exportimportconfig.h" #include "CrossLinker.hpp" #include "Monomer.hpp" #include "Polymer.hpp" namespace MsXpS { namespace libXpertMass { enum CrossLinkEncompassed { CROSS_LINK_ENCOMPASSED_NO = 0, CROSS_LINK_ENCOMPASSED_PARTIAL = 1, CROSS_LINK_ENCOMPASSED_FULL = 2, }; ///////////////////////////// CrossLink //////////////////////// ///////////////////////////// CrossLink //////////////////////// ///////////////////////////// CrossLink //////////////////////// ///// Look down for CrossLinkList class DECLSPEC CrossLink : public CrossLinker { public: CrossLink(PolChemDefCstSPtr, Polymer *, const QString &, const QString &, const QString & = QString()); CrossLink(const CrossLink &); CrossLink(const CrossLinker &, Polymer *, const QString & = QString()); ~CrossLink(); bool setMonomerAt(Monomer *, int); bool appendMonomer(const Monomer *); const Monomer *monomerAt(int); bool removeMonomerAt(int); void setPolymer(Polymer *); Polymer *polymer(); void setComment(const QString &); const QString &comment() const; using PolChemDefEntity::operator=; using Formula::operator=; using CrossLinker::operator=; using PolChemDefEntity::operator==; using Formula::operator==; using CrossLinker::operator==; virtual bool operator==(const CrossLink &) const; int populateMonomerList(QString); QList *monomerList(); int monomerIndexList(QList *); QString monomerIndexText(); QString monomerPosText(); int involvesMonomer(const Monomer *) const; const Monomer *firstMonomer() const; int encompassedBy(int, int, int *in = 0, int * = 0); int encompassedBy(const CoordinateList &, int *in = 0, int * = 0); int involvedMonomers(QList *); using PolChemDefEntity::validate; using Formula::validate; bool validate(); virtual bool calculateMasses(); virtual bool accountMasses(double * = 0, double * = 0, int = 1); virtual bool accountMasses(Ponderable *, int = 1); QString *prepareResultsTxtString(); protected: // We do not own these two monomers and polymer. Polymer *mp_polymer; QList m_monomerList; QString m_comment; }; ///////////////////////////// CrossLinkList //////////////////////// ///////////////////////////// CrossLinkList //////////////////////// ///////////////////////////// CrossLinkList //////////////////////// class DECLSPEC CrossLinkList : public QObject, public QList { Q_OBJECT friend class Polymer; public: CrossLinkList(); CrossLinkList(const QString &name, Polymer * = 0, const QString & = QString()); CrossLinkList(const CrossLinkList *other); virtual ~CrossLinkList(); CrossLinkList &operator=(const CrossLinkList &other); void setName(QString); QString name(); void setComment(QString); const QString &comment() const; void setPolymer(Polymer *); const Polymer *polymer() const; int crossLinksInvolvingMonomer(const Monomer *, QList *) const; private: QString m_name = "NOT_SET"; Polymer *mp_polymer; QString m_comment = "NOT_SET"; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/CrossLinker.hpp000664 001750 001750 00000005670 14647465366 027402 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Local includes #include "exportimportconfig.h" #include "PolChemDefEntity.hpp" #include "Formula.hpp" #include "Modif.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC CrossLinker : public PolChemDefEntity, public Formula, public Ponderable { public: CrossLinker(PolChemDefCstSPtr, const QString &, const QString &); CrossLinker(const CrossLinker &); ~CrossLinker(); bool setModifAt(Modif *, int); bool appendModif(Modif *); const Modif *modifAt(int) const; bool removeModifAt(int); QString formula() const; QList &modifList(); int hasModif(const QString &); using PolChemDefEntity::operator=; using Formula::operator=; using Ponderable::operator=; CrossLinker &operator=(const CrossLinker &other); using PolChemDefEntity::operator==; using Formula::operator==; using Ponderable::operator==; virtual bool operator==(const CrossLinker &) const; int isNameKnown(); static int isNameInList(const QString &, const QList &, CrossLinker * = 0); using Formula::validate; using PolChemDefEntity::validate; bool validate(); using Formula::accountMasses; using Ponderable::accountMasses; virtual bool accountMasses(double * = 0, double * = 0, int = 1); virtual bool accountMasses(Ponderable *, int = 1); bool calculateMasses(); bool renderXmlClkElement(const QDomElement &, int); QString *formatXmlClkElement(int, const QString & = QString(" ")); protected: // We do not own the modifications below, the pointers refer to // modifications in the polymer chemistry definition. QList m_modifList; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/CrossLinkerSpec.hpp000664 001750 001750 00000003717 14647465366 030215 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef CROSS_LINKER_SPEC_HPP #define CROSS_LINKER_SPEC_HPP /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { class DECLSPEC CrossLinkerSpec { public: CrossLinkerSpec(); ~CrossLinkerSpec(); void setName(const QString &); const QString &name(); void setVector(const QString &); const QString &vector(); void setSound(const QString &); const QString &sound(); static bool parseFile(QString &, QList *); protected: QString m_name; QString m_vector; QString m_sound; }; } // namespace libXpertMass } // namespace MsXpS #endif // CROSS_LINKER_SPEC_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Envemind.hpp000664 001750 001750 00000004271 14647465366 026705 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ***************************************************************************** * This specific code is a port to C++ of the Envemind Python code by Radzinski * and colleagues of IsoSpec fame (Lacki, Startek and company :-) * * See https://github.com/PiotrRadzinski/envemind. * ***************************************************************************** * * END software license */ #pragma once #include /////////////////////// pappsomspp includes #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { class DECLSPEC Envemind{ public: Envemind(); Envemind(const pappso::Trace &trace); Envemind(const Envemind &other); virtual ~Envemind(); Envemind &operator=(const Envemind &other); double monoIsotopicMassPrediction(); double getMostAbundant(bool *ok); protected: pappso::Trace m_trace; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Formula.hpp000664 001750 001750 00000012562 14650435530 026527 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "IsotopicData.hpp" #include "Ponderable.hpp" namespace MsXpS { namespace libXpertMass { /* Bitwise stuff (from StackOverflow) It is sometimes worth using an enum to name the bits: enum ThingFlags = { ThingMask = 0x0000, ThingFlag0 = 1 << 0, ThingFlag1 = 1 << 1, ThingError = 1 << 8, } Then use the names later on. I.e. write thingstate |= ThingFlag1; thingstate &= ~ThingFlag0; if (thing & ThingError) {...} to set, clear and test. This way you hide the magic numbers from the rest of your code. */ enum class FormulaSplitResult { NOT_SET = 0x0000, FAILURE = 1 << 0, //!< The splitting work failed. HAS_PLUS_COMPONENT = 1 << 1, //!< The action formula a plus component HAS_MINUS_COMPONENT = 1 << 2, //!< The action formula a minus component HAS_BOTH_COMPONENTS = (HAS_PLUS_COMPONENT | HAS_MINUS_COMPONENT) //!< The action formula had both component types }; class DECLSPEC Formula { public: Formula(const QString &formula = QString()); Formula(const Formula &other); virtual ~Formula(); void setFormula(const QString &formula); void setFormula(const Formula &formula); void appendFormula(const QString &formula); virtual QString toString() const; void setForceCountIndex(bool forceCountIndex); void clear(); const std::map getSymbolCountMap() const; std::size_t accountFormula(const QString &text, IsotopicDataCstSPtr isotopic_data_csp, double times = 1); virtual Formula &operator=(const Formula &other); virtual bool operator==(const Formula &other) const; virtual bool operator!=(const Formula &other) const; static QChar actions(const QString &formula); QChar actions() const; static bool checkSyntax(const QString &formula, bool forceCountIndex = false); bool checkSyntax() const; bool checkSyntaxRegExp(const QString &formula, bool forceCountIndex = false); virtual bool validate(IsotopicDataCstSPtr isotopic_data_csp); virtual bool validate(IsotopicDataCstSPtr isotopic_data_csp, bool, bool); virtual bool accountMasses(IsotopicDataCstSPtr isotopic_data_csp, double *mono = Q_NULLPTR, double *avg = Q_NULLPTR, double times = 1); virtual bool accountMasses(IsotopicDataCstSPtr isotopic_data_csp, Ponderable *ponderable, double times = 1); bool accountSymbolCounts(IsotopicDataCstSPtr isotopic_data_csp, int); QString elementalComposition(std::vector> *symbol_count_pairs_p = nullptr) const; double totalAtoms() const; double totalIsotopes(IsotopicDataCstSPtr isotopic_data_csp) const; double symbolCount(const QString &symbol) const; bool hasNetMinusPart(); bool renderXmlFormulaElement(const QDomElement &); protected: QString m_formula; QString m_plusFormula; QString m_minusFormula; // Map that relates the chemical element symbols encounted in formula(e) to // their count in the formula(e). The count can be a double or an int. std::map m_symbolCountMap; IsotopicDataCstSPtr mcsp_isotopicData = nullptr; //! Tell if elements present a single time need to have an index nonetheless /*! When a formula is like H2O, if true, then the formula does not validate if it is not defined like so: H2O1 (note the O1). */ bool m_forceCountIndex = false; void setPlusFormula(const QString &formula); const QString &plusFormula() const; void setMinusFormula(const QString &formula); const QString &minusFormula() const; int removeTitle(); int removeSpaces(); int splitActionParts(IsotopicDataCstSPtr isotopic_data_csp, double times = 1, bool store = false, bool reset = false); double accountSymbolCountPair(const QString &symbol, double count = 1); }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/FragRule.hpp000664 001750 001750 00000005442 14647465366 026650 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef FRAG_RULE_HPP #define FRAG_RULE_HPP /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" #include "PolChemDefEntity.hpp" #include "Formula.hpp" #include "Monomer.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC FragRule : public PolChemDefEntity, public Formula { public: FragRule(PolChemDefCstSPtr, QString, QString = QString(), QString = QString(), QString = QString(), QString = QString(), const QString & = QString()); FragRule(const FragRule &other); ~FragRule(); using PolChemDefEntity::operator=; using Formula::operator=; FragRule &operator=(const FragRule &other); void setPrevCode(const QString &); QString prevCode() const; void setCurrCode(const QString &); QString currCode() const; void setNextCode(const QString &); QString nextCode() const; QString formula() const; void setComment(const QString &); QString comment() const; static int isNameInList(const QString &, const QList &, FragRule *other = nullptr); using Formula::validate; using PolChemDefEntity::validate; bool validate(); bool renderXmlFgrElement(const QDomElement &); QString *formatXmlFgrElement(int, const QString & = QString(" ")); protected: QString m_prevCode; QString m_currCode; QString m_nextCode; QString m_comment; }; } // namespace libXpertMass } // namespace MsXpS #endif // FRAG_RULE_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/FragSpec.hpp000664 001750 001750 00000006260 14647465366 026632 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef FRAG_SPEC_HPP #define FRAG_SPEC_HPP /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "FragRule.hpp" namespace MsXpS { namespace libXpertMass { //! Fragmentation end. enum FragEnd { FRAG_END_NONE = 1 << 0, FRAG_END_LEFT = 1 << 1, FRAG_END_RIGHT = 1 << 2, FRAG_END_BOTH = (FRAG_END_LEFT | FRAG_END_RIGHT), }; class DECLSPEC FragSpec : public PolChemDefEntity, public Formula { public: FragSpec(PolChemDefCstSPtr, QString, QString = QString(), FragEnd = FRAG_END_NONE, const QString & = QString()); FragSpec(PolChemDefCstSPtr, QString, QString); FragSpec(const FragSpec &other); ~FragSpec(); using PolChemDefEntity::operator=; using Formula::operator=; FragSpec &operator=(const FragSpec &other); QList &ruleList(); void appendRule(FragRule *); void insertRuleAt(int, FragRule *); void removeRuleAt(int); void setFragEnd(FragEnd); FragEnd fragEnd() const; void setMonomerContribution(int); int monomerContribution(); QString formula() const; void setComment(const QString &); QString comment() const; static int isNameInList(const QString &, const QList &, FragSpec * = nullptr); using Formula::validate; using PolChemDefEntity::validate; bool validate(); bool renderXmlFgsElement(const QDomElement &element, int); QString *formatXmlFgsElement(int, const QString & = QString(" ")); protected: //! Fragmentation end. FragEnd m_fragEnd; //! Fragmented monomer's mass contribution. See fragSpecDefDlg.cpp //! for a detailed explanation of what this member is for. int m_monomerContribution; //! Comment. QString m_comment; //! List of fragmentation rules. QList m_ruleList; }; } // namespace libXpertMass } // namespace MsXpS #endif // FRAG_SPEC_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Ionizable.hpp000664 001750 001750 00000005744 14647465366 027062 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes /////////////////////// Local includes #include "exportimportconfig.h" #include "Ponderable.hpp" #include "PolChemDefEntity.hpp" #include "IonizeRule.hpp" namespace MsXpS { namespace libXpertMass { class PolChemDef; typedef std::shared_ptr PolChemDefCstSPtr; class DECLSPEC Ionizable : public PolChemDefEntity, public Ponderable { public: Ionizable(PolChemDefCstSPtr, const QString & = QString("NOT_SET"), const Ponderable & = Ponderable(), const IonizeRule & = IonizeRule(), bool = false); Ionizable(const Ionizable &other); virtual ~Ionizable(); const IonizeRule &ionizeRule() const; IonizeRule *ionizeRule(); bool isIonized() const; int setCharge(int); virtual int charge() const; virtual int ionize(); virtual int ionize(const IonizeRule &); static int ionize(Ionizable *, const IonizeRule &); virtual int deionize(); static int deionize(Ionizable *); virtual double molecularMass(MassType); using PolChemDefEntity::validate; virtual bool validate(); using PolChemDefEntity::operator=; using Ponderable::operator=; virtual Ionizable &operator=(const Ionizable &); using PolChemDefEntity::operator==; using Ponderable::operator==; virtual bool operator==(const Ionizable &) const; using PolChemDefEntity::operator!=; using Ponderable::operator!=; virtual bool operator!=(const Ionizable &) const; virtual bool calculateMasses() override; QString toString(); protected: //! IonizeRule that is used to ionize \c this Ionizable. IonizeRule m_ionizeRule; //! Indicates if \c this Ionizable has been effectively ionized. bool m_isIonized; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/IonizeRule.hpp000664 001750 001750 00000005160 14647465366 027223 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef IONIZE_RULE_HPP #define IONIZE_RULE_HPP /////////////////////// Local includes #include "exportimportconfig.h" #include "Formula.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC IonizeRule : public Formula { public: IonizeRule(); IonizeRule(const IonizeRule &other); void setCharge(int); int charge() const; void setLevel(int); int level() const; QString formula() const; using Formula::operator=; IonizeRule &operator=(const IonizeRule &other); using Formula::operator==; bool operator==(const IonizeRule &) const; using Formula::operator!=; bool operator!=(const IonizeRule &) const; // using Formula::validate; bool validate(IsotopicDataCstSPtr isotopic_data_csp) override; bool isValid() const; bool renderXmlIonizeRuleElement(const QDomElement &); QString *formatXmlIonizeRuleElement(int, const QString & = QString(" ")); void debugPutStdErr(); virtual QString toString() const override; protected: //! Charge brought by one ionization reaction. int m_charge; //! Level at which the ionization reaction should occur, that is // the number of times the ionization should occur(can be 0, in // which case the analyte is deionized). int m_level; //! Indicates if \c this IonizeRule is valid. Set only after a //! call to validate(). bool m_isValid; }; } // namespace libXpertMass } // namespace MsXpS #endif // IONIZE_RULE_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Isotope.hpp000664 001750 001750 00000010152 14647465366 026555 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { // #include // // extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int // elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; enum class IsotopeFields { ID = 0, ELEMENT = 1, SYMBOL = 2, ATOMIC_NUMBER = 3, MASS = 4, MASS_NUMBER = 5, EXTRA_NEUTRONS = 6, PROBABILITY = 7, LN_PROBABILITY = 8, RADIOACTIVE = 9, LAST = 10, }; class DECLSPEC Isotope { public: Isotope(int id, QString element, QString symbol, int atomicNo, double mass, int massNo, int extraNeutrons, double probability, double lnProbability, bool radioactive); Isotope(const Isotope &other); Isotope(const QString &text); virtual ~Isotope(); bool initialize(const QString &text); void setId(int id); int getId() const; void setElement(const QString &element); QString getElement() const; void setSymbol(const QString &symbol); QString getSymbol() const; void setAtomicNo(int atomic_number); int getAtomicNo() const; void setMass(double mass); double getMass() const; void setMassNo(int mass_number); int getMassNo() const; void setExtraNeutrons(int extra_neutrons); int getExtraNeutrons() const; void setProbability(double probability); double getProbability() const; void setLnProbability(double ln_probability); double getLnProbability() const; void setRadioactive(bool is_radioactive); bool getRadioactive() const; int validate(QString *errors_p = nullptr) const; virtual Isotope &operator=(const Isotope &other); bool operator==(const Isotope &other) const; bool operator!=(const Isotope &other) const; QString toString() const; protected: int m_id; QString m_element; QString m_symbol; int m_atomicNo; double m_mass; int m_massNo; int m_extraNeutrons; double m_probability; double m_lnProbability; bool m_radioactive; }; typedef std::shared_ptr IsotopeSPtr; typedef std::shared_ptr IsotopeCstSPtr; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/IsotopicClusterGenerator.hpp000664 001750 001750 00000012546 14647465366 032146 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once #include #include #include /////////////////////// Qt includes /////////////////////// pappsomspp includes #include #include "pappsomspp/trace/trace.h" #include "pappsomspp/types.h" /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" #include "Formula.hpp" #include "IsotopicData.hpp" namespace MsXpS { namespace libXpertMass { enum class IsotopicDataType { NOT_SET, LIBRARY_CONFIG, USER_CONFIG, MANUAL_CONFIG, }; using FormulaChargePair = std::pair; using IsotopicClusterChargePair = std::pair; // We feed pairs to the generator and it produces for each // pair an isotopic cluster stored as a pair. class DECLSPEC IsotopicClusterGenerator { public: IsotopicClusterGenerator(); IsotopicClusterGenerator(libXpertMass::IsotopicDataSPtr isotopic_data_sp); virtual ~IsotopicClusterGenerator(); void setIsotopicDataType(IsotopicDataType isotopic_data_type); void setIsotopicData(libXpertMass::IsotopicDataSPtr isotopic_data_sp); libXpertMass::IsotopicDataSPtr getIsotopicData() const; void setFormulaChargePairs( const std::vector &formula_charge_pairs); void setFormulaChargePair(FormulaChargePair &formula_charge_pair); void appendFormulaChargePair(FormulaChargePair &formula_charge_pair); void setMaxSummedProbability(double max_probability); void setNormalizationIntensity(int normalize_intensity); void setSortType(pappso::SortType sort_type); void setSortOrder(pappso::SortOrder sort_order); bool configureIsotopicData(std::map &symbol_count_map, int *&per_element_isotopes_count_array_p, int *&per_element_symbol_count_array_p, double **&per_element_isotope_masses_arrays_p_p, double **&per_element_isotope_probs_arrays_p_p); bool validateFormula(Formula &formula); bool validateAllFormulas(); pappso::TraceSPtr runIsotopicDataCalculations(std::size_t element_count, int charge, int *per_element_isotopes_count_array_p, int *per_element_symbol_count_array_p, double **per_element_isotope_masses_arrays_p_p, double **per_element_isotope_probs_arrays_p_p); IsotopicClusterChargePair generateIsotopicClusterCentroids(FormulaChargePair formula_charge_pair); std::size_t run(); QString clusterToString(const pappso::TraceCstSPtr &isotopic_cluster_sp) const; QString clustersToString() const; const std::vector & getIsotopicClusterChargePairs() const; protected: // Basic configuration libXpertMass::IsotopicDataSPtr msp_isotopicData = nullptr; IsotopicDataType m_isotopicDataType = IsotopicDataType::NOT_SET; double m_maxSummedProbability = 0.95; int m_normalizeIntensity = std::numeric_limits::min(); // Convenience for the useur pappso::SortType m_sortType = pappso::SortType::no_sort; pappso::SortOrder m_sortOrder = pappso::SortOrder::ascending; // Reminder // using FormulaChargePair = std::pair; // using IsotopicClusterChargePair = std::pair; // The starting material: a formula string and a corresponding charge. std::vector m_formulaChargePairs; // The clusters that are generated are stored using the charge. std::vector m_isotopicClusterChargePairs; void normalizeIntensities(pappso::TraceSPtr &isotopic_cluster_sp); void sortPeakCentroids(pappso::TraceSPtr &isotopic_cluster_sp); void sortPeakCentroidsByMz(pappso::TraceSPtr &isotopic_cluster_sp); void sortPeakCentroidsByIntensity(pappso::TraceSPtr &isotopic_cluster_sp); }; typedef std::shared_ptr IsotopicClusterGeneratorSPtr; typedef std::shared_ptr IsotopicClusterGeneratorCstSPtr; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/IsotopicClusterShaper.hpp000664 001750 001750 00000011744 14647465366 031441 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// StdLib includes #include /////////////////////// Qt includes #include /////////////////////// pappsomspp includes #include #include #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "PeakCentroid.hpp" #include "MassPeakShaperConfig.hpp" #include "MassPeakShaper.hpp" namespace MsXpS { namespace libXpertMass { using IsotopicClusterChargePair = std::pair; class DECLSPEC IsotopicClusterShaper { public: // Version where only one isotopic cluster (centroid data) is to be // processed. IsotopicClusterShaper(const pappso::Trace &isotopic_cluster, int charge, const libXpertMass::MassPeakShaperConfig &config); IsotopicClusterShaper( const std::vector &isotopic_cluster_charge_pairs, const libXpertMass::MassPeakShaperConfig &config); virtual ~IsotopicClusterShaper(); // Single isotopic cluster version. Reset automatically the shaped isotopic // clusters. void setIsotopicCluster(const pappso::Trace &isotopic_cluster, int charge); void setIsotopicCluster(pappso::TraceCstSPtr isotopic_cluster_sp, int charge); void setIsotopicCluster(IsotopicClusterChargePair isotopic_cluster_charge_pair); // Multiple isotopic clusters version. Reset automatically the shaped isotopic // clusters. void setIsotopicClusterChargePairs(const std::vector &isotopic_cluster_charge_pairs); void appendIsotopicCluster(const pappso::Trace &isotopic_cluster, int charge); void appendIsotopicClusterChargePairs(const std::vector &isotopic_cluster_charge_pairs); void setConfig(libXpertMass::MassPeakShaperConfig config); libXpertMass::MassPeakShaperConfig getConfig() const; // The normalizing intensity usually is the greatest intensity in the cluster // centroids that is to be used for normalization of the peaks in the cluster. void setNormalizeIntensity(int new_max_intensity); int getNormalizeIntensity() const; pappso::Trace &run(bool reset = true); QString shapeToString(); protected: // This is the starting material for the shaping. There can be as many // isotopic clusters (centroid data) in the vector as necessary. All these // clusters are going to be processed and the resulting shaped isotopic // cluster data are combined into the member MapTrace. std::vector m_isotopicClusterChargePairs; libXpertMass::MassPeakShaperConfig m_config; pappso::MapTrace m_mapTrace; pappso::Trace m_finalTrace; pappso::MzIntegrationParams m_mzIntegrationParams; // Of all the peak centroids' intensities, what is the m/z value of the most // intense? double m_mostIntensePeakMz = 0.0; double m_smallestMz = std::numeric_limits::max(); double m_greatestMz = std::numeric_limits::min(); int m_normalizeIntensity = 1; void setIsotopicCluster(pappso::TraceCstSPtr isotopic_cluster_sp, int charge, bool reset); void setIsotopicCluster(const pappso::Trace &isotopic_cluster, int charge, bool reset); void setIsotopicCluster(IsotopicClusterChargePair isotopic_cluster_charge_pair, bool reset); void setIsotopicClusterChargePairs( const std::vector &isotopic_cluster_charge_pairs, bool reset); }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/IsotopicData.hpp000664 001750 001750 00000012763 14647465366 027530 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once #include #include #include /////////////////////// Qt includes /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" #include "Isotope.hpp" namespace MsXpS { namespace libXpertMass { using IsotopeCstIterator = std::vector::const_iterator; using IsotopeIterator = std::vector::iterator; using IsotopeCstIteratorPair = std::pair; class DECLSPEC IsotopicData { friend class IsotopicDataBaseHandler; friend class IsotopicDataLibraryHandler; friend class IsotopicDataUserConfigHandler; friend class IsotopicDataManualConfigHandler; public: IsotopicData(); IsotopicData(const IsotopicData &other); virtual ~IsotopicData(); void appendNewIsotope(IsotopeSPtr isotope_sp, bool update_maps = true); bool insertNewIsotope(IsotopeSPtr isotope_sp, std::size_t index, bool update_maps = true); void appendNewIsotopes(const std::vector &isotopes, bool update_maps = true); std::vector::const_iterator eraseIsotopes( std::size_t begin_index, std::size_t end_index, bool update_maps); ///////////////////////////////////////////////////////// /// Function to be run each time something is loaded or anything is changed /// int the m_isotopes vector. bool updateMonoMassMap(const QString &symbol); std::size_t updateMonoMassMap(); bool updateAvgMassMap(const QString &symbol); std::size_t updateAvgMassMap(); double computeAvgMass(IsotopeCstIteratorPair iter_pair, std::vector &errors) const; void updateMassMaps(const QString &symbol); std::size_t updateMassMaps(); ///////////////////////////////////////////////////////// /// All the functions needed to access the various bits from the m_isotopes /// vector of IsotopeSPtr instances. // Get the monoisotopic mass of carbon. double getMonoMassBySymbol(const QString &symbol, bool *ok = nullptr) const; double getMonoMass(IsotopeCstIteratorPair iter_pair, std::vector &errors) const; double getAvgMassBySymbol(const QString &symbol, bool *ok = nullptr) const; double getCumulatedProbabilitiesBySymbol(const QString &symbol, std::vector &errors) const; double getCumulatedProbabilities(IsotopeCstIteratorPair iter_pair, std::vector &errors) const; IsotopeCstIteratorPair getIsotopesBySymbol(const QString &symbol) const; std::size_t getIsotopeCountBySymbol(const QString &symbol) const; IsotopeCstIteratorPair getIsotopesByName(const QString &name) const; std::vector getUniqueSymbolsInOriginalOrder() const; bool containsSymbol(const QString &symbol, int *count = nullptr) const; bool containsName(const QString &name, int *count = nullptr) const; QString isotopesAsStringBySymbol(const QString &symbol) const; bool isMonoMassIsotope(IsotopeCstSPtr isotope_csp); const std::vector &getIsotopes() const; IsotopicData &operator=(const IsotopicData &other); // The number of isotopes in m_isotopes. std::size_t size() const; // The number of different symbols (that is chemical elements) in the data std::size_t getUniqueSymbolsCount() const; int validate(std::vector &errors) const; bool validateBySymbol(const QString &symbol, std::vector &errors) const; bool validateAllBySymbol(std::vector &errors) const; void replace(IsotopeSPtr old_isotope_sp, IsotopeSPtr new_isotope_sp); void clear(); protected: // The vector should never be sorted as we want to keep the order of the // isotopes in the way the vector has been populated, either by looking into // the IsoSpec library tables or by reading data from a user-configured file. using IsotopeVectorCstIterator = std::vector::const_iterator; std::vector m_isotopes; std::map m_symbolMonoMassMap; std::map m_symbolAvgMassMap; }; typedef std::shared_ptr IsotopicDataSPtr; typedef std::shared_ptr IsotopicDataCstSPtr; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/IsotopicDataBaseHandler.hpp000664 001750 001750 00000005222 14647465366 031611 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once #include #include #include /////////////////////// Qt includes /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" #include "IsotopicData.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC IsotopicDataBaseHandler { public: IsotopicDataBaseHandler(const QString &file_name = QString()); IsotopicDataBaseHandler(IsotopicDataSPtr isotopic_data_sp, const QString &file_name = QString()); virtual ~IsotopicDataBaseHandler(); // Load all the isotopic data from the file. file_name cannot be empty and // must point to a valid file, of course. virtual std::size_t loadData(const QString &file_name); // Write the isotopic data to a file. Here the is some leeway (file_name might // be empty, some alternatives will be searched for). virtual std::size_t writeData(const QString &file_name = QString()); virtual void setIsotopicData(IsotopicDataSPtr isotopic_data_sp); virtual IsotopicDataSPtr getIsotopicData(); virtual void setFileName(const QString &file_name); virtual QString getFileName(); protected: IsotopicDataSPtr msp_isotopicData = nullptr; QString m_fileName; virtual std::size_t checkConsistency(); }; typedef std::shared_ptr IsotopicDataBaseHandlerSPtr; typedef std::shared_ptr IsotopicDataBaseHandlerCstSPtr; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/IsotopicDataLibraryHandler.hpp000664 001750 001750 00000005344 14647465366 032350 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Stdlib includes #include #include #include /////////////////////// Qt includes /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" #include "IsotopicData.hpp" #include "IsotopicDataBaseHandler.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC IsotopicDataLibraryHandler : public IsotopicDataBaseHandler { public: IsotopicDataLibraryHandler(); IsotopicDataLibraryHandler(IsotopicDataSPtr isotopic_data_sp); //IsotopicDataLibraryHandler(const QString &file_name = QString()); //IsotopicDataLibraryHandler(IsotopicDataSPtr isotopic_data_sp, const QString &file_name = QString()); virtual ~IsotopicDataLibraryHandler(); // Load all the isotopic data from the IsoSpec library tables. This // pseudo-override takes *no* file name as it does not need any. //using IsotopicDataBaseHandler::loadData; virtual std::size_t loadData(const QString &filename) override; virtual std::size_t loadData(); // Write the isotopic data to a file. Here the is some leeway (file_name might // be empty, some alternatives will be searched for). virtual std::size_t writeData(const QString &file_name = QString()) override; protected: virtual std::size_t checkConsistency() override; }; typedef std::shared_ptr IsotopicDataLibraryHandlerSPtr; typedef std::shared_ptr IsotopicDataLibraryHandlerCstSPtr; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/IsotopicDataManualConfigHandler.hpp000664 001750 001750 00000007053 14647465366 033306 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Stdlib includes #include #include #include /////////////////////// Qt includes /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" #include "IsotopicData.hpp" #include "IsotopicDataBaseHandler.hpp" namespace MsXpS { namespace libXpertMass { // This class specializes IsotopicDataBaseHandler by providing function // specific for the user's manual configuration of chemical formula associated // with elements' isotopic data. The format of the data on file is nothing // like the format used to store Library- or User-Config- isotopic data. // File format: // //[Element] // symbol C count 100 //[Isotopes] 2 // mass 12.000 prob 0.9899 // mass 13.000 prob 0.010 using SymbolCountMap = std::map; using SymbolCountMapIter = SymbolCountMap::iterator; class DECLSPEC IsotopicDataManualConfigHandler : public IsotopicDataBaseHandler { public: IsotopicDataManualConfigHandler(const QString &file_name = QString()); IsotopicDataManualConfigHandler(IsotopicDataSPtr isotopic_data_sp, const QString &file_name = QString()); virtual ~IsotopicDataManualConfigHandler(); virtual void setSymbolCountMap(const SymbolCountMap &map); virtual const SymbolCountMap &getSymbolCountMap() const; // Load all the isotopic data from the file. file_name cannot be empty and // must point to a valid file, of course. virtual std::size_t loadData(const QString &file_name) override; // Write the isotopic data to a file. Here the is some leeway (file_name might // be empty, some alternatives will be searched for). virtual std::size_t writeData(const QString &file_name = QString()) override; bool newChemicalSet(const QString &symbol, int element_count, const std::vector &isotopes, bool update_maps = true); QString craftFormula() const; protected: // Helper data to store symbol/count data. SymbolCountMap m_symbolCountMap; virtual std::size_t checkConsistency() override; }; typedef std::shared_ptr IsotopicDataManualConfigHandlerSPtr; typedef std::shared_ptr IsotopicDataManualConfigHandlerCstSPtr; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/IsotopicDataUserConfigHandler.hpp000664 001750 001750 00000005020 14647465366 032777 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once #include #include #include /////////////////////// Qt includes /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" #include "IsotopicData.hpp" #include "IsotopicDataBaseHandler.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC IsotopicDataUserConfigHandler : public IsotopicDataBaseHandler { public: IsotopicDataUserConfigHandler(const QString &file_name = QString()); IsotopicDataUserConfigHandler(IsotopicDataSPtr isotopic_data_sp, const QString &file_name = QString()); virtual ~IsotopicDataUserConfigHandler(); // Load all the isotopic data from the file. file_name cannot be empty and // must point to a valid file, of course. virtual std::size_t loadData(const QString &file_name) override; // Write the isotopic data to a file. Here the is some leeway (file_name might // be empty, some alternatives will be searched for). virtual std::size_t writeData(const QString &file_name = QString()) override; protected: virtual std::size_t checkConsistency() override; }; typedef std::shared_ptr IsotopicDataUserConfigHandlerSPtr; typedef std::shared_ptr IsotopicDataUserConfigHandlerCstSPtr; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/MassDataCborBaseHandler.hpp000664 001750 001750 00000006327 14647465366 031540 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Std lib includes /////////////////////// Qt includes #include #include #include /////////////////////// PAPPSO includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" namespace MsXpS { namespace libXpertMass { enum class MassDataType { UNSET = 0, MASS_SPECTRUM, DRIFT_SPECTRUM, TIC_CHROMATOGRAM, XIC_CHROMATOGRAM, MZ_RT_COLORMAP, DT_RT_COLORMAP, DT_MZ_COLORMAP, }; typedef std::shared_ptr QCborStreamReaderSPtr; typedef std::shared_ptr QCborStreamWriterSPtr; class DECLSPEC MassDataCborBaseHandler : public QObject { Q_OBJECT public: MassDataCborBaseHandler(QObject *parent_p); ~MassDataCborBaseHandler(); void setInputFileName(const QString &file_name); void setOutputFileName(const QString &file_name); MassDataType getMassDataType() const; static MassDataType readMassDataType(const QString &input_file_name); static MassDataType readMassDataType(const QByteArray &byte_array); static MassDataType readMassDataType(QCborStreamReaderSPtr &reader_sp); void setMassDataType(MassDataType mass_data_type); virtual bool readFile(const QString &input_file_name = QString()); virtual bool readByteArray(const QByteArray &byte_array); virtual bool writeFile(const QString &output_file_name = QString()); virtual void writeByteArray(QByteArray &byte_array); void setTitle(const QString &title); QString getTitle() const; protected: MassDataType m_massDataType = MassDataType::UNSET; QString m_title = QString(); QString m_currentKey; QCborStreamReaderSPtr msp_reader = nullptr; QCborStreamWriterSPtr msp_writer = nullptr; QString m_inputFileName = QString(); QString m_outputFileName = QString(); virtual bool readContext(QCborStreamReaderSPtr &reader_sp); }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/MassDataCborMassSpectrumHandler.hpp000664 001750 001750 00000006211 14647465366 033304 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Std lib includes /////////////////////// Qt includes #include #include #include /////////////////////// PAPPSO includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" #include "MassDataCborBaseHandler.hpp" namespace MsXpS { namespace libXpertMass { // enum class MassDataType //{ // UNSET = 0, // MASS_SPECTRUM, // DRIFT_SPECTRUM, // TIC_CHROMATOGRAM, // XIC_CHROMATOGRAM, // MZ_RT_COLORMAP, // DT_RT_COLORMAP, // DT_MZ_COLORMAP, //}; class DECLSPEC MassDataCborMassSpectrumHandler : public MassDataCborBaseHandler { Q_OBJECT public: MassDataCborMassSpectrumHandler(QObject *parent_p); MassDataCborMassSpectrumHandler(QObject *parent_p, const pappso::Trace &trace); ~MassDataCborMassSpectrumHandler(); virtual bool readFile(const QString &input_file_name = QString()) override; virtual bool readByteArray(const QByteArray &byte_array) override; virtual bool writeFile(const QString &output_file_name = QString()) override; virtual void writeByteArray(QByteArray &byte_array) override; void setXLabel(const QString &label); QString getXLabel() const; void setYLabel(const QString &label); QString getYLabel() const; void setTrace(const pappso::Trace &trace); pappso::Trace getTrace() const; void clearTrace(); void setTraceColor(const QByteArray &color_byte_array); QByteArray getTraceColor() const; protected: pappso::Trace m_trace; QByteArray m_colorByteArray; QByteArray m_xBase64Data; QByteArray m_yBase64Data; QString m_xLabel; QString m_yLabel; virtual bool readContext(QCborStreamReaderSPtr &reader_sp) override; }; typedef std::shared_ptr MassDataCborMassSpectrumHandlerSPtr; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/MassDataClient.hpp000664 001750 001750 00000005145 14647465366 027775 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ***************************************************************************** * This specific code is a port to C++ of the Envemind Python code by Radzinski * and colleagues of IsoSpec fame (Lacki, Startek and company :-) * * See https://github.com/PiotrRadzinski/envemind. * ***************************************************************************** * * END software license */ #pragma once /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { class DECLSPEC MassDataClient : public QObject { Q_OBJECT public: explicit MassDataClient(const QString &ip_address, int port_number, int connection_frequency = 1, QObject *parent = nullptr); virtual ~MassDataClient(); QString getStatus(); protected slots: void reportError(QAbstractSocket::SocketError socket_error); void requestNewData(); virtual void readData(); signals: void reportErrorSignal(const QString &error); void newDataSignal(const QByteArray &byte_array); void hostFoundSignal(); void connectedSignal(); protected: QDataStream m_inStream; QByteArray m_data; QString m_ipAddress; int m_portNumber; int m_connectionFrequency = 1; QTcpSocket *mpa_tcpSocket = nullptr; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/MassDataServer.hpp000664 001750 001750 00000004310 14647465366 030016 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ***************************************************************************** * This specific code is a port to C++ of the Envemind Python code by Radzinski * and colleagues of IsoSpec fame (Lacki, Startek and company :-) * * See https://github.com/PiotrRadzinski/envemind. * ***************************************************************************** * * END software license */ #pragma once /////////////////////// Qt includes #include #include #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { class DECLSPEC MassDataServer : public QTcpServer { Q_OBJECT public: MassDataServer(QObject *parent = nullptr); virtual ~MassDataServer(); void serveData(const QByteArray &byte_array); protected: virtual void incomingConnection(qintptr socketDescriptor) override; protected: QByteArray m_data; void error(QTcpSocket::SocketError socket_error); }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/MassDataServerThread.hpp000664 001750 001750 00000004511 14647465366 031151 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ***************************************************************************** * This specific code is a port to C++ of the Envemind Python code by Radzinski * and colleagues of IsoSpec fame (Lacki, Startek and company :-) * * See https://github.com/PiotrRadzinski/envemind. * ***************************************************************************** * * END software license */ #pragma once /////////////////////// Qt includes #include #include #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { class DECLSPEC MassDataServerThread : public QThread { Q_OBJECT public: MassDataServerThread(qintptr socket_descriptor, const QByteArray &data, QObject *parent); virtual ~MassDataServerThread(); void run() override; signals: void errorSignal(QTcpSocket::SocketError socket_error); void writtenDataSignal(std::size_t written_bytes); protected: qintptr m_socketDescriptor; QByteArray m_data; QTcpSocket *mpa_tcpSocket = nullptr; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/MassPeakShaper.hpp000664 001750 001750 00000006104 14647465366 030004 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// StdLib includes /////////////////////// Qt includes /////////////////////// pappsomspp includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "MassPeakShaperConfig.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC MassPeakShaper { public: MassPeakShaper(); MassPeakShaper(double mz, double intensity, const MassPeakShaperConfig &config); MassPeakShaper(const pappso::DataPoint &data_point, const MassPeakShaperConfig &config); MassPeakShaper(const MassPeakShaper &); virtual ~MassPeakShaper(); void setPeakCentroid(const pappso::DataPoint &data_point); const pappso::DataPoint &getPeakCentroid() const; void setConfig(const MassPeakShaperConfig &config); const MassPeakShaperConfig &getConfig() const; const pappso::Trace &getTrace() const; int computePeakShape(); static pappso::Trace computePeakShape(double mz, double intensity, const MassPeakShaperConfig &config); int computeGaussianPeakShape(); static pappso::Trace computeGaussianPeakShape( double mz, double intensity, const MassPeakShaperConfig &config); int computeLorentzianPeakShape(); static pappso::Trace computeLorentzianPeakShape( double mz, double intensity, const MassPeakShaperConfig &config); double intensityAt(double mz, pappso::PrecisionPtr precision_p, bool &ok); QString shapetoString(); bool shapeToFile(const QString &file_name); protected: pappso::DataPoint m_peakCentroid; MassPeakShaperConfig m_config; pappso::Trace m_trace; void clearTrace(); }; typedef std::shared_ptr MassPeakShaperSPtr; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/MassPeakShaperConfig.hpp000664 001750 001750 00000010265 14647465366 031135 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// StdLib includes /////////////////////// Qt includes /////////////////////// pappsomspp includes /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" #include "PeakCentroid.hpp" namespace MsXpS { namespace libXpertMass { extern int FWHM_PEAK_SPAN_FACTOR; enum class MassPeakShapeType { NOT_SET = 0, GAUSSIAN, LORENTZIAN, }; enum class MassPeakWidthLogic { NOT_SET = 0, FWHM, RESOLUTION }; class DECLSPEC MassPeakShaperConfig { public: MassPeakShaperConfig(); MassPeakShaperConfig(const MassPeakShaperConfig &other); virtual ~MassPeakShaperConfig(); void operator=(const MassPeakShaperConfig &other); void setResolution(int resolution); double getResolution() const; int resolution(bool *ok); // For the gaussion, that is the entirety of the fwhm. void setFwhm(double value); double getFwhm() const; double fwhm(bool *ok); // For the lorentzian, that is half of the fwhm. double halfFwhm(bool *ok); void setReferencePeakMz(double mz); double getReferencePeakMz() const; void setBinSize(double bin_size); double binSize(bool *ok); double getBinSize() const; void setWithBins(bool with_bins); bool withBins() const; void setBinSizeFixed(bool is_fixed); bool getBinSizeFixed(); void setBinSizeDivisor(int divisor); int getBinSizeDivisor() const; void setPointCount(int); int getPointCount() const; void setNormFactor(double); double normFactor(); MassPeakShapeType getMassPeakShapeType() const; void setMassPeakShapeType(MassPeakShapeType); void setMassPeakWidthLogic(MassPeakWidthLogic logic); MassPeakWidthLogic getMassPeakWidthLogic() const; double c(bool *ok); double a(bool *ok); double gamma(bool *ok); void setMzStep(double step); double getMzStep() const; double mzStep(bool *ok); bool resolve(); void reset(); QString toString(); protected: int m_resolution = 0; double m_fwhm = 0; MassPeakWidthLogic m_massPeakWidthLogic = MassPeakWidthLogic::NOT_SET; // The reference m/z value is the m/z value of the peak that is considered // to be the main peak in an isotopic cluster, for example. That is // typically the peak that is the most intense. This peak's intensity is // typically used to compute the FWHM on the basis of the resolution, for // example (see function fwhm(). When computing a Gaussian shape for a // single peak, then that reference peak's m/z value needs to be set as the // peak centroid for which the computation is performed. double m_referencePeakMz = 0; // Number of points to use to shape the peak int m_pointCount = 0; bool m_withBins = false; int m_binSizeDivisor = 6; double m_binSize = 0; bool m_isBinSizeFixed = false; // The delta bewteen two consecutive data points double m_mzStep = 0; // Type (GAUSSIAN | LORENTZIAN) of the peak shape MassPeakShapeType m_massPeakShapeType = MassPeakShapeType::NOT_SET; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Modif.hpp000664 001750 001750 00000007704 14647465366 026202 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef MODIF_HPP #define MODIF_HPP /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" #include "PolChemDefEntity.hpp" #include "Formula.hpp" #include "Ponderable.hpp" #include "Prop.hpp" #include "PropListHolder.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC Modif : public PolChemDefEntity, public Formula, public Ponderable, public PropListHolder { public: Modif(PolChemDefCstSPtr, QString, QString = QString()); Modif(const Modif &); ~Modif(); void reset(); QString &setTargets(QString); QString targets() const; int targets(QStringList &) const; bool hasMonomerTarget(QString) const; bool validateTargets(bool = true); void setMaxCount(int); int maxCount(); QString formula() const; using PolChemDefEntity::operator=; using PolChemDefEntity::operator==; using PolChemDefEntity::operator!=; using Formula::operator=; using Formula::operator==; using Formula::operator!=; using Ponderable::operator=; using Ponderable::operator==; using Ponderable::operator!=; using PropListHolder::operator=; Modif &operator=(const Modif &); bool operator==(const Modif &); bool operator!=(const Modif &); int isNameKnown(); static int isNameInList(const QString &, const QList &, Modif * = 0); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Woverloaded-virtual" using PolChemDefEntity::validate; using Formula::validate; bool validate(); using Formula::accountMasses; using Ponderable::accountMasses; bool accountMasses(double *mono = 0, double *avg = 0, int times = 1); #pragma clang diagnostic pop bool calculateMasses(); bool renderXmlMdfElement(const QDomElement &, int); QString *formatXmlMdfElement(int, const QString & = QString(" ")); void debugPutStdErr(); private: QString m_targets; int m_maxCount; }; //////////////////////// ModifProp //////////////////////// //! The ModifProp class provides a modif property. /*! A ModifProp property is a simple property in which the data is a pointer to an allocated modif object. */ class ModifProp : public Prop { public: ModifProp(Modif *); ModifProp(const ModifProp &other); virtual ~ModifProp(); virtual void deleteData(); using Prop::operator=; ModifProp &operator=(const ModifProp &other); ModifProp *cloneOut() const; virtual bool renderXmlElement(const QDomElement &, int); virtual QString *formatXmlElement(int, const QString & = QString(" ")); }; } // namespace libXpertMass } // namespace MsXpS #endif // MODIF_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Monomer.hpp000664 001750 001750 00000010251 14647465366 026547 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef MONOMER_HPP #define MONOMER_HPP //#warning "Entering MONOMER_HPP" /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" #include "PolChemDefEntity.hpp" #include "Formula.hpp" #include "Ponderable.hpp" #include "PropListHolder.hpp" #include "CalcOptions.hpp" #include "Modif.hpp" namespace MsXpS { namespace libXpertMass { //! The Monomer class provides a monomer. /*! A monomer is the building block of a polymer sequence. It is mainly characterized by a name, a code and a formula. */ class DECLSPEC Monomer : public PolChemDefEntity, public Formula, public Ponderable, public PropListHolder { public: Monomer(PolChemDefCstSPtr pol_chem_def_csp, QString name, QString code = QString(), QString formula = QString()); Monomer(const Monomer &other); ~Monomer(); void setCode(const QString &code); QString code() const; using PolChemDefEntity::operator=; using Formula::operator=; using Ponderable::operator=; using PropListHolder::operator=; Monomer &operator=(const Monomer &other); using PolChemDefEntity::operator==; using Formula::operator==; using Ponderable::operator==; bool operator==(const Monomer &) const; using PolChemDefEntity::operator!=; using Formula::operator!=; using Ponderable::operator!=; bool operator!=(const Monomer &) const; bool checkCodeSyntax() const; int isCodeKnown() const; static int isCodeInList(const QString &, const QList &, Monomer * = 0); int isNameKnown() const; static int isNameInList(const QString &, const QList &, Monomer * = 0); QString formula() const; QList *modifList() const; bool isModifTarget(const Modif &) const; bool modify(Modif *, bool, QStringList &); bool unmodify(); bool unmodify(Modif *); bool isModified() const; int modifCount(const QString &); using PolChemDefEntity::validate; using Formula::validate; bool validate(); using Ponderable::calculateMasses; virtual bool calculateMasses(int monomer_chemical_entities); Formula calculateFormula(int monomer_chemical_entities = MONOMER_CHEMENT_NONE) const; using Formula::accountMasses; bool accountMasses(double *mono = 0, double *avg = 0, int times = 1) const; bool accountMasses(Ponderable *, int) const; bool renderXmlMnmElement(const QDomElement &, int); QString *formatXmlMnmElement(int, const QString & = QString(" ")); bool renderXmlMonomerElement(const QDomElement &, int); QString *formatXmlMonomerElement(int, const QString & = QString(" ")) const; void debugPutStdErr() const; private: //! Code. QString m_code; QList *mpa_modifList; }; } // namespace libXpertMass } // namespace MsXpS #endif // MONOMER_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/MonomerDictionary.hpp000664 001750 001750 00000004730 14647465366 030602 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef MONOMER_DICTIONARY_HPP #define MONOMER_DICTIONARY_HPP /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" #include "PolChemDefEntity.hpp" #include "Formula.hpp" #include "Monomer.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC MonomerDictionary { public: MonomerDictionary(QString = QString(), const QStringList & = QStringList(), int = -1, int = -1); ~MonomerDictionary(); void setFilePath(QString &); void setInputChainStringList(const QStringList &); void setInputCodeLength(int); void setOutputCodeLength(int); bool loadDictionary(); QStringList *translate(const QStringList & = QStringList()); protected: bool isLineProperSectionDivider(const QString &); void skipSection(QTextStream *); int parseSection(QTextStream *); QHash m_dictionaryHash; bool m_dictionaryLoaded; QString m_filePath; QStringList m_inputChainStringList; int m_inputCodeLength; int m_outputCodeLength; }; } // namespace libXpertMass } // namespace MsXpS #endif // MONOMER_DICTIONARY_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/MonomerSpec.hpp000664 001750 001750 00000004130 14647465366 027361 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { class DECLSPEC MonomerSpec { public: MonomerSpec(); virtual ~MonomerSpec(); void setName(const QString &); const QString &name(); void setCode(const QString &); const QString &code(); void setRaster(const QString &); const QString &raster(); void setVector(const QString &); const QString &vector(); void setNameSound(const QString &); const QString &nameSound(); void setCodeSound(const QString &); const QString &codeSound(); static bool parseFile(QString &, QList *); protected: QString m_name; QString m_code; QString m_raster; QString m_vector; QString m_nameSound; QString m_codeSound; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Oligomer.hpp000664 001750 001750 00000016635 14647465366 026724 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Local includes #include "exportimportconfig.h" #include "Sequence.hpp" #include "Ponderable.hpp" #include "Ionizable.hpp" #include "CalcOptions.hpp" #include "IonizeRule.hpp" #include "Monomer.hpp" #include "Coordinates.hpp" #include "CrossLink.hpp" namespace MsXpS { namespace libXpertMass { class Polymer; //! The Oligomer class provides an oligomer. /*! An oligomer is a stretch of monomers belonging to a polymer. It is characterized by: \ \ \li The polymer(a pointer to a Polymer) in which it spans a given region; \li An index(integer) at which the region starts in the polymer sequence; \li An index(integer) at which the region stops in the polymer sequence The start index cannot be less than 0 and greater than the size of the polymer minus one, and the end index follows the same rule. Derived from Ponderable, an oligomer is also characterized by a monoisotopic mass and an average mass. All computations about an oligomer(fragmentation, composition, for example, isoelectric point, ...) can only be performed by referring to the sequence of its "enclosing" polymer. Therefore, an oligomer should never exist after the destruction of its enclosing polymer. */ class DECLSPEC Oligomer : public Sequence, public CoordinateList, public Ionizable, public PropListHolder { using Ionizable::operator=; using PropListHolder::operator=; public: Oligomer(Polymer *polymer, const QString &name, const QString &description, bool modified, const Ponderable &ponderable, const IonizeRule &ionizeRule, const CalcOptions &calcOptions, bool isIonized, int startIndex, int endIndex); Oligomer(PolChemDefCstSPtr polChemDefCstSPtr, const QString &name, const QString &description, bool modified, const Ponderable &ponderable, const IonizeRule &ionizeRule, const CalcOptions &calcOptions, bool isIonized, int startIndex, int endIndex); Oligomer(Polymer *polymer, const QString &name, const QString &description, bool modified = false, const Ponderable &ponderable = Ponderable(), int startIndex = -1, int endIndex = -1, const CalcOptions &calcOptions = CalcOptions()); Oligomer(PolChemDefCstSPtr polChemDefCstSPtr, const QString &name, const QString &description, bool modified = false, const Ponderable &ponderable = Ponderable(), const CalcOptions &calcOptions = CalcOptions(), int startIndex = -1, int endIndex = -1); Oligomer(const Ionizable &ionizable, const CalcOptions &calcOptions = CalcOptions(), int startIndex = -1, int endIndex = -1); Oligomer(Polymer *polymer, const QString &name, const QString &description, bool modified = false, double mono = 0, double avg = 0, int startIndex = -1, int endIndex = -1, const CalcOptions &calcOptions = CalcOptions()); Oligomer(PolChemDefCstSPtr polChemDefCstSPtr, const QString &name, const QString &description, bool modified = false, const CalcOptions &calcOptions = CalcOptions(), double mono = 0, double avg = 0, int startIndex = -1, int endIndex = -1); Oligomer(const Oligomer &other); virtual ~Oligomer(); const Polymer *polymer() const; void setModified(bool modified = true); // By default look into the polymer to check if the // oligomer is modified. virtual bool isModified(bool deep = true); void setStartEndIndices(int, int); void setStartIndex(int); int startIndex() const; void setEndIndex(int); int endIndex() const; void setDescription(const QString &); QString description() const; int appendCoordinates(CoordinateList *); void setIonizeRule(IonizeRule &); IonizeRule &ionizeRule(); void setCalcOptions(const CalcOptions &); const CalcOptions &calcOptions() const; void updateCalcOptions(); const Monomer &atLeftEnd() const; const Monomer &atRightEnd() const; const Monomer *monomerAt(int) const; QList *crossLinkList(); bool addCrossLink(CrossLink *); // ELEMENTAL CALCULATION FUNCTION ///////////////////////////////// virtual QString elementalComposition(); virtual QString elementalComposition(const CalcOptions &calc_options, const IonizeRule &ionize_rule); ///////////////////////////////// // ELEMENTAL CALCULATION FUNCTION virtual int makeMonomerText(); QString *monomerText(); virtual bool calculateMasses(); virtual bool calculateMasses(const CalcOptions *calcOptions, const IonizeRule *ionizeRule = 0); int size(); bool encompasses(int) const; bool encompasses(const Monomer *) const; protected: //! Polymer in which this oligomer spans a region. // At present, cannot be const because of Formula not being able to properly // work without modifying itself. FIXME: this is to be resolved. Polymer *mp_polymer; QString m_description; bool m_isModified; // !The list of the the cross-links that are involved in the // !formation of the cross-linked oligomer(the CrossLink * // !instances are in mp_polymer->crossLinkList()). We cannot use // !the CrossLinkList object because when destructed it would // !destroy the cross-links in it, while we only want to store // !pointers. QList m_crossLinkList; CalcOptions m_calcOptions; }; typedef std::shared_ptr OligomerSPtr; typedef std::shared_ptr OligomerCstSPtr; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/PeakCentroid.hpp000664 001750 001750 00000004400 14647465366 027502 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef PEAK_CENTROID_HPP #define PEAK_CENTROID_HPP #include /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { class PeakCentroid; typedef std::shared_ptr PeakCentroidSPtr; class DECLSPEC PeakCentroid { public: PeakCentroid(); PeakCentroid(double /* mz */, double /* intensity */); PeakCentroid(const PeakCentroid &); ~PeakCentroid(); PeakCentroid &operator=(const PeakCentroid &other); bool operator==(const PeakCentroid &other); bool operator!=(const PeakCentroid &other); void setMz(double); double mz() const; void setIntensity(double); void incrementIntensity(double); double intensity() const; QString intensity(int decimalPlaces) const; QString toString() const; protected: double m_mz; double m_intensity; }; } // namespace libXpertMass } // namespace MsXpS #endif // PEAK_CENTROID_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/PkaPhPi.hpp000664 001750 001750 00000005302 14647465366 026430 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" #include "Polymer.hpp" #include "Monomer.hpp" #include "Modif.hpp" #include "ChemicalGroup.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC PkaPhPi : public PropListHolder { public: PkaPhPi(Polymer &, CalcOptions &, QList * = 0, QList * = 0); ~PkaPhPi(); using PropListHolder::operator=; void setPh(double); double ph(); double pi(); double positiveCharges(); double negativeCharges(); void setCalcOptions(const CalcOptions &); void setMonomerList(QList *); void setModifList(QList *); int calculateCharges(); int calculatePi(); double calculateChargeRatio(double, bool); int accountPolymerEndModif(PolymerChemEnt end_modif, const ChemicalGroup &chemical_group); int accountMonomerModif(const Monomer &, ChemicalGroup &); protected: double m_ph = 7; double m_pi = 7; const Polymer &m_polymer; CalcOptions m_calcOptions; double m_positiveCharges = 0; double m_negativeCharges = 0; // Not allocated locally, but when set, *this takes ownership of // these lists, and these lists get freed upon destruction of *this. QList *mpa_monomerList; QList *mpa_modifList; bool *mp_aborted = nullptr; int *mp_progress = nullptr; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/PkaPhPiDataParser.hpp000664 001750 001750 00000003522 14647465366 030401 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Local includes #include "exportimportconfig.h" #include "PolChemDef.hpp" #include "PolChemDefEntity.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC PkaPhPiDataParser { public: PkaPhPiDataParser(const libXpertMass::PolChemDefCstSPtr, QString = QString()); ~PkaPhPiDataParser(); void setFilePath(const QString &); const QString &filePath(); bool renderXmlFile(QList *, QList *); protected: libXpertMass::PolChemDefCstSPtr mcsp_polChemDef; QString m_filePath; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/PolChemDef.hpp000664 001750 001750 00000017625 14647465366 027115 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "Formula.hpp" #include "IsotopicData.hpp" #include "Modif.hpp" #include "CrossLinker.hpp" #include "CleaveSpec.hpp" #include "FragSpec.hpp" #include "IonizeRule.hpp" #include "Polymer.hpp" #include "MonomerSpec.hpp" #include "ModifSpec.hpp" #include "CrossLinkerSpec.hpp" #include "PolChemDefSpec.hpp" namespace MsXpS { namespace libXpertMass { extern const int POL_CHEM_DEF_FILE_FORMAT_VERSION; class PolChemDef; typedef std::shared_ptr PolChemDefSPtr; typedef std::shared_ptr PolChemDefCstSPtr; //! The PolChemDef class provides a polymer chemistry definition. /*! A polymer chemistry definition allows one to characterize in a fine manner the chemistry of a polymer. That means that it should contain: - a list of atoms; - a list of monomers; - a list of modifications; - a list of cross-linkers; - a list of cleavage specifications; - a list of fragmentation specifications; and a number of chemical data, like the left and right cap formulas, the code length(that is the maximum number of characters that might be used to construct a monomer code). Because monomers have to be graphically rendered at some point, in the form of monomer vignettes in the sequence editor, the polymer chemistry definition also contains: - a list of monomer specifications, which tell the sequence editor what file is to be used to render any monomer in the polymer chemistry definition; - a list of modification specifications, which tell the sequence editor what file is to be used to render any chemical modification; */ class DECLSPEC PolChemDef { public: PolChemDef(); PolChemDef(const PolChemDefSpec &); virtual ~PolChemDef(); void setVersion(const QString &); QString version() const; void setName(const QString &); QString name() const; void setXmlDataFilePath(const QString &file_path); QString getXmlDataFilePath() const; QString getXmlDataDirPath() const; void setIsotopicDataFilePath(const QString &file_path); QString getIsotopicDataFilePath() const; QString deduceIsotopicDataFilePath() const; void setLeftCap(const Formula &); const Formula &leftCap() const; void setRightCap(const Formula &); const Formula &rightCap() const; void setCodeLength(int); int codeLength() const; bool calculateDelimitedCodes(); const QString &delimitedCodes(); void setIonizeRule(const IonizeRule &); const IonizeRule &ionizeRule() const; IonizeRule *ionizeRulePtr(); void setIsotopicDataSPtr(IsotopicDataSPtr isotopic_data_sp); IsotopicDataCstSPtr getIsotopicDataCstSPtr() const; IsotopicDataSPtr getIsotopicDataSPtr(); const QList &monomerList() const; QList *monomerListPtr(); const QList &modifList() const; QList *modifListPtr(); const QList &crossLinkerList() const; QList *crossLinkerListPtr(); const QList &cleaveSpecList() const; QList *cleaveSpecListPtr(); const QList &fragSpecList() const; QList *fragSpecListPtr(); QList &monomerSpecList() const; QList *monomerSpecListPtr(); QList &modifSpecList() const; QList *modifSpecListPtr(); QList &crossLinkerSpecList() const; QList *crossLinkerSpecListPtr(); bool referenceModifByName(const QString &, Modif * = 0) const; bool referenceCrossLinkerByName(const QString &, CrossLinker * = 0) const; static std::size_t loadIsotopicData(PolChemDefSPtr pol_chem_def_sp, const QString &file_path = QString()); static std::size_t writeIsotopicData(PolChemDefSPtr pol_chem_def_sp, const QString &file_path = QString()); static bool renderXmlPolChemDefFile(PolChemDefSPtr pol_chem_def_sp); QString *formatXmlDtd(); bool writeXmlFile(); QStringList *differenceBetweenMonomers(double, MassType); protected: //! Name. /*! Identifies the polymer definition, like "protein" or "dna", for example. */ QString m_name; //! File path. /*! Fully qualifies the file which contains the polymer chemistry definition. */ QString m_xmlDataFilePath; QString m_isotopicDataFilePath; //! Left cap formula. /*! Describes how the polymer should be capped on its left end. */ Formula m_leftCap; //! Right cap formula. /*! Describes how the polymer should be capped on its right end. */ Formula m_rightCap; //! Code length. /*! Maximum number of characters allowed to construct a monomer code. */ int m_codeLength; //! Delimited codes string. /*! String made with each code of each monomer separated using '@', like "@Ala@Tyr@Phe@". */ QString m_delimitedCodes; //! Ionization rule. /*! The ionization rule indicates how the polymer sequence needs be ionized by default. */ IonizeRule m_ionizeRule; //! Shared pointer to IsotopicData // We will access all the chemical elements data in this class instance. IsotopicDataSPtr msp_isotopicData = nullptr; //! List of monomers. QList m_monomerList; //! List of modifications. QList m_modifList; //! List of cross-linkers. QList m_crossLinkerList; //! List of cleavage specifications. QList m_cleaveSpecList; //! List of fragmentation specifications. QList m_fragSpecList; //! List of monomer specifications. /*! Each monomer in the polymer chemistry definition has an item in this list indicating where the graphics file is to be found for graphical rendering. */ mutable QList m_monomerSpecList; //! List of modification specifications. /*! Each modification in the polymer chemistry definition has an item in this list indicating where the graphics file is to be found for graphical rendering and the graphical mechanism to be used for rendering the modification graphically. */ mutable QList m_modifSpecList; //! List of cross-link specifications. /*! Each cross-link in the polymer chemistry definition has an item in this list indicating where the graphics file is to be found for graphical rendering. */ mutable QList m_crossLinkerSpecList; QList *mp_repositoryList; }; } // namespace libXpertMass } // namespace MsXpS Q_DECLARE_METATYPE(MsXpS::libXpertMass::PolChemDefSPtr); extern int polChemDefSPtrMetaTypeId; Q_DECLARE_METATYPE(MsXpS::libXpertMass::PolChemDefCstSPtr); extern int polChemDefCstSPtrMetaTypeId; libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/PolChemDefEntity.hpp000664 001750 001750 00000005555 14647465366 030311 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { // Avoid inclusion of the header file. class PolChemDef; typedef std::shared_ptr PolChemDefCstSPtr; //! The PolChemDefEntity class provides a chemical entity. /*! A polymer chemistry definition entity is something that relates to a polymer chemistry definition and as such has to maintain a close relationship with a polymer chemistry definition instance. That relationship is automatically generated upon construction, as a polymer definition chemical entity cannot be constructed without a pointer to a polymer chemistry definition instance. Aside from the pointer to a polymer chemistry definition, a polymer chemistry definition entity is also characterized by a name. */ class DECLSPEC PolChemDefEntity { public: PolChemDefEntity(PolChemDefCstSPtr, const QString & = QString("NOT_SET")); PolChemDefEntity(const PolChemDefEntity &other); virtual ~PolChemDefEntity(); void setName(const QString &name); QString name() const; PolChemDefCstSPtr getPolChemDefCstSPtr() const; void setPolChemDefCstSPtr(PolChemDefCstSPtr); virtual PolChemDefEntity &operator=(const PolChemDefEntity &other); virtual bool operator==(const PolChemDefEntity &other) const; virtual bool operator!=(const PolChemDefEntity &other) const; virtual bool validate() const; protected: //! Pointer to the reference polymer chemistry definition. PolChemDefCstSPtr mcsp_polChemDef; //! Name. QString m_name; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/PolChemDefSpec.hpp000664 001750 001750 00000004165 14647465366 027723 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef POL_CHEM_DEF_SPEC_HPP #define POL_CHEM_DEF_SPEC_HPP /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { class DECLSPEC PolChemDefSpec { private: // Name of the polymer definition(ie polymer type, 'protein'). QString m_name; // PolChemDef definition file(path relative to the polymer chemistry // definitions directory in the data directory, as found in the // catalogue files, like protein-1-letter/protein-1-letter.xml QString m_filePath; public: PolChemDefSpec(); ~PolChemDefSpec(); void setName(const QString &name); const QString &name() const; void setFilePath(const QString &file_path); QString getFilePath() const; QString dirPath(); }; } // namespace libXpertMass } // namespace MsXpS #endif // POL_CHEM_DEF_SPEC_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Polymer.hpp000664 001750 001750 00000016061 14647465366 026567 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once #include /////////////////////// Qt includes #include #include #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "Sequence.hpp" #include "IonizeRule.hpp" #include "Ionizable.hpp" #include "Modif.hpp" #include "CalcOptions.hpp" namespace MsXpS { namespace libXpertMass { const int POL_SEQ_FILE_FORMAT_VERSION = 1; class PolChemDef; typedef std::shared_ptr PolChemDefCstSPtr; class IonizeRule; class Ionizable; class CrossLink; class CrossLinkList; //! The Polymer class provides a polymer. /*! A polymer is a sequence(Sequence) to which data are aggregated to make it able to perform a number of tasks. The polymer has a name and a code, so that it is possible to refer to it in printed material... Because Polymer derives from Ionizable, which itself derives from PolChemDefEntity, it holds a pointer to the polymer chemistry definition of the polymer sequence. Without that datum, using the polymer in a complex context would be impossible, as the polymer chemistry definition will provide a huge amount of data required to operate the polymer efficiently. Because Ionizable inherits Ponderable, the polymer has a mono mass and an average mass. The polymer also has modifications(left-end and right-end) to characterize it better. */ class DECLSPEC Polymer : public QObject, public Sequence, public Ionizable { Q_OBJECT public: Polymer(PolChemDefCstSPtr pol_chem_def_csp, const QString & = QString("NOT_SET"), const QString & = QString("NOT_SET"), const QString & = QString("NOT SET")); virtual ~Polymer(); void setName(const QString &); QString name() const; void setCode(const QString &); QString code() const; void setAuthor(const QString &); QString author() const; void setFilePath(const QString &); QString filePath() const; void setDateTime(const QString &); QString dateTime() const; bool hasModifiedMonomer(int left_index = -1, int right_index = -1) const; bool setLeftEndModif(const QString & = QString()); bool setLeftEndModif(const Modif &); const Modif &leftEndModif() const; bool isLeftEndModified() const; bool setRightEndModif(const QString & = QString()); bool setRightEndModif(const Modif &); const Modif &rightEndModif() const; bool isRightEndModified() const; const CrossLinkList &crossLinkList() const; CrossLinkList *crossLinkListPtr(); bool crossLinkedMonomerIndexList(int, int, QList *, int *); bool crossLinkList(int, int, QList *, int *); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Woverloaded-virtual" virtual bool prepareMonomerRemoval(Monomer *); #pragma clang diagnostic pop using Ionizable::operator=; virtual bool removeMonomerAt(int); QByteArray md5Sum(int hash_data_specifs) const; // MASS CALCULATION FUNCTIONS ///////////////////////////// #pragma clang diagnostic push #pragma clang diagnostic ignored "-Woverloaded-virtual" bool accountMasses(const CalcOptions &); static bool accountMasses(Polymer *, const CalcOptions &, double *, double *); bool calculateMasses(const CalcOptions &, bool = true); static bool calculateMasses( Polymer *, const CalcOptions &, double *, double *, bool = true); #pragma clang diagnostic pop bool accountCappingMasses(int, int = 1); static bool accountCappingMasses(Polymer *, int, double *, double *, int = 1); bool accountEndModifMasses(int); static bool accountEndModifMasses(Polymer *, int, double *, double *); static bool accountEndModifMasses(Polymer *, int, Ponderable *); bool crossLink(CrossLink *); bool uncrossLink(CrossLink *); ///////////////////////////// // MASS CALCULATION FUNCTIONS // ELEMENTAL CALCULATION FUNCTION ///////////////////////////////// QString elementalComposition(const IonizeRule &, const CoordinateList &, const CalcOptions &); ///////////////////////////////// // ELEMENTAL CALCULATION FUNCTION bool renderXmlCodesElement(const QDomElement &element); static QString xmlPolymerFileGetPolChemDefName(const QString &file_path); bool renderXmlPolymerFile(QString = QString("")); bool renderXmlPolymerModifElement(const QDomElement &, int); bool renderXmlCrossLinksElement(const QDomElement &, int); QString *formatXmlDtd(); QString *formatXmlPolSeqElement(int, const QString & = QString(" ")); QString *formatXmlCrossLinksElement(int, const QString & = QString(" ")); bool writeXmlFile(); bool validate(); void debugPutStdErr(); signals: void polymerDestroyedSignal(Polymer *); void crossLinkChangedSignal(Polymer *); void crossLinksPartiallyEncompassedSignal(int) const; private: //! Name QString m_name; //! Code. QString m_code; //! Name of the last user having last modified the polymer sequence. QString m_author; //! File name. QString m_filePath; //! Date and time of the last modification. QDateTime m_dateTime; //! Left end modification. Modif m_leftEndModif; //! Right end modification. Modif m_rightEndModif; //! The list of CrossLink instances. CrossLinkList *mpa_crossLinkList; }; typedef std::shared_ptr PolymerSPtr; typedef std::shared_ptr PolymerCstSPtr; } // namespace libXpertMass } // namespace MsXpS Q_DECLARE_METATYPE(MsXpS::libXpertMass::Polymer); extern int polymerMetaTypeId; Q_DECLARE_METATYPE(MsXpS::libXpertMass::PolymerSPtr); extern int polymerSPtrMetaTypeId; Q_DECLARE_METATYPE(MsXpS::libXpertMass::PolymerCstSPtr); extern int polymerCstSPtrMetaTypeId; libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Ponderable.hpp000664 001750 001750 00000006310 14647465366 027207 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" namespace MsXpS { namespace libXpertMass { //! The Ponderable class provides a ponderable. /*! Ponderable stores a monoisotopic and an average molecular mass. This class is used to derive chemical entities that have monoisotopic and average masses. For example, an isotope is the simplest chemical entity found on earth. It has a monoisotopic mass but no average mass. It thus cannot be considered a ponderable. Instead, an atom is the simplest ponderable found in massXpert, as it has both a monoisotopic mass and an average mass. Atom thus derives from Ponderable, while Isotope does not. */ class DECLSPEC Ponderable { public: Ponderable(double = 0, double = 0); Ponderable(const Ponderable &other); virtual ~Ponderable(); virtual Ponderable &operator=(const Ponderable &); void setMasses(double, double); void setMass(double, MassType); void incrementMass(double, MassType); void masses(double * = 0, double * = 0) const; double mass(MassType) const; void clearMasses(); void setMono(double); bool setMono(const QString &); void incrementMono(double); void decrementMono(double); double mono() const; double &rmono(); QString monoString(int decimalPlaces) const; void setAvg(double); bool setAvg(const QString &); void incrementAvg(double); void decrementAvg(double); double avg() const; double &ravg(); QString avgString(int decimalPlaces) const; virtual bool operator==(const Ponderable &) const; virtual bool operator!=(const Ponderable &) const; virtual bool calculateMasses(); virtual bool accountMasses(double * = 0, double * = 0, int = 1) const; protected: //! Monoisotopic mass. double m_mono; //! Average mass. double m_avg; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Prop.hpp000664 001750 001750 00000011271 14647465366 026056 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef PROP_HPP #define PROP_HPP /////////////////////// Qt includes #include #include #include /////////////////////// Local includes #include "exportimportconfig.h" #include "globals.hpp" namespace MsXpS { namespace libXpertMass { class PolChemDef; typedef std::shared_ptr PolChemDefCstSPtr; //////////////////////// Prop //////////////////////// //////////////////////// Prop //////////////////////// class DECLSPEC Prop { public: Prop(); Prop(const QString &name); Prop(const Prop &other); // Destructor. virtual ~Prop(); void setName(QString &); const QString &name(); void setData(void *data_p); virtual void *data() const; virtual void deleteData(); virtual Prop &operator=(const Prop &other); virtual Prop *cloneOut() const = 0; virtual bool renderXmlElement(const QDomElement &, int = 1); virtual QString *formatXmlElement(int, const QString & = QString(" ")); protected: QString m_name = "NOT_SET"; void *mpa_data = nullptr; }; class DECLSPEC StringProp : public Prop { public: // constructors StringProp(const QString & = QString(), const QString & = QString()); StringProp(const QString & = QString(), QString * = nullptr); StringProp(const StringProp &other); // Destructor. virtual ~StringProp(); virtual void deleteData(); using Prop::operator=; virtual StringProp &operator=(const StringProp &other); virtual StringProp *cloneOut() const; virtual bool renderXmlElement(const QDomElement &, int = 1); virtual QString *formatXmlElement(int, const QString & = QString(" ")); }; //////////////////////// IntProp //////////////////////// //////////////////////// IntProp //////////////////////// class DECLSPEC IntProp : public Prop { public: // constructors IntProp(const QString & = QString(), int = 0); IntProp(const IntProp &other); // Destructor. virtual ~IntProp(); virtual void deleteData(); using Prop::operator=; virtual IntProp &operator=(const IntProp &other); virtual IntProp *cloneOut() const; virtual bool renderXmlElement(const QDomElement &, int = 1); virtual QString *formatXmlElement(int, const QString & = QString(" ")); }; //////////////////////// DoubleProp //////////////////////// //////////////////////// DoubleProp //////////////////////// class DECLSPEC DoubleProp : public Prop { public: DoubleProp(const QString & = QString(), double = 0); DoubleProp(const DoubleProp &other); virtual ~DoubleProp(); virtual void deleteData(); using Prop::operator=; virtual DoubleProp &operator=(const DoubleProp &other); virtual DoubleProp *cloneOut() const; virtual bool renderXmlElement(const QDomElement &, int = 1); virtual QString *formatXmlElement(int, const QString & = QString(" ")); }; /////////////////// NoDeletePointerProp /////////////////// /////////////////// NoDeletePointerProp /////////////////// class DECLSPEC NoDeletePointerProp : public Prop { public: NoDeletePointerProp(const QString & = QString(), void * = 0); NoDeletePointerProp(const NoDeletePointerProp &other); virtual ~NoDeletePointerProp(); virtual void deleteData(); using Prop::operator=; virtual NoDeletePointerProp &operator=(const NoDeletePointerProp &other); virtual NoDeletePointerProp *cloneOut() const; virtual bool renderXmlElement(const QDomElement &, int = 1); virtual QString *formatXmlElement(int, const QString & = QString(" ")); }; } // namespace libXpertMass } // namespace MsXpS #endif // PROP_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/PropListHolder.hpp000664 001750 001750 00000004503 14647465366 030050 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes /////////////////////// Local includes #include "exportimportconfig.h" #include "Prop.hpp" namespace MsXpS { namespace libXpertMass { //! The PropList class provides a list of allocated Prop instances. /*! PropList provides a list of allocated Prop instances that it owns. This class is used to derive classes needing to store dynamically allocated Prop objects without knowing with anticipation how many of such objects are going to be used. */ class DECLSPEC PropListHolder { public: PropListHolder(); PropListHolder(const PropListHolder &other); virtual ~PropListHolder(); virtual PropListHolder &operator=(const PropListHolder &other); const QList &propList() const; QList &propList(); Prop *prop(const QString &, int * = 0); int propIndex(const QString &, Prop * = 0); int propListSize() const; bool appendProp(Prop *); bool removeProp(Prop *); bool removeProp(const QString &); protected: QList m_propList; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/Sequence.hpp000664 001750 001750 00000005422 14647465366 026707 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #ifndef SEQUENCE_HPP #define SEQUENCE_HPP /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" #include "Monomer.hpp" #include "Prop.hpp" namespace MsXpS { namespace libXpertMass { class DECLSPEC Sequence { public: Sequence(const QString & = QString()); Sequence(const Sequence &other); virtual ~Sequence(); Sequence &operator=(const Sequence &other); void setMonomerText(const QString &); void appendMonomerText(const QString &); virtual int makeMonomerText(); const QString *monomerText(); QString *monomerText(int, int, bool) const; QString *monomerText(const CoordinateList &, bool, bool) const; const QList &monomerList() const; QList *monomerListPtr(); int nextCode(QString *, int *, QString *, int); const Monomer *at(int) const; int monomerIndex(const Monomer *); int size() const; bool isInBound(int index); void unspacifyMonomerText(); int makeMonomerList(PolChemDefCstSPtr, bool = true, QList * = 0); bool insertMonomerAt(const Monomer *, int); virtual bool prepareMonomerRemoval(const Monomer *); virtual bool removeMonomerAt(int); int findForwardMotif(Sequence *, PolChemDefCstSPtr, int *); bool validate(PolChemDefCstSPtr); quint16 checksum(int = -1, int = -1, bool = false) const; protected: QString m_monomerText; QList m_monomerList; }; } // namespace libXpertMass } // namespace MsXpS #endif // SEQUENCE_HPP libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/exportimportconfig.h000664 001750 001750 00000000645 14650440350 030517 0ustar00rusconirusconi000000 000000 #pragma once #include namespace MsXpS { namespace libXpertMass { #if defined(EXPORT_LIB_SYMBOLS) // #pragma message("EXPORT_LIB_SYMBOLS is defined: defining DECLSPEC Q_DECL_EXPORT") #define DECLSPEC Q_DECL_EXPORT #else // #pragma message("EXPORT_LIB_SYMBOLS is not defined: defining DECLSPEC Q_DECL_IMPORT") #define DECLSPEC Q_DECL_IMPORT #endif } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/globals.hpp000664 001750 001750 00000015667 14650463475 026567 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once ////////////////////////////// Qt includes #include #include #include #include /////////////////////// Local includes #include "exportimportconfig.h" /* Bitwise stuff (from StackOverflow) It is sometimes worth using an enum to name the bits: enum ThingFlags = { ThingMask = 0x0000, ThingFlag0 = 1 << 0, ThingFlag1 = 1 << 1, ThingError = 1 << 8, } Then use the names later on. I.e. write thingstate |= ThingFlag1; thingstate &= ~ThingFlag0; if (thing & ThingError) {...} to set, clear and test. This way you hide the magic numbers from the rest of your code. */ namespace MsXpS { namespace libXpertMass { // FIXME Should go to massXpert2 //! Type of user that makes use of resources. /*! * When looking for resources, this enum variables allow defining if the * resources are to be searched in system directories or in the user $HOME * directory. */ enum UserType { USER_TYPE_USER = 0, //!< User is a logon user USER_TYPE_SYSTEM = 1 //!< User is the system }; enum MassType { MASS_NONE = 0x0000, //!< No mass MASS_MONO = 1 << 0, //!< Monoisotopic mass MASS_AVG = 1 << 1, //!< Average mass MASS_BOTH = (MASS_MONO | MASS_AVG) //!< Both masses }; enum MassToleranceType { MASS_TOLERANCE_NONE = 0, //!< parts per million MASS_TOLERANCE_PPM, //!< m/z ratio MASS_TOLERANCE_MZ, //!< atomic mass units MASS_TOLERANCE_AMU, //!< resolution, that is [1 / m/z] MASS_TOLERANCE_RES, MASS_TOLERANCE_LAST }; enum PolymerEnd { END_NONE = 0 << 1, END_LEFT = 1 << 1, END_RIGHT = 2 << 1, END_BOTH = (END_LEFT | END_RIGHT) }; enum SelectionType { SELECTION_TYPE_RESIDUAL_CHAINS, SELECTION_TYPE_OLIGOMERS }; enum CapType { CAP_NONE = 0 << 1, CAP_LEFT = 1 << 1, CAP_RIGHT = 2 << 1, CAP_BOTH = (CAP_LEFT | CAP_RIGHT) }; //! Monomer chemical entities. /*! The monomer chemical entities can be nothing or modifications. */ enum MonomerChemEnt { MONOMER_CHEMENT_NONE = 0 << 1, MONOMER_CHEMENT_MODIF = 1 << 1, MONOMER_CHEMENT_CROSS_LINK = 1 << 2 }; //! Polymer chemical entities. /*! The polymer chemical entities can be nothing or left end * modification or right end modification or both ends modifications. */ enum PolymerChemEnt { POLYMER_CHEMENT_NONE = 1 << 0, POLYMER_CHEMENT_LEFT_END_MODIF = 1 << 1, POLYMER_CHEMENT_FORCE_LEFT_END_MODIF = 1 << 2, POLYMER_CHEMENT_RIGHT_END_MODIF = 1 << 3, POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF = 1 << 4, POLYMER_CHEMENT_BOTH_END_MODIF = (POLYMER_CHEMENT_LEFT_END_MODIF | POLYMER_CHEMENT_RIGHT_END_MODIF), POLYMER_CHEMENT_FORCE_BOTH_END_MODIF = (POLYMER_CHEMENT_FORCE_LEFT_END_MODIF | POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF) }; enum HashAccountData { HASH_ACCOUNT_SEQUENCE = 1 << 0, HASH_ACCOUNT_MONOMER_MODIF = 1 << 1, HASH_ACCOUNT_POLYMER_MODIF = 1 << 2 }; // This regexp extracts a formula from a larger action formula containing one // or more subformulas. For example it would extract from the // "+C12.1-H0.9Na2-N0.5" formula the following subformulas: +C12.1 and -H0.9 // and Na2 and -N0.5. This regular expression should be used with the // globalMatch() feature of QRegularExpression. extern QRegularExpression subFormulaRegExp; //! Regular expression that tracks the end of line in text files. extern QRegularExpression gXyFormatMassDataRegExp; //! Regular expression that tracks the m/z,i pairs in text files. extern QRegularExpression gEndOfLineRegExp; extern QMap massToleranceTypeMap; extern DECLSPEC int ATOM_DEC_PLACES; extern DECLSPEC int OLIGOMER_DEC_PLACES; extern DECLSPEC int POLYMER_DEC_PLACES; extern DECLSPEC int PH_PKA_DEC_PLACES; extern DECLSPEC QRegularExpression gXyFormatMassDataRegExp; extern DECLSPEC QRegularExpression gEndOfLineRegExp; DECLSPEC void doubleListStatistics(QList list, double *sum, double *average, double *variance, double *stdDeviation, double *nonZeroSmallest, double *smallest, double *greatest, double *median); DECLSPEC void doubleVectorStatistics(std::vector &vector, double *sum, double *average, double *variance, double *stdDeviation, double *nonZeroSmallest, double *smallest, double *greatest, double *median); DECLSPEC bool almostEqual(double value1, double value2, int decimalPlaces = 10); DECLSPEC QTextStream &qStdOut(); DECLSPEC QString &unspacifyString(QString &text); DECLSPEC QString binaryRepresentation(int value); DECLSPEC QString elideText(const QString &text, int charsLeft = 4, int charsRight = 4, const QString &delimitor = "..."); DECLSPEC QString stanzify(const QString &text, int width); DECLSPEC QString stanzifyParagraphs(const QString &text, int width); DECLSPEC int countZeroDecimals(double value); DECLSPEC double ppm(double value, double ppm); DECLSPEC double addPpm(double value, double ppm); DECLSPEC double removePpm(double value, double ppm); DECLSPEC double res(double value, double res); DECLSPEC double addRes(double value, double res); DECLSPEC double removeRes(double value, double res); DECLSPEC QString pointerAsString(void *pointer); DECLSPEC QString configSettingsFilePath(const QString &module_name); } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/includes/libXpertMass/ModifSpec.hpp000664 001750 001750 00000004035 14650475310 026767 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMass { enum CompositingType { OPAQUE = 1 << 0, TRANSPARENT = 1 << 1 }; class DECLSPEC ModifSpec { public: ModifSpec(); virtual ~ModifSpec(); void setName(const QString &); const QString &name(); void setAction(int); int action(); void setRaster(const QString &); const QString &raster(); void setVector(const QString &); const QString &vector(); void setSound(const QString &); const QString &sound(); static bool parseFile(QString &, QList *); private: QString m_name; int m_action; QString m_raster; QString m_vector; QString m_sound; }; } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMass/ModifSpec.cpp000664 001750 001750 00000020224 14650460711 022534 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2018 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Qt includes #include #include #include #include /////////////////////// Local includes #include "ModifSpec.hpp" namespace MsXpS { namespace libXpertMass { /*! \class MsXpS::libXpertMass::ModifSpec \inmodule libXpertMass \ingroup PolChemDefBuildingdBlocks \inheaderfile ModifSpec.hpp \brief The ModifSpec class provides the specification about how \l Modif objects are represented. The ModifSpec class specifies how a \l Modif object is represented graphically, mainly by connecting its modif name to a graphics SVG file that is located in the Polymer chemistry definition directory. That connection is performed in the "modification_dictionary" dictionary file itself also located in the polymer chemistry definition directory. Its contents look like this: \code Phosphorylation%T%phospho.svg Sulphation%T%sulpho.svg AmidationAsp%O%asparagine.svg Acetylation%T%acetyl.svg AmidationGlu%O%glutamine.svg Oxidation%T%oxidation.svg SulfideBond%T%sulfbond.svg ProtonLoss%T%protonloss.svg \endcode The \c{Phosphorylation%T%phospho.svg} line indicates that, when a Monomer object modified by a Modif object by name "Phosphorylation" is to be rendered graphically, the corresponding vignette to be used is in the file named "phospho.svg" in the polymer chemistry definition directory. The 'T' and 'O' special directives, indicate the way the modification is to be represented graphically in the sequence editor. When a monomer is modified, the modification event can be shown by overlaying onto the monomer's vignette a transparent vignette or by replacing it totally with a new one. These two behaviours are documented above with the letters 'T' (transparent overlay) or 'O' (opaque overwrite). */ /*! \variable int MsXpS::libXpertMass::ModifSpec::m_name \brief The name of the modification. */ /*! \variable int MsXpS::libXpertMass::ModifSpec::m_action \brief The graphics action (Opaque, that is, overwrite ; Transparent, that is, overlay) of the modification. */ /*! \variable int MsXpS::libXpertMass::ModifSpec::m_raster \brief The file name of the raster representation of the modification. */ /*! \variable int MsXpS::libXpertMass::ModifSpec::m_vector \brief The filename of the vector representation of the modification. */ /*! \variable int MsXpS::libXpertMass::ModifSpec::m_sound \brief The file name of the sound for the name of the modification. */ /*! \brief Constructs a ModifSpec instance. */ ModifSpec::ModifSpec() { m_action = 0; } /*! \brief Destructs this ModifSpec instance. */ ModifSpec::~ModifSpec() { } /*! \brief Sets the monomer \a name. */ void ModifSpec::setName(const QString &name) { m_name = name; } /*! \brief Returns the monomer name. */ const QString & ModifSpec::name() { return m_name; } /*! \brief Sets the \a action. The action is the way the Modif's vignette applies itself on top of the \l{Monomer}'s vignette: either in an Opaque manner, thus overwriting the initial vignette, or in a Transparent manner, thus only partially overlaying the initial vignette. */ void ModifSpec::setAction(int action) { m_action = action; } /*! \brief Returns the action. \sa setAction() */ int ModifSpec::action() { return m_action; } /*! \brief Sets the raster image file name to \a raster. */ void ModifSpec::setRaster(const QString &raster) { m_raster = raster; } /*! \brief Returns the raster image file name. */ const QString & ModifSpec::raster() { return m_raster; } /*! \brief Sets the vector image file name to \a vector. */ void ModifSpec::setVector(const QString &vector) { m_vector = vector; } /*! \brief Returns the vector image file name. */ const QString & ModifSpec::vector() { return m_vector; } /*! \brief Sets the file name of the Modif's \a sound file. */ void ModifSpec::setSound(const QString &sound) { m_sound = sound; } /*! \brief Returns the file name of the Modif's sound file. */ const QString & ModifSpec::sound() { return m_sound; } /*! \brief Parses the \a file_path dictionary containing the Modif specifications. At the moment the file has this format: \code Phosphorylation%T%phospho.svg Sulphation%T%sulpho.svg AmidationAsp%O%asparagine.svg Acetylation%T%acetyl.svg AmidationGlu%O%glutamine.svg Oxidation%T%oxidation.svg \endcode Upon parsing, the \a modif_spec_list of ModifSpec instances will be filled with instances created on the basis of each parsed line in the file. Returns true if the parsing was successful, false otherwise. */ bool ModifSpec::parseFile(QString &file_path, QList *modif_spec_list) { ModifSpec *modifSpec = 0; qint64 lineLength; QString line; QString temp; char buffer[1024]; Q_ASSERT(modif_spec_list != 0); if(file_path.isEmpty()) return false; QFile file(file_path); if(!file.open(QFile::ReadOnly)) return false; // The lines we have to parse are of the following type: // Phosphorylation%T%phospho.svg // Any line starting with '#' are not parsed. // Get the first line of the file. Next we enter in to a // while loop. lineLength = file.readLine(buffer, sizeof(buffer)); while(lineLength != -1) { // The line is now in buffer, and we want to convert // it to Unicode by setting it in a QString. line = buffer; // The line that is in line should contain something like: // Phosphorylation%T%phospho.svg // Remove all the spaces from the borders: Whitespace means any // character for which QChar::isSpace() returns true. This // includes the ASCII characters '\t', '\n', '\v', '\f', '\r', // and ' '. line = line.trimmed(); if(line.isEmpty() || line.startsWith('#', Qt::CaseInsensitive)) { lineLength = file.readLine(buffer, sizeof(buffer)); continue; } qDebug() << "Reading line:" << line; // Now some other checks. Remember the format of the line: // Phosphorylation%T%phospho.svg QString modif_name; QString file_name; QString opacity; QRegularExpression reg_exp(QString("^(.*)%([TO])%(.*)$")); QRegularExpressionMatch match = reg_exp.match(line); if(match.hasMatch()) { modif_name = match.captured(1); opacity = match.captured(2); file_name = match.captured(3); } else return false; // qDebug() << "modif_name:" << modif_name << "opacity:" << opacity // << "file_name:" << file_name; // OK, we finally can allocate a new ModifSpec *. modifSpec = new ModifSpec(); modifSpec->m_name = modif_name; modifSpec->m_vector = file_name; if(opacity == "O") modifSpec->m_action = CompositingType::OPAQUE; else if(opacity == "T") modifSpec->m_action = CompositingType::TRANSPARENT; else qFatal("Programming error."); modif_spec_list->append(modifSpec); lineLength = file.readLine(buffer, sizeof(buffer)); } return true; } } // namespace libXpertMass } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/000775 001750 001750 00000000000 14651507337 020633 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/src/XpertMassGui/CMakeLists.txt000664 001750 001750 00000012146 14650430447 023373 0ustar00rusconirusconi000000 000000 set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES /usr/include) set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES /usr/include) # Whatever the platform, this always works. find_package(Qt6 COMPONENTS Widgets Core Gui PrintSupport Svg Xml Network REQUIRED) message("\n${BoldGreen}Now configuring library libXpertMassGui${ColourReset}\n") ######################################################## # Files set(XpertMassGui_SRCS # ColorSelector.cpp IsotopicClusterGeneratorDlg.cpp IsotopicClusterShaperDlg.cpp IsotopicDataTableView.cpp IsotopicDataTableViewModel.cpp MassDataClientServerConfigDlg.cpp MassPeakShaperConfigDlg.cpp MassPeakShaperConfigWidget.cpp) set(XpertMassGui_UIS ui/IsotopicClusterGeneratorDlg.ui ui/MassPeakShaperConfigDlg.ui ui/IsotopicClusterShaperDlg.ui ui/ElementGroupBoxWidget.ui ui/MassDataClientServerConfigDlg.ui ui/MassPeakShaperConfigWidget.ui ) qt6_wrap_ui(XpertMassGui_UIS_H ${XpertMassGui_UIS}) qt6_add_resources(XpertMassGui_QRC_CPP ressources.qrc) # Because the header files are in their own directory and not along the source # files, we need to have them listed explicitely for automoc to work properly # below. # Create a variable to hold the 'includes' directory *relative* to the # current CMakeLists.txt file. set(INCLUDES_DIR "${CMAKE_CURRENT_LIST_DIR}/includes/libXpertMassGui") # Use the directory as is (see below, will be modified) # to create the list of header files. file(GLOB XpertMassGui_HEADERS ${INCLUDES_DIR}/*.hpp) list(APPEND XpertMassGui_HEADERS ${INCLUDES_DIR}/exportimportconfig.h) message(STATUS "Included the header files from ${INCLUDES_DIR}: \n\ ${XpertMassGui_HEADERS}") # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) ############################################################### # Configuration of the binary to be built ############################################################### # Only now can we add the library, because we have defined the sources. ############################################################# # Build the static lib add_library(Gui_static STATIC ${XpertMassGui_HEADERS} ${XpertMassGui_SRCS} ${XpertMassGui_UIS_H} ${XpertMassGui_QRC_CPP} ${PLATFORM_SPECIFIC_SOURCES}) set_target_properties(Gui_static PROPERTIES OUTPUT_NAME XpertMassGui LINK_FLAGS "-Wl,--whole-archive") target_link_libraries(Gui_static PappsoMSpp::Core IsoSpec++::IsoSpec++ Qt6::Core Qt6::Gui Qt6::Widgets Qt6::PrintSupport Qt6::Svg Qt6::Xml Qt6::Network Core_static) # The install interface that is ${prefix}/include target_include_directories(Gui_static PUBLIC $) # These include directories are for building of this lib. target_include_directories(Gui_static PUBLIC $) target_include_directories(Gui_static PUBLIC $) target_include_directories(Gui_static PUBLIC $) get_target_property(Gui_static_INCLUDES Gui_static INCLUDE_DIRECTORIES) message(STATUS "Gui_static_INCLUDES: \ ${Gui_static_INCLUDES}") install(TARGETS Gui_static ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) ############################################################# # Build the shared lib add_library(Gui SHARED ${XpertMassGui_HEADERS} ${XpertMassGui_SRCS} ${XpertMassGui_UIS_H} ${${TARGET}_QRC_CPP} ${PLATFORM_SPECIFIC_SOURCES}) set_target_properties(Gui PROPERTIES OUTPUT_NAME XpertMassGui VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} LINK_FLAGS "-Wl,--no-as-needed" ) target_link_libraries(Gui PappsoMSpp::Core IsoSpec++::IsoSpec++ Qt6::Core Qt6::Gui Qt6::Widgets Qt6::PrintSupport Qt6::Svg Qt6::Xml Qt6::Network Core) # The install interface that is ${prefix}/include # These include directories are for users of this lib. target_include_directories(Gui PUBLIC $) # These include directories are for building of this lib. target_include_directories(Gui PUBLIC $) target_include_directories(Gui PUBLIC $) target_include_directories(Gui PUBLIC $) get_target_property(Gui_INCLUDES Gui INCLUDE_DIRECTORIES) message(STATUS "Gui_INCLUDES: \ ${Gui_INCLUDES}") target_compile_definitions(Gui PRIVATE "EXPORT_LIB_SYMBOLS") # This is to avoid the "include_next(math.h) file not found" error. if(WIN32) set_target_properties(Gui_static PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) set_target_properties(Gui PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) endif() ############################################################# # Common installation configuration install(FILES ${XpertMassGui_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libXpertMassGui") # See parent directory's list file. message(STATUS "CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}") message("") message(STATUS "${BoldGreen}Finished configuration of libXpertMassGui.${ColourReset}") message("") libxpertmass-1.1.0/src/XpertMassGui/IsotopicClusterGeneratorDlg.cpp000664 001750 001750 00000170651 14650445760 027002 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Std lib includes #include #include /////////////////////// Qt includes #include #include #include #include #include #include /////////////////////// IsoSpec #include #include // extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const int // elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; // extern const double // elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; /////////////////////// PAPPSO includes /////////////////////// libmass includes #include "libXpertMass/globals.hpp" #include "libXpertMass/Isotope.hpp" #include "libXpertMass/IsotopicData.hpp" #include "libXpertMass/IsotopicDataBaseHandler.hpp" #include "libXpertMass/IsotopicDataLibraryHandler.hpp" #include "libXpertMass/MassDataCborBaseHandler.hpp" #include "libXpertMass/IsotopicDataUserConfigHandler.hpp" #include "libXpertMass/IsotopicDataManualConfigHandler.hpp" #include "libXpertMass/PeakCentroid.hpp" #include "libXpertMass/IsotopicClusterGenerator.hpp" #include "libXpertMass/MassDataCborBaseHandler.hpp" /////////////////////// Local includes #include "IsotopicDataTableViewModel.hpp" #include "IsotopicClusterGeneratorDlg.hpp" #include "ColorSelector.hpp" #include "IsotopicClusterGeneratorDlg.hpp" #include "IsotopicClusterShaperDlg.hpp" #include "ui_IsotopicClusterGeneratorDlg.h" // For the element data groupbox widgets that are packed dynamically #include "ui_ElementGroupBoxWidget.h" namespace MsXpS { namespace libXpertMassGui { /*! \class MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg \inmodule libXpertMassGui \ingroup XpertMassGuiMassCalculations \inheaderfile IsotopicClusterGeneratorDlg.hpp \brief The IsotopicClusterGeneratorDlg class provides a graphical user interface for the generation of an isotopic cluster. The isotopic cluster is generated starting from a Formula. */ /*! \variable IsotopicClusterGeneratorDlg::mp_ui \brief The graphical user interface definition. */ /*! \variable IsotopicClusterGeneratorDlg::m_applicationName \brief The name of the application. */ /*! \variable IsotopicClusterGeneratorDlg::m_windowDescription \brief The text to be used for the window title. */ /*! \variable IsotopicClusterGeneratorDlg::m_formula \brief The formula that is used as the starting point for the calculations. The formula holds the elemental composition of the analyte for which the calculation should be performed. It must account for the chemical agent responsible for its ionization. */ /*! \variable IsotopicClusterGeneratorDlg::mp_programWindow \brief The main program window. */ /*! \variable IsotopicClusterGeneratorDlg::m_normalizeIntensity \brief The intensity value that the most intense peak in the cluster should have. All the other peaks intensities are normalized against this value. */ /*! \variable IsotopicClusterGeneratorDlg::m_maxSummedProbability \brief The sum of all the intensities of the cluster peaks should not exceed this value. By default, the value is 0.95. */ /*! \variable IsotopicClusterGeneratorDlg::m_charge \brief The charge of the analyte for which the calculations are performed. */ /*! \variable IsotopicClusterGeneratorDlg::m_sortType \brief The type of sorting to use to sort the obtained isotopic cluster's peak centroids. */ /*! \variable IsotopicClusterGeneratorDlg::m_sortOrder \brief The order for the sorting (if any) of the obtained isotopic cluster's peak centroids. */ /*! \variable IsotopicClusterGeneratorDlg::msp_isotopicDataLibrary \brief The isotopic data from the IsoSpec library tables. */ /*! \variable IsotopicClusterGeneratorDlg::msp_isotopicDataUserConfig \brief The isotopic data from the IsoSpec-like library tables as modified by the user. */ /*! \variable IsotopicClusterGeneratorDlg::msp_isotopicDataUserManualConfig \brief The isotopic data from the user's manual configuration. */ /*! \variable IsotopicClusterGeneratorDlg::mpa_libraryStaticTableViewModel \brief The table view model that manages the library static IsoSpec standard data. */ /*! \variable IsotopicClusterGeneratorDlg::mpa_userStaticTableViewModel \brief The table view model that manages the library user-modified IsoSpec standard data. */ /*! \variable IsotopicClusterGeneratorDlg::m_colorByteArray \brief The color that should be used for displaying the mass spectrum when the isotopic cluster is later shaped into a mass spectrum. */ /*! \variable IsotopicClusterGeneratorDlg::m_clusterGenerator \brief The isotopic cluster generator that will be used for the computations. */ /*! \brief Constructs a IsotopicClusterGeneratorDlg instance. \list \li \a program_window_p: the program's main window. \li \a applicationName: the name of the application, typically massXpert2 or mineXpert2, for example. \li \a description: the string describing what this dialog window is for (used for the window title). \endlist */ IsotopicClusterGeneratorDlg::IsotopicClusterGeneratorDlg( QWidget *program_window_p, const QString &applicationName, const QString &description) : QDialog(program_window_p), mp_ui(new Ui::IsotopicClusterGeneratorDlg), m_applicationName{applicationName}, m_windowDescription(description), mp_programWindow(program_window_p) { if(!program_window_p) qFatal("Programming error."); mp_ui->setupUi(this); // We want to destroy the dialog when it is closed. setAttribute(Qt::WA_DeleteOnClose); // Right on creation, we need to allocate the isotopic data objects. We will // use the proper isotopic data handler depending on the context (library, // user config or manual config). msp_isotopicDataLibrary = std::make_shared(); msp_isotopicDataUserConfig = std::make_shared(); msp_isotopicDataUserManualConfig = std::make_shared(); setupDialog(); // Update the window title because the window title element in mp_ui->might be // either erroneous or empty. setWindowTitle(QString("%1 - %2").arg(applicationName).arg(description)); } /*! \brief Upon closing of the dialog window (unused \a event), writes the settings to the application configuration file. */ void IsotopicClusterGeneratorDlg::closeEvent([[maybe_unused]] QCloseEvent *event) { writeSettings(libXpertMass::configSettingsFilePath(m_applicationName)); } /*! \brief Destructs this IsotopicClusterGeneratorDlg instance. */ IsotopicClusterGeneratorDlg::~IsotopicClusterGeneratorDlg() { writeSettings(libXpertMass::configSettingsFilePath(m_applicationName)); delete mp_ui; } /*! \brief Saves the settings of this dialog window to the application configuration file (\a config_settings_file_path). The saved configuration is read from the file to set back the dialog window in the same status. */ void IsotopicClusterGeneratorDlg::writeSettings( const QString &config_settings_file_path) { QSettings settings(config_settings_file_path, QSettings::IniFormat); settings.beginGroup("IsotopicClusterGeneratorDlg"); settings.setValue("geometry", saveGeometry()); settings.setValue("splitter", mp_ui->splitter->saveState()); // Write all the formulas that sit in the combo box settings.beginWriteArray("libraryformulas"); int count = mp_ui->libraryFormulaComboBox->count(); for(int iter = 0; iter < count; ++iter) { settings.setArrayIndex(iter); QString index; index.setNum(iter); settings.setValue(index, mp_ui->libraryFormulaComboBox->itemText(iter)); } settings.endArray(); // Done writing all the formulas. // Write all the formulas that sit in the combo box settings.beginWriteArray("userconfigformulas"); count = mp_ui->userConfigFormulaComboBox->count(); for(int iter = 0; iter < count; ++iter) { settings.setArrayIndex(iter); QString index; index.setNum(iter); settings.setValue(index, mp_ui->userConfigFormulaComboBox->itemText(iter)); } settings.endArray(); // Done writing all the formulas. settings.endGroup(); } /*! \brief Reads the settings of this dialog window from the application configuration file (\a config_settings_file_path). The configuration is read from the file to set back the dialog window in the same status. */ void IsotopicClusterGeneratorDlg::readSettings(const QString &config_settings_file_path) { QSettings settings(config_settings_file_path, QSettings::IniFormat); settings.beginGroup("IsotopicClusterGeneratorDlg"); restoreGeometry(settings.value("geometry").toByteArray()); mp_ui->splitter->restoreState(settings.value("splitter").toByteArray()); int count = settings.beginReadArray("libraryformulas"); for(int iter = 0; iter < count; ++iter) { settings.setArrayIndex(iter); QString index; index.setNum(iter); QString value = settings.value(index).toString(); mp_ui->libraryFormulaComboBox->insertItem(iter, value); } settings.endArray(); count = settings.beginReadArray("userconfigformulas"); for(int iter = 0; iter < count; ++iter) { settings.setArrayIndex(iter); QString index; index.setNum(iter); QString value = settings.value(index).toString(); mp_ui->userConfigFormulaComboBox->insertItem(iter, value); } settings.endArray(); settings.endGroup(); } /*! \brief Sets up the dialog window. Returns true upon success, or false if the IsoSpec-based isotopic data could not be loaded. */ bool IsotopicClusterGeneratorDlg::setupDialog() { mp_ui->ionChargeSpinBox->setRange(1, 10000); mp_ui->ionChargeSpinBox->setValue(1); readSettings(libXpertMass::configSettingsFilePath(m_applicationName)); ///////////////// Connections for the different contexts ///////////////// /////////////////// Library isotopic data /////////////////// Library isotopic data // There is no load function because that is automatically performed when // setting up the dialog. connect(mp_ui->saveLibraryPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::saveLibrary); connect(mp_ui->runLibraryPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::runLibrary); connect(mp_ui->addLibraryFormulaPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::addLibraryFormula); connect(mp_ui->removeLibraryFormulaPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::removeLibraryFormula); connect(mp_ui->addUserConfigFormulaPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::addUserConfigFormula); connect(mp_ui->removeUserConfigFormulaPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::removeUserConfigFormula); /////////////////// User config isotopic data /////////////////// User config isotopic data connect(mp_ui->loadUserConfigPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::loadUserConfig); connect(mp_ui->saveUserConfigPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::saveUserConfig); connect(mp_ui->runUserConfigPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::runUserConfig); /////////////////// User manual config isotopic data /////////////////// User manual config isotopic data connect(mp_ui->loadUserManualConfigPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::loadUserManualConfig); connect(mp_ui->saveUserManualConfigPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::saveUserManualConfig); connect(mp_ui->runUserManualConfigPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::runUserManualConfig); ////////////////////// Peak shaping features ///////////////////// connect(mp_ui->toIsotopicClusterShaperPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::toIsotopicClusterShaper); /////////////////// Manual config isotopic data helpers //////////////////// connect(mp_ui->addElementPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::addElementGroupBox); // The color button connect(mp_ui->colorSelectorPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::traceColorPushButtonClicked); // We systematically load the library isotopic data using the dedicated // handler. libXpertMass::IsotopicDataLibraryHandler isotopic_data_handler( msp_isotopicDataLibrary); std::size_t count = isotopic_data_handler.loadData(); if(!count) { message("Failed to load IsoSpec entities from the C++ headers", 5000); qDebug() << "Failed to load IsoSpec entities from the C++ headers."; return false; } // qDebug() << "Loaded isotopic data with" << msp_isotopicDataLibrary->size() //<< "isotopes."; // At dialog creation time, we only load the IsoSpec standard static // element/isotope configuration from the library headers. While loading // these data we populate the model and thus the table view. setupIsoSpecStandardStaticTableView(); return true; } /*! \brief Sets up the IsoSpec tables model and table view. */ void IsotopicClusterGeneratorDlg::setupIsoSpecStandardStaticTableView() { // qDebug(); // Now we can link the data to the table view model. mpa_libraryStaticTableViewModel = new IsotopicDataTableViewModel(this, msp_isotopicDataLibrary); mp_ui->libraryStaticIsotopicDataTableView->setModel( mpa_libraryStaticTableViewModel); mp_ui->libraryStaticIsotopicDataTableView->setIsotopicData( msp_isotopicDataLibrary); mp_ui->libraryStaticIsotopicDataTableView->setParent(this); mpa_libraryStaticTableViewModel->setTableView( mp_ui->libraryStaticIsotopicDataTableView); } /*! \brief Sets up the user-configured IsoSpec-like tables model and table view. */ void IsotopicClusterGeneratorDlg::setupIsoSpecStandardUserTableView() { // qDebug(); // Now we can link the data to the table view model. mpa_userStaticTableViewModel = new IsotopicDataTableViewModel(this, msp_isotopicDataUserConfig); mp_ui->userStaticIsotopicDataTableView->setModel(mpa_userStaticTableViewModel); mp_ui->userStaticIsotopicDataTableView->setIsotopicData( msp_isotopicDataUserConfig); mp_ui->userStaticIsotopicDataTableView->setParent(this); mpa_userStaticTableViewModel->setTableView( mp_ui->userStaticIsotopicDataTableView); } /*! \brief Loads the user-configured IsoSpec-like data. Returns true if successful, false otherwise. */ bool IsotopicClusterGeneratorDlg::loadUserConfig() { QString file_name = QFileDialog::getOpenFileName( this, tr("Load User IsoSpec table"), QDir::home().absolutePath()); if(file_name.isEmpty()) { message("The file name to read from is empty!", 5000); return false; } QFile file(file_name); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { message("Failed to open file for reading.", 5000); return false; } libXpertMass::IsotopicDataUserConfigHandler isotopic_data_handler( msp_isotopicDataUserConfig); if(!isotopic_data_handler.loadData(file_name)) { message("Failed to load the isotopic data from the file.", 5000); file.close(); return false; } // Also setup the isospec standard *user* table view, even if that the // beginning it is empty. The user might load user tables from file. setupIsoSpecStandardUserTableView(); file.close(); return true; } /*! \brief Returns true if the IsoSpec isotopic data could be written to file, false otherwise. */ bool IsotopicClusterGeneratorDlg::saveLibrary() { QFileDialog fileDlg(this); fileDlg.setFileMode(QFileDialog::AnyFile); fileDlg.setAcceptMode(QFileDialog::AcceptSave); QString file_name = fileDlg.getSaveFileName( this, tr("Save table"), QDir::home().absolutePath()); if(file_name.isEmpty()) { message("Please provide a file name.", 5000); return false; } libXpertMass::IsotopicDataLibraryHandler isotopic_data_handler( msp_isotopicDataLibrary); return isotopic_data_handler.writeData(file_name); } /*! \brief Returns true if the IsoSpec-like user-modified isotopic data could be written to file, false otherwise. */ bool IsotopicClusterGeneratorDlg::saveUserConfig() { QFileDialog fileDlg(this); fileDlg.setFileMode(QFileDialog::AnyFile); fileDlg.setAcceptMode(QFileDialog::AcceptSave); QString file_name = fileDlg.getSaveFileName( this, tr("Save table"), QDir::home().absolutePath()); if(file_name.isEmpty()) { message("Please provide a file name.", 5000); return false; } libXpertMass::IsotopicDataUserConfigHandler isotopic_data_handler( msp_isotopicDataUserConfig); return isotopic_data_handler.writeData(file_name); } /*! \brief Reacts to a click onto the push button. Allows selecting a color for the mass spectrum trace to be created later on the basis of the isotopic cluster calculated here. */ void IsotopicClusterGeneratorDlg::traceColorPushButtonClicked() { QPushButton *colored_push_button = mp_ui->colorSelectorPushButton; // Allow (true) the user to select a color that has been chosen already. QColor color = ColorSelector::chooseColor(true); if(color.isValid()) { QPalette palette = colored_push_button->palette(); palette.setColor(QPalette::Button, color); colored_push_button->setAutoFillBackground(true); colored_push_button->setPalette(palette); colored_push_button->update(); // Now prepare the color in the form of a QByteArray QDataStream stream(&m_colorByteArray, QIODevice::WriteOnly); stream << color; } } /*! \brief Copies the results of the computation to an \l IsotopicClusterShaperDlg instance. */ void IsotopicClusterGeneratorDlg::toIsotopicClusterShaper() { // The calculation result is set into a vector that might contain more than // one pair (charge,cluster). In the present case, the dialog window allows // for only one elemental composition to perform the calculation, so we just // need to take the first (and in fact there must be only one) charge,cluster // pair from the vector. We get the trace and we sent that to the peak shaper // dialog. We also want to set the color that might have been set by the user // for plotting the trace. const std::vector &isotopic_cluster_charge_pairs = m_clusterGenerator.getIsotopicClusterChargePairs(); if(!isotopic_cluster_charge_pairs.size()) return; // In the dialog window, only one formula is handled at a time. assert(isotopic_cluster_charge_pairs.size() == 1); // Get the isotopic cluster as a Trace out of the pair. pappso::TraceCstSPtr isotopic_cluster_sp = isotopic_cluster_charge_pairs.front().first; // Make sure we pass the ProgramWindow pointer as the parent object of the // PeakShaperDlg, otherwise crash when using ConsoleWnd. IsotopicClusterShaperDlg *isotopic_cluster_shaper_dlg_p = new IsotopicClusterShaperDlg(mp_programWindow, m_applicationName, "Isotopic cluster shaper", isotopic_cluster_sp, m_normalizeIntensity); isotopic_cluster_shaper_dlg_p->setColorByteArray(m_colorByteArray); isotopic_cluster_shaper_dlg_p->setMassSpectrumTitle( mp_ui->massSpectrumTitleLineEdit->text()); // In that shaper dialog, when the computation has finished, the user may ask // to display the spectrum. So the dialog window emits the signal below. We // want to relay that signal to the other listeners, typically the program // window, so that they can take action. If the program window is of // massXpert, they'll have to craft a CBOR-encoded packet and serve it so that // any listening program can display it (typically mineXpert). If that program // window is of mineXpert, then simply display the mass spectrum in the mass // spectra window... connect(isotopic_cluster_shaper_dlg_p, &IsotopicClusterShaperDlg::displayMassSpectrumSignal, [this](const QString &title, const QByteArray &color_byte_array, pappso::TraceCstSPtr trace) { emit displayMassSpectrumSignal(title, color_byte_array, trace); }); isotopic_cluster_shaper_dlg_p->show(); isotopic_cluster_shaper_dlg_p->raise(); isotopic_cluster_shaper_dlg_p->activateWindow(); } /*! \brief Validates the user manual isotopic data configuration using the \a config_handler configuration handler. The manual configuration is performed by entering by hand the chemical elements (as a symbol) and their isotope (mass,prob) pairs. The element is defined using the symbol. The count of the element in the formula is set in a spin box. Validating the manual configuration means iterating in all the widgets that have been created by the user, extracting from them all the isotopic data. Returns the count of elements in the configuration. */ std::size_t IsotopicClusterGeneratorDlg::validateManualConfig( libXpertMass::IsotopicDataManualConfigHandler &config_handler) { // Make clean room. msp_isotopicDataUserManualConfig->clear(); // Each element (symbol, count, isotopes) is packed in a group box. First // get the list of all the QGroupBox *elementGroupBox; that are packed in the // mp_ui->scrollAreaWidgetContents std::size_t element_count; QList elementGroupBoxList = mp_ui->scrollAreaWidgetContents->findChildren("elementGroupBox"); element_count = elementGroupBoxList.size(); // qDebug() << "The number of elements manually configured is:" << // element_count; if(!element_count) { message("There is currently not a single element defined.", 5000); return 0; } // This loop iterates through the element group boxes, each containing all the // other widgets that are required to define: // - the element symbol // - the count of such elements in the final formula // - the isotopes (listing mass/prob for each isotope) // Vector to collect the isotopes for a given element symbol. Will be cleared // at each new element-symbol/isotopes addition to the isotopic data. std::vector element_isotopes; // This set is to ensure that we do not have twice the same element frame // (that is, with the same symbol). std::set symbol_set; // This loop iterates through all the element frames that contain the element // symbol and count pair. for(std::size_t iter = 0; iter < element_count; ++iter) { // qDebug() << "Iterating in element group box index" << iter; // Now starting a new element group box (symbol, count, istopes). QGroupBox *currentGroupBox = elementGroupBoxList.at(iter); QLineEdit *symbolLineEdit = currentGroupBox->findChild("symbolLineEdit"); if(symbolLineEdit == nullptr) qFatal("Programming error."); QString symbol = symbolLineEdit->text(); // qDebug() << "The current symbol is" << symbol; // Now check if that symbol was encountered already, which would be an // error. auto res = symbol_set.insert(symbol); if(!res.second) { // We did not insert the symbol because one already existed. That is // an error. message(QString("An element by symbol %1 has already been processed: " "not permitted.") .arg(symbol), 5000); return 0; } // Get the atom count (the index of an element in a formula) int count = currentGroupBox->findChild("atomCountSpinBox")->value(); if(!count) { message(QString("The element by symbol %1 has a count of 0: " "not permitted.") .arg(symbol), 5000); return 0; } // Now handle the isotopic mass/abundance specifications. // // The (mass,prob) isotope pairs are packed in individual isotopeFrame // widgets. // Let's iterate in these frame widgets to extract in turn all the // (mass,prob) pairs for the current element. QList isotopeFrameList = currentGroupBox->findChildren("isotopeFrame"); // qDebug() << "The number of isotopes configured for current element is:" //<< isotopeFrameList.size(); if(!isotopeFrameList.size()) { message(QString("The element by symbol %1 has no isotope defined: " "not permitted.") .arg(symbol), 4000); return 0; } // This loop iterates through all the frames that contain isotope // specifications for a given element symbol-count pair. for(int jter = 0; jter < isotopeFrameList.size(); ++jter) { QFrame *currentFrame = isotopeFrameList.at(jter); QDoubleSpinBox *massSpinBox = currentFrame->findChild( "isotopeMassDoubleSpinBox"); if(massSpinBox == nullptr) qFatal("Programming error."); double mass = massSpinBox->value(); QDoubleSpinBox *probSpinBox = currentFrame->findChild( "isotopeProbDoubleSpinBox"); if(probSpinBox == nullptr) qFatal("Programming error."); double prob = probSpinBox->value(); if(!prob) { message( QString( "The element by symbol %1 has a naught-probability isotope: " "not permitted.") .arg(symbol), 5000); return 0; } // qDebug() << "Iterated in isotope:" << mass << "/" << prob; // At this point create a brand new Isotope with the relevant data. // Isotope::Isotope(int id, // QString element, // QString symbol, // int atomicNo, // double mass, // int massNo, // int extraNeutrons, // double probability, // double logProbability, // bool radioactive) // There are a number of fields that are left to value 0 but this is // of no worries. Store the isotope in the vector of isotopes that // will be added to the isotopic data later when finishing the parsing // of the element frame widget. element_isotopes.push_back(std::make_shared( 0, symbol, symbol, 0, mass, 0, 0, prob, 0, false)); } // End of iterating in the isotope frame list for a given element symbol. // At this point that we know that for symbol the isotopes were all // correct, we can record all that information to the isotopic data via // the handler. Note that the handler will also record the // element-symbol-count pair for later use. config_handler.newChemicalSet(symbol, count, element_isotopes); // At this point we can reset variables for a new loop iteration. element_isotopes.clear(); symbol = ""; } // End of iterating in the element group box list. // Sanity check: there must be in the symbol/count map member of the isotopic // handler as many different symbols as we had element frames during manual // config data validation. if(element_count != config_handler.getSymbolCountMap().size()) qFatal( "Programming error. The number of stored symbols does not match the " "user's data."); // At this point, make sure we update the maps in the IsotopicData. msp_isotopicDataUserManualConfig->updateMassMaps(); return element_count; } /*! \brief Adds a frame for the definition of an isotope. Returns a pair of spin box widgets, the first for the isotope mass and the second for the probability (abundance). */ std::pair IsotopicClusterGeneratorDlg::addIsotopeFrame() { // qDebug(); // We need to get the QPushButton object that sent the signal. QPushButton *sender = static_cast(QObject::sender()); // qDebug() "sender:" << sender; // The + button was created with the isotopeFrame as its parent. QFrame *isotopeFrame = static_cast(sender->parent()); // The parent of the isotope frame is the elementGroupBox QGroupBox *elementGroupBox = static_cast(isotopeFrame->parent()); // Now get the elementGroupBox's layout where we'll pack the new // isotopeFrame. QVBoxLayout *elementGroupVBoxLayout = static_cast(elementGroupBox->layout()); // Now create the new isotope frame. QFrame *newIsotopeFrame = new QFrame(elementGroupBox); newIsotopeFrame->setObjectName(QStringLiteral("isotopeFrame")); newIsotopeFrame->setFrameShape(QFrame::NoFrame); newIsotopeFrame->setFrameShadow(QFrame::Plain); QGridLayout *newIsotopeFrameGridLayout = new QGridLayout(newIsotopeFrame); newIsotopeFrameGridLayout->setObjectName( QStringLiteral("isotopeFrameGridLayout")); QDoubleSpinBox *newIsotopeMassDoubleSpinBox = new QDoubleSpinBox(newIsotopeFrame); newIsotopeMassDoubleSpinBox->setObjectName( QStringLiteral("isotopeMassDoubleSpinBox")); // if(mass != nullptr) // newIsotopeMassDoubleSpinBox->setValue(*mass); newIsotopeMassDoubleSpinBox->setDecimals(60); newIsotopeMassDoubleSpinBox->setMinimum(0); newIsotopeMassDoubleSpinBox->setMaximum(1000); newIsotopeMassDoubleSpinBox->setToolTip( "Enter the mass of the isotope (like 12.0000 for 12[C])"); newIsotopeFrameGridLayout->addWidget(newIsotopeMassDoubleSpinBox, 0, 0, 1, 1); QDoubleSpinBox *newIsotopeProbDoubleSpinBox = new QDoubleSpinBox(newIsotopeFrame); newIsotopeProbDoubleSpinBox->setObjectName( QStringLiteral("isotopeProbDoubleSpinBox")); // if(prob != nullptr) // newIsotopeProbDoubleSpinBox->setValue(*mass); newIsotopeProbDoubleSpinBox->setDecimals(60); newIsotopeProbDoubleSpinBox->setMinimum(0); newIsotopeProbDoubleSpinBox->setMaximum(1); newIsotopeProbDoubleSpinBox->setToolTip( "Enter the abundance of the isotope (that is, a probability <= 1)"); newIsotopeFrameGridLayout->addWidget(newIsotopeProbDoubleSpinBox, 0, 1, 1, 1); QPushButton *addIsotopePushButton = new QPushButton(newIsotopeFrame); addIsotopePushButton->setObjectName(QStringLiteral("addIsotopePushButton")); connect(addIsotopePushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::addIsotopeFrame); QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth( addIsotopePushButton->sizePolicy().hasHeightForWidth()); addIsotopePushButton->setSizePolicy(sizePolicy); QIcon icon1; icon1.addFile(QStringLiteral(":/images/svg/add-isotope.svg"), QSize(), QIcon::Normal, QIcon::Off); addIsotopePushButton->setIcon(icon1); newIsotopeFrameGridLayout->addWidget(addIsotopePushButton, 0, 2, 1, 1); QPushButton *removeIsotopePushButton = new QPushButton(isotopeFrame); removeIsotopePushButton->setObjectName( QStringLiteral("removeIsotopePushButton")); connect(removeIsotopePushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::removeIsotopeFrame); sizePolicy.setHeightForWidth( removeIsotopePushButton->sizePolicy().hasHeightForWidth()); removeIsotopePushButton->setSizePolicy(sizePolicy); QIcon icon; icon.addFile(QStringLiteral(":/images/svg/remove-isotope.svg"), QSize(), QIcon::Normal, QIcon::Off); removeIsotopePushButton->setIcon(icon); newIsotopeFrameGridLayout->addWidget(removeIsotopePushButton, 0, 3, 1, 1); elementGroupVBoxLayout->addWidget(newIsotopeFrame); return std::pair( newIsotopeMassDoubleSpinBox, newIsotopeProbDoubleSpinBox); } /*! \brief Removes an isotope frame. The isotope frame to be removed is determined from the identity of the push button that triggered this function. */ void IsotopicClusterGeneratorDlg::removeIsotopeFrame() { // qDebug(); // We need to get the QPushButton object that sent the signal. QPushButton *sender = static_cast(QObject::sender()); // qDebug() "sender:" << sender; // The - button was created with the isotopeFrame as its parent. QFrame *isotopeFrame = static_cast(sender->parent()); // The isotope frame was created with the element group box as its parent. QGroupBox *elementGroupBox = static_cast(isotopeFrame->parent()); QList childrenList = elementGroupBox->findChildren("isotopeFrame"); // We do not want to remove *all* the isotope frames, one must always be // there, // otherwise the element group box is not more usable. if(childrenList.size() < 2) { message("Cannot remove last isotope widget set."); return; } delete isotopeFrame; } /*! \brief Creates a group box that will enshrine a chemical element definition. The group box is populated with a line edit widget for the element symbol and with a spin box for the count of the atoms in the final elemental composition defined here. The isotope-related widgets are not created. \sa addElementGroupBox() */ QGroupBox * IsotopicClusterGeneratorDlg::addElementSkeletonGroupBox() { QGroupBox *elementGroupBox; elementGroupBox = new QGroupBox(mp_ui->scrollAreaWidgetContents); elementGroupBox->setObjectName(QStringLiteral("elementGroupBox")); QVBoxLayout *elementGroupVBoxLayout; elementGroupVBoxLayout = new QVBoxLayout(elementGroupBox); elementGroupVBoxLayout->setObjectName( QStringLiteral("elementGroupVBoxLayout")); QHBoxLayout *symbolLineEditHBoxLayout = new QHBoxLayout(); QLineEdit *symbolLineEdit; symbolLineEdit = new QLineEdit(elementGroupBox); symbolLineEdit->setObjectName(QStringLiteral("symbolLineEdit")); symbolLineEdit->setToolTip("Enter the symbol of the chemical element"); symbolLineEditHBoxLayout->addWidget(symbolLineEdit); QSpinBox *atomCountSpinBox; atomCountSpinBox = new QSpinBox(elementGroupBox); atomCountSpinBox->setObjectName(QStringLiteral("atomCountSpinBox")); atomCountSpinBox->setRange(1, 100000000); atomCountSpinBox->setToolTip( "Enter the count of the chemical element in the formula"); symbolLineEditHBoxLayout->addWidget(atomCountSpinBox); QSpacerItem *symbolLineEditHBoxLayoutHorizontalSpacer; symbolLineEditHBoxLayoutHorizontalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); symbolLineEditHBoxLayout->addItem(symbolLineEditHBoxLayoutHorizontalSpacer); QPushButton *minusElementPushButton; minusElementPushButton = new QPushButton(elementGroupBox); minusElementPushButton->setObjectName( QStringLiteral("minusElementPushButton")); connect(minusElementPushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::removeElementGroupBox); QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth( minusElementPushButton->sizePolicy().hasHeightForWidth()); minusElementPushButton->setSizePolicy(sizePolicy); QIcon icon; icon.addFile(QStringLiteral(":/images/svg/remove-chemical-element.svg"), QSize(), QIcon::Normal, QIcon::Off); minusElementPushButton->setIcon(icon); symbolLineEditHBoxLayout->addWidget(minusElementPushButton); elementGroupVBoxLayout->addLayout(symbolLineEditHBoxLayout); // Setting SetFixedSize helps maintaining the widgets most compact without // having to resort to vertical spacers. mp_ui->scrollAreaWidgetContentsVerticalLayout->setSizeConstraint( QLayout::SetFixedSize); mp_ui->scrollAreaWidgetContentsVerticalLayout->addWidget(elementGroupBox); return elementGroupBox; } /*! \brief Returns a newly created frame inside \a elementGroupBox that will enshrine all the specifications about the isotopes being configured. */ QFrame * IsotopicClusterGeneratorDlg::createIsotopeFrame(QGroupBox *elementGroupBox) { QFrame *isotopeFrame; // If elementGroupBox is nullptr, the frame is not parented. It is then up // to // the caller to parent it. isotopeFrame = new QFrame(elementGroupBox); isotopeFrame->setObjectName(QStringLiteral("isotopeFrame")); isotopeFrame->setFrameShape(QFrame::NoFrame); isotopeFrame->setFrameShadow(QFrame::Plain); QGridLayout *isotopeFrameGridLayout; isotopeFrameGridLayout = new QGridLayout(isotopeFrame); isotopeFrameGridLayout->setObjectName( QStringLiteral("isotopeFrameGridLayout")); QDoubleSpinBox *isotopeMassDoubleSpinBox; isotopeMassDoubleSpinBox = new QDoubleSpinBox(isotopeFrame); isotopeMassDoubleSpinBox->setObjectName( QStringLiteral("isotopeMassDoubleSpinBox")); isotopeMassDoubleSpinBox->setDecimals(60); isotopeMassDoubleSpinBox->setMinimum(0); isotopeMassDoubleSpinBox->setMaximum(1000); isotopeMassDoubleSpinBox->setToolTip( "Enter the mass of the isotope (like 12.0000 for 12[C])"); isotopeFrameGridLayout->addWidget(isotopeMassDoubleSpinBox, 0, 0, 1, 1); QDoubleSpinBox *isotopeProbDoubleSpinBox; isotopeProbDoubleSpinBox = new QDoubleSpinBox(isotopeFrame); isotopeProbDoubleSpinBox->setObjectName( QStringLiteral("isotopeProbDoubleSpinBox")); isotopeProbDoubleSpinBox->setDecimals(60); isotopeProbDoubleSpinBox->setMinimum(0); isotopeProbDoubleSpinBox->setMaximum(1); isotopeProbDoubleSpinBox->setToolTip( "Enter the abundance of the isotope (that is, a probability <= 1)"); isotopeFrameGridLayout->addWidget(isotopeProbDoubleSpinBox, 0, 1, 1, 1); QPushButton *addIsotopePushButton; addIsotopePushButton = new QPushButton(isotopeFrame); addIsotopePushButton->setObjectName(QStringLiteral("addIsotopePushButton")); connect(addIsotopePushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::addIsotopeFrame); QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth( addIsotopePushButton->sizePolicy().hasHeightForWidth()); addIsotopePushButton->setSizePolicy(sizePolicy); QIcon icon1; icon1.addFile(QStringLiteral(":/images/svg/add-isotope.svg"), QSize(), QIcon::Normal, QIcon::Off); addIsotopePushButton->setIcon(icon1); isotopeFrameGridLayout->addWidget(addIsotopePushButton, 0, 2, 1, 1); QPushButton *removeIsotopePushButton; removeIsotopePushButton = new QPushButton(isotopeFrame); removeIsotopePushButton->setObjectName( QStringLiteral("removeIsotopePushButton")); connect(removeIsotopePushButton, &QPushButton::clicked, this, &IsotopicClusterGeneratorDlg::removeIsotopeFrame); sizePolicy.setHeightForWidth( removeIsotopePushButton->sizePolicy().hasHeightForWidth()); removeIsotopePushButton->setSizePolicy(sizePolicy); QIcon icon; icon.addFile(QStringLiteral(":/images/svg/remove-isotope.svg"), QSize(), QIcon::Normal, QIcon::Off); removeIsotopePushButton->setIcon(icon); isotopeFrameGridLayout->addWidget(removeIsotopePushButton, 0, 3, 1, 1); QVBoxLayout *elementGroupVBoxLayout = static_cast(elementGroupBox->layout()); elementGroupVBoxLayout->addWidget(isotopeFrame); return isotopeFrame; } /*! \brief Creates a group box that will enshrine a chemical element definition. The group box is populated with a line edit widget for the element symbol and with a spin box for the count of the atoms in the final elemental composition defined here. The isotope-related frame is then created inside the newly created group box. \sa addElementSkeletonGroupBox() */ QGroupBox * IsotopicClusterGeneratorDlg::addElementGroupBox() { QGroupBox *elementGroupBox = addElementSkeletonGroupBox(); // And now add the first isotope frame createIsotopeFrame(elementGroupBox); return elementGroupBox; } /*! \brief Removes an element group box. The group box to be removed is determined from the identity of the push button that triggered this function. */ void IsotopicClusterGeneratorDlg::removeElementGroupBox() { // qDebug(); // We need to get the QPushButton object that sent the signal. QPushButton *sender = static_cast(QObject::sender()); // qDebug() "sender:" << sender; // The - button was created with the element group box as its parent. QGroupBox *elementGroupBox = static_cast(sender->parent()); delete elementGroupBox; } /*! \brief Prints \a message in the message line edit widget, that will be erased after the \a timeout. */ void IsotopicClusterGeneratorDlg::message(const QString &message, int timeout) { mp_ui->messageLineEdit->setText(message); QTimer::singleShot(timeout, [this]() { mp_ui->messageLineEdit->setText(""); }); } /*! \brief Returns true if all the parameters are correct and consistent, false otherwise. */ bool IsotopicClusterGeneratorDlg::checkParameters() { // We need to check a number of parameters before running the computation. // Let's get the summed probs value configure by the user m_maxSummedProbability = mp_ui->probabilityDoubleSpinBox->value(); m_charge = mp_ui->ionChargeSpinBox->value(); if(!m_charge) qFatal("The charge cannot be 0 here."); double gaussian_apex_intensity = 0; if(mp_ui->intensityGroupBox->isChecked()) { bool ok = false; gaussian_apex_intensity = mp_ui->gaussianIntensityValueLineEdit->text().toDouble(&ok); if(!ok) { message( "The Gaussian apex intensity value failed to convert to double. " "Please, fix it.", 5000); return false; } if(gaussian_apex_intensity < 0) { message( "The Gaussian apex intensity value is negative, converted to " "positive. ", 5000); gaussian_apex_intensity = -gaussian_apex_intensity; } if(!gaussian_apex_intensity) { message( "The Gaussian apex intensity value is naught. Please, fix " "it.", 5000); return false; } // qDebug() << "gaussian_apex_intensity:" << gaussian_apex_intensity; // Now that we know that the value is reliable, we can set it to the // member datum. m_normalizeIntensity = gaussian_apex_intensity; // qDebug() << "Should perform normalization."; } else { m_normalizeIntensity = std::numeric_limits::min(); } bool should_sort = !mp_ui->noSortRadioButton->isChecked(); if(!should_sort) m_sortType = pappso::SortType::no_sort; else { if(mp_ui->sortByMzRadioButton->isChecked()) m_sortType = pappso::SortType::x; else m_sortType = pappso::SortType::y; // At this point the sort order. if(mp_ui->ascendingSortRadioButton->isChecked()) m_sortOrder = pappso::SortOrder::ascending; else m_sortOrder = pappso::SortOrder::descending; } return true; } /*! \brief Runs the computation using the IsoSpec-based isotopic data. Returns true upon success, false otherwise. */ bool IsotopicClusterGeneratorDlg::runLibrary() { // qDebug(); // This is the configuration of elements/isotopes that is loaded from the // IsoSpec library headers, that is, the configuration is static. The isotopic // data were loaded at setupDialog() time. assert(msp_isotopicDataLibrary->size()); if(!checkParameters()) { message("The parameters failed to check.", 6000); return false; } // Get the formula for which the isotopic computation is to be performed. QString formula_text = mp_ui->libraryFormulaComboBox->currentText(); if(formula_text.isEmpty()) { message( "The formula cannot be empty. Make sure it has the H2O1 syntax (note " "the 1 index for element O", 5000); return false; } // Now configure the generator. m_clusterGenerator.setIsotopicData(msp_isotopicDataLibrary); m_clusterGenerator.setIsotopicDataType( libXpertMass::IsotopicDataType::LIBRARY_CONFIG); // Create the pair that we'll feed to the generator. std::pair formula_charge_pair(formula_text, m_charge); m_clusterGenerator.setFormulaChargePair(formula_charge_pair); m_clusterGenerator.setMaxSummedProbability(m_maxSummedProbability); m_clusterGenerator.setNormalizationIntensity(m_normalizeIntensity); m_clusterGenerator.setSortType(m_sortType); m_clusterGenerator.setSortOrder(m_sortOrder); if(m_clusterGenerator.run()) { reportResults(); return true; } else message("Failed to perform the computation.", 6000); return false; } /*! \brief Runs the computation using the IsoSpec-like user-modified isotopic data. Returns true upon success, false otherwise. */ bool IsotopicClusterGeneratorDlg::runUserConfig() { // qDebug(); // When running this function, the user must have a correct atom/isotope // configuration in the tableview and a formula that matches the available // chemical elements. // The correct isotopic data handler is used to perform the preparation of the // arrays needed by IsoSpec. if(!msp_isotopicDataUserConfig->size()) { QMessageBox::warning( this, QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription), "Please, load a user-defined isotopic data table.\n" "Alternatively, use the static data table view (other tab)\n" "and modify it in place, then save it to disk.\n" "Finally load these isotopic data from file into this table view.", QMessageBox::Ok); return false; } if(!checkParameters()) { message("The parameters failed to check.", 6000); return false; } // Get the formula for which the isotopic computation is to be performed. QString formula_text = mp_ui->userConfigFormulaComboBox->currentText(); if(formula_text.isEmpty()) { message( "The formula cannot be empty. Make sure it has the H2O1 syntax (note " "the 1 index for element O", 5000); return false; } // Now configure the generator. m_clusterGenerator.setIsotopicData(msp_isotopicDataUserConfig); m_clusterGenerator.setIsotopicDataType( libXpertMass::IsotopicDataType::USER_CONFIG); // Create the pair that we'll feed to the m_clusterGenerator. std::pair formula_charge_pair(formula_text, m_charge); m_clusterGenerator.setFormulaChargePair(formula_charge_pair); m_clusterGenerator.setMaxSummedProbability(m_maxSummedProbability); m_clusterGenerator.setNormalizationIntensity(m_normalizeIntensity); m_clusterGenerator.setSortType(m_sortType); m_clusterGenerator.setSortOrder(m_sortOrder); if(m_clusterGenerator.run()) { reportResults(); return true; } else message("Failed to perform the computation.", 6000); return false; } /*! \brief Runs the computation using the user manually-configured isotopic data. Returns true upon success, false otherwise. */ bool IsotopicClusterGeneratorDlg::runUserManualConfig() { // qDebug(); // First validate the configuration. Errors are fatal in the IsoSpec // library. In order to validate the configuration and keep interesting data // in it, we first instantiate a specific handler that we'll pass to the // validation function. // Make clean room. msp_isotopicDataUserManualConfig->clear(); // Here, we do not have a formula to make the computation with because the // user has manually configured the isotopes and for each symbol as also told // how many such atoms are the the compound. The determination of the // symbol/count pairs is done in the validateManualConfig() call below. libXpertMass::IsotopicDataManualConfigHandler isotopic_data_handler( msp_isotopicDataUserManualConfig); std::size_t element_count = validateManualConfig(isotopic_data_handler); if(!element_count) { // No messages because the validation function does the job. return false; } QString formula_text = isotopic_data_handler.craftFormula(); assert(msp_isotopicDataUserManualConfig->size()); if(!checkParameters()) { message("The parameters failed to check.", 6000); return false; } // Now configure the generator. m_clusterGenerator.setIsotopicData(msp_isotopicDataUserManualConfig); m_clusterGenerator.setIsotopicDataType( libXpertMass::IsotopicDataType::MANUAL_CONFIG); // Create the pair that we'll feed to the m_clusterGenerator. std::pair formula_charge_pair(formula_text, m_charge); m_clusterGenerator.setFormulaChargePair(formula_charge_pair); m_clusterGenerator.setMaxSummedProbability(m_maxSummedProbability); m_clusterGenerator.setNormalizationIntensity(m_normalizeIntensity); m_clusterGenerator.setSortType(m_sortType); m_clusterGenerator.setSortOrder(m_sortOrder); if(m_clusterGenerator.run()) { reportResults(); return true; } else message("Failed to perform the computation.", 6000); return false; } /*! \brief Loads the user manually-configured isotopic data configuration. All the widgets required to actually display the user manual configuration are created and configured automatically. Returns true upon success, false otherwise. */ bool IsotopicClusterGeneratorDlg::loadUserManualConfig() { // File format: // //[Element] // symbol C count 100 //[Isotopes] 2 // mass 12.000 prob 0.9899 // mass 13.000 prob 0.010 QString file_name = QFileDialog::getOpenFileName( this, tr("Load configuration"), QDir::home().absolutePath()); if(file_name.isEmpty()) { message("The file name to read from is empty!", 5000); return false; } QFile file(file_name); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { message("Failed to open file for reading.", 5000); return false; } // Make sure we clear the room. msp_isotopicDataUserManualConfig->clear(); // Instantiate the proper handler! libXpertMass::IsotopicDataManualConfigHandler isotopic_data_handler( msp_isotopicDataUserManualConfig); std::size_t isotope_count = isotopic_data_handler.loadData(file_name); if(!isotope_count) { qDebug() << "Failed to load any isotope from the configuration file."; return false; } // At this point, we have all the required data to start creating the // widgets! initializeUserManualConfigurationWidgets(isotopic_data_handler); return true; } /*! \brief Saves the user manually-entered isotopic data configuration to file. The user is provided with a file selection dialog window. Returns true upon succes, false if the manual isotopic data configuration does not validate successfully. */ bool IsotopicClusterGeneratorDlg::saveUserManualConfig() { // We need to iterate into the various widgets, exactly as done for the // run of the manual configuration. // Make clean room. msp_isotopicDataUserManualConfig->clear(); // Here, we do not have a formula to make the computation with because the // user has manually configured the isotopes and for each symbol as also told // how many such atoms are the the compound. The determination of the // symbol/count pairs is done in the validateManualConfig() call below. libXpertMass::IsotopicDataManualConfigHandler isotopic_data_handler( msp_isotopicDataUserManualConfig); std::size_t element_count = validateManualConfig(isotopic_data_handler); if(!element_count) { // No messages because the validation function does the job. return false; } // At this point all the isotopic data have been parsed and are available for // writing. We ask the isotopic data handler to write to the file. // At this point let the user choose a file for that config. QFileDialog fileDlg(this); fileDlg.setFileMode(QFileDialog::AnyFile); fileDlg.setAcceptMode(QFileDialog::AcceptSave); QString file_name = fileDlg.getSaveFileName( this, tr("Save configuration"), QDir::home().absolutePath()); if(file_name.isEmpty()) { message("Please provide a file name.", 5000); return false; } isotopic_data_handler.writeData(file_name); message(QString("Configuration saved in %1").arg(file_name), 5000); return true; } /*! \brief Initializes all the widgets required to host the user manually-configured isotopic data. This function should be called right after having loaded a user manually-defined isotopic data configuration. \a config_handler is the isotopic data configuration handler that had previously loaded the data. Returns true upon success, false if the user manual configuration is empty. \sa loadUserManualConfig() */ bool IsotopicClusterGeneratorDlg::initializeUserManualConfigurationWidgets( const libXpertMass::IsotopicDataManualConfigHandler &config_handler) { // This function must be called right after having loaded a manual // configuration file because during that file reading, all the atomistic info // have been set and the handler holds some more of use. if(!msp_isotopicDataUserManualConfig->size()) { qDebug() << "There are no data to display in the widgets."; return false; } // Now iterate in all the data and create widgets to display them. using SymbolCountMap = std::map; const SymbolCountMap &map = config_handler.getSymbolCountMap(); // qDebug() << "The symbol/count map has this count of items:" << map.size(); // Get all the unqiue isotope symbols that were set the isotopic data object. // Remember that in the manual config context, only isotopes from the user's // manual configuration have been set in the IsotopicData object (which is not // true for library/user isotopic data. std::vector symbols = msp_isotopicDataUserManualConfig->getUniqueSymbolsInOriginalOrder(); // We can now iterate in each symbol and start getting the data. for(auto symbol : symbols) { // This call throws if the symbol is not there. int symbol_count = map.at(symbol); // qDebug() << "For symbol" << symbol << "the count is:" << symbol_count; // This is where we contruct a new elementGroupBox QGroupBox *elementGroupBox = addElementSkeletonGroupBox(); // Now the symbol line edit widget QLineEdit *symbolLineEdit = elementGroupBox->findChild("symbolLineEdit"); symbolLineEdit->setText(symbol); // Now the symbol count spin box widget QSpinBox *atomCountSpinBox = elementGroupBox->findChild("atomCountSpinBox"); atomCountSpinBox->setValue(symbol_count); // Now get iterators to the range of isotopes by that symbol in the // isotopic data. std::pair::const_iterator, std::vector::const_iterator> iter_pair = msp_isotopicDataUserManualConfig->getIsotopesBySymbol(symbol); // We can now iterate in the range and extract the isotopes. while(iter_pair.first != iter_pair.second) { libXpertMass::IsotopeSPtr isotope_sp = *iter_pair.first; // The frame is automatically parented to the skeleton // elementGroupBox that we created above. QFrame *isotopeFrame = createIsotopeFrame(elementGroupBox); QDoubleSpinBox *massSpinBox = isotopeFrame->findChild( "isotopeMassDoubleSpinBox"); massSpinBox->setValue(isotope_sp->getMass()); QDoubleSpinBox *probSpinBox = isotopeFrame->findChild( "isotopeProbDoubleSpinBox"); probSpinBox->setValue(isotope_sp->getProbability()); ++iter_pair.first; } } // End // iterating in the unique symbols in their original order. return true; } /*! \brief Adds the currently displayed formula for the IsoSpec-based table view to the corresponding combo box widget. */ void IsotopicClusterGeneratorDlg::addLibraryFormula() { QString text = mp_ui->libraryFormulaComboBox->currentText(); if(text.isEmpty()) return; mp_ui->libraryFormulaComboBox->addItem(text); } /*! \brief Removes the currently displayed formula for the IsoSpec-based table view from the corresponding combo box widget. */ void IsotopicClusterGeneratorDlg::removeLibraryFormula() { int currentIndex = mp_ui->libraryFormulaComboBox->currentIndex(); if(currentIndex != -1) mp_ui->libraryFormulaComboBox->removeItem(currentIndex); } /*! \brief Adds the currently displayed formula for the IsoSpec-like user-modified table view to the corresponding combo box widget. */ void IsotopicClusterGeneratorDlg::addUserConfigFormula() { QString text = mp_ui->userConfigFormulaComboBox->currentText(); if(text.isEmpty()) return; mp_ui->userConfigFormulaComboBox->addItem(text); } /*! \brief Removes the currently displayed formula for the IsoSpec-like user-modified table view from the corresponding combo box widget. */ void IsotopicClusterGeneratorDlg::removeUserConfigFormula() { int currentIndex = mp_ui->userConfigFormulaComboBox->currentIndex(); if(currentIndex != -1) mp_ui->userConfigFormulaComboBox->removeItem(currentIndex); } /*! \brief Exports the computation results to a string that is set to the plain text edit widget in the results tab widget tab. */ void IsotopicClusterGeneratorDlg::reportResults() { // Finally export the results as a string. // Note how we do export the relative intensity. If there was normalization, // that value was updated, otherwise it had been initialized identical to // the intensity upon creation of the PeakCentroid instances in the // vector. libXpertMass::IsotopicClusterChargePair isotopic_cluster_charge_pair = m_clusterGenerator.getIsotopicClusterChargePairs().front(); QString results = m_clusterGenerator.clustersToString(); // qDebug().noquote() << results; mp_ui->isoSpecTabWidget->setCurrentWidget(mp_ui->isoSpecTabWidgetResultsTab); mp_ui->isoSpecOutputDataPlainTextEdit->setPlainText(results); } } // namespace libXpertMassGui } // namespace MsXpS #if 0 Example from IsoSpec. const int elementNumber = 2; const int isotopeNumbers[2] = {2,3}; const int atomCounts[2] = {2,1}; const double hydrogen_masses[2] = {1.00782503207, 2.0141017778}; const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610}; const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses}; const double hydrogen_probs[2] = {0.5, 0.5}; const double oxygen_probs[3] = {0.5, 0.3, 0.2}; const double* probs[2] = {hydrogen_probs, oxygen_probs}; IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts, isotope_masses, probs), 0.99); #endif libxpertmass-1.1.0/src/XpertMassGui/IsotopicClusterShaperDlg.cpp000664 001750 001750 00000077730 14647465366 026314 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// StdLib includes #include #include #include // for std::numeric_limits /////////////////////// Qt includes #include #include #include /////////////////////// libmass includes #include "libXpertMass/globals.hpp" #include "libXpertMass/MassDataCborMassSpectrumHandler.hpp" #include "libXpertMass/IsotopicClusterGenerator.hpp" #include "libXpertMass/MassPeakShaper.hpp" #include "libXpertMass/MassPeakShaperConfig.hpp" #include "libXpertMass/PeakCentroid.hpp" /////////////////////// pappsomspp includes #include #include #include #include #include /////////////////////// Local includes #include "ui_IsotopicClusterShaperDlg.h" #include "IsotopicClusterShaperDlg.hpp" #include "ColorSelector.hpp" namespace MsXpS { namespace libXpertMassGui { /*! \class MsXpS::libXpertMassGui::IsotopicClusterShaperDlg \inmodule libXpertMassGui \ingroup XpertMassGuiMassCalculations \inheaderfile IsotopicClusterShaperDlg.hpp \brief The IsotopicClusterShaperDlg class provides a graphical user interface for the full peak shaping process. Each peak centroid in the isotopic cluster is shaped independently and all the obtained shapes are combined into a single mass spectrum. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::mp_ui \brief The graphical user interface definition. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::mp_massPeakShaperConfigWidget \brief The widget where the mass peak shaping process configuration is done.. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::mp_programWindow \brief The main program window. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_applicationName \brief The name of the application. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_description \brief The description of this IsotopicClusterShaperDlg dialog window instance. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_fileName \brief The name of the file to which results might be written. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_mapTrace \brief The map trace that is used to combine each individual peak shape into a single mass spectrum. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_finalTrace \brief The finale pappso::Trace in which to store the final mass spectrum. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_colorByteArray \brief The byte array representation of the color with which the spectrum might be traced. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_mzIntegrationParams \brief The mass spectrum combination parameters (size of the bins, for example). */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_referencePeakMz \brief The m/z value that is used as the reference peak when convertring full with at half maximum values to and from resolving power values. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_normalizingIntensity \brief The intensity value that the most intense peak in the cluster should have. All the other peaks intensities are normalized against this value. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_isotopicCluster \brief The isotopic cluster in the form of a pappso::Trace instance. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_peakShapers \brief The list of peak shapers used to shape the peak around each isotopic cluster peak centroid. Each peak centroid of the isotopic cluster will be handled by its own peak shaper instance. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::m_config \brief The peak shaping process configuration. */ /*! \variable MsXpS::libXpertMassGui::IsotopicClusterShaperDlg::msp_msgTimer \brief The timer used to erase messages output to the user. */ /*! \brief Constructs a IsotopicClusterShaperDlg instance. \list \li \a program_window_p: the program's main window. \li \a applicationName: the name of the application, typically massXpert2 or mineXpert2, for example. \li \a description: the string describing what this dialog window is for (used for the window title). \endlist */ IsotopicClusterShaperDlg::IsotopicClusterShaperDlg( QWidget *program_window_p, const QString &applicationName, const QString &description) : QDialog(static_cast(program_window_p)), mp_ui(new Ui::IsotopicClusterShaperDlg), mp_programWindow(program_window_p), m_applicationName(applicationName), m_description(description) { if(!program_window_p) qFatal("Programming error. Program aborted."); setupDialog(); // Update the window title because the window title element in m_ui might be // either erroneous or empty. setWindowTitle(QString("%1 - %2").arg(applicationName).arg(description)); } /*! \brief Constructs a IsotopicClusterShaperDlg instance. \list \li \a program_window_p: the program's main window. \li \a applicationName: the name of the application, typically massXpert2 or mineXpert2, for example. \li \a description: the string describing what this dialog window is for (used for the window title). \li \a isotopic_cluster_sp: the isotopic cluster that is to be processed in order to shape each one of its peak centroids. \endlist */ IsotopicClusterShaperDlg::IsotopicClusterShaperDlg( QWidget *program_window_p, const QString &applicationName, const QString &description, pappso::TraceCstSPtr isotopic_cluster_sp, int normalizing_intensity) : QDialog(static_cast(program_window_p)), mp_ui(new Ui::IsotopicClusterShaperDlg), mp_programWindow(program_window_p), m_applicationName(applicationName), m_description(description) { if(!program_window_p) qFatal("Programming error. Program aborted."); setupDialog(); // Update the window title because the window title element in m_ui might be // either erroneous or empty. setWindowTitle(QString("%1 - %2").arg(applicationName).arg(description)); m_normalizingIntensity = normalizing_intensity; mp_ui->normalizingIntensitySpinBox->setValue(normalizing_intensity); // Logically if setting the normalizing intensity, then activate the group // box. mp_ui->normalizationGroupBox->setChecked(true); setIsotopiCluster(isotopic_cluster_sp); } /*! \brief Upon closing of the dialog window (unused \a event), writes the settings to the application configuration file. */ void IsotopicClusterShaperDlg::closeEvent([[maybe_unused]] QCloseEvent *event) { // qDebug(); writeSettings(libXpertMass::configSettingsFilePath(m_applicationName)); } /*! \brief Destructs this IsotopicClusterShaperDlg instance. */ IsotopicClusterShaperDlg::~IsotopicClusterShaperDlg() { writeSettings(libXpertMass::configSettingsFilePath(m_applicationName)); delete mp_ui; } /*! \brief Saves the settings of this dialog window to the application configuration file (\a config_settings_file_path). The saved configuration is read from the file to set back the dialog window in the same status. */ void IsotopicClusterShaperDlg::writeSettings(const QString &config_settings_file_path) { // qDebug(); // First save the config widget state. mp_massPeakShaperConfigWidget->writeSettings(config_settings_file_path); QSettings settings(config_settings_file_path, QSettings::IniFormat); settings.beginGroup("IsotopicClusterShaperDlg"); settings.setValue("geometry", saveGeometry()); settings.setValue("charge", mp_ui->ionChargeSpinBox->value()); settings.setValue("splitter", mp_ui->splitter->saveState()); settings.endGroup(); } /*! \brief Reads the settings of this dialog window from the application configuration file (\a config_settings_file_path). The configuration is read from the file to set back the dialog window in the same status. */ void IsotopicClusterShaperDlg::readSettings(const QString &config_settings_file_path) { // qDebug(); // First read the config widget state. mp_massPeakShaperConfigWidget->readSettings(config_settings_file_path); QSettings settings(config_settings_file_path, QSettings::IniFormat); settings.beginGroup("IsotopicClusterShaperDlg"); restoreGeometry(settings.value("geometry").toByteArray()); mp_ui->ionChargeSpinBox->setValue(settings.value("charge", 1).toInt()); mp_ui->splitter->restoreState(settings.value("splitter").toByteArray()); settings.endGroup(); } /*! \brief Sets up the dialog window. */ void IsotopicClusterShaperDlg::setupDialog() { mp_ui->setupUi(this); // We want to destroy the dialog when it is closed. setAttribute(Qt::WA_DeleteOnClose); // Setup the peak shaper configuration widget inside of the frame QVBoxLayout *v_box_layout_p = new QVBoxLayout(this); // Trasmit the config member object reference so that it can be modified by // the configuration widget. mp_massPeakShaperConfigWidget = new MassPeakShaperConfigWidget(this, m_config); v_box_layout_p->addWidget(mp_massPeakShaperConfigWidget); mp_ui->peakShapeConfigFrame->setLayout(v_box_layout_p); // Set the message timer to be singleShot. This time is used to erase the text shown in the message line edit widget after 3 seconds. msp_msgTimer->setInterval(3000); msp_msgTimer->setSingleShot(true); connect(msp_msgTimer.get(), &QTimer::timeout, [this] (){ mp_ui->messageLineEdit->setText("");}); connect(mp_ui->normalizationGroupBox, &QGroupBox::toggled, this, &IsotopicClusterShaperDlg::normalizingGrouBoxToggled); connect(mp_ui->normalizingIntensitySpinBox, &QSpinBox::valueChanged, this, &IsotopicClusterShaperDlg::normalizingIntensityValueChanged); // Set the default color of the mass spectra trace upon mass spectrum // synthesis QColor color("black"); // Now prepare the color in the form of a QByteArray QDataStream stream(&m_colorByteArray, QIODevice::WriteOnly); stream << color; // The color button connect(mp_ui->colorSelectorPushButton, &QPushButton::clicked, this, &IsotopicClusterShaperDlg::traceColorPushButtonClicked); // The values above are actually set in readSettings (with default values if // missing in the config settings.) readSettings(libXpertMass::configSettingsFilePath(m_applicationName)); // Always start the dialog with the first page of the tab widget, // the input data page. mp_ui->tabWidget->setCurrentIndex(static_cast(TabWidgetPage::INPUT_DATA)); // Throughout of *this* dialog window, the normalization factor // for the peak shapes (Gaussian, specifically) is going to be 1. connect(mp_ui->importPushButton, &QPushButton::clicked, this, &IsotopicClusterShaperDlg::importFromText); connect(mp_ui->checkParametersPushButton, &QPushButton::clicked, this, &IsotopicClusterShaperDlg::checkParameters); connect(mp_ui->runPushButton, &QPushButton::clicked, this, &IsotopicClusterShaperDlg::run); connect(mp_ui->outputFilePushButton, &QPushButton::clicked, this, &IsotopicClusterShaperDlg::outputFileName); connect(mp_ui->displayMassSpectrumPushButton, &QPushButton::clicked, this, &IsotopicClusterShaperDlg::displayMassSpectrum); connect(mp_ui->copyMassSpectrumToClipboardPushButton, &QPushButton::clicked, this, &IsotopicClusterShaperDlg::copyMassSpectrumToClipboard); } /*! \brief Returns a string with the mass spectrum name. The mass spectrum is the result of the isotopic cluster shaping process. */ QString IsotopicClusterShaperDlg::craftMassSpectrumName() { // The user might have forgotten to insert a preferred name in the mass // spectrum file name edit widget. We thus craft one on the basis of the // centroid peaks and the current time. QString name = mp_ui->massSpectrumNameResultsLineEdit->text(); if(name.isEmpty()) { name = QString("%1-%2 @ %3") .arg(m_peakShapers.at(0)->getPeakCentroid().x) .arg(m_peakShapers.at(m_peakShapers.size() - 1)->getPeakCentroid().x) .arg(QTime::currentTime().toString("hh:mm:ss.zzz")); } else { name.append(" @ "); name.append(QTime::currentTime().toString("hh:mm:ss.zzz")); } // qDebug() << "Returning mass spectrum name:" << name; return name; } /*! \brief Creates all the peak shapers required to process all the peak centroids in the isotopic cluster. */ std::size_t IsotopicClusterShaperDlg::fillInThePeakShapers() { // qDebug(); // Clear the peak shapers that might have been created in a previous run. m_peakShapers.clear(); int charge = mp_ui->ionChargeSpinBox->value(); // We compute the m/z value below, so z cannot be 0. if(charge <= 0) qFatal( "Programming error. By this time the charge should have been validated > " "0"); for(auto data_point : m_isotopicCluster) { // We need to account for the charge. Note the division below. m_peakShapers.append(std::make_shared( pappso::DataPoint(data_point.x / charge, data_point.y), m_config)); } // qDebug() << "m_config:" << m_config.asText(800); message( QString("The number of peak centroids is: %1").arg(m_peakShapers.size())); return m_peakShapers.size(); } /*! \brief Checks that the configuration parameters are correct.. */ bool IsotopicClusterShaperDlg::checkParameters() { qDebug(); // The general idea is that a number of parameters are inter-dependent. We // need to check these parameters in a precise order because of these // inter-dependencies. // Remove all the text in the log text edit widget. mp_ui->logPlainTextEdit->clear(); // Remove all text in the results text edit widget. mp_ui->resultPlainTextEdit->clear(); // Make sure we have some (mz,i) values to crunch. if(!m_isotopicCluster.size()) { message("There are no centroid peaks to process"); qDebug("There are no centroid peaks to process"); return false; } if(m_referencePeakMz <= 0) { message( "The reference peak m/z value is not set. Please enter one m/z value."); qDebug() << "The reference peak m/z value is not set"; return false; } //////////////////// THE ION CHARGE ///////////////////// int charge = mp_ui->ionChargeSpinBox->value(); // m/z has charge at the denominator! if(charge <= 0) { message("The ion charge must be >= 1", 6000); return false; } /////////////////// Ask the widget to perform the check /////////////////// QString text; if(!mp_massPeakShaperConfigWidget->checkTheParameters(text)) { message("There were errors in the configuration: " + text, 10000); return false; } // No errors ? Then output the configuration. text = m_config.toString(); qDebug().noquote() << "The configuration:\n" << text; mp_ui->logPlainTextEdit->appendPlainText(text); // Finally do a resolve of all the config bits to something actually useful. if(!m_config.resolve()) { message( "Failed to finally resolve the configuration. Check the LOG tab for " "details."); qDebug().noquote() << text; return false; } // At this point, because we have set all the relevant values to the // m_config PeakShapeConfig, we can create the PeakShaper instances and set // to them the m_config. if(!fillInThePeakShapers()) { QMessageBox::warning( 0, tr("mineXpert: Peak shaper (Gaussian or Lorentzian)"), tr("Please, insert at least one (m/z i) pair in the following " "format:\n\n" "\n\n" "With being any non numerical character \n" "(space or non-'.' punctuation, for example).\n\n" "Once the data have been pasted in the edit widget, click the " "'Import from text' button above.\n" "Also, make sure that you fill-in the resolution or the FWHM " "value and the ion charge."), QMessageBox::Ok); return false; } mp_ui->logPlainTextEdit->appendPlainText( QString("Number of input peak centroids to process: %1.\n") .arg(m_peakShapers.size())); mp_ui->logPlainTextEdit->appendPlainText(m_config.toString()); message("The configuration validated fine. Check the LOG tab for details."); // At this point we'll have some things to report to the LOG tab, // switch to it now. // mp_ui->tabWidget->setCurrentIndex(static_cast(TabWidgetPage::LOG)); // qDebug() << "Now returning true after having checked the parameters."; return true; } /*! \brief Set \a message to the message line edit widget with \a timeout. At the end of the time out the message is erased. */ void IsotopicClusterShaperDlg::message(const QString &message, int timeout) { mp_ui->messageLineEdit->setText(message); msp_msgTimer->stop(); msp_msgTimer->setInterval(timeout); msp_msgTimer->start(); } /*! \brief Runs the computations. */ void IsotopicClusterShaperDlg::run() { qDebug(); if(!checkParameters()) { QMessageBox::warning( 0, tr("mineXpert: Peak shaper (Gaussian or Lorentzian)"), tr("Please, insert at least one (m/z i) pair in the following " "format:\n\n" "\n\n" "With being any non numerical character \n" "(space or non-'.' punctuation, for example).\n\n" "Once the data have been pasted in the edit widget, click the " "'Import from text' button above.\n" "Also, make sure that you fill-in the resolution or the FWHM " "value and the ion charge."), QMessageBox::Ok); return; } // At this point all the data are set, we can start the computation on all // the various peak shapers. double minMz = std::numeric_limits::max(); double maxMz = std::numeric_limits::min(); // Clear the map trace that will receive the results of the combinations. m_mapTrace.clear(); // Now actually shape a peak around each peak centroid (contained in each // peak shaper). int processed = 0; for(auto peak_shaper_sp : m_peakShapers) { if(!peak_shaper_sp->computePeakShape()) { QMessageBox::warning( 0, tr("mineXpert: Peak shaper (Gaussian or Lorentzian)"), QString("Failed to compute a Trace for peak shape at m/z %1.") .arg(peak_shaper_sp->getPeakCentroid().x, QMessageBox::Ok)); return; } // Now that we have computed the full shape around the centroid, we'll // be able to extract the smallest and greatest mz values of the whole // shape. We will need these two bounds to craft the bins in the // MzIntegrationParams object. if(peak_shaper_sp->getTrace().size()) { double smallestMz = peak_shaper_sp->getTrace().front().x; minMz = std::min(smallestMz, minMz); double greatestMz = peak_shaper_sp->getTrace().back().x; maxMz = std::max(greatestMz, maxMz); } ++processed; } // Now create the actual spectrum by combining all the shapes into a cluster // shape. if(m_config.withBins()) { // Bins were requested. // There are two situations: // 1. The bin size is fixed. // 2. The bin size is dynamically calculated on the basis of the // resolution and of the bin size divisor. double bin_size = m_config.getBinSize(); if(m_config.getBinSizeFixed()) { // The bin size is fixed, easy situation. qDebug() << "The bin size is FIXED."; m_mzIntegrationParams = pappso::MzIntegrationParams( minMz, maxMz, pappso::BinningType::ARBITRARY, -1, pappso::PrecisionFactory::getResInstance(bin_size), 1, // bin size divisor, 1 = no-op true); } else { // The bin size is dynamically calculated. if(m_config.getMassPeakWidthLogic() == libXpertMass::MassPeakWidthLogic::FWHM) { qDebug() << "The mass peak width logic is FWHM."; m_mzIntegrationParams = pappso::MzIntegrationParams( minMz, maxMz, pappso::BinningType::ARBITRARY, -1, pappso::PrecisionFactory::getDaltonInstance(bin_size), 1, true); } else if(m_config.getMassPeakWidthLogic() == libXpertMass::MassPeakWidthLogic::RESOLUTION) { qDebug() << "The mass peak width logic is RESOLUTION."; m_mzIntegrationParams = pappso::MzIntegrationParams( minMz, maxMz, pappso::BinningType::ARBITRARY, -1, pappso::PrecisionFactory::getResInstance( m_config.getResolution()), m_config.getBinSizeDivisor(), true); } else qFatal( "Programming error. At this point, the peak width logic should " "have been set."); } // Now compute the bins. std::vector bins = m_mzIntegrationParams.createBins(); // We will need to perform combinations, positive combinations. pappso::MassSpectrumPlusCombiner mass_spectrum_plus_combiner; mass_spectrum_plus_combiner.setBins(bins); for(auto mass_peak_shaper_sp : m_peakShapers) { mass_spectrum_plus_combiner.combine(m_mapTrace, mass_peak_shaper_sp->getTrace()); } } else { // No bins were required. We can combine simply: qDebug() << "NO BINS requested."; pappso::TracePlusCombiner trace_plus_combiner(-1); for(auto mass_peak_shaper_sp : m_peakShapers) { // The combiner does not handle any data point for which y = 0. // So the result trace does not have a single such point; trace_plus_combiner.combine(m_mapTrace, mass_peak_shaper_sp->getTrace()); } } message(QString("Successfully processed %1 peak centroids").arg(processed)); // This is actually where the normalization needs to be performed. The // user might have asked that the apex of the shape be at a given // intensity. This is actually true when the IsoSpec++-based calculations // have been performed, that is the peak centroids have the expected // intensities normalized to the expected value. But then we have binned // all the numerous peak centroids into bins that are way larger than the // mz_step above. This binning is crucial to have a final spectrum that // actually looks like a real one. But it comes with a drawback: the // intensities of the m/z bins are no more the one of the initial // centroids, they are way larger. So we need to normalize the obtained // trace. m_finalTrace.clear(); if(mp_ui->normalizationGroupBox->isChecked()) { m_normalizingIntensity = mp_ui->normalizingIntensitySpinBox->value(); // qDebug() << "Now normalizing to intensity = " << // m_normalizingIntensity; pappso::Trace trace = m_mapTrace.toTrace(); m_finalTrace = trace.filter( pappso::FilterNormalizeIntensities(m_normalizingIntensity)); // double max_int = normalized_trace.maxYDataPoint().y; // qDebug() << "After normalization max int:" << max_int; } else m_finalTrace = m_mapTrace.toTrace(); mp_ui->resultPlainTextEdit->appendPlainText(m_finalTrace.toString()); // Now switch to the page that contains these results: // Provide the title of the mass spectrum there for the user to have a last // opportunity to change it. mp_ui->massSpectrumNameResultsLineEdit->setText( mp_ui->massSpectrumTitleLineEdit->text()); mp_ui->tabWidget->setCurrentIndex(static_cast(TabWidgetPage::RESULTS)); } /*! \brief Provides the user with a dialog for selecting a file name where to output the results of the computation. */ void IsotopicClusterShaperDlg::outputFileName() { m_fileName = QFileDialog::getSaveFileName( this, tr("Export to text file"), QDir::homePath(), tr("Any file type(*)")); } /*! \brief Crafts a mass spectrum name and emits a signal to display the mass spectrum. */ void IsotopicClusterShaperDlg::displayMassSpectrum() { // We want that the process going on from now on has an unambiguous // file/sample name, so we craft the name here. // Finally, publish the mass spectrum. // qDebug() << "Going to write this trace:" << m_finalTrace.toString(); QString title = craftMassSpectrumName(); emit displayMassSpectrumSignal( title, m_colorByteArray, std::make_shared(m_finalTrace)); } /*! \brief Copies the mass spectrum to the clipboard. The mass spectrum is the result of the combination of all the peak shapes into a single trace. */ void IsotopicClusterShaperDlg::copyMassSpectrumToClipboard() { // Simply copy the results shown in the text edit widget to the clipboard. // We cannot use m_mapTrace for that because if normalization was asked for, // then we would not see that in m_mapTrace (normalization is local). QString trace_text = mp_ui->resultPlainTextEdit->toPlainText(); if(trace_text.isEmpty()) { message("The mass spectrum is empty."); return; } QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(trace_text); } /*! \brief Selects a color for the mass spectrum trace to be plotted. */ void IsotopicClusterShaperDlg::traceColorPushButtonClicked() { // Allow (true) the user to select a color that has been chosen already. QColor color = ColorSelector::chooseColor(true); if(!color.isValid()) return; // Store the color as a QByteArray in the member datum. QDataStream stream(&m_colorByteArray, QIODevice::WriteOnly); stream << color; // Update the color of the button. QPushButton *colored_push_button = mp_ui->colorSelectorPushButton; colorizePushButton(colored_push_button, m_colorByteArray); } /*! \brief Sets the \a isotopic_cluster_sp isotopic cluster that is the starting material for the computations. */ void IsotopicClusterShaperDlg::setIsotopiCluster( pappso::TraceCstSPtr isotopic_cluster_sp) { // Start by copying the centroids locally in the form of a Trace collecting // DataPoint objects. m_isotopicCluster.assign(isotopic_cluster_sp->begin(), isotopic_cluster_sp->end()); // Now create the text to display that in the text edit widget. QString result_as_text; // Convert the data into a string that can be displayed in the text edit // widget. // Reset the value to min so that we can test for new intensities below. double greatestIntensity = std::numeric_limits::min(); for(auto data_point : m_isotopicCluster) { result_as_text += QString("%1 %2\n") .arg(data_point.x, 0, 'f', 30) .arg(data_point.y, 0, 'f', 30); // Store the peak centroid that has the greatest intensity. // Used to compute the FWHM value starting from resolution. And // vice-versa. if(data_point.y > greatestIntensity) { m_referencePeakMz = data_point.x; greatestIntensity = data_point.y; } } // qDebug() << "Reference (most intense) peak's m/z value:" << // m_referencePeakMz; m_config.setReferencePeakMz(m_referencePeakMz); mp_massPeakShaperConfigWidget->setReferencePeakMz(m_referencePeakMz); mp_ui->inputDataPointsPlainTextEdit->setPlainText(result_as_text); } /*! \brief Sets \a title as the mass spectrum title to the line edit widget. */ void IsotopicClusterShaperDlg::setMassSpectrumTitle(const QString &title) { mp_ui->massSpectrumTitleLineEdit->setText(title); } /*! \brief Sets the \a color_byte_array color. Colorizes the push button to provide a visual feedback of the color that was selected. */ void IsotopicClusterShaperDlg::setColorByteArray(QByteArray color_byte_array) { m_colorByteArray = color_byte_array; // Update the color of the button. colorizePushButton(mp_ui->colorSelectorPushButton, color_byte_array); } /*! \brief Colorizes the \a push_button_p push button with the color encoded in \a color_byte_array. */ void IsotopicClusterShaperDlg::colorizePushButton(QPushButton *push_button_p, QByteArray color_byte_array) { QColor color; QDataStream stream(&color_byte_array, QIODevice::ReadOnly); stream >> color; if(color.isValid()) { QPalette palette = push_button_p->palette(); palette.setColor(QPalette::Button, color); push_button_p->setAutoFillBackground(true); push_button_p->setPalette(palette); push_button_p->update(); // Now prepare the color in the form of a QByteArray QDataStream stream(&m_colorByteArray, QIODevice::WriteOnly); stream << color; } } /*! \brief Imports the peak centrois from the text widget and make a pappso::Trace out of them that is then set as the isotopic cluster. \setIsotopiCluster() */ void IsotopicClusterShaperDlg::importFromText() { // We have peak centroids as in the text edit widget // // 59.032643588680002721957862377167 0.021811419157258506856811308694 // // and we want to import them. QString peak_centroids_text = mp_ui->inputDataPointsPlainTextEdit->toPlainText(); // Easily transfom that into a Trace pappso::Trace trace(peak_centroids_text); pappso::TraceSPtr temp_cluster_sp = std::make_shared(); for(auto datapoint : trace) { temp_cluster_sp->append(datapoint); } // Finally do the real import. setIsotopiCluster(temp_cluster_sp); message("The data were imported. Please check the import results."); } /*! \brief Signals that the normalize intensity value has changed. The new value is set to the member datum m_normalizingIntensity. */ void IsotopicClusterShaperDlg::normalizingIntensityValueChanged() { m_normalizingIntensity = mp_ui->normalizingIntensitySpinBox->value(); } /*! \brief Signals that the the normalization group box was toggled. If the group box is checked, the normalizing intensity is read from the spin box widget and set to the member datum m_normalizingIntensity. Otherwise, the member datum m_normalizingIntensity is set to std::numeric_limits::min(). */ void IsotopicClusterShaperDlg::normalizingGrouBoxToggled(bool checked) { if(!checked) m_normalizingIntensity = std::numeric_limits::min(); else m_normalizingIntensity = mp_ui->normalizingIntensitySpinBox->value(); } } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/IsotopicDataTableView.cpp000664 001750 001750 00000011664 14647465366 025547 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Std lib includes #include /////////////////////// Qt includes #include #include #include #include #include #include /////////////////////// libmassincludes #include "libXpertMass/globals.hpp" #include "libXpertMass/Isotope.hpp" /////////////////////// Local includes #include "IsotopicDataTableView.hpp" namespace MsXpS { namespace libXpertMassGui { /*! \class MsXpS::libXpertMassGui::IsotopicDataTableView \inmodule libXpertMassGui \ingroup XpertMassGuiMassCalculations \inheaderfile IsotopicDataTableView.hpp \brief The IsotopicDataTableView class provides a table view widget to display isotopic data. */ /*! \variable IsotopicDataTableView::mp_parent \brief The parent widget. */ /*! \variable IsotopicDataTableView::msp_isotopicData \brief The isotopic data. */ /*! \variable IsotopicDataTableView::m_dragStartPos \brief The start drag position. */ /*! \brief Constructs a IsotopicDataTableView instance with \a parent_p widget. */ IsotopicDataTableView::IsotopicDataTableView(QWidget *parent_p) : QTableView(parent_p), mp_parent(parent_p) { setAlternatingRowColors(true); setTextElideMode(Qt::ElideMiddle); setSortingEnabled(false); setSelectionMode(QAbstractItemView::ContiguousSelection); // Select items, not only rows or columns setSelectionBehavior(QAbstractItemView::SelectItems); QHeaderView *headerView = horizontalHeader(); headerView->setSectionsClickable(true); headerView->setSectionsMovable(true); } /*! \brief Destructs this IsotopicDataTableView instance. */ IsotopicDataTableView::~IsotopicDataTableView() { } /*! \brief Sets the isotopic data \a isotopic_data_sp. */ void IsotopicDataTableView::setIsotopicData(libXpertMass::IsotopicDataSPtr isotopic_data_sp) { msp_isotopicData = isotopic_data_sp; } /*! \brief Returns the isotopic data. */ libXpertMass::IsotopicDataSPtr IsotopicDataTableView::getIsotopicData() { return msp_isotopicData; } /*! \brief Sets the parent widget \a parent_p. */ void IsotopicDataTableView::setParent(QWidget *parent_p) { Q_ASSERT(parent_p); mp_parent = parent_p; } /*! \brief Returns the parent widget. */ QWidget * IsotopicDataTableView::parent() { return mp_parent; } /*! \brief Reacts to a selection change in the table view. The \a selected parameter lists item that have undergone selection during a (de)selection operation, not the items that have been effectively committed to selection (release of key or release of mouse drag). Likewise for \a deselected. This function elaborates a list of selected rows (not selected \e cells) and emits the selectedRowsChangedSignal with the set of selected rows. */ void IsotopicDataTableView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { // The selected selection parameter lists item that have undergone selection // during a (de)selection operation, not the items that have been effectively // committed to selection (release of key or release of mouse drag). emit selectionChangedSignal(selected, deselected); // Take advantage to emit a signal with a list of effectively committed // selected indices. emit allSelectedIndicesSignal(selectedIndexes()); // Finally, take advantage to actually determine which unique rows do harbor // the selected indices (selection indices might contain multiple cells from a // given single row!) QModelIndexList selected_indices = selectedIndexes(); std::set rows_set; for(int iter = 0; iter < selected_indices.size(); ++iter) { rows_set.insert(selected_indices.at(iter).row()); } emit selectedRowsChangedSignal(rows_set); } } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/IsotopicDataTableViewModel.cpp000664 001750 001750 00000047761 14647465366 026537 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Std lib includes #include /////////////////////// Qt includes /////////////////////// libmass includes #include "libXpertMass/Isotope.hpp" /////////////////////// Local includes #include "IsotopicDataTableViewModel.hpp" #include "IsotopicClusterGeneratorDlg.hpp" namespace MsXpS { namespace libXpertMassGui { /*! \class MsXpS::libXpertMassGui::IsotopicDataTableViewModel \inmodule libXpertMassGui \ingroup XpertMassGuiMassCalculations \inheaderfile IsotopicDataTableViewModel.hpp \brief The IsotopicDataTableViewModel class provides a table view widget to display isotopic data. */ /*! \variable MsXpS::libXpertMassGui::IsotopicDataTableViewModel::mp_parent \brief The parent widget. */ /*! \variable MsXpS::libXpertMassGui::IsotopicDataTableViewModel::msp_isotopicData \brief The isotopic data. */ /*! \variable MsXpS::libXpertMassGui::IsotopicDataTableViewModel::mp_tableView \brief The table view widget. */ /*! \brief Constructs a IsotopicDataTableViewModel instance. \list \li \a parent_p: the parent widget. \li \a isotopic_data_sp: the isotopic data. \endlist */ IsotopicDataTableViewModel::IsotopicDataTableViewModel( QObject *parent_p, libXpertMass::IsotopicDataSPtr isotopic_data_sp) : QAbstractTableModel(parent_p), msp_isotopicData(isotopic_data_sp) { if(!parent_p) qFatal("Programming error."); mp_parent = dynamic_cast(parent_p); connect(this, &IsotopicDataTableViewModel::editCompletedSignal, this, &IsotopicDataTableViewModel::editCompleted); } /*! \brief Destructs this IsotopicDataTableViewModel instance. */ IsotopicDataTableViewModel::~IsotopicDataTableViewModel() { } /*! \brief Returns the parent widget. */ const QWidget * IsotopicDataTableViewModel::parent() const { return mp_parent; } /*! \brief Sets the table view widget \a table_view_p. */ void IsotopicDataTableViewModel::setTableView(IsotopicDataTableView *table_view_p) { if(!table_view_p) qFatal("Programming error."); mp_tableView = table_view_p; } /*! \brief Returns the table view widget. */ IsotopicDataTableView * IsotopicDataTableViewModel::tableView() { return mp_tableView; } /*! \brief Returns the count of rows in this table view. \a parent is unused. */ int IsotopicDataTableViewModel::rowCount([[maybe_unused]] const QModelIndex &parent) const { return msp_isotopicData->size(); } /*! \brief Returns the count of columns in this table view. \a parent is unused. */ int IsotopicDataTableViewModel::columnCount([[maybe_unused]] const QModelIndex &parent) const { return static_cast(libXpertMass::IsotopeFields::LAST); } /*! \brief Returns true if the row could be inserted at reference model index \a parent. The insertion occurs at the index right before \a row such that the newly inserted row will be effectively at \a row index. The cells of the inserted row will be set with default values. */ bool IsotopicDataTableViewModel::insertRow(int row, const QModelIndex &parent) { // The insertion occurs at index right *before* the row parameter. int first = row; int last = first; qDebug() << "Preparing to insert before index:" << row; beginInsertRows(parent, first, last); libXpertMass::IsotopeSPtr isotope_sp = std::make_shared( 0, "NOT_SET", "NOT_SET", 0, 0.000000000, 0, 0, 1.00000000, 0, false); // false: do not update the mass maps as the newly inserted isotope is fake. // insert above row in the isotope vector. bool res = msp_isotopicData->insertNewIsotope(isotope_sp, row, false); endInsertRows(); // Because we want to edit the newly inserted row, select it. mp_tableView->selectRow(row); emit modifiedSignal(); return res; } /*! \brief Returns the header data for \a section. If \a orientation is Qt::Vertical returns the row number; if \a orientation is Qt::Horizontal, returns the text in the header. If \a role is not Qt::DisplayRole, returns QVariant(). */ QVariant IsotopicDataTableViewModel::headerData(int section, Qt::Orientation orientation, int role) const { if(role != Qt::DisplayRole) return QVariant(); if(orientation == Qt::Vertical) { // Return the row number. QString valueString; valueString.setNum(section); } else if(orientation == Qt::Horizontal) { // Return the header of the column. switch(section) { case static_cast(libXpertMass::IsotopeFields::ID): return "Id"; case static_cast(libXpertMass::IsotopeFields::ELEMENT): return "Element"; case static_cast(libXpertMass::IsotopeFields::SYMBOL): return "Symbol"; case static_cast(libXpertMass::IsotopeFields::ATOMIC_NUMBER): return "Atomic N°"; case static_cast(libXpertMass::IsotopeFields::MASS): return "Mass"; case static_cast(libXpertMass::IsotopeFields::MASS_NUMBER): return "Mass N°"; case static_cast(libXpertMass::IsotopeFields::EXTRA_NEUTRONS): return "Extra neutrons"; case static_cast(libXpertMass::IsotopeFields::PROBABILITY): return "Prob."; case static_cast(libXpertMass::IsotopeFields::LN_PROBABILITY): return "Log prob."; case static_cast(libXpertMass::IsotopeFields::RADIOACTIVE): return "Radio."; default: qFatal("Programming error."); } } // Should never get here. return QVariant(); } /*! \brief Returns the data for cell at \a index, for \a role. If \a role is Qt::TextAlignmentRole, returns (Qt::AlignRight | Qt::AlignVCenter). If \a role is Qt::DisplayRole, returns the text in the cell described in \a index in the form of a QVariant. */ QVariant IsotopicDataTableViewModel::data(const QModelIndex &index, int role) const { if(!index.isValid()) return QVariant(); if(role == Qt::TextAlignmentRole) { return int(Qt::AlignRight | Qt::AlignVCenter); } else if(role == Qt::DisplayRole) { int row = index.row(); int column = index.column(); // Let's get the data for the right column and the right // row. Let's find the row first, so that we get to the proper // entity. libXpertMass::IsotopeSPtr isotope_sp = msp_isotopicData->getIsotopes().at(row); // Now see what's the column that is asked for. Prepare a // string that we'll feed with the right data before returning // it as a QVariant. QString valueString; if(column == static_cast(libXpertMass::IsotopeFields::ID)) { valueString.setNum(isotope_sp->getId()); } else if(column == static_cast(libXpertMass::IsotopeFields::ELEMENT)) { valueString = isotope_sp->getElement(); } else if(column == static_cast(libXpertMass::IsotopeFields::SYMBOL)) { valueString = isotope_sp->getSymbol(); } else if(column == static_cast(libXpertMass::IsotopeFields::ATOMIC_NUMBER)) { valueString.setNum(isotope_sp->getAtomicNo()); } else if(column == static_cast(libXpertMass::IsotopeFields::MASS)) { valueString = QString("%1").arg(isotope_sp->getMass(), 0, 'f', 12); } else if(column == static_cast(libXpertMass::IsotopeFields::MASS_NUMBER)) { valueString.setNum(isotope_sp->getMassNo()); } else if(column == static_cast(libXpertMass::IsotopeFields::EXTRA_NEUTRONS)) { valueString.setNum(isotope_sp->getExtraNeutrons()); } else if(column == static_cast(libXpertMass::IsotopeFields::PROBABILITY)) { valueString = QString("%1").arg(isotope_sp->getProbability(), 0, 'f', 60); } else if(column == static_cast(libXpertMass::IsotopeFields::LN_PROBABILITY)) { valueString = QString("%1").arg(isotope_sp->getLnProbability(), 0, 'f', 60); } else if(column == static_cast(libXpertMass::IsotopeFields::RADIOACTIVE)) { valueString = isotope_sp->getRadioactive() ? "true" : "false"; } else { qFatal("Programming error."); } return valueString; } // End of // else if(role == Qt::DisplayRole) return QVariant(); } /*! \brief Set the data to \a value for the cell described by \a index. If \a role is not Qt::EditRole, returns false. Returns true if \a value could be converted to the right type depending on the column of the cell, true otherwise. The data in the cell described by \a index is read, converted to the right type and set to the matching isotope instance. The row datum in \a index points to the index of the isotope in the isotopica data. If conversion from QVariant to the proper data type fails, returns false, otherwise returns true. */ bool IsotopicDataTableViewModel::setData(const QModelIndex &index, const QVariant &value, int role) { if(role != Qt::EditRole) return false; if(!checkIndex(index)) return false; // We want to access the isotope in the isotopic data. This is done by using // the index, that is, the row. int isotopic_data_index = index.row(); int column = index.column(); // Store the original isotope pointer! libXpertMass::IsotopeSPtr old_isotope_sp = msp_isotopicData->getIsotopes().at(isotopic_data_index); // Prepare a copy that we'll modify the proper field of. libXpertMass::IsotopeSPtr new_isotope_sp = std::make_shared(*old_isotope_sp); // Now determine what is the column of the cell (that is, the field) that was // modified and modify the new_isotope_sp accordingly. bool ok = false; if(column == static_cast(libXpertMass::IsotopeFields::ID)) { int new_value = value.toInt(&ok); if(!ok) return false; new_isotope_sp->setId(new_value); } else if(column == static_cast(libXpertMass::IsotopeFields::ELEMENT)) { new_isotope_sp->setElement(value.toString()); } else if(column == static_cast(libXpertMass::IsotopeFields::SYMBOL)) { new_isotope_sp->setSymbol(value.toString()); } else if(column == static_cast(libXpertMass::IsotopeFields::ATOMIC_NUMBER)) { int new_value = value.toInt(&ok); if(!ok) return false; new_isotope_sp->setAtomicNo(new_value); } else if(column == static_cast(libXpertMass::IsotopeFields::MASS)) { double new_value = value.toDouble(&ok); if(!ok) return false; new_isotope_sp->setMass(new_value); } else if(column == static_cast(libXpertMass::IsotopeFields::MASS_NUMBER)) { int new_value = value.toInt(&ok); if(!ok) return false; new_isotope_sp->setMassNo(new_value); } else if(column == static_cast(libXpertMass::IsotopeFields::EXTRA_NEUTRONS)) { int new_value = value.toInt(&ok); if(!ok) return false; new_isotope_sp->setExtraNeutrons(new_value); } else if(column == static_cast(libXpertMass::IsotopeFields::PROBABILITY)) { double new_value = value.toDouble(&ok); if(!ok) return false; new_isotope_sp->setProbability(new_value); // How about computing the prob log? We could emit the right signal for // the user of this model to make profit of it. Attention, ths is the // ln(), not log10! new_isotope_sp->setLnProbability(std::log(new_value)); } else if(column == static_cast(libXpertMass::IsotopeFields::LN_PROBABILITY)) { double new_value = value.toDouble(&ok); if(!ok) return false; new_isotope_sp->setLnProbability(new_value); // How about computing the prob? Attention, ths is the exp() not the // 10^()! //new_isotope_sp->setProbability(std::exp(new_value)); } else if(column == static_cast(libXpertMass::IsotopeFields::RADIOACTIVE)) { bool new_value = value.toBool(); new_isotope_sp->setRadioactive(new_value); } else { qFatal("Programming error."); } // At this point we know the new isotope has fine data in it, just replace the // old with the new. msp_isotopicData->replace(old_isotope_sp, new_isotope_sp); // emit editCompleted(new_isotope_sp->toString()); // Not sure if this is really necessary. emit(dataChanged(index, index)); emit modifiedSignal(); return true; } void IsotopicDataTableViewModel::refreshAfterNewData() { // This is bad code to ensure that the table view items are refreshed // throughout all the data set. QModelIndex parent; beginInsertRows(parent, 0, rowCount() - 1); endInsertRows(); emit modifiedSignal(); } /*! \brief Removes all the rows from the table view. Returns true if rows were effectively removed, false otherwise. */ bool IsotopicDataTableViewModel::clearAllData() { // How many rows do we have? int row_count = rowCount(); if(!row_count) // We did not remove any Isotope from the IsotopicData return false; // The begin_index and end_index are *inclusive* in the beginRemoveRows() // call below. beginRemoveRows(QModelIndex(), 0, row_count - 1); // true: update the maps because we need to clear all the items there. msp_isotopicData->eraseIsotopes(0, row_count - 1, true); endRemoveRows(); emit modifiedSignal(); // We did remove items. return true; } /*! \brief Returns the flags for the \a index. The returned flags include Qt::ItemIsEditable. */ Qt::ItemFlags IsotopicDataTableViewModel::flags(const QModelIndex &index) const { return Qt::ItemIsEditable | QAbstractTableModel::flags(index); } /*! \brief Signals that the editing of a cell is completed with final \a text. */ void IsotopicDataTableViewModel::editCompleted(const QString &text) { qDebug() << "After editing the new isotope:" << text; emit modifiedSignal(); } /*! \brief Inserts a new row above the current index. */ void IsotopicDataTableViewModel::insertIsotopeRowAbove() { // We need to maintain a perfect correlation between the data in the // IsotopicData object and the rows in the table view. int insertion_row_index = 0; // We want to add a row above the current index. Note that all the functions // dealing with models and containers insert above the index provided to the // insertion function. int row_count = rowCount(); if(!row_count) { // qDebug() << "There are no isotopes."; // We do not care, because we cannot, if there is a current index. } else { // qDebug() << "There are currently" << row_count << "isotopes in the // table"; // In this case, we want to know if there is a current index. QItemSelectionModel *selection_model = mp_tableView->selectionModel(); QModelIndex current_index = selection_model->currentIndex(); if(!current_index.isValid()) { return; } insertion_row_index = current_index.row(); // qDebug() << "The current index row is at index" << insertion_row_index; } // insertRow() inserts a single row before the given row in the child items of // the parent specified. This means that we just use that row index without // modifying it. insertRow(insertion_row_index); emit modifiedSignal(); } /*! \brief Inserts a new row below the current index. */ void IsotopicDataTableViewModel::insertIsotopeRowBelow() { // We need to maintain a perfect correlation between the data in the // IsotopicData object and the rows in the table view. int insertion_row_index = 0; // We want to add a row below the current index, but beware that all the // functions dealing with models and containers insert *above* the index // provided to the insertion function. int row_count = rowCount(); if(!row_count) { // qDebug() << "There are no isotopes."; // We do not care, because we cannot, if there is a current index. } else { // qDebug() << "There are currently" << row_count << "isotopes in the // table"; // In this case, we want to know if there is a current index. QItemSelectionModel *selection_model = mp_tableView->selectionModel(); QModelIndex current_index = selection_model->currentIndex(); if(!current_index.isValid()) { return; } insertion_row_index = current_index.row(); // qDebug() << "The current index row is at index" << insertion_row_index; } // insertRow() inserts a single row before the given row in the child items of // the parent specified. This means that we'll have to increment the // insertion_row_index by 1. ++insertion_row_index; insertRow(insertion_row_index); emit modifiedSignal(); } /*! \brief Removes all the selected rows. */ void IsotopicDataTableViewModel::removeSelected() { // We need to maintain a perfect correlation between the data in the // IsotopicData object and the rows in the table view. // int row_count = rowCount(); // qDebug() << "There are currently" << row_count //<< "isotopes in the table view model."; QItemSelectionModel *selection_model = mp_tableView->selectionModel(); QModelIndexList selected_indices = selection_model->selectedIndexes(); // qDebug() << "The number of selected indices:" << selected_indices.size(); // We know that the indices list might contain multiple indices for the same // row. We need to extract a unique list of rows for which at least one index // is selected. Then, we'll use that list of rows to perform the removal. std::set selected_rows; for(int iter = 0; iter < selected_indices.size(); ++iter) { selected_rows.insert(selected_indices.at(iter).row()); } if(!selected_rows.size()) { // qDebug() << "There are not selected rows in the table view."; return; } int begin_index = *selected_rows.begin(); // The end index is at position end() - 1 of the std::set. int end_index = *std::prev(selected_rows.end()); // qDebug() << "The rows to be removed are in the fully inclusive index range: // [" //<< begin_index << "-" << end_index << "]."; // The begin_index and end_index are *inclusive* in the beginRemoveRows() // call below. beginRemoveRows(QModelIndex(), begin_index, end_index); // true: update the maps because the remaining isotopes are valid. msp_isotopicData->eraseIsotopes(begin_index, end_index, true); endRemoveRows(); emit modifiedSignal(); // Now select the item that was right below the last selected removed item. if(!rowCount()) { // Do nothing. } else { if(begin_index >= rowCount()) { begin_index = rowCount() - 1; } } mp_tableView->selectRow(begin_index); } } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/MassDataClientServerConfigDlg.cpp000664 001750 001750 00000015125 14647465366 027155 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// Std lib includes /////////////////////// Qt includes #include #include #include /////////////////////// Local includes #include "MassDataClientServerConfigDlg.hpp" #include "ui_MassDataClientServerConfigDlg.h" namespace MsXpS { namespace libXpertMassGui { /*! \class MsXpS::libXpertMassGui::MassDataClientServerConfigDlg \inmodule libXpertMassGui \ingroup XpertMassGuiUtilities \inheaderfile MassDataClientServerConfigDlg.hpp \brief The MassDataClientServerConfigDlg class provides a mass data client-server network connection configuration dialog window. */ /*! \variable MassDataClientServerConfigDlg::mp_ui \brief The graphical user interface definition. */ /*! \variable MassDataClientServerConfigDlg::m_applicationName \brief The name of the application. */ /*! \variable MassDataClientServerConfigDlg::mp_programWindow \brief The main program window. */ /*! \brief Constructs a MassDataClientServerConfigDlg instance. \list \li \a program_window_p: the program's main window. \li \a applicationName: the name of the application, typically massXpert2 or mineXpert2, for example. \li \a description: the string describing what this dialog window is for (used for the window title). \endlist */ MassDataClientServerConfigDlg::MassDataClientServerConfigDlg( QWidget *program_window_p, const QString &applicationName, const QString &description) : QDialog(program_window_p), m_applicationName{applicationName}, mp_programWindow(program_window_p), mp_ui(new Ui::MassDataClientServerConfigDlg) { if(!program_window_p) qFatal("Programming error."); mp_ui->setupUi(this); // Update the window title because the window title element in mp_ui->might be // either erroneous or empty. setWindowTitle(QString("%1 - %2").arg(applicationName).arg(description)); //// We want to destroy the dialog when it is closed. // setAttribute(Qt::WA_DeleteOnClose); // Perform all the connections. // Typically, the user of startServerSignal is ProgramWindow that created this // dialog window. connect(mp_ui->startServerPushButton, &QPushButton::clicked, this, [this]() { emit startServerSignal(); }); // Typically, the user of stopServerSignal is ProgramWindow that created this connect(mp_ui->stopServerPushButton, &QPushButton::clicked, this, [this]() { emit stopServerSignal(); }); connect(mp_ui->startClientPushButton, &QPushButton::clicked, this, &MassDataClientServerConfigDlg::startClient); connect(mp_ui->stopClientPushButton, &QPushButton::clicked, this, [this]() { emit stopClientSignal(); }); connect( mp_ui->connectionFrequencySlider, &QSlider::valueChanged, [this](int value) { message(QString("Set the connection frequency to %1 times per second") .arg(value)); }); } /*! \brief Destructs this MassDataClientServerConfigDlg instance. */ MassDataClientServerConfigDlg::~MassDataClientServerConfigDlg() { delete mp_ui; } /*! \brief Does not close the window, just hides it (\a event is not used). */ void MassDataClientServerConfigDlg::closeEvent([[maybe_unused]] QCloseEvent *event) { qDebug(); hide(); } /*! \brief Starts the client using the configuration entered in the dialog window. */ void MassDataClientServerConfigDlg::startClient() { // We need to know what is the requested configuration. QString ip_address = mp_ui->remoteServerIpAddressLineEdit->text(); if(ip_address.isEmpty()) { message("Please, configure the remote server IP address."); return; } QString port_number_string = mp_ui->remoteServerPortNumberLineEdit->text(); if(port_number_string.isEmpty()) { message("Please, configure the remote server port number"); return; } bool ok = false; int port_number = port_number_string.toInt(&ok); if(!ok) { message( "Please, configure the remote server port number (that should be an " "integer)"); return; } qDebug() << "Asking that a client be started to connect to a remote server:" << ip_address << ":" << port_number; int connection_frequency = mp_ui->connectionFrequencySlider->value(); // Typically the user of startClientSignal is ProgramWindow that created this // dialog window. emit startClientSignal(ip_address, port_number, connection_frequency); } /*! \brief Writes the \a message to the message line edit widget. After \a timeout milliseconds, the message is erased. By default that timeout is 3000 ms. */ void MassDataClientServerConfigDlg::message(const QString &message, int timeout) { if(mp_ui->messageLineEdit->text() == message) return; mp_ui->messageLineEdit->setText(message); QTimer::singleShot(timeout, [this]() { mp_ui->messageLineEdit->setText(""); }); } /*! \brief Update the client configuration on the basis of \a ip_address and \a port_number. */ void MassDataClientServerConfigDlg::updateClientConfigurationData( const QString &ip_address, int port_number) { mp_ui->localServerIpAddressLineEdit->setText(ip_address); mp_ui->localServerPortNumberLineEdit->setText(QString::number(port_number)); } /*! \brief Writes the \a error to the message line edit widget. After a imeout of 10 seconds, the message is erased. */ void MassDataClientServerConfigDlg::clientFailingFeedback(const QString &error) { qDebug(); message(error, 10000); } } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/MassPeakShaperConfigDlg.cpp000664 001750 001750 00000015521 14647465366 026001 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// StdLib includes #include #include #include // for std::numeric_limits /////////////////////// Qt includes #include #include /////////////////////// libmass includes #include "libXpertMass/globals.hpp" #include "libXpertMass/MassPeakShaperConfig.hpp" #include "libXpertMass/MassDataCborMassSpectrumHandler.hpp" /////////////////////// pappsomspp includes #include #include #include #include #include /////////////////////// Local includes #include "ui_MassPeakShaperConfigDlg.h" #include "MassPeakShaperConfigDlg.hpp" namespace MsXpS { namespace libXpertMassGui { /*! \class MsXpS::libXpertMassGui::MassPeakShaperConfigDlg \inmodule libXpertMassGui \ingroup XpertMassGuiMassCalculations \inheaderfile MassPeakShaperConfigDlg.hpp \brief The MassPeakShaperConfigDlg class provides a graphical user interface for the configuration of the mass peak shaping process. The dialog window contains all the widgets required to fully configure the mass peak shaping process. */ /*! \variable MassPeakShaperConfigDlg::mp_ui \brief The graphical user interface definition. */ /*! \variable MassPeakShaperConfigDlg::mp_massPeakShaperConfigWidget \brief The widget holding the configuration of the mass peak shaping procedure. */ /*! \variable MassPeakShaperConfigDlg::mp_parent \brief The parent widget. */ /*! \variable MassPeakShaperConfigDlg::m_applicationName \brief The name of the application. */ /*! \variable MassPeakShaperConfigDlg::m_config \brief The \l{libXpertMass::MassPeakShaperConfig} configuration for the mass peak shaping process. */ /*! \brief Constructs a MassPeakShaperConfigDlg instance. \list \li \a parent_p: the parent widget. \li \a applicationName: the name of the application, typically massXpert2 or mineXpert2, for example. \li \a description: the string describing what this dialog window is for (used for the window title). \endlist */ MassPeakShaperConfigDlg::MassPeakShaperConfigDlg(QWidget *parent_p, const QString &applicationName, const QString &description) : QDialog(static_cast(parent_p)), mp_ui(new Ui::MassPeakShaperConfigDlg), mp_parent(parent_p), m_applicationName{applicationName} { if(!parent_p) qFatal("Programming error. Program aborted."); mp_ui->setupUi(this); // Update the window title because the window title element in m_ui might be // either erroneous or empty. setWindowTitle(QString("%1 - %2").arg(applicationName).arg(description)); setupDialog(); } /*! \brief Upon closing of the dialog window writes the settings of the dialog windows (\a event is unused). \sa writeSettings() */ void MassPeakShaperConfigDlg::closeEvent([[maybe_unused]] QCloseEvent *event) { // qDebug(); writeSettings(libXpertMass::configSettingsFilePath(m_applicationName)); } /*! \brief Destructs this MassPeakShaperConfigDlg instance. */ MassPeakShaperConfigDlg::~MassPeakShaperConfigDlg() { // qDebug(); writeSettings(libXpertMass::configSettingsFilePath(m_applicationName)); } /*! \brief Writes the settings of the dialog window to \a config_settings_file_path for later restoration. */ void MassPeakShaperConfigDlg::writeSettings(const QString &config_settings_file_path) { // qDebug(); // First save the config widget state. mp_massPeakShaperConfigWidget->writeSettings(config_settings_file_path); QSettings settings(config_settings_file_path, QSettings::IniFormat); settings.beginGroup("MassPeakShaperConfigDlg"); settings.setValue("geometry", saveGeometry()); settings.endGroup(); } /*! \brief Reads the settings of this dialog window from \a config_settings_file_path. */ void MassPeakShaperConfigDlg::readSettings(const QString &config_settings_file_path) { // qDebug(); // First read the config widget state. mp_massPeakShaperConfigWidget->readSettings(config_settings_file_path); QSettings settings(config_settings_file_path, QSettings::IniFormat); settings.beginGroup("MassPeakShaperConfigDlg"); restoreGeometry(settings.value("geometry").toByteArray()); settings.endGroup(); } /*! \brief Sets up this dialog window. */ void MassPeakShaperConfigDlg::setupDialog() { // We want to destroy the dialog when it is closed. // setAttribute(Qt::WA_DeleteOnClose); // Setup the peak shaper configuration widget inside of the frame QVBoxLayout *v_box_layout_p = new QVBoxLayout(this); // Trasmit the config member object reference so that it can be modified by // the configuration widget. mp_massPeakShaperConfigWidget = new MassPeakShaperConfigWidget(this, m_config); v_box_layout_p->addWidget(mp_massPeakShaperConfigWidget); mp_ui->peakShapeConfigFrame->setLayout(v_box_layout_p); connect(mp_massPeakShaperConfigWidget, &MassPeakShaperConfigWidget::updatedMassPeakShaperConfigSignal, [this](const libXpertMass::MassPeakShaperConfig &config) { m_config = config; //qDebug().noquote() << "Now got the config:" << m_config.toString(); // Relay the config to the user of this dialog window. emit updatedMassPeakShaperConfigSignal(m_config); }); // The values above are actually set in readSettings (with default values if // missing in the config settings.) readSettings(libXpertMass::configSettingsFilePath(m_applicationName)); } } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/MassPeakShaperConfigWidget.cpp000664 001750 001750 00000062213 14650443644 026503 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// StdLib includes #include #include #include // for std::numeric_limits /////////////////////// Qt includes #include /////////////////////// pappsomspp includes /////////////////////// libmass includes #include "libXpertMass/globals.hpp" #include "libXpertMass/MassPeakShaperConfig.hpp" /////////////////////// Local includes #include "ui_MassPeakShaperConfigWidget.h" #include "MassPeakShaperConfigWidget.hpp" namespace MsXpS { namespace libXpertMassGui { /*! \class MsXpS::libXpertMassGui::MassPeakShaperConfigWidget \inmodule libXpertMassGui \ingroup XpertMassGuiMassCalculations \inheaderfile MassPeakShaperConfigWidget.hpp \brief The MassPeakShaperConfigWidget class provides a widget for the configuration of the mass peak shaping process. This composite widget contains all the widgets and all the logic required to fully configure the mass peak shaping process. */ /*! \variable MassPeakShaperConfigWidget::mp_ui \brief The graphical user interface definition. */ /*! \variable MassPeakShaperConfigWidget::mp_parent \brief The parent widget. */ /*! \variable MassPeakShaperConfigWidget::m_referencePeakMz \brief The reference m/z value used for peak width calculations. This reference m/z value is used to compute the full width at half maximum of the shaped peak on the basis of the resolution and backwards. */ /*! \variable MassPeakShaperConfigWidget::m_config \brief The configuration of the pass peak shaping process. */ /*! \variable MassPeakShaperConfigWidget::m_normalizingIntensity \brief The intensity value that is used to normalize the peak height.. */ /*! \variable MassPeakShaperConfigWidget::msp_msgTimer \brief The timer object used to clear messages to the messages line edit box. */ /*! \brief Constructs a MassPeakShaperConfigWidget instance. \list \li \a parent_p: the parent widget. \li \a config: the configuration of the mass peak shaping process \endlist */ MassPeakShaperConfigWidget::MassPeakShaperConfigWidget( QWidget *parent_p, libXpertMass::MassPeakShaperConfig &config) : QWidget(parent_p), mp_ui(new Ui::MassPeakShaperConfigWidget), m_config(config) { qDebug() << "Creating MassPeakShaperConfigWidget"; if(!parent_p) qFatal("Programming error. Program aborted."); setupWidget(); } /*! \brief Destructs this MassPeakShaperConfigWidget instance. */ MassPeakShaperConfigWidget::~MassPeakShaperConfigWidget() { // The timer should not try to access a deleted widget! qDebug() << "Stopping the timer."; if(msp_msgTimer.get()) msp_msgTimer.reset(); delete mp_ui; } /*! \brief Writes the settings of this widget to \a config_settings_file_path for later restoration. */ void MassPeakShaperConfigWidget::writeSettings(const QString &config_settings_file_path) { QSettings settings(config_settings_file_path, QSettings::IniFormat); settings.beginGroup("MassPeakShaperConfigWidget"); settings.setValue("pointCount", mp_ui->pointCountSpinBox->value()); settings.setValue("resolution", mp_ui->resolutionSpinBox->value()); settings.setValue("fwhm", mp_ui->fwhmDoubleSpinBox->value()); settings.setValue("binSize", mp_ui->binSizeDoubleSpinBox->value()); settings.setValue("binSizeGroupBoxState", mp_ui->binSizeGroupBox->isChecked()); settings.setValue("fmwhToBinSizeDivisor", mp_ui->fmwhToBinSizeDivisorSpinBox->value()); settings.endGroup(); } /*! \brief Reads the settings of this widget from \a config_settings_file_path. */ void MassPeakShaperConfigWidget::readSettings(const QString &config_settings_file_path) { // qDebug(); QSettings settings(config_settings_file_path, QSettings::IniFormat); settings.beginGroup("MassPeakShaperConfigWidget"); restoreGeometry(settings.value("geometry").toByteArray()); mp_ui->pointCountSpinBox->setValue(settings.value("pointCount", 150).toInt()); mp_ui->resolutionSpinBox->setValue(settings.value("resolution", 45000).toInt()); mp_ui->fwhmDoubleSpinBox->setValue(settings.value("fwhm", 0).toDouble()); mp_ui->binSizeGroupBox->setChecked( settings.value("binSizeGroupBoxState", 0).toInt()); mp_ui->binSizeDoubleSpinBox->setValue(settings.value("binSize", 0).toDouble()); mp_ui->fmwhToBinSizeDivisorSpinBox->setValue( settings.value("fwhmToBinSizeDivisor", 6).toInt()); settings.endGroup(); } /*! \brief Sets the reference m/z value to \a mz. */ void MassPeakShaperConfigWidget::setReferencePeakMz(double mz) { m_referencePeakMz = mz; m_config.setReferencePeakMz(mz); mp_ui->referenceMassLineEdit->setText( QString::number(m_referencePeakMz, 'f', 12)); } /*! \brief Returns the reference m/z value. */ double MassPeakShaperConfigWidget::getReferencePeakMz() { if(m_referencePeakMz <= 0) { bool ok = false; m_referencePeakMz = mp_ui->referenceMassLineEdit->text().toDouble(&ok); if(!ok) { qDebug() << "Failed to convert string to double."; return 0; } } return m_referencePeakMz; } /*! \brief Sets up this widget. */ void MassPeakShaperConfigWidget::setupWidget() { mp_ui->setupUi(this); // Ranges for various numerical values mp_ui->resolutionSpinBox->setRange(0, 2000000); mp_ui->fwhmDoubleSpinBox->setRange(0, 10); mp_ui->pointCountSpinBox->setRange(5, 1000); // By default we want a gaussian-type shape. m_config.setMassPeakShapeType(libXpertMass::MassPeakShapeType::GAUSSIAN); mp_ui->gaussianRadioButton->setChecked(true); // Get the number of points used to craft the peak curve. m_config.setPointCount(mp_ui->pointCountSpinBox->value()); // Set the message timer to be singleShot. This time is used to erase the text shown in the message line edit widget after 3 seconds. msp_msgTimer->setInterval(3000); msp_msgTimer->setSingleShot(true); connect(msp_msgTimer.get(), &QTimer::timeout, [this] (){ mp_ui->messageLineEdit->setText("");}); connect(mp_ui->referenceMassLineEdit, &QLineEdit::textEdited, this, &MassPeakShaperConfigWidget::referencePeakMzEdited); connect(mp_ui->fmwhToBinSizeDivisorSpinBox, &QSpinBox::valueChanged, this, &MassPeakShaperConfigWidget::fwhmBinSizeDivisorValueChanged); connect(mp_ui->resolutionSpinBox, &QSpinBox::editingFinished, this, &MassPeakShaperConfigWidget::resolutionEditingFinished); connect(mp_ui->fwhmDoubleSpinBox, &QSpinBox::editingFinished, this, &MassPeakShaperConfigWidget::fwhmEditingFinished); connect(mp_ui->pointCountSpinBox, &QSpinBox::editingFinished, this, &MassPeakShaperConfigWidget::pointCountEditingFinished); connect(mp_ui->gaussianRadioButton, &QRadioButton::toggled, this, &MassPeakShaperConfigWidget::gaussianRadioButtonToggled); connect(mp_ui->lorentzianRadioButton, &QRadioButton::toggled, this, &MassPeakShaperConfigWidget::lorentzianRadioButtonToggled); connect(mp_ui->checkParametersPushButton, &QPushButton::clicked, this, &MassPeakShaperConfigWidget::checkParameters); connect(mp_ui->noBinsCheckBox, &QCheckBox::stateChanged, this, &MassPeakShaperConfigWidget::noBinsCheckBoxStateChanged); } /*! \brief Signals that the check box widget has changed \a state. */ void MassPeakShaperConfigWidget::noBinsCheckBoxStateChanged(int state) { if(state == Qt::Checked) { // The user does not want bins to be used. mp_ui->binSizeLogicGroupBox->setEnabled(false); mp_ui->binSizeDoubleSpinBox->setEnabled(false); } else { mp_ui->binSizeLogicGroupBox->setEnabled(true); mp_ui->binSizeDoubleSpinBox->setEnabled(true); } } /*! \brief Determines the bin configuration from the data entered by the user. Returns true if the calculations succeeded, false otherwise. */ bool MassPeakShaperConfigWidget::processBinSizeConfig() { // We want to understand what the user is expecting: create bins or not, // and do they want to have the bin size configured manually or do they // expect that the bin size be determined automatically. double bin_size = 0; bool set_bin_size_manually = mp_ui->binSizeGroupBox->isChecked(); bool no_bins = mp_ui->noBinsCheckBox->isChecked(); bool ok = false; // First off, report the usage of bins. If no bins are to be calculated, // then it is not necessary to bother with the fwhm / bin_size factor (see // below). if(no_bins) { // qDebug() << "The user requests no bins."; m_config.setWithBins(false); m_config.setBinSize(0); return true; } else { m_config.setWithBins(true); // qDebug() << "The user requests bins, either automatic of manual."; if(set_bin_size_manually) { // We are checking the manual settings configuration. // qDebug() << "Configuring the bin size by direct setting."; m_config.setBinSizeFixed(true); // Since the bin size is set manually, let the config object know that // it does not need to perform any computation. m_config.setBinSizeDivisor(1); // Since the user checked the group box and they did not check the "no // bins" checkbox, then that means that they want to // actually set the bin size. bin_size = mp_ui->binSizeDoubleSpinBox->value(); if(!bin_size) { qDebug() << "The bin size cannot be 0."; message("The bin size cannot be 0."); return false; } m_config.setBinSize(bin_size); // qDebug() << "Bin size set manually:" << bin_size; } else { // qDebug() << "Configuring the bin size by calculation."; // The user did not elect to set the bin size manually, we can // compute it and update the value in its spin box. // The mass peak shaper will compute the bin size simply by dividing // FWHM by that factor. The idea is that it takes roughly that value // data points to craft a peak that has that FWHM. int divisor = mp_ui->fmwhToBinSizeDivisorSpinBox->value(); if(!divisor) { qDebug() << "The FWHM / bin size divisor cannot be 0."; message( "The FWHM / bin size divisor cannot be 0. Please, fix it."); return false; } // That divisor will be accounted for in the config when // asked to compute the bin size = fwhm / divisor. An empirically good // value is 6. m_config.setBinSizeDivisor(divisor); // qDebug() << "Set bin a size divisor of" << ratio; m_config.setBinSizeFixed(false); double bin_size = m_config.binSize(&ok); if(!ok) { message("Could not compute the bin size."); qDebug() << "Could not compute the bin size."; return false; } mp_ui->binSizeDoubleSpinBox->setValue(bin_size); //qDebug() << "The configuration has computed bin size:" << bin_size; } } return true; } /*! \brief Signals that the user has finished entering the resolving power value. */ void MassPeakShaperConfigWidget::resolutionEditingFinished() { double resolution = mp_ui->resolutionSpinBox->value(); if(!resolution) { // Tell the user to set a valid FWHM value, then. message("Will use the FWHM. Please, set a valid FWHM value"); return; } // At this point we know that resolution contains a proper value. m_config.setResolution(resolution); // We can compute the FWHM using the resolution only if the reference peaks's // m/z value has been set. if(m_config.getReferencePeakMz() <= 0) { message("Please fill-in the reference peak's m/z value."); return; } QString msg; msg += "Will try to use the resolution... "; message(msg); bool ok = false; double fwhm = m_config.fwhm(&ok); if(!ok) { msg += "Failed. Check the parameters."; message(msg); return; } mp_ui->fwhmDoubleSpinBox->setValue(fwhm); // Check if we have enough of data to actually compute the bin size (or not // depending on the user's requirements). ok = processBinSizeConfig(); if(ok && m_config.withBins()) { QString msg = QString("Could compute the bin size: %1") .arg(m_config.getBinSize(), 0, 'f', 10); message(msg); // Since we used the resolution as the starting point, show that fact. mp_ui->resolutionBasedRadioButton->setChecked(true); } return; } /*! \brief Signals that the user has finished entering the FWHM value. The FWHM is the full width at half maximum, that is the width of the peak at its half-height. This value is a direct reflection of the resolving power of the mass spectrometer. */ void MassPeakShaperConfigWidget::fwhmEditingFinished() { double fwhm = mp_ui->fwhmDoubleSpinBox->value(); if(!fwhm) { // Tell the user to set a valid resolution value, then. message("Will use the resolution. Please, set a valid resolution value"); return; } // At this point we know that fwhm contains a proper value. m_config.setFwhm(fwhm); // We can compute the resolution using FWHM only if the reference peaks's m/z // value has been set. if(m_config.getReferencePeakMz() <= 0) { message("Please fill-in the reference peak's m/z value."); return; } QString msg; msg += "Will use the FWHM... "; message(msg); bool ok = false; double resolution = m_config.resolution(&ok); // This is a peculiar situation, because from here we MUST be able to // compute the resolution, because all that is required is the FWHM, that we // have, and the reference peak centroid that SHOULD be there also. So, make // the error fatal. if(!ok) qFatal( "Programming error. At this point we should be able to compute the " "resolution."); mp_ui->resolutionSpinBox->setValue(resolution); // Check if we have enough of data to actually compute the bin size (or not // depending on the user's requirements). ok = processBinSizeConfig(); if(ok && m_config.withBins()) { QString msg = QString("Could compute the bin size: %1") .arg(m_config.getBinSize(), 0, 'f', 10); // Since we used the FWHM as the starting point, show that fact. mp_ui->fwhmBasedRadioButton->setChecked(true); message(msg); } return; } /*! \brief Signals that the user has finished editing the count of points needed to shape the peak. */ void MassPeakShaperConfigWidget::pointCountEditingFinished() { // The points have changed, we'll use that value to compute the m/z step // between two consecutive points in the shaped peak. // All we need is either FWHM or resolution to advance the configuration. // Just check what we have. int point_count = mp_ui->pointCountSpinBox->value(); if(point_count < 5) { message("The number of points to craft the shape is too small."); return; } m_config.setPointCount(point_count); // At this point try to perform some calculations. double fwhm = mp_ui->fwhmDoubleSpinBox->value(); int resolution = mp_ui->resolutionSpinBox->value(); if(fwhm) fwhmEditingFinished(); else if(resolution) resolutionEditingFinished(); // The bin size configuration is handled by the functions above. // That's all we can do. } /*! \brief Signals that the user edited the reference m/z value with text \a text. */ void MassPeakShaperConfigWidget::referencePeakMzEdited(const QString &text) { bool ok = false; double mz = text.toDouble(&ok); if(!ok) { message("Please fix the reference peaks' m/z value."); return; } m_config.setReferencePeakMz(mz); } /*! \brief Signals that the value by which the FWHM value should be divided is now \a value. The \a value is the number by which FWHM should be divided to yield the actual bin size. */ void MassPeakShaperConfigWidget::fwhmBinSizeDivisorValueChanged( [[maybe_unused]] int value) { // Recalculate all the bin size stuff. processBinSizeConfig(); } /*! \brief Signals that the peak shape type has changed. If \a checked is true, then the type is libXpertMass::MassPeakShapeType::GAUSSIAN, else it is libXpertMass::MassPeakShapeType::LORENTZIAN. */ void MassPeakShaperConfigWidget::gaussianRadioButtonToggled(bool checked) { if(checked) m_config.setMassPeakShapeType(libXpertMass::MassPeakShapeType::GAUSSIAN); else m_config.setMassPeakShapeType(libXpertMass::MassPeakShapeType::LORENTZIAN); } /*! \brief Signals that the peak shape type has changed. If \a checked is true, then the type is libXpertMass::MassPeakShapeType::LORENTZIAN, else it is libXpertMass::MassPeakShapeType::GAUSSIAN. */ void MassPeakShaperConfigWidget::lorentzianRadioButtonToggled(bool checked) { if(checked) m_config.setMassPeakShapeType(libXpertMass::MassPeakShapeType::LORENTZIAN); else m_config.setMassPeakShapeType(libXpertMass::MassPeakShapeType::GAUSSIAN); } /*! \brief Writes \a message to the message line edit widget. The message is erase after \a timeout in milliseconds. */ void MassPeakShaperConfigWidget::message(const QString &message, int timeout) { mp_ui->messageLineEdit->setText(message); msp_msgTimer->stop(); msp_msgTimer->setInterval(timeout); msp_msgTimer->start(); } /*! \brief Returns the resolving power spin box widget. */ QSpinBox * MassPeakShaperConfigWidget::getResolutionSpinBox() { return mp_ui->resolutionSpinBox; } /*! \brief Returns the FWHM spin box widget. */ QDoubleSpinBox * MassPeakShaperConfigWidget::getFwhmDoubleSpinBox() { return mp_ui->fwhmDoubleSpinBox; } /*! \brief Returns the FWHM to bin size divisor spin box widget. */ QSpinBox * MassPeakShaperConfigWidget::getFmwhToBinSizeDivisorSpinBox() { return mp_ui->fmwhToBinSizeDivisorSpinBox; } /*! \brief Returns the shape point count spin box widget. */ QSpinBox * MassPeakShaperConfigWidget::getPointCountSpinBox() { return mp_ui->pointCountSpinBox; } /*! \brief Returns the Gaussian shape type radio button widget. */ QRadioButton * MassPeakShaperConfigWidget::getGaussianRadioButton() { return mp_ui->gaussianRadioButton; } /*! \brief Returns the Lorentzian shape type radio button widget. */ QRadioButton * MassPeakShaperConfigWidget::getLorentzianRadioButton() { return mp_ui->lorentzianRadioButton; } /*! \brief Checks that all the parameters are consistent and correct. Returns true is the check succeeded, false otherwise. \sa checkTheParameters(QString &errors) */ bool MassPeakShaperConfigWidget::checkParameters() { QString errors; return checkTheParameters(errors); } /*! \overload \brief Checks that all the parameters are consistent and correct. If errors occurs, they ware added to \a errors. Returns true is the check succeeded, false otherwise. \sa checkParameters() */ bool MassPeakShaperConfigWidget::checkTheParameters(QString &errors) { m_config.reset(); // The general idea is that a number of parameters are inter-dependent. We // need to check these parameters in a precise order because of these // inter-dependencies. bool ok = false; m_referencePeakMz = QString(mp_ui->referenceMassLineEdit->text()).toDouble(&ok); if(!ok || m_referencePeakMz <= 0) { QString msg("Please fill in the reference peak m/z value."); message(msg); errors = msg; qDebug() << msg; return false; } // We need to set back the reference peak centroid because the m_config was // reset above. m_config.setReferencePeakMz(m_referencePeakMz); // We will need this value for computations below. int point_count = mp_ui->pointCountSpinBox->value(); if(point_count < 3) { QString msg( "The number of points allowed to craft the shape is too small. "); message(msg); errors = msg; qDebug() << msg; return false; } m_config.setPointCount(point_count); // qDebug() << "The set point count:" << m_config.getPointCount(); // Get to know if we want gaussian or lorentzian shapes. if(mp_ui->gaussianRadioButton->isChecked()) { m_config.setMassPeakShapeType(libXpertMass::MassPeakShapeType::GAUSSIAN); } else { m_config.setMassPeakShapeType(libXpertMass::MassPeakShapeType::LORENTZIAN); } // Now check the resolution/fwhm data that are competing one another. double fwhm = mp_ui->fwhmDoubleSpinBox->value(); int resolution = mp_ui->resolutionSpinBox->value(); // Both values cannot be naught. if(!resolution && !fwhm) { QString msg( "Please, fix the Resolution / FWHM value (one or the other)."); message(msg); errors = msg; qDebug() << msg; // By default we favor the resolving power-based calculation. mp_ui->resolutionBasedRadioButton->setChecked(true); return false; } if(resolution) { // We will use the resolution m_config.setResolution(resolution); // We have to compute the FWHM value because that is a fundamental // parameter for the peak shaping. We use the most intense peak // centroid's mz value for that. bool ok = false; // With all the data successfully tested above, we must be able to // compute the FWHM. The config will store the computed value. double fwhm = m_config.fwhm(&ok); if(!ok) { QString msg("Failed to compute FWHM starting from resolution."); message(msg); errors = msg; qDebug() << msg; // By default we favor the resolving power-based calculation. mp_ui->resolutionBasedRadioButton->setChecked(true); return false; } mp_ui->fwhmDoubleSpinBox->setValue(fwhm); if(m_config.getMassPeakWidthLogic() == libXpertMass::MassPeakWidthLogic::RESOLUTION) mp_ui->resolutionBasedRadioButton->setChecked(true); else if(m_config.getMassPeakWidthLogic() == libXpertMass::MassPeakWidthLogic::FWHM) mp_ui->fwhmBasedRadioButton->setChecked(true); } // End of // if(resolution) else { // We will use the FWHM m_config.setFwhm(fwhm); // We all the data above, we should be able to compute the resolution // and set it to the config. bool ok = false; resolution = m_config.resolution(&ok); if(!ok) { QString msg("Failed to compute the resolution."); message(msg); errors = msg; qDebug() << msg; // By default we favor the resolving power-based calculation. mp_ui->resolutionBasedRadioButton->setChecked(true); return false; } mp_ui->resolutionSpinBox->setValue(resolution); if(m_config.getMassPeakWidthLogic() == libXpertMass::MassPeakWidthLogic::RESOLUTION) mp_ui->resolutionBasedRadioButton->setChecked(true); else if(m_config.getMassPeakWidthLogic() == libXpertMass::MassPeakWidthLogic::FWHM) mp_ui->fwhmBasedRadioButton->setChecked(true); } // End of // ! if(resolution), that is use FWHM //////////////////// THE BIN SIZE ///////////////////// // qDebug() << "Now processing the bin size configuration."; if(!processBinSizeConfig()) { QString msg("Failed to compute the bin size."); message(msg); errors = msg; qDebug() << msg; return false; } // Craft a string describing the whole set of params as we have been // working on them in the configuration instance. QString info = m_config.toString(); //qDebug().noquote() << "Configuration:" << info; if(!m_config.resolve()) { QString msg("Failed to finally resolve the configuration."); message(msg); errors = msg; qDebug() << msg; return false; } //qDebug() << "Emitting signal with the configuration."; emit updatedMassPeakShaperConfigSignal(m_config); message("The parameters were validated successfully."); return true; } } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/includes/000775 001750 001750 00000000000 14651507337 022441 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/src/XpertMassGui/includes/libXpertMassGui/000775 001750 001750 00000000000 14651507337 025523 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/src/XpertMassGui/includes/libXpertMassGui/ColorSelector.hpp000664 001750 001750 00000005241 14647465366 031027 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2019 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// StdLib includes #include /////////////////////// Qt includes #include /////////////////////// pappsomspp includes /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMassGui { struct colorCompare { bool operator()(const QColor &a, const QColor &b) const { // The convention is to return a < b; // Here I decide that the comparison work is like this: // // get the HSV representation of the color, and sequentially test H, S and V // for inequality between colors a and b. int h_a; int s_a; int v_a; a.getHsv(&h_a, &s_a, &v_a); int h_b; int s_b; int v_b; b.getHsv(&h_b, &s_b, &v_b); if(h_a < h_b) return true; if(s_a < s_b) return true; if(v_a < v_b) return true; return false; } }; using ColorUsagePair = std::pair; class DECLSPEC ColorSelector { public: ColorSelector(); virtual ~ColorSelector(); void addColor(const QColor &color); static QColor getColor(bool make_random = true); static QColor chooseColor(bool allow_used = false); static QColor getRandomColor(); static QColor getColor(const QColor &color); static void releaseColor(const QColor &color); static bool isUsedColor(const QColor &color); private: static std::vector m_usageOfColors; }; } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/includes/libXpertMassGui/IsotopicClusterGeneratorDlg.hpp000664 001750 001750 00000014160 14647465366 033701 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// stdlib includes #include #include #include /////////////////////// Qt includes #include #include #include #include #include ////////////////////// pappso includes #include /////////////////////// libmass includes #include "libXpertMass/globals.hpp" #include "libXpertMass/Formula.hpp" #include "libXpertMass/PeakCentroid.hpp" #include "libXpertMass/Isotope.hpp" #include "libXpertMass/IsotopicData.hpp" #include "libXpertMass/IsotopicDataLibraryHandler.hpp" #include "libXpertMass/IsotopicDataUserConfigHandler.hpp" #include "libXpertMass/IsotopicDataManualConfigHandler.hpp" #include "libXpertMass/IsotopicClusterGenerator.hpp" /////////////////////// Local includes #include "exportimportconfig.h" #include "IsotopicDataTableViewModel.hpp" namespace Ui { class IsotopicClusterGeneratorDlg; } namespace MsXpS { namespace libXpertMassGui { class IsotopicDataTableViewModel; class IsotopicDataTableViewSortProxyModel; class DECLSPEC IsotopicClusterGeneratorDlg : public QDialog { Q_OBJECT public: IsotopicClusterGeneratorDlg(QWidget *program_window_p, const QString &applicationName, const QString &description); virtual ~IsotopicClusterGeneratorDlg(); bool initializeUserManualConfigurationWidgets( const libXpertMass::IsotopicDataManualConfigHandler &config_handler); public slots: // std::pair addElementSkeletonGroupBox(); QGroupBox *addElementSkeletonGroupBox(); QGroupBox *addElementGroupBox(); void removeElementGroupBox(); QFrame *createIsotopeFrame(QGroupBox *elementGroupBox = nullptr); std::pair addIsotopeFrame(); void removeIsotopeFrame(); /////////////////////// The run functions ////////////////////// /// These function are adapted to the three different contexts // Running IsoSpec computations based on the static library isotopic data bool runLibrary(); // Running IsoSpec computations based on the user config isotopic data bool runUserConfig(); // Running IsoSpec computations based on the manual config isotopic data bool runUserManualConfig(); void toIsotopicClusterShaper(); void addLibraryFormula(); void removeLibraryFormula(); void addUserConfigFormula(); void removeUserConfigFormula(); void traceColorPushButtonClicked(); signals: void displayMassSpectrumSignal(const QString &title, const QByteArray &color_byte_array, pappso::TraceCstSPtr trace); protected: Ui::IsotopicClusterGeneratorDlg *mp_ui; QString m_applicationName; QString m_windowDescription; libXpertMass::Formula m_formula; QWidget *mp_programWindow = nullptr; int m_normalizeIntensity = std::numeric_limits::min(); double m_maxSummedProbability = 0.95; int m_charge = 1; pappso::SortType m_sortType = pappso::SortType::no_sort; pappso::SortOrder m_sortOrder = pappso::SortOrder::ascending; ///////////// Isotopic data for three different contexts //////////// // Isotopic data from the IsoSpec library tables libXpertMass::IsotopicDataSPtr msp_isotopicDataLibrary = nullptr; // Isotopic data from the user's personal file libXpertMass::IsotopicDataSPtr msp_isotopicDataUserConfig = nullptr; // Isotopic data from the user's personal GUI-based manual config file libXpertMass::IsotopicDataSPtr msp_isotopicDataUserManualConfig = nullptr; ///////////// Table view models for two different contexts //////////// // The table view model that manages the library static IsoSpec standard data IsotopicDataTableViewModel *mpa_libraryStaticTableViewModel; // The table view model that manages the user config IsoSpec standard data IsotopicDataTableViewModel *mpa_userStaticTableViewModel; // For the mass spectra that are synthesized and served. QByteArray m_colorByteArray; // Note that there is no model for the user manual config data because this // configuration is not entered in a table view but using another paradigm. libXpertMass::IsotopicClusterGenerator m_clusterGenerator; void closeEvent(QCloseEvent *event); void writeSettings(const QString &configSettingsFilePath); void readSettings(const QString &configSettingsFilePath); std::size_t validateManualConfig( libXpertMass::IsotopicDataManualConfigHandler &config_handler); void setupIsoSpecStandardStaticTableView(); void setupIsoSpecStandardUserTableView(); bool setupDialog(); bool loadLibrary(); bool saveLibrary(); bool loadUserConfig(); bool saveUserConfig(); bool loadUserManualConfig(); bool saveUserManualConfig(); bool checkParameters(); void reportResults(); void message(const QString &message, int timeout = 3000); }; } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/includes/libXpertMassGui/IsotopicClusterShaperDlg.hpp000664 001750 001750 00000012533 14647465366 033177 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// StdLib includes #include /////////////////////// Qt includes #include #include /////////////////////// pappsomspp includes #include #include #include /////////////////////// libmass includes #include "libXpertMass/PeakCentroid.hpp" #include "libXpertMass/MassPeakShaperConfig.hpp" #include "libXpertMass/MassPeakShaper.hpp" #include "libXpertMass/IsotopicClusterGenerator.hpp" /////////////////////// Local includes #include "exportimportconfig.h" #include "MassPeakShaperConfigWidget.hpp" namespace Ui { class IsotopicClusterShaperDlg; } namespace MsXpS { namespace libXpertMassGui { class ProgramWindow; class DECLSPEC IsotopicClusterShaperDlg : public QDialog { Q_OBJECT public: IsotopicClusterShaperDlg(QWidget *program_window_p, const QString &applicationName, const QString &description); IsotopicClusterShaperDlg( QWidget *program_window_p, const QString &applicationName, const QString &description, pappso::TraceCstSPtr isotopic_cluster_sp, int normalizing_intensity = std::numeric_limits::min()); virtual ~IsotopicClusterShaperDlg(); void setIsotopiCluster(pappso::TraceCstSPtr isotopic_cluster_sp); // The normalizing intensity usually is the greatest intensity in the cluster // centroids that is to be used for normalization of the peaks in the cluster. void setNormalizingIntensity(double new_max_intensity); void setMassSpectrumTitle(const QString &title); void setColorByteArray(QByteArray color_byte_array); enum class TabWidgetPage { INPUT_DATA = 0x000, LOG, RESULTS, }; public slots: void traceColorPushButtonClicked(); signals: void displayMassSpectrumSignal(const QString &title, const QByteArray &color_byte_array, pappso::TraceCstSPtr trace); protected: Ui::IsotopicClusterShaperDlg *mp_ui; MassPeakShaperConfigWidget *mp_massPeakShaperConfigWidget = nullptr; QWidget *mp_programWindow = nullptr; QString m_applicationName; QString m_description; QString m_fileName; pappso::MapTrace m_mapTrace; pappso::Trace m_finalTrace; // For the mass spectra that are synthesized and served. QByteArray m_colorByteArray; pappso::MzIntegrationParams m_mzIntegrationParams; // Of all the peak centroids' intensities, what is the m/z value of the most // intense? double m_referencePeakMz = 0.0; int m_normalizingIntensity = 1; // This is the starting material for the shaping. Each peak centroid in this // Trace is the apex of a peak to be shaped. There can be as many peak // centroids a desired, they will all be treated separately. pappso::Trace m_isotopicCluster; // Each (m/z,i) pair (the i is in fact a probability that can later be // converted to a relative intensity) in the text edit widget will be // converted into a PeakShaper instance. Each PeakShaper instance will craft // the m_pointCount DataPoints to create the trace corresponding to the // starting peak centroid. QList m_peakShapers; libXpertMass::MassPeakShaperConfig m_config; std::shared_ptr msp_msgTimer = std::make_shared(); void writeSettings(const QString &configSettingsFilePath); void readSettings(const QString &configSettingsFilePath); void closeEvent(QCloseEvent *event); void setupDialog(); QString craftMassSpectrumName(); std::size_t fillInThePeakShapers(); void colorizePushButton(QPushButton *push_button_p, QByteArray color_byte_array); void message(const QString &message, int timeout = 3000); private slots: void importFromText(); bool checkParameters(); // void inputDataSelectionChanged(); void run(); void outputFileName(); void displayMassSpectrum(); void copyMassSpectrumToClipboard(); void normalizingGrouBoxToggled(bool checked); void normalizingIntensityValueChanged(); }; } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/includes/libXpertMassGui/IsotopicDataTableView.hpp000664 001750 001750 00000005174 14647465366 032443 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Std lib includes #include /////////////////////// Qt includes #include /////////////////////// libmass includes #include "libXpertMass/Isotope.hpp" #include "libXpertMass/IsotopicData.hpp" /////////////////////// Local includes #include "exportimportconfig.h" namespace MsXpS { namespace libXpertMassGui { class IsotopicClusterGeneratorDlg; class DECLSPEC IsotopicDataTableView : public QTableView { Q_OBJECT public: IsotopicDataTableView(QWidget *parent = 0); ~IsotopicDataTableView(); void setIsotopicData(libXpertMass::IsotopicDataSPtr isotopic_data_sp); libXpertMass::IsotopicDataSPtr getIsotopicData(); QWidget *parent(); void setParent(QWidget *parent_p); void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); signals: void selectionChangedSignal(const QItemSelection &selected, const QItemSelection &deselected); void allSelectedIndicesSignal(QModelIndexList selected_indices); void selectedRowsChangedSignal(std::set selected_rows); protected: QWidget *mp_parent; libXpertMass::IsotopicDataSPtr msp_isotopicData; // For drag operations. QPoint m_dragStartPos; // We do not want to hide the parent class function here. using QAbstractItemView::startDrag; void startDrag(); }; } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/includes/libXpertMassGui/IsotopicDataTableViewModel.hpp000664 001750 001750 00000006131 14650462653 033404 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include "libXpertMass/IsotopicData.hpp" #include #include /////////////////////// libmass includes #include "libXpertMass/Isotope.hpp" /////////////////////// Local includes #include "exportimportconfig.h" #include "IsotopicDataTableView.hpp" namespace MsXpS { namespace libXpertMassGui { class DECLSPEC IsotopicClusterGeneratorDlg; class DECLSPEC IsotopicDataTableViewModel : public QAbstractTableModel { Q_OBJECT public: IsotopicDataTableViewModel(QObject *object, libXpertMass::IsotopicDataSPtr isotopic_data_sp); virtual ~IsotopicDataTableViewModel(); virtual const QWidget *parent() const; void setTableView(IsotopicDataTableView *); IsotopicDataTableView *tableView(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant headerData(int, Qt::Orientation, int role = Qt::DisplayRole) const override; bool insertRow(int row, const QModelIndex &parent = QModelIndex()); QVariant data(const QModelIndex &parent = QModelIndex(), int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; void refreshAfterNewData(); bool clearAllData(); Qt::ItemFlags flags(const QModelIndex &index) const override; void editCompleted(const QString &text); void insertIsotopeRowAbove(); void insertIsotopeRowBelow(); void removeSelected(); signals: void modifiedSignal(); protected: QWidget *mp_parent = nullptr; libXpertMass::IsotopicDataSPtr msp_isotopicData; IsotopicDataTableView *mp_tableView; signals: void editCompletedSignal(const QString &text); }; } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/includes/libXpertMassGui/MassDataClientServerConfigDlg.hpp000664 001750 001750 00000005156 14647465366 034055 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include /////////////////////// Local includes #include "exportimportconfig.h" namespace Ui { class MassDataClientServerConfigDlg; } namespace MsXpS { namespace libXpertMassGui { class DECLSPEC MassDataClientServerConfigDlg : public QDialog { Q_OBJECT public: MassDataClientServerConfigDlg(QWidget *parent, const QString &applicationName, const QString &description); virtual ~MassDataClientServerConfigDlg(); void updateClientConfigurationData(const QString &ip_address, int port_number); void clientFailingFeedback(const QString &error); void message(const QString &message, int timeout = 3000); public slots: signals: void startServerSignal(); void startClientSignal(const QString ip_address, int port_number, int connection_frequency); void stopServerSignal(); void stopClientSignal(); private slots: void startClient(); protected: QString m_applicationName; QWidget *mp_programWindow = nullptr; void closeEvent(QCloseEvent *event); void writeSettings(const QString &configSettingsFilePath); void readSettings(const QString &configSettingsFilePath); private: Ui::MassDataClientServerConfigDlg *mp_ui; }; } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/includes/libXpertMassGui/MassPeakShaperConfigDlg.hpp000664 001750 001750 00000005627 14647465366 032704 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// StdLib includes /////////////////////// Qt includes #include #include /////////////////////// pappsomspp includes #include #include #include #include #include "libXpertMass/PeakCentroid.hpp" #include "libXpertMass/MassPeakShaperConfig.hpp" #include "libXpertMass/MassPeakShaper.hpp" /////////////////////// Local includes #include "exportimportconfig.h" #include "MassPeakShaperConfigWidget.hpp" namespace Ui { class MassPeakShaperConfigDlg; } namespace MsXpS { namespace libXpertMassGui { class DECLSPEC MassPeakShaperConfigDlg : public QDialog { Q_OBJECT public: MassPeakShaperConfigDlg( QWidget *program_window_p, const QString &applicationName, const QString &description); virtual ~MassPeakShaperConfigDlg(); void writeSettings(const QString &configSettingsFilePath); void readSettings(const QString &configSettingsFilePath); // The normalizing intensity usually is the greatest intensity in the cluster // centroids that is to be used for normalization of the peaks in the cluster. void setNormalizingIntensity(double new_max_intensity); protected: Ui::MassPeakShaperConfigDlg *mp_ui; MassPeakShaperConfigWidget *mp_massPeakShaperConfigWidget = nullptr; QWidget *mp_parent = nullptr; QString m_applicationName; libXpertMass::MassPeakShaperConfig m_config; void closeEvent(QCloseEvent *event); void setupDialog(); signals: void updatedMassPeakShaperConfigSignal(const libXpertMass::MassPeakShaperConfig &config); }; } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/includes/libXpertMassGui/MassPeakShaperConfigWidget.hpp000664 001750 001750 00000006742 14647465366 033420 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright (C) 2009--2020 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ #pragma once /////////////////////// Qt includes #include "libXpertMass/PeakCentroid.hpp" #include #include #include #include #include /////////////////////// libmass includes #include "libXpertMass/MassPeakShaperConfig.hpp" /////////////////////// Local includes #include "exportimportconfig.h" namespace Ui { class MassPeakShaperConfigWidget; } namespace MsXpS { namespace libXpertMassGui { class DECLSPEC MassPeakShaperConfigWidget : public QWidget { Q_OBJECT public: MassPeakShaperConfigWidget(QWidget *parent_p, libXpertMass::MassPeakShaperConfig &config); virtual ~MassPeakShaperConfigWidget(); void writeSettings(const QString &configSettingsFilePath); void readSettings(const QString &configSettingsFilePath); void setReferencePeakMz(double mz); QSpinBox *getResolutionSpinBox(); QDoubleSpinBox *getFwhmDoubleSpinBox(); QSpinBox *getFmwhToBinSizeDivisorSpinBox(); QSpinBox *getPointCountSpinBox(); QRadioButton *getGaussianRadioButton(); QRadioButton *getLorentzianRadioButton(); double getReferencePeakMz(); // This function needs to be public because it is necessary to the user of // this widget when checking correctness of the parameters. bool processBinSizeConfig(); bool checkParameters(); bool checkTheParameters(QString &errors); public slots: void resolutionEditingFinished(); void fwhmEditingFinished(); void fwhmBinSizeDivisorValueChanged(int value); void pointCountEditingFinished(); void gaussianRadioButtonToggled(bool checked); void lorentzianRadioButtonToggled(bool checked); void referencePeakMzEdited(const QString &text); void noBinsCheckBoxStateChanged(int state); signals: void updatedMassPeakShaperConfigSignal( const libXpertMass::MassPeakShaperConfig &config); protected: Ui::MassPeakShaperConfigWidget *mp_ui; QWidget *mp_parent = nullptr; double m_referencePeakMz = 0.0; libXpertMass::MassPeakShaperConfig &m_config; double m_normalizingIntensity = std::numeric_limits::min(); std::shared_ptr msp_msgTimer = std::make_shared(); void setupWidget(); void message(const QString &message, int timeout = 3000); }; } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/includes/libXpertMassGui/exportimportconfig.h000664 001750 001750 00000000651 14650440314 031626 0ustar00rusconirusconi000000 000000 #pragma once #include namespace MsXpS { namespace libXpertMassGui { #if defined(EXPORT_LIB_SYMBOLS) // #pragma message("EXPORT_LIB_SYMBOLS is defined: defining DECLSPEC Q_DECL_EXPORT") #define DECLSPEC Q_DECL_EXPORT #else // #pragma message("EXPORT_LIB_SYMBOLS is not defined: defining DECLSPEC Q_DECL_IMPORT") #define DECLSPEC Q_DECL_IMPORT #endif } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/XpertMassGui/ressources.qrc000664 001750 001750 00000000314 14647465366 023547 0ustar00rusconirusconi000000 000000 ../../images/svg/add-isotope.svg ../../images/svg/remove-isotope.svg ../../images/svg/remove-chemical-element.svg libxpertmass-1.1.0/src/XpertMassGui/ui/000775 001750 001750 00000000000 14651507337 021250 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/src/XpertMassGui/ui/ElementGroupBoxWidget.ui000664 001750 001750 00000010002 14647465366 026035 0ustar00rusconirusconi000000 000000 ElementGroupBoxWidget 0 0 607 395 Form 100 60 250 124 Element Type the symbol, like C for Carbon 0 0 Remove this whole element :/images/svg/remove-chemical-element.svg:/images/svg/remove-chemical-element.svg QFrame::StyledPanel QFrame::Raised Isotope's mass Isotope's abundance 0 0 Add a new isotope :/images/svg/add-isotope.svg:/images/svg/add-isotope.svg 0 0 Remove this isotope :/images/svg/remove-isotope.svg:/images/svg/remove-isotope.svg libxpertmass-1.1.0/src/XpertMassGui/ui/IsotopicClusterGeneratorDlg.ui000664 001750 001750 00000043645 14647465366 027266 0ustar00rusconirusconi000000 000000 IsotopicClusterGeneratorDlg Qt::NonModal 0 0 921 949 Dialog :/images/icons/32x32/minexpert2.png:/images/icons/32x32/minexpert2.png Qt::Horizontal Configuration QFrame::Box QFrame::Sunken The formula needs to account for the ionization level (charge below) Qt::PlainText Qt::AlignCenter Ionization &Ion charge: ionChargeSpinBox 1 1000 Gaussian apex intensity value (a.u.) true 1 Max. cumulative probability 4 0.000000000000000 1.000000000000000 0.100000000000000 0.990000000000000 Cluster's centroids sorting &No sort true By &m/z false By &intensity (probability) Sort order &Ascending true &Descending Mass spectrum synthesis Set the title of the mass spectrum Select the color to be used for the trace plotting Trace color false Qt::Vertical 20 40 0 IsoSpec standard static data Formula true 0 0 Add current formula to memory + 0 0 Remove current formula from memory - Actions Save table data to file for further editing false Run false IsoSpec standard user data Formula true 0 0 Add current formula to memory + 0 0 Remove current formula from memory - Actions Load isotopic data from file Save table data to file for further editing Run Manual configuration Qt::ScrollBarAlwaysOn Qt::ScrollBarAlwaysOn true 0 0 476 709 Actions Add element Run Load configuration Save configuration IsoSpec results To isotopic cluster shaper MsXpS::libXpertMassGui::IsotopicDataTableView QTableView
IsotopicDataTableView.hpp
libxpertmass-1.1.0/src/XpertMassGui/ui/IsotopicClusterShaperDlg.ui000664 001750 001750 00000055004 14647465366 026552 0ustar00rusconirusconi000000 000000 IsotopicClusterShaperDlg 0 0 836 644 This windows allows one to compute, for each (m/z,i) pair in the corresponding edit wiget, to compute all the points needed to create a peak shape in the form of either a Gaussian curve or of a Lorentzian curve. Once all the peaks have been computed they are combined to create a full mass spectrum with as many peaks as there are values in the Data points (m/z,i) text edit widget. 0 Input data Qt::Horizontal 0 0 Data centroid points (format: m/z<space>i) Once the list of isotopic peaks has been edited in place or pasted, click the button above to validate the new text. To modify manually the peak centroid list edit the text below and click onto "Import from text" Import from text true 0 0 404 467 QFrame::StyledPanel QFrame::Raised 0 0 Charge The charge reaction must have been accounted for Shape the peak centroids as charged ions: Note that the ionizing protons must have been accounted for in the m/z value. ionChargeSpinBox Charge level for which the isotopic cluster centroid peaks were calculated 1 10000 0 0 Normalize spectrum intensities true The most intense peak will have this intensity: 1 1000000000 0 0 Mass spectrum synthesis Set the title of the mass spectrum Select the color to be used for the trace plotting Trace color false 0 0 Actions &Run false false Choose a file in which to store the data for the plotting of the isotopic peak. &Output file... false &Check parameters Log Results 0 0 Mass spectrum name false true Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse Actions &Copy mass spectrum to clipboard &Display mass spectrum Help <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This mass peak shaper feature helps create the Gaussian peak shape around a peak centroid.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The Input data tab contains two different sides:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The left hand side contains the list of peak centroids for which the Gaussian/Lorentzian shape must be created;</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The right hand side contains all the parameters that are needed to configure the way the shape has to be created.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A Gaussian shape is characterized by three main parameters:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The peak position (the m/z value, in our case);</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The peak height (intensity, in our case);</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The peak width (full width at half maximum [FWHM], in our case).</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The peak position (x axis) is the m/z value of the peak centroid;</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The peak height is the intensity of the peak centroid;</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The peak width (FWHM). The smallest this value, the thinnest the peak. The FWHM of a peak relates to the resolving power (R) of a mass spectrometer: the greater R is, the smaller FWHM will be (a highly resolutive instrument gives very thin peaks).</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">In this window, the configuration logic is as follows:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If the user elicits to enter a resolving power value, then the program will compute automatically the FWHM on the basis of the m/z value of the most intense peak centroid. That value is updated in the corresponding spin box widget.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If the user elicits to enter a FWHM value, that value is going to be used irrespective of the resolving power value possibly displayed in the corresponding spin box widget.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The program then needs to determine the proper m/z bin size to properly combine all the peak shapes that have been computed into a single mass spectrum. The m/z bin size is determined according to the following logic:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If the user does not activate the corresponding group box, the program will update the bin size automatically based on the following parameters:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The m/z value of the most intense peak centroid;</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The FWHM (either set manually or deduced using the set resolving power);</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The number of points requested for actually creating the peak shape (Points in the Peak shape group box).</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Instead, if the user activates the &quot;Bin size&quot; group box, either two possibilities are presented:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If the user checks the &quot;Do not create bins&quot; check box, then the integration of all the peak shapes into a single mass spectrum will be performed without binning.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If the user sets a bin size manually, then, that bin size will be used no matter how they have configured the resolving power or the FWHM settings.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> outputFilePushButton runPushButton libxpertmass-1.1.0/src/XpertMassGui/ui/MassDataClientServerConfigDlg.ui000664 001750 001750 00000023154 14647465366 027426 0ustar00rusconirusconi000000 000000 MassDataClientServerConfigDlg Qt::NonModal 0 0 520 409 Mass data client/server configuration :/images/icons/32x32/minexpert2.png:/images/icons/32x32/minexpert2.png Client-Server configuration Server configuration (configure this running program as the local server) Qt::Horizontal 40 20 Start server false Stop server false Qt::Horizontal 40 20 Local IP address: localServerIpAddressLineEdit false true This will show the IP address of the local server once started Local port number: localServerPortNumberLineEdit false true This will show the port number of the local server once started Client configuration (configure this running program to connect to the remote server) Remote IP address: localServerIpAddressLineEdit Enter the IP address of the remote server to which this program will connect Remote port number: localServerPortNumberLineEdit Enter the port number of the remote server to which this program will connect Connection frequency: The client will connect to the server at that frequency (times per second) 1 200 Qt::Horizontal false QSlider::TicksBelow 10 Qt::Horizontal 40 20 Start client false Stop client false Qt::Horizontal 40 20 libxpertmass-1.1.0/src/XpertMassGui/ui/MassPeakShaperConfigDlg.ui000664 001750 001750 00000001607 14647465366 026251 0ustar00rusconirusconi000000 000000 MassPeakShaperConfigDlg 0 0 327 301 QFrame::StyledPanel QFrame::Raised libxpertmass-1.1.0/src/XpertMassGui/ui/MassPeakShaperConfigWidget.ui000664 001750 001750 00000030672 14647465366 026772 0ustar00rusconirusconi000000 000000 MassPeakShaperConfigWidget 0 0 394 635 0 0 Form 0 0 Peak shaper configuration 0 0 Peak shape &Gaussian true &Lorentzian Shape the peak using points: Number of points that will make the peak shape 5 5000 200 0 0 Resolving power Full width at half maximum (the width of the peak at 50 % height) FWH&M: Qt::AutoText fwhmDoubleSpinBox The resolving power determines the FWHM The bin size will correspond to FWHM / ratio. Empirically, a good ratio is 6 if the number of points in the Gaussian/Lorentzian is 200. This somehow amounts to the idea that it takes 6 bins to make a peak of the FWHM. 1 10000 6 The bin size will correspond to FWHM / ratio. Empirically, a good ratio is 6 if the number of points in the Gaussian/Lorentzian is 200. This somehow amounts to the idea that it takes 6 bins to make a peak of the FWHM. FWHM to bin size divisor: Resolution of the mass spectrometer. 0 2000000 1000 0 Full width at half maximum (the width of the peak at 50 % height) 10 0.005000000000000 Resol&ving power: resolutionSpinBox Reference mass: This reference m/z value is used to relate the resolving power to the FWHM (full with at half maximum) peak width. Mass used to relate resolving power and FWHM 0 0 Bin size logic Qt::Horizontal 40 20 Resolving power Qt::Horizontal 40 20 FWHM Qt::Horizontal 40 20 0 0 Manually set the bin size true false Do not create bins Bin si&ze (Th): binSizeDoubleSpinBox Set this parameter to a larger value if the peaks seem to be spiky 10 0.000000000000000 1.000000000000000 0.001000000000000 0.000000000000000 Check parameters libxpertmass-1.1.0/src/XpertMassGui/ui/MassPeakShaperDlg.ui000664 001750 001750 00000046767 14647465366 025143 0ustar00rusconirusconi000000 000000 MassPeakShaperDlg 0 0 629 755 This windows allows one to compute, for each (m/z,i) pair in the corresponding edit wiget, to compute all the points needed to create a peak shape in the form of either a Gaussian curve or of a Lorentzian curve. Once all the peaks have been computed they are combined to create a full mass spectrum with as many peaks as there are values in the Data points (m/z,i) text edit widget. 0 Input data Qt::Horizontal 0 0 Data centroid points (format: m/z<space>i) To modify manually the peak centroid list edit the text below and click onto "Import from text" Import from text QFrame::StyledPanel QFrame::Raised 0 0 Charge The charge reaction must have been accounted for Shape the peak centroids as charged ions: Note that the ionizing protons must have been accounted for in the m/z value. ionChargeSpinBox Charge level for which the isotopic cluster centroid peaks were calculated 1 10000 0 0 Normalize spectrum intensities true The most intense peak will have this intensity: 1 1000000000 0 0 Actions &Run true Choose a file in which to store the data for the plotting of the isotopic peak. &Output file... false &Check parameters Log Results 0 0 Mass spectrum name false true Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse Actions &Copy mass spectrum to clipboard &Display mass spectrum Help <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This mass peak shaper feature helps create the Gaussian peak shape around a peak centroid.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The Input data tab contains two different sides:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The left hand side contains the list of peak centroids for which the Gaussian/Lorentzian shape must be created;</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The right hand side contains all the parameters that are needed to configure the way the shape has to be created.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A Gaussian shape is characterized by three main parameters:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The peak position (the m/z value, in our case);</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The peak height (intensity, in our case);</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The peak width (full width at half maximum [FWHM], in our case).</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The peak position (x axis) is the m/z value of the peak centroid;</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The peak height is the intensity of the peak centroid;</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The peak width (FWHM). The smallest this value, the thinnest the peak. The FWHM of a peak relates to the resolving power (R) of a mass spectrometer: the greater R is, the smaller FWHM will be (a highly resolutive instrument gives very thin peaks).</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">In this window, the configuration logic is as follows:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If the user elicits to enter a resolving power value, then the program will compute automatically the FWHM on the basis of the m/z value of the most intense peak centroid. That value is updated in the corresponding spin box widget.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If the user elicits to enter a FWHM value, that value is going to be used irrespective of the resolving power value possibly displayed in the corresponding spin box widget.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The program then needs to determine the proper m/z bin size to properly combine all the peak shapes that have been computed into a single mass spectrum. The m/z bin size is determined according to the following logic:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If the user does not activate the corresponding group box, the program will update the bin size automatically based on the following parameters:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The m/z value of the most intense peak centroid;</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The FWHM (either set manually or deduced using the set resolving power);</p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- The number of points requested for actually creating the peak shape (Points in the Peak shape group box).</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Instead, if the user activates the &quot;Bin size&quot; group box, either two possibilities are presented:</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If the user checks the &quot;Do not create bins&quot; check box, then the integration of all the peak shapes into a single mass spectrum will be performed without binning.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If the user sets a bin size manually, then, that bin size will be used no matter how they have configured the resolving power or the FWHM settings.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> outputFilePushButton runPushButton libxpertmass-1.1.0/src/XpertMassGui/ColorSelector.cpp000664 001750 001750 00000030034 14647753773 024132 0ustar00rusconirusconi000000 000000 /* BEGIN software license * * MsXpertSuite - mass spectrometry software suite * ----------------------------------------------- * Copyright(C) 2009,...,2019 Filippo Rusconi * * http://www.msxpertsuite.org * * This file is part of the MsXpertSuite project. * * The MsXpertSuite project is the successor of the massXpert project. This * project now includes various independent modules: * * - massXpert, model polymer chemistries and simulate mass spectrometric data; * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner; * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * END software license */ /////////////////////// StdLib includes #include /////////////////////// Qt includes #include #include /////////////////////// pappsomspp includes /////////////////////// Local includes #include "ColorSelector.hpp" namespace MsXpS { namespace libXpertMassGui { /*! \class MsXpS::libXpertMassGui::ColorSelector \inmodule libXpertMassGui \ingroup ColorSelector \inheaderfile ColorSelector.hpp \brief The ColorSelector class provides a pool of colors that are associated to a boolean value that is a flag indicating if the colors are in use or not. Facilities allow for recycling colors when they are not used anymore. */ /*! \typealias ColorUsagePair Synonym for std::pair. The boolean value is a flag that tells if the color is currently in use. */ /*! \variable ColorSelector::m_usageOfColors \brief Pool of available colors associated to their usage status. The pool of colors is in the form of a static vector of \l ColorUsagePair instances. */ // Fill-in the colors that can be used. Set them to unused to start with. using color_usage_pair = std::pair; std::vector ColorSelector::m_usageOfColors = { ColorUsagePair(QColor().fromRgb(0xff0000), false), ColorUsagePair(QColor().fromRgb(0x0000ff), false), ColorUsagePair(QColor().fromRgb(0x2a7622), false), ColorUsagePair(QColor().fromRgb(0xff00ff), false), ColorUsagePair(QColor().fromRgb(0xffa400), false), ColorUsagePair(QColor().fromRgb(0x00ffd7), false), ColorUsagePair(QColor().fromRgb(0x7a00ff), false), ColorUsagePair(QColor().fromRgb(0xff009c), false), ColorUsagePair(QColor().fromRgb(0x0072ff), false), ColorUsagePair(QColor().fromRgb(0xf08080), false), ColorUsagePair(QColor().fromRgb(0x4682b4), false), ColorUsagePair(QColor().fromRgb(0x556b2f), false), ColorUsagePair(QColor().fromRgb(0x7dbee7), false)}; /*! \brief Constructs a ColorSelector instance. */ ColorSelector::ColorSelector() { // List all the available colors. // for(auto &&pair : ColorSelector::m_usageOfColors) // qDebug() << "Mapped color:" << pair.first.rgb(); } /*! \brief Destructs this ColorSelector instance. */ ColorSelector::~ColorSelector() { } /*! \brief Add \a color to the vector of colors. If \a color is not already in the member vector of colors, it is added to it with a usage flag set to false. */ void ColorSelector::addColor(const QColor &color) { if(!color.isValid()) { // qDebug() << "The color to add is invalid."; return; } auto result = std::find_if( m_usageOfColors.begin(), m_usageOfColors.end(), [color](const ColorUsagePair &item) { return item.first == color; }); if(result == m_usageOfColors.end()) { m_usageOfColors.push_back(ColorUsagePair(color, false)); } else { // qDebug() << "The color to be added is already present in the pool." // "Doing nothing."; } } /*! \brief Returns a color. If there is a color in the pool that is not used, it is returned and its use flag is set to true. If there is not a single color in the pool that is available for use, a new color needs to be prepared. Either \a make_random is true and a random color is created, put in the pool with the use flag set to true and returned, or the user is provided with the standard color-choosing dialog. \sa chooseColor() */ QColor ColorSelector::getColor(bool make_random) { // qDebug(); // We are asked to provide a color. Either we have one available, or we need // to provide the user with the opportunity to choose a new one or to // construct a new one randomly. // Search if there is still a color in the pool that is *not* used. bool is_used = false; auto result = std::find_if( m_usageOfColors.begin(), m_usageOfColors.end(), [is_used](const ColorUsagePair &item) { return item.second == is_used; }); QColor color; if(result != m_usageOfColors.end()) { // qDebug() << "The color from the map is fine. Set it to used."; // Something was returned. By necessity it is not used (find_if above). color = result->first; // qDebug() << "Was it used?" << isUsedColor(color); // Now set it as a used color. result->second = true; // qDebug() << "The returned color is:" << color.rgb(); // qDebug() << "And now is it used?" << isUsedColor(color); return color; } else { // No color was available (pair value false), we need to let the user // choose one with the color chooser or make one random color. if(make_random) { while(1) { color = getRandomColor(); // We need to ensure that the random color is not used already. if(!isUsedColor(color)) { // qDebug() //<< "The color randomly generated is fine. Set it to used."; // At this point we will return a new color that we set as // used in the pool map. m_usageOfColors.push_back(ColorUsagePair(color, true)); return color; } else { // Otherwise keep creating random colors. // qDebug() << "The color randomly generated was used already // !"; } } } // At this point we know the caller wants to provide the user the // opportunity to select a color from the QColorDialog window. return chooseColor(); } return QColor(); } /*! \brief Returns a color. The user is provided with the color-choosing dialog. The choosed color is checked for its presence in the pool of colors. If the color is found in the pool of colors and \a allow_used is true, the color is returned. Else, the user is provided a new opportunity to select an unused color. In that case, the new color is added to the pool of colors and the usage flag is set to true. */ QColor ColorSelector::chooseColor(bool allow_used) { // The user wants to actually choose a color directly from the Qt color // selector dialog. QColor color; while(1) { color = QColorDialog::getColor( Qt::white /* initial color of the returned color */, nullptr, "Please select a color for the plot"); if(!color.isValid()) { // The user cancels the operation. // qDebug() << "The color selection was cancelled by the user."; return color; } // Now test if the color that was selected exists already in the // pool of colors. if(!isUsedColor(color)) { // The color was not used, so we will return it but first insert // it in the map a a used color. // qDebug() //<< "The color selected by the user is not used, so use it!"; m_usageOfColors.push_back(ColorUsagePair(color, true)); return color; } else { // The color has been choosen already. Only return if we can choose an // already chosen color. if(allow_used) return color; } // We'll continue forever :-). Until the user either select an // unused color or abandons the process. // qDebug() << "The color selected by the user is already used or " //"is invalid, please choose another one."; } return color; } /*! \brief Returns a new color randomly created. The color is generated from random H,S,V triplets. */ QColor ColorSelector::getRandomColor() { // Generate random H,S,V triplets. // stackoverflow.com/questions/5008804/generating-random-integer-from-a-range std::random_device rd; // only used once to initialise (seed) engine std::mt19937 rng( rd()); // random-number engine used (Mersenne-Twister in this case) std::uniform_int_distribution uni_h(0, 359); // guaranteed unbiased auto random_h = uni_h(rng); std::uniform_int_distribution uni_s(125, 255); auto random_s = uni_s(rng); std::uniform_int_distribution uni_v(125, 255); auto random_v = uni_s(rng); // Now create the color. return QColor::fromHsv(random_h, random_s, random_v); } /*! \brief Returns a color matching. \a color is searched for in the pool of colors. If it is found, it is returned and the usage flag is set to true (it might have been true already). If \a color is not found in the pool, then it is added to the pool, its usage flag is set to true and that same color is returned. */ QColor ColorSelector::getColor(const QColor &color) { // We are asked to provide a specific color. The only unacceptable situation // would be that the color is in the pool and that it is already used. auto result = std::find_if( m_usageOfColors.begin(), m_usageOfColors.end(), [color](const ColorUsagePair &item) { return item.first == color; }); if(result == m_usageOfColors.end()) { // The color is not in the pool, easy, just add it in the used state. m_usageOfColors.push_back(ColorUsagePair(color, true)); return color; } else { // The color is in the pool, check if it is used or not. if(!result->second) { // The color is not used. Set it to the used state. result->second = true; return color; } } // The color is already used, return an invalid color so the caller knows // it. return QColor(); } /*! \brief Releases \a color. \a color is searched for in the pool of colors. If it is not found, \a color is added to the pool with its usage flag set to false. If \a color is found, its usage flag is set to false (it might have been false already). */ void ColorSelector::releaseColor(const QColor &color) { if(!color.isValid()) return; // Confirm that the color was effectively used. auto result = std::find_if( m_usageOfColors.begin(), m_usageOfColors.end(), [color](const ColorUsagePair &item) { return item.first == color; }); if(result == m_usageOfColors.end()) { // qDebug() << "The color to release does not appear to be in the pool." //<< "Setting it to the pool in unused status."; m_usageOfColors.push_back(ColorUsagePair(color, false)); return; } if(result->second == false) { // qDebug() << "The color to release does not appear to be in use."; return; } // The color was found and is used, just set it to unused status. result->second = false; } /*! \brief Returns true if \a color is in the pool of colors and its usage flag is true, otherwise returns false. */ bool ColorSelector::isUsedColor(const QColor &color) { auto result = std::find_if( m_usageOfColors.begin(), m_usageOfColors.end(), [color](const ColorUsagePair &item) { return item.first == color; }); if(result != m_usageOfColors.end()) { return result->second; } return false; } } // namespace libXpertMassGui } // namespace MsXpS libxpertmass-1.1.0/src/CMakeLists.txt000664 001750 001750 00000004220 14650425154 020771 0ustar00rusconirusconi000000 000000 # Now we can configure the build of both libraries add_subdirectory(XpertMass) add_subdirectory(XpertMassGui) # And finally, we wan export the targets # The header files for the libraries are installed in the # corresponding directory's list file. # Install and export static targets install(TARGETS Core_static Gui_static EXPORT XpertMassStaticTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) # Export static targets install(EXPORT XpertMassStaticTargets FILE XpertMassStaticTargets.cmake NAMESPACE XpertMass:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/XpertMass) export(EXPORT XpertMassStaticTargets FILE "${CMAKE_CURRENT_BINARY_DIR}/XpertMassStaticTargets.cmake" NAMESPACE XpertMass::) # Install and export shared targets install(TARGETS Core Gui EXPORT XpertMassSharedTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) # Export shared targets install(EXPORT XpertMassSharedTargets FILE XpertMassSharedTargets.cmake NAMESPACE XpertMass:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/XpertMass) export(EXPORT XpertMassSharedTargets FILE "${CMAKE_CURRENT_BINARY_DIR}/XpertMassSharedTargets.cmake" NAMESPACE XpertMass::) # Generate and install config files include(CMakePackageConfigHelpers) if(UNIX) configure_package_config_file(${CMAKE_UTILS_PATH}/XpertMassConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/XpertMassConfig.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/XpertMass) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/XpertMassConfigVersion.cmake" VERSION "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}" COMPATIBILITY SameMinorVersion) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/XpertMassConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/XpertMassConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/XpertMass) endif() libxpertmass-1.1.0/tests/000775 001750 001750 00000000000 14651507337 016613 5ustar00rusconirusconi000000 000000 libxpertmass-1.1.0/tests/CMakeLists.txt000664 001750 001750 00000002310 14647465366 021361 0ustar00rusconirusconi000000 000000 message(\n${BoldRed}"Now configuring tests for ${CMAKE_PROJECT_NAME} ${ColourReset}\n") find_package(Catch2 REQUIRED) get_property(libmass_INCLUDE_DIRS GLOBAL PROPERTY glob_prop_libmass_INCLUDE_DIRS) get_property(libmass_LIBRARIES GLOBAL PROPERTY glob_prop_libmass_LIBRARIES) message(STATUS "Found libmass at: ${libmass_LIBRARIES} with include dir: ${libmass_INCLUDE_DIRS}") set(catch2_tests_SRCS test_Isotope.cpp test_Formula.cpp ) include_directories( ${libmass_INCLUDE_DIRS}) add_executable(catch2-tests ${catch2_tests_SRCS} ${${TARGET}_QRC_CPP} ) # Finally actually set the linking dependencies to the executable. target_link_libraries(catch2-tests -Wl,--whole-archive ${libmass_LIBRARIES} ${libmassgui_LIBRARIES} -Wl,--no-whole-archive -Wl,--no-as-needed PappsoMSpp::Core -Wl,--as-needed IsoSpec++::IsoSpec++ Qt6::Core Qt6::Xml Qt6::Network Catch2::Catch2 Catch2::Catch2WithMain ) set_property(TARGET catch2-tests PROPERTY CXX_STANDARD 17) # we want C++17 # Add the Catch2-based single binary test file to the CMake's test suite so # that it gets called using 'make test'. To see the output, add "ARGS=-V" to # the call. add_test(catch2-tests "catch2-tests") libxpertmass-1.1.0/tests/catch2-test-main.cpp000664 001750 001750 00000000476 14647465366 022403 0ustar00rusconirusconi000000 000000 #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do // this in one cpp file #define CATCH_CONFIG_ENABLE_BENCHMARKING #ifdef CATCH2_MAJOR_VERSION_2 #include #elif CATCH2_MAJOR_VERSION_3 #include using namespace Catch; #endif libxpertmass-1.1.0/tests/test_Formula.cpp000664 001750 001750 00000000670 14647465366 022000 0ustar00rusconirusconi000000 000000 // tests/catch2-tests [section] -s #include #include #include #include TEST_CASE("Formula test suite", "[Formula]") { // Set the debugging message formatting pattern. qSetMessagePattern(QString("%{file}@%{line}, %{function}(): %{message}")); SECTION("..:: Formula initialization ::..", "[Formula]") { bool ok = true; REQUIRE(ok == true); } } libxpertmass-1.1.0/tests/test_Isotope.cpp000664 001750 001750 00000001123 14647465366 022007 0ustar00rusconirusconi000000 000000 // ./tests/catch2-tests [section] -s /////////////////////// Qt includes #include #include /////////////////////// Catch2 includes #include #include /////////////////////// Local includes #include TEST_CASE("Isotope test suite", "[Isotope]") { // Set the debugging message formatting pattern. qSetMessagePattern(QString("%{file}@%{line}, %{function}(): %{message}")); SECTION("..:: Isotope initialization ::..", "[Isotope]") { bool ok = true; REQUIRE(ok == true); } }