pax_global_header00006660000000000000000000000064137273731000014515gustar00rootroot0000000000000052 comment=d17646e343d45733cd876b9c6260b632d0847051 freesasa-python-2.1.0/000077500000000000000000000000001372737310000146255ustar00rootroot00000000000000freesasa-python-2.1.0/.gitignore000066400000000000000000000001471372737310000166170ustar00rootroot00000000000000build dist freesasa.egg-info *~ freesasa.c *.so __pycache__ test.pyc docs/build .eggs .DS_Store src/*.cfreesasa-python-2.1.0/.gitmodules000066400000000000000000000001211372737310000167740ustar00rootroot00000000000000[submodule "lib"] path = lib url = https://github.com/mittinatten/freesasa.git freesasa-python-2.1.0/.travis.yml000066400000000000000000000031671372737310000167450ustar00rootroot00000000000000# A lot of inspiration from https://github.com/project-rig/rig_c_sa language: generic sudo: false cache: directories: - "$HOME/.cache/pip" - "$HOME/.pyenv_cache" dist: xenial os: - linux - osx env: matrix: - PYVER=3.6 - PYVER=3.7 - PYVER=3.8 global: - USE_CYTHON=1 install: # Install 'pyenv': a utility for downloading and switching between multiple # Python interpreters on the same system. - git clone https://github.com/yyuu/pyenv.git ~/.pyenv - PYENV_ROOT="$HOME/.pyenv" - PATH="$PYENV_ROOT/bin:$PATH" - eval "$(pyenv init -)" # Install the latest release of the specified Python version using pyenv. - PYVER="$(pyenv install --list | grep -E "^\\s*$PYVER" | sort -n -t. -k3 | tail -n1)" - echo "Selected Python $PYVER" - pyenv install $PYVER # Make the newly installed version the default "python" command. - pyenv global $PYVER - python --version - pip install -r requirements.txt - git submodule update --init script: - python setup.py install - python setup.py test after_success: - > if [ -n "$TRAVIS_TAG" ]; then # can only upload binaries for osx if [ "x$TRAVIS_OS_NAME" == "xosx" ]; then python setup.py bdist_wheel; # only submit sdist once if [ "x$PYVER" == "x3.8" ]; then python setup.py sdist fi fi # twine env variables are set in Travis web config twine upload --skip-existing dist/*; else echo "Will only deploy tagged releases" fi freesasa-python-2.1.0/CHANGELOG.md000066400000000000000000000004041372737310000164340ustar00rootroot00000000000000# Changelog # 2.1.0 - Added changelog - Can access absolute and relative SASA for individual residues through `Result.residueAreas()` - Can set options and classifier for a `Structure` initiated without an input file for later use in `Structure.addAtom()` freesasa-python-2.1.0/LICENSE.txt000066400000000000000000000020751372737310000164540ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Simon Mitternacht Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. freesasa-python-2.1.0/MANIFEST.in000066400000000000000000000000351372737310000163610ustar00rootroot00000000000000include LICENSE.txt config.h freesasa-python-2.1.0/README.md000066400000000000000000000026661372737310000161160ustar00rootroot00000000000000FreeSASA Python module ====================== [![Appveyor build status](https://ci.appveyor.com/api/projects/status/nyo51pv2ufj2yhcj/branch/master?svg=true)](https://ci.appveyor.com/project/mittinatten/freesasa-python/branch/master) [![Travis build status](https://travis-ci.org/freesasa/freesasa-python.svg?branch=master)](https://travis-ci.org/freesasa/freesasa-python) The module provides Python bindings for the [FreeSASA C Library](https://github.com/mittinatten/freesasa). There are PyPi packages for Python 3.6+, on Linux, Mac OS X and Windows. And it can be built from source for 2.7+ (Or by downloading older PyPi packages). Documentation can be found at http://freesasa.github.io/python/. Install the module by ~~~~sh pip install freesasa ~~~~ Developers can clone the library, and then build the module by the following ~~~~sh git submodule update --init USE_CYTHON=1 python setup.py build ~~~~ Tests can be run using ~~~~sh python setup.py test ~~~~ Adding new features =================== This Python module provides a limited mapping to the C API of FreeSASA. I wish to extend the module with more functionality out of the box, to match the capabilities of the C API more closely, and perhaps also add more complex analysis that would be cumbersome to write in C. Feel free to submit feature request as GitHub issues. A few simple suggestions are already listed as issues. I only work on FreeSASA in my spare time, so PRs are always welcome. freesasa-python-2.1.0/appveyor.yml000066400000000000000000000070151372737310000172200ustar00rootroot00000000000000environment: global: # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the # /E:ON and /V:ON options are not enabled in the batch script intepreter # See: http://stackoverflow.com/a/13751649/163740 CMD_IN_ENV: "cmd /E:ON /V:O N /C .\\appveyor\\run_with_env.cmd" USE_CYTHON: 1 REQUIREMENTS: requirements.txt TWINE_USERNAME: mittinatten TWINE_PASSWORD: secure: PE3DJXf2VYmUlH1Vo6CoMA== matrix: # Pre-installed Python versions, which Appveyor may upgrade to # a later point release. # See: http://www.appveyor.com/docs/installed-software#python - PYTHON: "C:\\Python36" PYTHON_VERSION: "3.6.x" PYTHON_ARCH: "32" - PYTHON: "C:\\Python36-x64" PYTHON_VERSION: "3.6.x" PYTHON_ARCH: "64" - PYTHON: "C:\\Python37" PYTHON_VERSION: "3.7.x" PYTHON_ARCH: "32" - PYTHON: "C:\\Python37-x64" PYTHON_VERSION: "3.7.x" PYTHON_ARCH: "64" - PYTHON: "C:\\Python38-x64" PYTHON_VERSION: "3.8.x" PYTHON_ARCH: "32" - PYTHON: "C:\\Python38-x64" PYTHON_VERSION: "3.8.x" PYTHON_ARCH: "64" install: # If there is a newer build queued for the same PR, cancel this one. # The AppVeyor 'rollout builds' option is supposed to serve the same # purpose but it is problematic because it tends to cancel builds pushed # directly to master instead of just PR builds (or the converse). # credits: JuliaLang developers. - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` throw "There are newer queued builds for this pull request, failing early." } - ECHO "Filesystem root:" - ps: "ls \"C:/\"" - ECHO "Installed SDKs:" - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\"" # Install Python (from the official .msi of http://python.org) and pip when # not already installed. - ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 } # Prepend newly installed Python to the PATH of this build (this cannot be # done from inside the powershell script as it would require to restart # the parent CMD process). - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" # Check that we have the expected version and architecture for Python - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" # Upgrade to the latest version of pip to avoid it displaying warnings # about it being out of date. - "python -m pip install --upgrade pip" # Install the build dependencies of the project. If some dependencies contain # compiled extensions and are not provided as pre-built wheel packages, # pip will build them from source using the MSVC compiler matching the # target Python version and architecture - "%CMD_IN_ENV% pip install -r %REQUIREMENTS%" - git submodule update --init build_script: # Build the compiled extension - "%CMD_IN_ENV% python setup.py build" test_script: # Run the project tests - "%CMD_IN_ENV% python setup.py test" after_test: # If tests are successful, create binary packages for the project. - "%CMD_IN_ENV% python setup.py bdist_wheel" - ps: "ls dist" artifacts: - path: dist\* name: pypiartifacts deploy_script: - if "%APPVEYOR_REPO_TAG%"=="true" ( twine upload dist\* --skip-existing ) else ( echo "Will only deploy tagged commits" ) freesasa-python-2.1.0/appveyor/000077500000000000000000000000001372737310000164725ustar00rootroot00000000000000freesasa-python-2.1.0/appveyor/install.ps1000066400000000000000000000160331372737310000205700ustar00rootroot00000000000000# Sample script to install Python and pip under Windows # Authors: Olivier Grisel, Jonathan Helmus, Kyle Kastner, and Alex Willmer # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ $MINICONDA_URL = "http://repo.continuum.io/miniconda/" $BASE_URL = "https://www.python.org/ftp/python/" $GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" $GET_PIP_PATH = "C:\get-pip.py" $PYTHON_PRERELEASE_REGEX = @" (?x) (?\d+) \. (?\d+) \. (?\d+) (?[a-z]{1,2}\d+) "@ function Download ($filename, $url) { $webclient = New-Object System.Net.WebClient $basedir = $pwd.Path + "\" $filepath = $basedir + $filename if (Test-Path $filename) { Write-Host "Reusing" $filepath return $filepath } # Download and retry up to 3 times in case of network transient errors. Write-Host "Downloading" $filename "from" $url $retry_attempts = 2 for ($i = 0; $i -lt $retry_attempts; $i++) { try { $webclient.DownloadFile($url, $filepath) break } Catch [Exception]{ Start-Sleep 1 } } if (Test-Path $filepath) { Write-Host "File saved at" $filepath } else { # Retry once to get the error message if any at the last try $webclient.DownloadFile($url, $filepath) } return $filepath } function ParsePythonVersion ($python_version) { if ($python_version -match $PYTHON_PRERELEASE_REGEX) { return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro, $matches.prerelease) } $version_obj = [version]$python_version return ($version_obj.major, $version_obj.minor, $version_obj.build, "") } function DownloadPython ($python_version, $platform_suffix) { $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version if (($major -le 2 -and $micro -eq 0) ` -or ($major -eq 3 -and $minor -le 2 -and $micro -eq 0) ` ) { $dir = "$major.$minor" $python_version = "$major.$minor$prerelease" } else { $dir = "$major.$minor.$micro" } if ($prerelease) { if (($major -le 2) ` -or ($major -eq 3 -and $minor -eq 1) ` -or ($major -eq 3 -and $minor -eq 2) ` -or ($major -eq 3 -and $minor -eq 3) ` ) { $dir = "$dir/prev" } } if (($major -le 2) -or ($major -le 3 -and $minor -le 4)) { $ext = "msi" if ($platform_suffix) { $platform_suffix = ".$platform_suffix" } } else { $ext = "exe" if ($platform_suffix) { $platform_suffix = "-$platform_suffix" } } $filename = "python-$python_version$platform_suffix.$ext" $url = "$BASE_URL$dir/$filename" $filepath = Download $filename $url return $filepath } function InstallPython ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -eq "32") { $platform_suffix = "" } else { $platform_suffix = "amd64" } $installer_path = DownloadPython $python_version $platform_suffix $installer_ext = [System.IO.Path]::GetExtension($installer_path) Write-Host "Installing $installer_path to $python_home" $install_log = $python_home + ".log" if ($installer_ext -eq '.msi') { InstallPythonMSI $installer_path $python_home $install_log } else { InstallPythonEXE $installer_path $python_home $install_log } if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallPythonEXE ($exepath, $python_home, $install_log) { $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home" RunCommand $exepath $install_args } function InstallPythonMSI ($msipath, $python_home, $install_log) { $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home" $uninstall_args = "/qn /x $msipath" RunCommand "msiexec.exe" $install_args if (-not(Test-Path $python_home)) { Write-Host "Python seems to be installed else-where, reinstalling." RunCommand "msiexec.exe" $uninstall_args RunCommand "msiexec.exe" $install_args } } function RunCommand ($command, $command_args) { Write-Host $command $command_args Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru } function InstallPip ($python_home) { $pip_path = $python_home + "\Scripts\pip.exe" $python_path = $python_home + "\python.exe" if (-not(Test-Path $pip_path)) { Write-Host "Installing pip..." $webclient = New-Object System.Net.WebClient $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) Write-Host "Executing:" $python_path $GET_PIP_PATH & $python_path $GET_PIP_PATH } else { Write-Host "pip already installed." } } function DownloadMiniconda ($python_version, $platform_suffix) { if ($python_version -eq "3.4") { $filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe" } else { $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe" } $url = $MINICONDA_URL + $filename $filepath = Download $filename $url return $filepath } function InstallMiniconda ($python_version, $architecture, $python_home) { Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home if (Test-Path $python_home) { Write-Host $python_home "already exists, skipping." return $false } if ($architecture -eq "32") { $platform_suffix = "x86" } else { $platform_suffix = "x86_64" } $filepath = DownloadMiniconda $python_version $platform_suffix Write-Host "Installing" $filepath "to" $python_home $install_log = $python_home + ".log" $args = "/S /D=$python_home" Write-Host $filepath $args Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru if (Test-Path $python_home) { Write-Host "Python $python_version ($architecture) installation complete" } else { Write-Host "Failed to install Python in $python_home" Get-Content -Path $install_log Exit 1 } } function InstallMinicondaPip ($python_home) { $pip_path = $python_home + "\Scripts\pip.exe" $conda_path = $python_home + "\Scripts\conda.exe" if (-not(Test-Path $pip_path)) { Write-Host "Installing pip..." $args = "install --yes pip" Write-Host $conda_path $args Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru } else { Write-Host "pip already installed." } } function main () { InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON InstallPip $env:PYTHON } main freesasa-python-2.1.0/appveyor/run_with_env.cmd000066400000000000000000000064461372737310000217000ustar00rootroot00000000000000:: To build extensions for 64 bit Python 3, we need to configure environment :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) :: :: To build extensions for 64 bit Python 2, we need to configure environment :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) :: :: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific :: environment configurations. :: :: Note: this script needs to be run with the /E:ON and /V:ON flags for the :: cmd interpreter, at least for (SDK v7.0) :: :: More details at: :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows :: http://stackoverflow.com/a/13751649/163740 :: :: Author: Olivier Grisel :: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ :: :: Notes about batch files for Python people: :: :: Quotes in values are literally part of the values: :: SET FOO="bar" :: FOO is now five characters long: " b a r " :: If you don't want quotes, don't include them on the right-hand side. :: :: The CALL lines at the end of this file look redundant, but if you move them :: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y :: case, I don't know why. @ECHO OFF SET COMMAND_TO_RUN=%* SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf :: Extract the major and minor versions, and allow for the minor version to be :: more than 9. This requires the version number to have two dots in it. SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1% IF "%PYTHON_VERSION:~3,1%" == "." ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1% ) ELSE ( SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2% ) :: Based on the Python version, determine what SDK version to use, and whether :: to set the SDK for 64-bit. IF %MAJOR_PYTHON_VERSION% == 2 ( SET WINDOWS_SDK_VERSION="v7.0" SET SET_SDK_64=Y ) ELSE ( IF %MAJOR_PYTHON_VERSION% == 3 ( SET WINDOWS_SDK_VERSION="v7.1" IF %MINOR_PYTHON_VERSION% LEQ 4 ( SET SET_SDK_64=Y ) ELSE ( SET SET_SDK_64=N IF EXIST "%WIN_WDK%" ( :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ REN "%WIN_WDK%" 0wdf ) ) ) ELSE ( ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" EXIT 1 ) ) IF %PYTHON_ARCH% == 64 ( IF %SET_SDK_64% == Y ( ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture SET DISTUTILS_USE_SDK=1 SET MSSdk=1 "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ELSE ( ECHO Using default MSVC build environment for 64 bit architecture ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) ) ELSE ( ECHO Using default MSVC build environment for 32 bit architecture ECHO Executing: %COMMAND_TO_RUN% call %COMMAND_TO_RUN% || EXIT 1 ) freesasa-python-2.1.0/config.h000066400000000000000000000005571372737310000162520ustar00rootroot00000000000000/* Name of package */ #define PACKAGE "freesasa" /* Define to the full name of this package. */ #define PACKAGE_NAME "FreeSASA" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "FreeSASA 2.0.2" /* Define to the version of this package. */ #define PACKAGE_VERSION "2.0.2" #define USE_XML 0 #define USE_JSON 0 #define USE_CHECK 0 freesasa-python-2.1.0/docs/000077500000000000000000000000001372737310000155555ustar00rootroot00000000000000freesasa-python-2.1.0/docs/Makefile000066400000000000000000000012501372737310000172130ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = FreeSASA SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @cd ../; USE_CYTHON=1 python3 setup.py build_ext --inplace; cd docs; @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) freesasa-python-2.1.0/docs/source/000077500000000000000000000000001372737310000170555ustar00rootroot00000000000000freesasa-python-2.1.0/docs/source/_templates/000077500000000000000000000000001372737310000212125ustar00rootroot00000000000000freesasa-python-2.1.0/docs/source/_templates/layout.html000066400000000000000000000006101372737310000234120ustar00rootroot00000000000000{% extends "!layout.html" %} {% block footer %} {{ super() }} {% endblock %} freesasa-python-2.1.0/docs/source/classes.rst000066400000000000000000000007671372737310000212560ustar00rootroot00000000000000Classes ======= Classifier ---------- .. autoclass:: freesasa.Classifier :members: :special-members: .. _config-files: http://freesasa.github.io/doxygen/Config-file.html Parameters ---------- .. autoclass:: freesasa.Parameters :members: :special-members: Result ------ .. autoclass:: freesasa.Result :members: ResidueArea ----------- .. autoclass:: freesasa.ResidueArea :members: Structure --------- .. autoclass:: freesasa.Structure :members: :special-members: freesasa-python-2.1.0/docs/source/conf.py000066400000000000000000000116621372737310000203620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/stable/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('../../')) # -- Project information ----------------------------------------------------- project = 'FreeSASA Python Module' copyright = '2020, Simon Mitternacht' author = 'Simon Mitternacht' # The short X.Y version version = '' # The full version, including alpha/beta/rc tags release = '2.1.0b1' # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.ifconfig', 'sphinx.ext.githubpages', 'sphinx.ext.napoleon', 'sphinx.ext.autosummary' ] napoleon_google_docstring = True napoleon_use_param = False napoleon_use_ivar = True # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'FreeSASAdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'FreeSASA.tex', 'FreeSASA Documentation', 'Simon Mitternacht', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'freesasa', 'FreeSASA Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'FreeSASA', 'FreeSASA Documentation', author, 'FreeSASA', 'One line description of project.', 'Miscellaneous'), ] # -- Extension configuration ------------------------------------------------- freesasa-python-2.1.0/docs/source/functions.rst000066400000000000000000000011341372737310000216160ustar00rootroot00000000000000Functions ============================= .. currentmodule:: freesasa .. autosummary:: calc calcBioPDB calcCoord classifyResults getVerbosity selectArea setVerbosity .. autofunction:: calc .. autofunction:: calcBioPDB .. autofunction:: calcCoord .. autofunction:: classifyResults .. autofunction:: getVerbosity .. autofunction:: setVerbosity .. autofunction:: selectArea .. autofunction:: structureArray .. autofunction:: structureFromBioPDB .. _select-syntax: http://freesasa.github.io/doxygen/Selection.html .. _C API: http://freesasa.github.io/doxygen/API.html freesasa-python-2.1.0/docs/source/index.rst000066400000000000000000000015311372737310000207160ustar00rootroot00000000000000.. toctree:: :maxdepth: 2 intro functions classes FreeSASA Python Module ====================== The module provides Python bindings for the `FreeSASA C Library `_. Python 3.6+, on Linux, Mac OS X and Windows are officially supported (it will probably still run on older Python versions if you build it from source, or use older PyPi packages). The source is available as a PyPi source distribution and on `GitHub `_. Install the FreeSASA Python Module by .. code:: pip install freesasa Developers can clone the library, and then build the module by the following .. code:: git clone https://github.com/freesasa/freesasa-python.git git submodule update --init python setyp.py build Tests are run by .. code:: python setup.py test freesasa-python-2.1.0/docs/source/intro.rst000066400000000000000000000105271372737310000207470ustar00rootroot00000000000000.. currentmodule:: freesasa Introduction ============ This package provides Python bindings for the `FreeSASA C Library `_. It can be installed using .. code:: pip install freesasa Binaries are available for Python 2.7, 3.5, 3.6 and 3.7 for Mac OS X and Windows, in addition to the source distribution. Basic calculations ------------------ Using defaults everywhere a simple calculation can be carried out as follows (assuming the file ``1ubq.pdb`` is available) .. code:: python import freesasa structure = freesasa.Structure("1ubq.pdb") result = freesasa.calc(structure) area_classes = freesasa.classifyResults(result, structure) print "Total : %.2f A2" % result.totalArea() for key in area_classes: print key, ": %.2f A2" % area_classes[key] Which would give the following output .. code:: Total : 4804.06 A2 Polar : 2504.22 A2 Apolar : 2299.84 A2 The following does a high precision L&R calculation .. code:: python result = freesasa.calc(structure, freesasa.Parameters({'algorithm' : freesasa.LeeRichards, 'n-slices' : 100})) Using the results from a calculation we can also integrate SASA over a selection of atoms, using a subset of the Pymol `selection syntax`_: .. _selection syntax: http://freesasa.github.io/doxygen/Selection.html .. code:: python selections = freesasa.selectArea(('alanine, resn ala', 'r1_10, resi 1-10'), structure, result) for key in selections: print key, ": %.2f A2" % selections[key] which gives the output .. code:: alanine : 120.08 A2 r1_10 : 634.31 A2 Customizing atom classification ------------------------------- This uses the NACCESS parameters (the file ``naccess.config`` is available in the ``share/`` directory of the repository). .. code:: python classifier = freesasa.Classifier("naccess.config") structure = freesasa.Structure("1ubq.pdb", classifier) result = freesasa.calc(structure) area_classes = freesasa.classifyResults(result, structure, classifier) Classification can be customized also by extending the :py:class:`.Classifier` interface. The code below is an illustration of a classifier that classes nitrogens separately, and assigns radii based on element only (and crudely). .. code:: python import freesasa import re class DerivedClassifier(freesasa.Classifier): # this must be set explicitly in all derived classifiers purePython = True def classify(self, residueName, atomName): if re.match('\s*N', atomName): return 'Nitrogen' return 'Not-nitrogen' def radius(self, residueName, atomName): if re.match('\s*N',atomName): # Nitrogen return 1.6 if re.match('\s*C',atomName): # Carbon return 1.7 if re.match('\s*O',atomName): # Oxygen return 1.4 if re.match('\s*S',atomName): # Sulfur return 1.8 return 0; # everything else (Hydrogen, etc) classifier = DerivedClassifier() # use the DerivedClassifier to calculate atom radii structure = freesasa.Structure("1ubq.pdb", classifier) result = freesasa.calc(structure) # use the DerivedClassifier to classify atoms area_classes = freesasa.classifyResults(result,structure,classifier) Of course, this example is somewhat contrived, if we only want the integrated area of nitrogen atoms, the simpler choice would be .. code:: python selection = freesasa.selectArea('nitrogen, symbol n', structure, result) However, extending :py:class:`.Classifier`, as illustrated above, allows classification to arbitrary complexity and also lets us redefine the radii used in the calculation. Bio.PDB ------- FreeSASA can also calculate the SASA of a ``Bio.PDB`` structure from BioPython .. code:: python from Bio.PDB import PDBParser parser = PDBParser() structure = parser.get_structure("Ubiquitin", "1ubq.pdb") result, sasa_classes = freesasa.calcBioPDB(structure) If one needs more control over the analysis the structure can be converted to a :py:class:`.Structure` using :py:func:`.structureFromBioPDB()` and the calculation can be performed the normal way using this structure. freesasa-python-2.1.0/lib/000077500000000000000000000000001372737310000153735ustar00rootroot00000000000000freesasa-python-2.1.0/requirements.txt000066400000000000000000000000421372737310000201050ustar00rootroot00000000000000twine cython biopython wheel nose freesasa-python-2.1.0/setup.cfg000066400000000000000000000000001372737310000164340ustar00rootroot00000000000000freesasa-python-2.1.0/setup.py000066400000000000000000000050171372737310000163420ustar00rootroot00000000000000from setuptools import setup, Extension import os import sys from glob import glob USE_CYTHON = False try: USE_CYTHON = os.environ['USE_CYTHON'] except KeyError: if not os.path.isfile(os.path.join("src", "freesasa.c")): sys.stderr.write("No C source detected, define environment variable USE_CYTHON to build from Cython source.\n") sys.exit() else: print ("Define environment variable USE_CYTHON to build from Cython source") # not using wild cards because we're leaving out xml and json sources = list(map(lambda file: os.path.join('lib', 'src', file), ["classifier.c", "classifier_protor.c", "classifier_oons.c", "classifier_naccess.c", "coord.c", "freesasa.c", "lexer.c", "log.c", "nb.c", "node.c", "parser.c", "pdb.c", "rsa.c", "sasa_lr.c", "sasa_sr.c", "selection.c", "structure.c", "util.c"])) extensions = None ext = '.pyx' if USE_CYTHON else '.c' sources.append(os.path.join('src', 'freesasa' + ext)) compile_args=['-DHAVE_CONFIG_H'] if os.name == 'posix': compile_args.append('-std=gnu99') extension_src = [ Extension("freesasa", sources, language='c', include_dirs=[os.path.join('lib', 'src'), '.'], extra_compile_args = compile_args ) ] if USE_CYTHON: from Cython.Build import cythonize, build_ext extensions = cythonize(extension_src) else: extensions = extension_src long_description = \ "This module provides bindings for the FreeSASA C library. " + \ "See http://freesasa.github.io/python/ for documentation." setup( name='freesasa', version= '2.1.0', description='Calculate solvent accessible surface areas of proteins', long_description=long_description, author='Simon Mitternacht', url='http://freesasa.github.io/', license='MIT', ext_modules=extensions, keywords=['structural biology', 'proteins', 'bioinformatics'], headers=glob(os.path.join('lib', 'src', '*')), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Topic :: Scientific/Engineering :: Bio-Informatics', 'Topic :: Scientific/Engineering :: Chemistry', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', # Will also build for python 2.7 but not officialy supported ], setup_requires=['cython>=0.29.13'], test_suite='test' ) freesasa-python-2.1.0/src/000077500000000000000000000000001372737310000154145ustar00rootroot00000000000000freesasa-python-2.1.0/src/cfreesasa.pxd000066400000000000000000000216471372737310000200770ustar00rootroot00000000000000from libc.stdio cimport FILE cdef extern from "freesasa.h": ctypedef enum freesasa_algorithm: FREESASA_LEE_RICHARDS, FREESASA_SHRAKE_RUPLEY ctypedef enum freesasa_verbosity: FREESASA_V_NORMAL, FREESASA_V_NOWARNINGS, FREESASA_V_SILENT, FREESASA_V_DEBUG ctypedef enum freesasa_atom_class: FREESASA_ATOM_APOLAR, FREESASA_ATOM_POLAR, FREESASA_ATOM_UNKNOWN cdef const int FREESASA_SUCCESS cdef const int FREESASA_FAIL cdef const int FREESASA_WARN cdef const int FREESASA_INCLUDE_HETATM cdef const int FREESASA_INCLUDE_HYDROGEN cdef const int FREESASA_SEPARATE_CHAINS cdef const int FREESASA_SEPARATE_MODELS cdef const int FREESASA_JOIN_MODELS cdef const int FREESASA_HALT_AT_UNKNOWN cdef const int FREESASA_SKIP_UNKNOWN cdef const int FREESASA_MAX_SELECTION_NAME ctypedef struct freesasa_parameters: freesasa_algorithm alg double probe_radius int shrake_rupley_n_points int lee_richards_n_slices int n_threads ctypedef struct freesasa_result: double total double *sasa int n_atoms ctypedef struct freesasa_nodearea: const char *name double total double main_chain double side_chain double polar double apolar double unknown ctypedef struct freesasa_node: pass ctypedef enum freesasa_nodetype: pass ctypedef struct freesasa_classifier: pass ctypedef struct freesasa_structure: pass ctypedef struct freesasa_selection: pass cdef extern const freesasa_parameters freesasa_default_parameters cdef extern const freesasa_classifier freesasa_default_classifier cdef extern const freesasa_classifier freesasa_residue_classifier cdef extern const freesasa_classifier freesasa_naccess_classifier cdef extern const freesasa_classifier freesasa_protor_classifier cdef extern const freesasa_classifier freesasa_oons_classifier freesasa_result* freesasa_calc_structure(const freesasa_structure *structure, const freesasa_parameters *parameters) freesasa_result* freesasa_calc_coord(const double *xyz, const double *radii, int n, const freesasa_parameters *parameters) void freesasa_result_free(freesasa_result *result) freesasa_classifier* freesasa_classifier_from_file(FILE *file) void freesasa_classifier_free(freesasa_classifier *classifier) int freesasa_structure_chain_residues(const freesasa_structure *structure, char chain, int *first, int *last) double freesasa_classifier_radius(const freesasa_classifier *classifier, const char *res_name, const char *atom_name) freesasa_atom_class freesasa_classifier_class(const freesasa_classifier *classifier, const char *res_name, const char *atom_name) const char* freesasa_classifier_class2str(freesasa_atom_class the_class) freesasa_selection * freesasa_selection_new(const char *command, const freesasa_structure *structure, const freesasa_result *result) void freesasa_selection_free(freesasa_selection *selection) const char * freesasa_selection_name(const freesasa_selection* selection) const char * freesasa_selection_command(const freesasa_selection* selection) double freesasa_selection_area(const freesasa_selection* selection) int freesasa_selection_n_atoms(const freesasa_selection* selection) int freesasa_write_pdb(FILE *output, freesasa_result *result, const freesasa_structure *structure) int freesasa_per_residue_type(FILE *output, freesasa_result *result, const freesasa_structure *structure) int freesasa_per_residue(FILE *output, freesasa_result *result, const freesasa_structure *structure) int freesasa_set_verbosity(freesasa_verbosity v) freesasa_verbosity freesasa_get_verbosity() freesasa_structure* freesasa_structure_from_pdb(FILE *pdb, const freesasa_classifier* classifier, int options) freesasa_structure** freesasa_structure_array(FILE *pdb, int *n, const freesasa_classifier* classifier, int options) freesasa_structure* freesasa_structure_new() int freesasa_structure_n(freesasa_structure *structure) void freesasa_structure_free(freesasa_structure* structure) const double* freesasa_structure_radius(const freesasa_structure *structure) void freesasa_structure_set_radius(freesasa_structure *structure, const double *radii) int freesasa_structure_add_atom(freesasa_structure *structure, const char* atom_name, const char* residue_name, const char* residue_number, char chain_label, double x, double y, double z) int freesasa_structure_add_atom_wopt(freesasa_structure *structure, const char* atom_name, const char* residue_name, const char* residue_number, char chain_label, double x, double y, double z, const freesasa_classifier *classifier, int options) const char* freesasa_structure_atom_name(const freesasa_structure *structure, int i) const char* freesasa_structure_atom_res_name(const freesasa_structure *structure, int i) const char* freesasa_structure_atom_res_number(const freesasa_structure *structure, int i) double freesasa_structure_atom_radius(const freesasa_structure *structure, int i) void freesasa_structure_atom_set_radius(const freesasa_structure *structure, int i, double radius) char freesasa_structure_atom_chain(const freesasa_structure *structure, int i) const double* freesasa_structure_coord_array(const freesasa_structure *structure) freesasa_nodearea freesasa_result_classes(const freesasa_structure *structure, const freesasa_result *result) freesasa_node * freesasa_tree_init(const freesasa_result *result, const freesasa_structure *structure, const char *name) int freesasa_node_free(freesasa_node *root) const freesasa_nodearea * freesasa_node_area(const freesasa_node *node) freesasa_node * freesasa_node_children(freesasa_node *node) freesasa_node * freesasa_node_next(freesasa_node *node) freesasa_node * freesasa_node_parent(freesasa_node *node) freesasa_nodetype freesasa_node_type(const freesasa_node *node) const char * freesasa_node_name(const freesasa_node *node) const char * freesasa_node_classified_by(const freesasa_node *node) int freesasa_node_atom_is_polar(const freesasa_node *node) int freesasa_node_atom_is_mainchain(const freesasa_node *node) double freesasa_node_atom_radius(const freesasa_node *node) const char * freesasa_node_atom_pdb_line(const freesasa_node *node) const char * freesasa_node_residue_number(const freesasa_node *node) int freesasa_node_residue_n_atoms(const freesasa_node *node) const freesasa_nodearea * freesasa_node_residue_reference(const freesasa_node *node) int freesasa_node_chain_n_residues(const freesasa_node *node) int freesasa_node_structure_n_chains(const freesasa_node *node) int freesasa_node_structure_n_atoms(const freesasa_node *node) const char * freesasa_node_structure_chain_labels(const freesasa_node *node) int freesasa_node_structure_model(const freesasa_node *node) const freesasa_result * freesasa_node_structure_result(const freesasa_node *node)freesasa-python-2.1.0/src/classifier.pyx000066400000000000000000000110101372737310000202730ustar00rootroot00000000000000from cfreesasa cimport * from libc.stdio cimport FILE, fopen, fclose cdef class Classifier: """ Assigns class and radius to atom by residue and atom name. Subclasses derived from :py:class:`.Classifier` can be used to define custom atomic radii and/or classes. Can also be initialized from config-files_ with a custom classifier. If initialized without arguments the default classifier is used. Derived classifiers must set the member :py:attr:`.purePython` to ``True`` Residue names should be of the format ``"ALA"``, ``"ARG"``, etc. Atom names should be of the format ``"CA"``, ``"N"``, etc. """ # this reference is used for classification cdef const freesasa_classifier *_c_classifier # if the classifier is read from a file we store it here, # with a reference in _c_classifier (for the sake of const-correctness) cdef freesasa_classifier *_dynamic_c_classifier # to be used by derived classes purePython = False def __init__ (self, fileName=None): """Constructor. If no file is provided the default classifier is used. Args: fileName (str): Name of file with classifier configuration. Raises: IOError: Problem opening/reading file Exception: Problem parsing provided configuration or initializing defaults """ cdef FILE *config self._c_classifier = NULL self._dynamic_c_classifier = NULL if fileName is not None: config = fopen(fileName, 'rb') if config is NULL: raise IOError("File '%s' could not be opened." % fileName) self._dynamic_c_classifier = freesasa_classifier_from_file(config) fclose(config) self._c_classifier = self._dynamic_c_classifier; if self._c_classifier is NULL: raise Exception("Error parsing configuration in '%s'." % fileName) else: self._c_classifier = &freesasa_default_classifier # The destructor def __dealloc__(self): if (self._isCClassifier()): freesasa_classifier_free(self._dynamic_c_classifier) @staticmethod def getStandardClassifier(type): """ Get a standard classifier (ProtOr, OONS or NACCESS) Args: type (str): The type, can have values ``'protor'``, ``'oons'`` or ``'naccess'`` Returns: :py:class:`.Classifier`: The requested classifier Raises: Exception: If type not recognized """ classifier = Classifier() if type == 'naccess': classifier._c_classifier = &freesasa_naccess_classifier elif type == 'oons': classifier._c_classifier = &freesasa_oons_classifier elif type == 'protor': classifier._c_classifier = &freesasa_protor_classifier else: raise Exception("Uknown classifier '%s'" % type) return classifier # This is used internally to determine if a Classifier wraps a C # classifier or not (necessary when generating structures) # returns Boolean def _isCClassifier(self): return not self.purePython def classify(self, residueName, atomName): """Class of atom. Depending on the configuration these classes can be anything, but typically they will be ``"Polar"`` and ``"Apolar"``. Unrecognized atoms will get the class ``"Unknown"``. Args: residueName (str): Residue name (`"ALA"`, `"ARG"`,...). atomName (str): Atom name (`"CA"`, `"C"`,...). Returns: str: Class name """ classIndex = freesasa_classifier_class(self._c_classifier, residueName, atomName) return freesasa_classifier_class2str(classIndex) def radius(self,residueName,atomName): """Radius of atom. This allows the classifier to be used to calculate the atomic radii used in calculations. Unknown atoms will get a negative radius. Args: residueName (str): Residue name (`"ALA"`, `"ARG"`, ...). atomName (str): Atom name (`"CA"`, `"C"`, ...). Returns: float: The radius in Å. """ return freesasa_classifier_radius(self._c_classifier, residueName, atomName) # the address obtained is a pointer to const def _get_address(self, size_t ptr2ptr): cdef freesasa_classifier **p = ptr2ptr p[0] = self._c_classifier # const cast freesasa-python-2.1.0/src/freesasa.pyx000066400000000000000000000167141372737310000177600ustar00rootroot00000000000000# -*- mode: python; python-indent-offset: 4 -*- # # The cython directives will fail if we don't have a few lines of comments above them. (Why?) # # cython: c_string_type=str, c_string_encoding=ascii """ The :py:mod:`freesasa` python module wraps the FreeSASA `C API`_ """ from libc.stdio cimport FILE, fopen, fclose from libc.stdlib cimport free, realloc, malloc from libc.string cimport memcpy from cfreesasa cimport * include "parameters.pyx" include "result.pyx" include "classifier.pyx" include "structure.pyx" ## Used for classification polar = 'Polar' ## Used for classification apolar = 'Apolar' ## int: Suppress all warnings and errors (used by setVerbosity()) silent = FREESASA_V_SILENT ## int: Suppress all warnings but not errors (used by setVerbosity()) nowarnings = FREESASA_V_NOWARNINGS ## int: Normal verbosity (used by setVerbosity()) normal = FREESASA_V_NORMAL ## int: Print debug messages (used by setVerbosity()) debug = FREESASA_V_DEBUG def calc(structure,parameters=None): """ Calculate SASA of Structure Args: structure: :py:class:`.Structure` to be used parameters: :py:class:`.Parameters` to use (if not specified defaults are used) Returns: :py:class:`.Result`: The results Raises: Exception: something went wrong in calculation (see C library error messages) """ cdef const freesasa_parameters *p = NULL cdef const freesasa_structure *s = NULL if parameters is not None: parameters._get_address(&p) structure._get_address(&s) result = Result() result._c_result = freesasa_calc_structure(s,p) result._c_root_node = freesasa_tree_init(result._c_result, s, "Structure") if result._c_result is NULL: raise Exception("Error calculating SASA.") return result def calcCoord(coord, radii, parameters=None): """ Calculate SASA for a set of coordinates and radii Args: coord (list): array of size 3*N with atomic coordinates `(x1, y1, z1, x2, y2, z2, ..., x_N, y_N, z_N)`. radii (list): array of size N with atomic radii `(r_1, r_2, ..., r_N)`. parameters: :py:class:`.Parameters` to use (if not specified, defaults are used) Raises: AssertionError: mismatched array-sizes Exception: Out of memory Exception: something went wrong in calculation (see C library error messages) """ assert(len(coord) == 3*len(radii)) cdef const freesasa_parameters *p = NULL cdef double *c = malloc(len(coord)*sizeof(double)) cdef double *r = malloc(len(radii)*sizeof(double)) if c is NULL or r is NULL: raise Exception("Memory allocation error") for i in xrange(len(coord)): c[i] = coord[i] for i in xrange(len(radii)): r[i] = radii[i] if parameters is not None: parameters._get_address(&p) result = Result() result._c_result = freesasa_calc_coord(c, r, len(radii), p) if result._c_result is NULL: raise Exception("Error calculating SASA.") free(c) free(r) return result def classifyResults(result,structure,classifier=None): """ Break SASA result down into classes. Args: result: :py:class:`.Result` from SASA calculation. structure: :py:class:`Structure` used in calculation. classifier: :py:class:`.Classifier` to use (if not specified default is used). Returns: dict: Dictionary with names of classes as keys and their SASA values as values. Raises: Exception: Problems with classification, see C library error messages (or Python exceptions if run with derived classifier). """ if classifier is None: classifier = Classifier() ret = dict() for i in range(0,structure.nAtoms()): name = classifier.classify(structure.residueName(i),structure.atomName(i)) if name not in ret: ret[name] = 0 ret[name] += result.atomArea(i) return ret def selectArea(commands, structure, result): """ Sum SASA result over a selection of atoms Args: commands (list): A list of commands with selections using Pymol syntax, e.g. ``"s1, resn ala+arg"`` or ``"s2, chain A and resi 1-5"``. See `select-syntax`_. structure: A :py:class:`.Structure`. result: :py:class:`.Result` from sasa calculation on structure. Returns: dict: Dictionary with names of selections (``"s1"``, ``"s2"``, ...) as keys, and the corresponding SASA values as values. Raises: Exception: Parser failed (typically syntax error), see library error messages. """ cdef freesasa_structure *s cdef freesasa_result *r cdef freesasa_selection *selection structure._get_address( &s) result._get_address( &r) value = dict() for cmd in commands: selection = freesasa_selection_new(cmd, s, r) if selection == NULL: raise Exception("Error parsing '%s'" % cmd) value[freesasa_selection_name(selection)] = freesasa_selection_area(selection) freesasa_selection_free(selection) return value def setVerbosity(verbosity): """ Set global verbosity Args: verbosity (int): Can have values :py:const:`.silent`, :py:const:`.nowarnings` or :py:const:`.normal` Raises: AssertionError: if verbosity has illegal value """ assert(verbosity in [silent, nowarnings, normal]) freesasa_set_verbosity(verbosity) def getVerbosity(): """ Get global verbosity Returns: int: Verbosity :py:const:`.silent`, :py:const:`.nowarnings` or :py:const:`.normal` """ return freesasa_get_verbosity() def calcBioPDB(bioPDBStructure, parameters = Parameters(), classifier = None, options = Structure.defaultOptions): """ Calc SASA from `BioPython` PDB structure. Usage:: result, sasa_classes, residue_areas = calcBioPDB(structure, ...) Experimental, not thorougly tested yet Args: bioPDBStructure: A `Bio.PDB` structure parameters: A :py:class:`.Parameters` object (uses default if none specified) classifier: A :py:class:`.Classifier` object (uses default if none specified) options (dict): Options supported are 'hetatm', 'skip-unknown' and 'halt-at-unknown' (uses :py:attr:`.Structure.defaultOptions` if none specified Returns: A :py:class:`.Result` object, a dictionary with classes defined by the classifier and associated areas, and a dictionary of the type returned by :py:meth:`.Result.residueAreas`. Raises: Exception: if unknown atom is encountered and the option 'halt-at-unknown' is active. Passes on exceptions from :py:func:`.calc()`, :py:func:`.classifyResults()` and :py:func:`.structureFromBioPDB()`. """ structure = structureFromBioPDB(bioPDBStructure, classifier, options) result = calc(structure, parameters) # Hack!: # This calculation depends on the structure not having been deallocated, # calling it later will cause seg-faults. By calling it now the result # is stored. # TODO: See if there is a refactoring that solves this in a more elegant way # residue_areas = result.residueAreas() sasa_classes = classifyResults(result, structure, classifier) return result, sasa_classes #, residue_areas freesasa-python-2.1.0/src/parameters.pyx000066400000000000000000000123641372737310000203270ustar00rootroot00000000000000from cfreesasa cimport * ## Used to specify the algorithm by Shrake & Rupley ShrakeRupley = 'ShrakeRupley' ## Used to specify the algorithm by Lee & Richards LeeRichards = 'LeeRichards' cdef class Parameters: """ Stores parameter values to be used by calculation. Default parameters are :: Parameters.defaultParameters = { 'algorithm' : LeeRichards, 'probe-radius' : freesasa_default_parameters.probe_radius, 'n-points' : freesasa_default_parameters.shrake_rupley_n_points, 'n-slices' : freesasa_default_parameters.lee_richards_n_slices, 'n-threads' : freesasa_default_parameters.n_threads } Attributes: defaultParamers (dict): The default parameters """ cdef freesasa_parameters _c_param defaultParameters = { 'algorithm' : LeeRichards, 'probe-radius' : freesasa_default_parameters.probe_radius, 'n-points' : freesasa_default_parameters.shrake_rupley_n_points, 'n-slices' : freesasa_default_parameters.lee_richards_n_slices, 'n-threads' : freesasa_default_parameters.n_threads } def __init__(self,param=None): """ Initializes Parameters object. Args: param (dict): optional argument to specify parameter-values, see :py:attr:`.Parameters.defaultParameters`. Raises: AssertionError: Invalid parameter values supplied """ self._c_param = freesasa_default_parameters if param != None: if 'algorithm' in param: self.setAlgorithm(param['algorithm']) if 'probe-radius' in param: self.setProbeRadius(param['probe-radius']) if 'n-points' in param: self.setNPoints(param['n-points']) if 'n-slices' in param: self.setNSlices(param['n-slices']) if 'n-threads' in param: self.setNThreads(param['n-threads']) unknownKeys = [] for key in param: if not key in self.defaultParameters: unknownKeys.append(key) if len(unknownKeys) > 0: raise AssertionError('Key(s): ',unknownKeys,', unknown') def setAlgorithm(self,alg): """ Set algorithm. Args: alg (str): algorithm name, only allowed values are :py:data:`freesasa.ShrakeRupley` and :py:data:`freesasa.LeeRichards` Raises: AssertionError: unknown algorithm specified """ if alg == ShrakeRupley: self._c_param.alg = FREESASA_SHRAKE_RUPLEY elif alg == LeeRichards: self._c_param.alg = FREESASA_LEE_RICHARDS else: raise AssertionError("Algorithm '%s' is unknown" % alg) def algorithm(self): """ Get algorithm. Returns: str: Name of algorithm """ if self._c_param.alg == FREESASA_SHRAKE_RUPLEY: return ShrakeRupley if self._c_param.alg == FREESASA_LEE_RICHARDS: return LeeRichards raise Exception("No algorithm specified, shouldn't be possible") def setProbeRadius(self,r): """ Set probe radius. Args: r (float): probe radius in Å (>= 0) Raises: AssertionError: r < 0 """ assert(r >= 0) self._c_param.probe_radius = r def probeRadius(self): """ Get probe radius. Returns: float: Probe radius in Å """ return self._c_param.probe_radius def setNPoints(self,n): """ Set number of test points in Shrake & Rupley algorithm. Args: n (int): Number of points (> 0). Raises: AssertionError: n <= 0. """ assert(n > 0) self._c_param.shrake_rupley_n_points = n def nPoints(self): """ Get number of test points in Shrake & Rupley algorithm. Returns: int: Number of points. """ return self._c_param.shrake_rupley_n_points def setNSlices(self,n): """ Set the number of slices per atom in Lee & Richards algorithm. Args: n (int): Number of slices (> 0) Raises: AssertionError: n <= 0 """ assert(n> 0) self._c_param.lee_richards_n_slices = n def nSlices(self): """ Get the number of slices per atom in Lee & Richards algorithm. Returns: int: Number of slices. """ return self._c_param.lee_richards_n_slices def setNThreads(self,n): """ Set the number of threads to use in calculations. Args: n (int): Number of points (> 0) Raises: AssertionError: n <= 0 """ assert(n>0) self._c_param.n_threads = n def nThreads(self): """ Get the number of threads to use in calculations. Returns: int: Number of threads. """ return self._c_param.n_threads # not pretty, but only way I've found to pass pointers around def _get_address(self, size_t ptr2ptr): cdef freesasa_parameters **p = ptr2ptr p[0] = &self._c_paramfreesasa-python-2.1.0/src/result.pyx000066400000000000000000000135021372737310000174750ustar00rootroot00000000000000from cfreesasa cimport * class ResidueArea: """ Stores absolute and relative areas for a residue Attributes: residueType (str): Type of Residue residueNumber (str): Residue number hasRelativeAreas (bool): False if there was noe reference area to calculate relative areas from total (float): Total SASA of residue polar (float): Polar SASA apolar (float): Apolar SASA mainChain (float): Main chain SASA sideChain (float): Side chain SASA relativeTotal (float): Relative total SASA relativePolar (float): Relative polar SASA relativeApolar (float): Relative Apolar SASA relativeMainChain (float): Relative main chain SASA relativeSideChain (float): Relative side chain SASA """ residueType = "" residueNumber = "" hasRelativeAreas = False total = 0 polar = 0 apolar = 0 mainChain = 0 sideChain = 0 relativeTotal = 0 relativePolar = 0 relativeApolar = 0 relativeMainChain = 0 relativeSideChain = 0 cdef class Result: """ Stores results from SASA calculation. The type of object returned by :py:func:`freesasa.calc()`, not intended to be used outside of that context. """ cdef freesasa_result* _c_result cdef freesasa_node* _c_root_node ## The constructor def __init__ (self): self._c_result = NULL self._c_root_node = NULL ## The destructor def __dealloc__(self): if self._c_result is not NULL: freesasa_result_free(self._c_result) if self._c_root_node is not NULL: freesasa_node_free(self._c_root_node) def nAtoms(self): """ Number of atoms in the results. Returns: int: Number of atoms. """ if self._c_result is not NULL: return self._c_result.n_atoms return 0 def totalArea(self): """ Total SASA. Returns: The total area in Å^2. Raises: AssertionError: If no results have been associated with the object. """ assert(self._c_result is not NULL) return self._c_result.total def atomArea(self,i): """ SASA for a given atom. Args: i (int): index of atom. Returns: float: SASA of atom i in Å^2. Raise: AssertionError: If no results have been associated with the object or if index is out of bounds """ assert(self._c_result is not NULL) assert(i < self._c_result.n_atoms) return self._c_result.sasa[i] def residueAreas(self): """ Get SASA for all residues including relative areas if available for the classifier used. Returns dictionary of results where first dimension is chain label and the second dimension residue number. I.e. ``result["A"]["5"]`` gives the :py:class:`.ResidueArea` of residue number 5 in chain A. Relative areas are normalized to 1, but can be > 1 for residues in unusual conformations or at the ends of chains. Returns: dictionary Raise: AssertionError: If no results or structure has been associated with the object. """ assert(self._c_result is not NULL) assert(self._c_root_node is not NULL, "Result.residueAreas can only be called on results generated directly or indirectly by freesasa.calc()") cdef freesasa_node* result_node = freesasa_node_children(self._c_root_node) cdef freesasa_node* structure = freesasa_node_children(result_node) cdef freesasa_node* chain cdef freesasa_node* residue cdef freesasa_nodearea* c_area cdef freesasa_nodearea* c_ref_area result = {} chain = freesasa_node_children(structure) while (chain != NULL): residue = freesasa_node_children(chain) chainLabel = freesasa_node_name(chain) result[chainLabel] = {} while (residue != NULL): c_area = freesasa_node_area(residue) c_ref_area = freesasa_node_residue_reference(residue) residueNumber = freesasa_node_residue_number(residue).strip() residueType = freesasa_node_name(residue).strip() area = ResidueArea() area.residueType = residueType area.residueNumber = residueNumber area.total = c_area.total area.mainChain = c_area.main_chain area.sideChain = c_area.side_chain area.polar = c_area.polar area.apolar = c_area.apolar if (c_ref_area is not NULL): area.hasRelativeAreas = True area.relativeTotal = self._safe_div(c_area.total, c_ref_area.total) area.relativeMainChain = self._safe_div(c_area.main_chain, c_ref_area.main_chain) area.relativeSideChain = self._safe_div(c_area.side_chain, c_ref_area.side_chain) area.relativePolar = self._safe_div(c_area.polar, c_ref_area.polar) area.relativeApolar = self._safe_div(c_area.apolar, c_ref_area.apolar) result[chainLabel][residueNumber] = area residue = freesasa_node_next(residue) chain = freesasa_node_next(chain) return result def _safe_div(self,a,b): try: return a/b except ZeroDivisionError: return float('nan') def _get_address(self, size_t ptr2ptr): cdef freesasa_result **p = ptr2ptr p[0] = self._c_result freesasa-python-2.1.0/src/structure.pyx000066400000000000000000000424441372737310000202260ustar00rootroot00000000000000from cfreesasa cimport * from libc.stdio cimport FILE, fopen, fclose cdef class Structure: """ Represents a protein structure, including its atomic radii. Initialized from PDB-file. Calculates atomic radii using default classifier, or custom one provided as argument to initalizer. Default options are :: Structure.defaultOptions = { 'hetatm' : False, # False: skip HETATM # True: include HETATM 'hydrogen' : False, # False: ignore hydrogens # True: include hydrogens 'join-models' : False, # False: Only use the first MODEL # True: Include all MODELs 'skip-unknown' : False, # False: Guess radius for unknown atoms # based on element # True: Skip unknown atoms 'halt-at-unknown' : False # False: set radius for unknown atoms, # that can not be guessed to 0. # True: Throw exception on unknown atoms. } Attributes: defaultOptions: Default options for reading structure from PDB. """ cdef freesasa_structure* _c_structure cdef const freesasa_classifier* _c_classifier cdef int _c_options defaultOptions = { 'hetatm' : False, 'hydrogen' : False, 'join-models' : False, 'skip-unknown' : False, 'halt-at-unknown' : False } defaultStructureArrayOptions = { 'hetatm' : False, 'hydrogen' : False, 'separate-chains' : True, 'separate-models' : False } def __init__(self, fileName=None, classifier=None, options = defaultOptions): """ Constructor If a PDB file is provided, the structure will be constructed based on the file. If not, this simply initializes an empty structure with the given classifier and options. Atoms will then have to be added manually using `:py:meth:`.Structure.addAtom()`. Args: fileName (str): PDB file (if `None` empty structure generated). classifier: An optional :py:class:`.Classifier` to calculate atomic radii, uses default if none provided. This classifier will also be used in calls to :py:meth:`.Structure.addAtom()` but only if it's the default classifier, one of the standard classifiers from :py:meth:`.Classifier.getStandardClassifier()`, or defined by a config-file (i.e. if it uses the underlying C API). options (dict): specify which atoms and models to include, default is :py:attr:`.Structure.defaultOptions` Raises: IOError: Problem opening/reading file. Exception: Problem parsing PDB file or calculating atomic radii. Exception: If option 'halt-at-unknown' selected and unknown atom encountered. """ self._c_structure = NULL self._c_classifier = NULL if classifier is None: classifier = Classifier() if classifier._isCClassifier(): classifier._get_address(&self._c_classifier) self._c_options = Structure._get_structure_options(options) if fileName is None: self._c_structure = freesasa_structure_new() else: self._initFromFile(fileName, classifier) def _initFromFile(self, fileName, classifier): cdef FILE *input input = fopen(fileName,'rb') if input is NULL: raise IOError("File '%s' could not be opened." % fileName) if not classifier._isCClassifier(): # supress warnings setVerbosity(silent) self._c_structure = freesasa_structure_from_pdb(input, self._c_classifier, self._c_options) if not classifier._isCClassifier(): setVerbosity(normal) fclose(input) if self._c_structure is NULL: raise Exception("Error reading '%s'." % fileName) # for pure Python classifiers we use the default # classifier above to initialize the structure and then # reassign radii using the provided classifier here if (not classifier._isCClassifier()): self.setRadiiWithClassifier(classifier) def addAtom(self, atomName, residueName, residueNumber, chainLabel, x, y, z): """ Add atom to structure. This function is meant to be used if the structure was not initialized from a PDB. The options and classifier passed to the constructor for the :py:class:`.Structure` will be used (see the documentation of the constructor for restrictions). The radii set by the classifier can be overriden by calling :py:meth:`.Structure.setRadiiWithClassifier()` afterwards. There are no restraints on string lengths for the arguments, but the atom won't be added if the classifier doesn't recognize the atom and also cannot deduce its element from the atom name. Args: atomName (str): atom name (e.g. `"CA"`) residueName (str): residue name (e.g. `"ALA"`) residueNumber (str or int): residue number (e.g. `'12'`) or integer. Some PDBs have residue-numbers that aren't regular numbers. Therefore treated as a string primarily. chainLabel (str): 1-character string with chain label (e.g. 'A') x,y,z (float): coordinates Raises: Exception: Residue-number invalid AssertionError: """ if (type(residueNumber) is str): resnum = residueNumber elif (type(residueNumber) is int): resnum = "%d" % residueNumber else: raise Exception("Residue-number invalid, must be either string or number") cdef const char *label = chainLabel ret = freesasa_structure_add_atom_wopt( self._c_structure, atomName, residueName, resnum, label[0], x, y, z, self._c_classifier, self._c_options) assert(ret != FREESASA_FAIL) def setRadiiWithClassifier(self,classifier): """ Assign radii to atoms in structure using a classifier. Args: classifier: A :py:class:`.Classifier` to use to calculate radii. Raises: AssertionError: if structure not properly initialized """ assert(self._c_structure is not NULL) n = self.nAtoms() r = [] for i in range(0,n): r.append(classifier.radius(self.residueName(i), self.atomName(i))) self.setRadii(r) def setRadii(self,radiusArray): """ Set atomic radii from an array Args: radiusArray (list): Array of atomic radii in Ångström, should have nAtoms() elements. Raises: AssertionError: if radiusArray has wrong dimension, structure not properly initialized, or if the array contains negative radii (not properly classified?) """ assert(self._c_structure is not NULL) n = self.nAtoms() assert len(radiusArray) == n cdef double *r = malloc(sizeof(double)*n) assert(r is not NULL) for i in range(0,n): r[i] = radiusArray[i] assert(r[i] >= 0), "Error: Radius array is <= 0 for the residue: " + self.residueName(i) + " ,atom: " + self.atomName(i) freesasa_structure_set_radius(self._c_structure, r) def nAtoms(self): """ Number of atoms. Returns: int: Number of atoms Raises: AssertionError: if not properly initialized """ assert(self._c_structure is not NULL) return freesasa_structure_n(self._c_structure) def radius(self,i): """ Radius of atom. Args: i (int): Index of atom. Returns: float: Radius in Å. Raises: AssertionError: if index out of bounds, object not properly initalized. """ assert(i >= 0 and i < self.nAtoms()) assert(self._c_structure is not NULL) cdef const double *r = freesasa_structure_radius(self._c_structure) assert(r is not NULL) return r[i] def setRadius(self, atomIndex, radius): """ Set radius for a given atom Args: atomIndex (int): Index of atom radius (float): Value of radius Raises: AssertionError: if index out of bounds, radius negative, or structure not properly initialized """ assert(self._c_structure is not NULL) assert(atomIndex >= 0 and atomIndex < self.nAtoms()) assert(radius >= 0) freesasa_structure_atom_set_radius(self._c_structure, atomIndex, radius) def atomName(self,i): """ Get atom name Args: i (int): Atom index. Returns: str: Atom name as 4-character string. Raises: AssertionError: if index out of range or Structure not properly initialized. """ assert(i >= 0 and i < self.nAtoms()) assert(self._c_structure is not NULL) return freesasa_structure_atom_name(self._c_structure,i) def residueName(self,i): """ Get residue name of given atom. Args: i (int): Atom index. Returns: str: Residue name as 3-character string. Raises: AssertionError: if index out of range or Structure not properly initialized """ assert(i >= 0 and i < self.nAtoms()) assert(self._c_structure is not NULL) return freesasa_structure_atom_res_name(self._c_structure,i) def residueNumber(self,i): """ Get residue number for given atom. Residue number will include the insertion code if there is one. Args: i (int): Atom index. Returns: str: Residue number as 5-character string (last character is either whitespace or insertion code) Raises: AssertionError: if index out of range or Structure not properly initialized """ assert(i >= 0 and i < self.nAtoms()) assert(self._c_structure is not NULL) return freesasa_structure_atom_res_number(self._c_structure,i) def chainLabel(self,i): """ Get chain label for given atom. Args: i (int): Atom index. Returns: str: Chain label as 1-character string. Raises: AssertionError: if index out of range or Structure not properly initialized """ assert(i >= 0 and i < self.nAtoms()) assert(self._c_structure is not NULL) cdef char label[2] label[0] = freesasa_structure_atom_chain(self._c_structure,i) label[1] = '\0' return label def coord(self, i): """ Get coordinates of given atom. Args: i (int): Atom index. Returns: list: array of x, y, and z coordinates Raises: AssertionError: if index out of range or Structure not properly initialized """ assert(i >= 0 and i < self.nAtoms()) assert(self._c_structure is not NULL) cdef const double *coord = freesasa_structure_coord_array(self._c_structure) return [coord[3*i], coord[3*i+1], coord[3*i+2]] @staticmethod def _get_structure_options(param): options = 0 # check validity of options knownOptions = {'hetatm','hydrogen','join-models','separate-models', 'separate-chains','skip-unknown','halt-at-unknown'} unknownOptions = [] for key in param: if not key in knownOptions: unknownOptions.append(key) if len(unknownOptions) > 0: raise AssertionError("Option(s): ",unknownOptions," unknown.") # calculate bitfield if 'hetatm' in param and param['hetatm']: options |= FREESASA_INCLUDE_HETATM if 'hydrogen' in param and param['hydrogen']: options |= FREESASA_INCLUDE_HYDROGEN if 'join-models' in param and param['join-models']: options |= FREESASA_JOIN_MODELS if 'separate-models' in param and param['separate-models']: options |= FREESASA_SEPARATE_MODELS if 'separate-chains' in param and param['separate-chains']: options |= FREESASA_SEPARATE_CHAINS if 'skip-unknown' in param and param['skip-unknown']: options |= FREESASA_SKIP_UNKNOWN if 'halt-at-unknown' in param and param['halt-at-unknown']: options |= FREESASA_HALT_AT_UNKNOWN return options def _get_address(self, size_t ptr2ptr): cdef freesasa_structure **p = ptr2ptr p[0] = self._c_structure def _set_address(self, size_t ptr2ptr): cdef freesasa_structure **p = ptr2ptr self._c_structure = p[0] ## The destructor def __dealloc__(self): if self._c_structure is not NULL: freesasa_structure_free(self._c_structure) def structureArray(fileName, options = Structure.defaultStructureArrayOptions, classifier = None): """ Create array of structures from PDB file. Split PDB file into several structures by either by treating chains separately, by treating each MODEL as a separate structure, or both. Args: fileName (str): The PDB file. options (dict): Specification for how to read the PDB-file (see :py:attr:`.Structure.defaultStructureArrayOptions` for options and default value). classifier: :py:class:`.Classifier` to assign atoms radii, default is used if none specified. Returns: list: An array of :py:class:`.Structure` Raises: AssertionError: if `fileName` is None AssertionError: if an option value is not recognized AssertionError: if neither of the options `'separate-chains'` and `'separate-models'` are specified. IOError: if can't open file Exception: if there are problems parsing the input """ assert fileName is not None # we need to have at least one of these assert(('separate-chains' in options and options['separate-chains'] is True) or ('separate-models' in options and options['separate-models'] is True)) structure_options = Structure._get_structure_options(options) cdef FILE *input input = fopen(fileName,'rb') if input is NULL: raise IOError("File '%s' could not be opened." % fileName) cdef int n verbosity = getVerbosity() if classifier is not None: setVerbosity(silent) cdef freesasa_structure** sArray = freesasa_structure_array(input,&n,NULL,structure_options) fclose(input) if classifier is not None: setVerbosity(verbosity) if sArray is NULL: raise Exception("Problems reading structures in '%s'." % fileName) structures = [] for i in range(0,n): structures.append(Structure()) structures[-1]._set_address( &sArray[i]) if classifier is not None: structures[-1].setRadiiWithClassifier(classifier) free(sArray) return structures def structureFromBioPDB(bioPDBStructure, classifier=None, options = Structure.defaultOptions): """ Create a freesasa structure from a Bio.PDB structure Experimental, not thorougly tested yet. Structures generated this way will not preserve whitespace in residue numbers, etc, as in :py:class:`.Structure`. Args: bioPDBStructure: a `Bio.PDB` structure classifier: an optional :py:class:`.Classifier` to specify atomic radii options (dict): Options supported are `'hetatm'`, `'skip-unknown'` and `'halt-at-unknown'` Returns: :py:class:`.Structure`: The structure Raises: Exception: if option 'halt-at-unknown' is selected and unknown atoms are encountered. Passes on exceptions from :py:meth:`.Structure.addAtom()` and :py:meth:`.Structure.setRadiiWithClassifier()`. """ structure = Structure() if (classifier is None): classifier = Classifier() optbitfield = Structure._get_structure_options(options) atoms = bioPDBStructure.get_atoms() for a in atoms: r = a.get_parent() hetflag, resseq, icode = r.get_id() resname = r.get_resname() if (hetflag is not ' ' and not (optbitfield & FREESASA_INCLUDE_HETATM)): continue c = r.get_parent() v = a.get_vector() if (icode): resseq = str(resseq) + str(icode) if (classifier.classify(resname, a.get_fullname()) is 'Unknown'): if (optbitfield & FREESASA_SKIP_UNKNOWN): continue if (optbitfield & FREESASA_HALT_AT_UNKNOWN): raise Exception("Halting at unknown atom") structure.addAtom(a.get_fullname(), r.get_resname(), resseq, c.get_id(), v[0], v[1], v[2]) structure.setRadiiWithClassifier(classifier) return structure freesasa-python-2.1.0/test.py000066400000000000000000000405271372737310000161660ustar00rootroot00000000000000from freesasa import * import unittest import math import os import faulthandler # this class tests using derived classes to create custom Classifiers class DerivedClassifier(Classifier): purePython = True def classify(self,residueName,atomName): return 'bla' def radius(self,residueName,atomName): return 10 class FreeSASATestCase(unittest.TestCase): def testParameters(self): d = Parameters.defaultParameters p = Parameters() self.assertTrue(p.algorithm() == LeeRichards) self.assertTrue(p.algorithm() == d['algorithm']) self.assertTrue(p.probeRadius() == d['probe-radius']) self.assertTrue(p.nPoints() == d['n-points']) self.assertTrue(p.nSlices() == d['n-slices']) self.assertTrue(p.nThreads() == d['n-threads']) self.assertRaises(AssertionError,lambda: Parameters({'not-an-option' : 1})) self.assertRaises(AssertionError,lambda: Parameters({'n-slices' : 50, 'not-an-option' : 1})) self.assertRaises(AssertionError,lambda: Parameters({'not-an-option' : 50, 'also-not-an-option' : 1})) p.setAlgorithm(ShrakeRupley) self.assertTrue(p.algorithm() == ShrakeRupley) p.setAlgorithm(LeeRichards) self.assertTrue(p.algorithm() == LeeRichards) self.assertRaises(AssertionError,lambda: p.setAlgorithm(-10)) p.setProbeRadius(1.5) self.assertTrue(p.probeRadius() == 1.5) self.assertRaises(AssertionError,lambda: p.setProbeRadius(-1)) p.setNPoints(20) self.assertTrue(p.nPoints() == 20) self.assertRaises(AssertionError,lambda: p.setNPoints(0)) p.setNSlices(10) self.assertTrue(p.nSlices() == 10) self.assertRaises(AssertionError,lambda: p.setNSlices(0)) p.setNThreads(2) self.assertTrue(p.nThreads() == 2) self.assertRaises(AssertionError, lambda: p.setNThreads(0)) def testResult(self): r = Result() self.assertRaises(AssertionError,lambda: r.totalArea()) self.assertRaises(AssertionError,lambda: r.atomArea(0)) def testClassifier(self): c = Classifier() self.assertTrue(c._isCClassifier()) self.assertTrue(c.classify("ALA"," CB ") == apolar) self.assertTrue(c.classify("ARG"," NH1") == polar) self.assertTrue(c.radius("ALA"," CB ") == 1.88) setVerbosity(silent) self.assertRaises(Exception,lambda: Classifier("lib/tests/data/err.config")) self.assertRaises(IOError,lambda: Classifier("")) setVerbosity(normal) c = Classifier("lib/tests/data/test.config") self.assertTrue(c.classify("AA","aa") == "Polar") self.assertTrue(c.classify("BB","bb") == "Apolar") self.assertTrue(c.radius("AA","aa") == 1.0) self.assertTrue(c.radius("BB","bb") == 2.0) c = Classifier("lib/share/oons.config") self.assertTrue(c.radius("ALA"," CB ") == 2.00) c = DerivedClassifier() self.assertTrue(not c._isCClassifier()) self.assertTrue(c.radius("ALA"," CB ") == 10) self.assertTrue(c.radius("ABCDEFG","HIJKLMNO") == 10) self.assertTrue(c.classify("ABCDEFG","HIJKLMNO") == "bla") def testStructure(self): self.assertRaises(IOError,lambda: Structure("xyz#$%")) setVerbosity(silent) # test any file that's not a PDB file self.assertRaises(Exception,lambda: Structure("lib/tests/data/err.config")) self.assertRaises(Exception,lambda: Structure("lib/tests/data/empty.pdb")) self.assertRaises(Exception,lambda: Structure("lib/tests/data/empty_model.pdb")) setVerbosity(normal) s = Structure("lib/tests/data/1ubq.pdb") self.assertTrue(s.nAtoms() == 602) self.assertTrue(s.radius(1) == 1.88) self.assertTrue(s.chainLabel(1) == 'A') self.assertTrue(s.atomName(1) == ' CA ') self.assertTrue(s.residueName(1) == 'MET') self.assertTrue(s.residueNumber(1) == ' 1 ') s2 = Structure("lib/tests/data/1ubq.pdb",Classifier("lib/share/oons.config")) self.assertTrue(s.nAtoms() == 602) self.assertTrue(math.fabs(s2.radius(1) - 2.0) < 1e-5) s2 = Structure("lib/tests/data/1ubq.pdb",Classifier("lib/share/protor.config")) for i in range (0,601): self.assertTrue(math.fabs(s.radius(i)- s2.radius(i)) < 1e-5) self.assertRaises(Exception,lambda: Structure("lib/tests/data/1ubq.pdb","lib/tests/data/err.config")) s = Structure() s.addAtom(' CA ','ALA',' 1','A',1,1,1) self.assertTrue(s.nAtoms() == 1) self.assertTrue(s.atomName(0) == ' CA ') self.assertTrue(s.residueName(0) == 'ALA') self.assertTrue(s.residueNumber(0) == ' 1') self.assertTrue(s.chainLabel(0) == 'A') self.assertTrue(s.nAtoms() == 1) x, y, z = s.coord(0) self.assertTrue(x == 1 and y ==1 and z ==1) s.addAtom(' CB ','ALA',2,'A',2,1,1) self.assertTrue(s.nAtoms() == 2) self.assertTrue(s.residueNumber(1) == '2') self.assertRaises(AssertionError, lambda: s.atomName(3)) self.assertRaises(AssertionError, lambda: s.residueName(3)) self.assertRaises(AssertionError, lambda: s.residueNumber(3)) self.assertRaises(AssertionError, lambda: s.chainLabel(3)) self.assertRaises(AssertionError, lambda: s.coord(3)) self.assertRaises(AssertionError, lambda: s.radius(3)) s.setRadiiWithClassifier(Classifier()) self.assertTrue(s.radius(0) == 1.88) self.assertTrue(s.radius(1) == 1.88) s.setRadiiWithClassifier(DerivedClassifier()) self.assertTrue(s.radius(0) == s.radius(1) == 10.0) s.setRadii([1.0,3.0]) self.assertTrue(s.radius(0) == 1.0) self.assertTrue(s.radius(1) == 3.0) s.setRadius(0, 10.0) self.assertTrue(s.radius(0) == 10.0); self.assertRaises(AssertionError,lambda: s.setRadius(2,10)); self.assertRaises(AssertionError,lambda: s.setRadii([1])) self.assertRaises(AssertionError,lambda: s.setRadii([1,2,3])) self.assertRaises(AssertionError,lambda: s.atomName(2)) self.assertRaises(AssertionError,lambda: s.residueName(2)) self.assertRaises(AssertionError,lambda: s.residueNumber(2)) self.assertRaises(AssertionError,lambda: s.chainLabel(2)) setVerbosity(nowarnings) s = Structure("lib/tests/data/1d3z.pdb",None,{'hydrogen' : True}) self.assertTrue(s.nAtoms() == 1231) s = Structure("lib/tests/data/1d3z.pdb",None,{'hydrogen' : True, 'join-models' : True}) self.assertTrue(s.nAtoms() == 12310) s = Structure("lib/tests/data/1ubq.pdb",None,{'hetatm' : True}) self.assertTrue(s.nAtoms() == 660) s = Structure("lib/tests/data/1d3z.pdb",None,{'hydrogen' : True, 'skip-unknown' : True}) self.assertTrue(s.nAtoms() == 602) setVerbosity(silent) self.assertRaises(Exception, lambda : Structure("lib/tests/data/1d3z.pdb", None, {'hydrogen' : True, 'halt-at-unknown' : True})) setVerbosity(normal) s = Structure(options = { 'halt-at-unknown': True }) setVerbosity(silent) self.assertRaises(Exception, lambda: s.addAtom(' XX ','ALA',' 1','A',1,1,1)) setVerbosity(normal) s = Structure(options = { 'skip-unknown': True }) setVerbosity(silent) s.addAtom(' XX ','ALA',' 1','A',1,1,1) self.assertEqual(s.nAtoms(), 0) setVerbosity(normal) s = Structure(classifier = Classifier.getStandardClassifier("naccess")) s.addAtom(' CA ', 'ALA',' 1','A',1,1,1) self.assertEqual(s.radius(0), 1.87) def testStructureArray(self): # default separates chains, only uses first model (129 atoms per chain) ss = structureArray("lib/tests/data/2jo4.pdb") self.assertTrue(len(ss) == 4) for s in ss: self.assertTrue(s.nAtoms() == 129) # include all models, separate chains, and include hydrogen and hetatm (286 atoms per chain) setVerbosity(nowarnings) ss = structureArray("lib/tests/data/2jo4.pdb",{'separate-models' : True, 'hydrogen' : True, 'hetatm' : True, 'separate-chains' : True}) self.assertTrue(len(ss) == 4*10) for s in ss: self.assertTrue(s.nAtoms() == 286) # include all models, and include hydrogen and hetatm (286 atoms per chain) ss = structureArray("lib/tests/data/2jo4.pdb",{'separate-models' : True, 'hydrogen' : True, 'hetatm' : True}) self.assertTrue(len(ss) == 10) for s in ss: self.assertTrue(s.nAtoms() == 286*4) setVerbosity(normal) # check that the structures initialized this way can be used for calculations ss = structureArray("lib/tests/data/1ubq.pdb") self.assertTrue(len(ss) == 1) self.assertTrue(ss[0].nAtoms() == 602) result = calc(ss[0],Parameters({'algorithm' : ShrakeRupley})) self.assertTrue(math.fabs(result.totalArea() - 4834.716265) < 1e-5) # Test exceptions setVerbosity(silent) self.assertRaises(AssertionError,lambda: structureArray(None)) self.assertRaises(IOError,lambda: structureArray("")) self.assertRaises(Exception,lambda: structureArray("lib/tests/data/err.config")) self.assertRaises(AssertionError,lambda: structureArray("lib/tests/data/2jo4.pdb",{'not-an-option' : True})) self.assertRaises(AssertionError, lambda: structureArray("lib/tests/data/2jo4.pdb", {'not-an-option' : True, 'hydrogen' : True})) self.assertRaises(AssertionError, lambda: structureArray("lib/tests/data/2jo4.pdb", {'hydrogen' : True})) setVerbosity(normal) def testCalc(self): # test default settings structure = Structure("lib/tests/data/1ubq.pdb") result = calc(structure,Parameters({'algorithm' : ShrakeRupley})) self.assertTrue(math.fabs(result.totalArea() - 4834.716265) < 1e-5) sasa_classes = classifyResults(result,structure) self.assertTrue(math.fabs(sasa_classes['Polar'] - 2515.821238) < 1e-5) self.assertTrue(math.fabs(sasa_classes['Apolar'] - 2318.895027) < 1e-5) # test residue areas residueAreas = result.residueAreas() a76 = residueAreas['A']['76'] self.assertEqual(a76.residueType, "GLY") self.assertEqual(a76.residueNumber, "76") self.assertTrue(a76.hasRelativeAreas) self.assertTrue(math.fabs(a76.total - 142.1967898) < 1e-5) self.assertTrue(math.fabs(a76.mainChain - 142.1967898) < 1e-5) self.assertTrue(math.fabs(a76.sideChain - 0) < 1e-5) self.assertTrue(math.fabs(a76.polar - 97.297889) < 1e-5) self.assertTrue(math.fabs(a76.apolar - 44.898900) < 1e-5) self.assertTrue(math.fabs(a76.relativeTotal - 1.75357) < 1e-4) self.assertTrue(math.fabs(a76.relativeMainChain - 1.75357) < 1e-4) self.assertTrue(math.isnan(a76.relativeSideChain)) self.assertTrue(math.fabs(a76.relativePolar - 2.17912) < 1e-4) self.assertTrue(math.fabs(a76.relativeApolar - 1.23213) < 1e-4) # test L&R result = calc(structure,Parameters({'algorithm' : LeeRichards, 'n-slices' : 20})) sasa_classes = classifyResults(result,structure) self.assertTrue(math.fabs(result.totalArea() - 4804.055641) < 1e-5) self.assertTrue(math.fabs(sasa_classes['Polar'] - 2504.217302) < 1e-5) self.assertTrue(math.fabs(sasa_classes['Apolar'] - 2299.838339) < 1e-5) # test extending Classifier with derived class sasa_classes = classifyResults(result,structure,DerivedClassifier()) self.assertTrue(math.fabs(sasa_classes['bla'] - 4804.055641) < 1e-5) ## test calculating with user-defined classifier ## classifier = Classifier("lib/share/oons.config") # classifier passed to assign user-defined radii, could also have used setRadiiWithClassifier() structure = Structure("lib/tests/data/1ubq.pdb",classifier) result = calc(structure,Parameters({'algorithm' : ShrakeRupley})) self.assertTrue(math.fabs(result.totalArea() - 4779.5109924) < 1e-5) sasa_classes = classifyResults(result,structure,classifier) # classifier passed to get user-classes self.assertTrue(math.fabs(sasa_classes['Polar'] - 2236.9298941) < 1e-5) self.assertTrue(math.fabs(sasa_classes['Apolar'] - 2542.5810983) < 1e-5) def testCalcCoord(self): # one unit sphere radii = [1] coord = [0,0,0] parameters = Parameters() parameters.setNSlices(5000) parameters.setProbeRadius(0) parameters.setNThreads(1) result = calcCoord(coord, radii, parameters) self.assertTrue(math.fabs(result.totalArea() - 4*math.pi) < 1e-3) # two separate unit spheres radii = [1,1] coord = [0,0,0, 4,4,4] result = calcCoord(coord, radii, parameters) self.assertTrue(math.fabs(result.totalArea() - 2*4*math.pi) < 1e-3) self.assertRaises(AssertionError, lambda: calcCoord(radii, radii)) def testSelectArea(self): structure = Structure("lib/tests/data/1ubq.pdb") result = calc(structure,Parameters({'algorithm' : ShrakeRupley})) # will only test that this gets through to the C interface, # extensive checking of the parser is done in the C unit tests selections = selectArea(('s1, resn ala','s2, resi 1'),structure,result) self.assertTrue(math.fabs(selections['s1'] - 118.35) < 0.1) self.assertTrue(math.fabs(selections['s2'] - 50.77) < 0.1) def testBioPDB(self): try: from Bio.PDB import PDBParser except ImportError: print("Can't import Bio.PDB, tests skipped") pass else: parser = PDBParser(QUIET=True) bp_structure = parser.get_structure("29G11","lib/tests/data/1a0q.pdb") s1 = structureFromBioPDB(bp_structure) s2 = Structure("lib/tests/data/1a0q.pdb") self.assertTrue(s1.nAtoms() == s2.nAtoms()) # make sure we got the insertion code self.assertEqual(s1.residueNumber(2286), '82A') for i in range(0, s2.nAtoms()): self.assertTrue(s1.radius(i) == s2.radius(i)) # there can be tiny errors here self.assertTrue(math.fabs(s1.coord(i)[0] - s2.coord(i)[0]) < 1e-5) self.assertTrue(math.fabs(s1.coord(i)[1] - s2.coord(i)[1]) < 1e-5) self.assertTrue(math.fabs(s1.coord(i)[2] - s2.coord(i)[2]) < 1e-5) # whitespace won't match self.assertIn(s1.residueNumber(i), s2.residueNumber(i)) # because Bio.PDB structures will have slightly different # coordinates (due to rounding errors) we set the # tolerance as high as 1e-3 result = calc(s1, Parameters({'algorithm' : LeeRichards, 'n-slices' : 20})) self.assertTrue(math.fabs(result.totalArea() - 18923.280586) < 1e-3) sasa_classes = classifyResults(result, s1) self.assertTrue(math.fabs(sasa_classes['Polar'] - 9143.066411) < 1e-3) self.assertTrue(math.fabs(sasa_classes['Apolar'] - 9780.2141746) < 1e-3) residue_areas = result.residueAreas() self.assertTrue(math.fabs(residue_areas['L']['2'].total - 43.714) < 1e-2) faulthandler.enable() result, sasa_classes = calcBioPDB(bp_structure, Parameters({'algorithm' : LeeRichards, 'n-slices' : 20})) self.assertTrue(math.fabs(result.totalArea() - 18923.280586) < 1e-3) self.assertTrue(math.fabs(sasa_classes['Polar'] - 9143.066411) < 1e-3) self.assertTrue(math.fabs(sasa_classes['Apolar'] - 9780.2141746) < 1e-3) residue_areas = result.residueAreas() self.assertTrue(math.fabs(residue_areas['L']['2'].total - 43.714) < 1e-2) if __name__ == '__main__': # make sure we're in the right directory (if script is called from # outside the directory) abspath = os.path.abspath(__file__) dirname = os.path.dirname(abspath) os.chdir(dirname) unittest.main()