diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..2ef845b63 --- /dev/null +++ b/.flake8 @@ -0,0 +1,10 @@ +[flake8] +ignore = E741, C901, W503, F401, F841, F811 +exclude = .eggs,*.egg,build +filename = *.pyx,*.px*, *py +max-line-length = 88 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 + + +## E203, E266, E501, F403 diff --git a/.gitignore b/.gitignore index 669233f0b..a01f41455 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,6 @@ wheels/ .installed.cfg cython_debug/** *.egg -MANIFEST # PyInstaller # Usually these files are written by a python script from a template @@ -32,7 +31,6 @@ MANIFEST # Installer logs pip-log.txt -pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ @@ -64,8 +62,10 @@ instance/ # Sphinx documentation docs/_build/ -docs/_static/ -docs/_templates +docs/api_c + +# Doxygen output +doxyoutput/ # PyBuilder target/ @@ -76,12 +76,6 @@ target/ # pyenv .python-version -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - # Environments .env .venv @@ -95,9 +89,6 @@ venv.bak/ .spyderproject .spyproject -# Rope project settings -.ropeproject - # mkdocs documentation /site @@ -110,14 +101,26 @@ venv.bak/ # build *.DSYM/** +# folders +src/c_lib/test_c/** + # files *.out *.prof *.ipynb *.mp4 *.html -*.png test_os.py - -# Cython build file -src/c_lib/mrmethods/nmr_methods.c +note.txt +sideband2.c +dmfit_comparision.py +simulate_lineshape.py +sideband.3 +sandbox.c +nmr_methods.c +main.c +mrsimulator.code-workspace +jquery.js +setup.py.src +setup.temp +__config__.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb9de8b8d..a40f19fe6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: python3.7 default_stages: [commit, push, manual] repos: -- repo: https://github.com/ambv/black + - repo: https://github.com/ambv/black rev: stable hooks: - id: black @@ -13,23 +13,23 @@ repos: types: [python] files: \.pyi?$ -- repo: https://github.com/asottile/blacken-docs + - repo: https://github.com/asottile/blacken-docs rev: v1.0.0 hooks: - - id: blacken-docs - additional_dependencies: [black==19.3b0] - language: python - language_version: python3.7 + - id: blacken-docs + additional_dependencies: [black==19.3b0] + language: python + language_version: python3.7 -- repo: https://github.com/pre-commit/pre-commit-hooks + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v1.2.3 hooks: - id: check-yaml language: python - id: check-added-large-files language: python - # - id: fix-encoding-pragma - # language: python + - id: fix-encoding-pragma + language: python - id: check-docstring-first language: python - id: flake8 @@ -46,7 +46,6 @@ repos: language: python types: [python] - # - repo: https://github.com/asottile/reorder_python_imports # rev: v1.4.0 # hooks: diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..4aad5db38 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,20 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + + +# Optionally build your docs in additional formats such as PDF and ePub +formats: all + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: docs/requirements.txt diff --git a/.travis.yml b/.travis.yml index 09e474b7f..f1c85f091 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,23 @@ -os: linux +os: + - linux + dist: xenial language: python -cache: pip + python: - - "3.6" + - '3.6' + - '3.7.3' + +env: + # - PKG1=mkl PKG2=mkl-include + - PKG1=nomkl PKG2=openblas + +cache: + pip: true + directories: + - $HOME/.ccache + +# safelist branches: only: - master @@ -18,11 +32,12 @@ before_install: - conda info -a - conda create -q -n test-env python=$TRAVIS_PYTHON_VERSION pytest pytest-cov - source activate test-env - - conda install fftw mkl-service - - pip install -r requirements.txt - - pip install -r requirements-dev.txt + - conda install -c anaconda $PKG1 $PKG2 --file requirements.txt + - pip install -r requirements-dev.txt install: - python setup.py develop + +# command to run tests script: - pytest diff --git a/MANIFEST.in b/MANIFEST.in index b36a76421..6ac8a1648 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,4 @@ include README.md -include src/c_lib/lib/*.c -include src/c_lib/mrmethods/*.c -include src/c_lib/include/*.* -include src/c_lib/test/*.* + +recursive-include src/mrsimulator/examples * +include src/mrsimulator/isotope_data.json diff --git a/README.md b/README.md index fde059652..ff17a42c5 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,9 @@ This will display the following message on the screen 1H site 0 from isotopomer 0 @ 100.0% abundance ---------------------------------------------- - isotropic chemical shift = 0.0 ppm - chemical shift anisotropy = 13.89 ppm - chemical shift asymmetry = 0.25 + Isotropic chemical shift = 0.0 ppm + Shielding anisotropy = 13.89 ppm + Shielding asymmetry = 0.25 Setting up the virtual NMR spectrometer --------------------------------------- Adjusting the magnetic flux density to 9.4 T. @@ -79,9 +79,9 @@ This will display the following message on the screen 1H site 0 from isotopomer 0 @ 100.0% abundance ---------------------------------------------- - isotropic chemical shift = 0.0 ppm - chemical shift anisotropy = 13.89 ppm - chemical shift asymmetry = 0.25 + Isotropic chemical shift = 0.0 ppm + Shielding anisotropy = 13.89 ppm + Shielding asymmetry = 0.25 and the corresponding plot shown below. diff --git a/dmfit_comparision.py.scr b/dmfit_comparision.py.scr new file mode 100644 index 000000000..9b7b27663 --- /dev/null +++ b/dmfit_comparision.py.scr @@ -0,0 +1,51 @@ +from mrsimulator.miscellaneous import read_dmfit_files +import matplotlib.pyplot as plt +from mrsimulator.python.simulator import simulator +from mrsimulator import Simulator +import numpy as np + + +def _dmfit(): + file_address = '/Users/deepansh/Documents/CSA_20kHz_0.1.txt' + + freq, amp = read_dmfit_files(file_address) + max_amp = amp.max() + freq_at_max = freq[np.argmax(amp)] + print(freq_at_max, max_amp) + + spectrum = { + "direct_dimension": { + "magnetic_flux_density": "9.4 T", + "rotor_frequency": "0 kHz", + "rotor_angle": "54.735 deg", + "number_of_points": 2048, + "spectral_width": "100 kHz", + "reference_offset": "0 kHz", + "isotope": "1H", + } + } + isotopomers = [ + { + "sites": [ + { + "isotope": "1H", + "isotropic_chemical_shift": "0 Hz", + "shielding_symmetric": {"anisotropy": "20 kHz", "asymmetry": 0.1}, + }, + ] + } + ] + sp = Simulator() + sp.isotopomers = isotopomers + sp.spectrum = spectrum + f, a = simulator(isotopomers=sp._isotopomers_c, spectrum=sp._spectrum_c) + fig, ax = plt.subplots(2, 1) + ax[0].plot(freq, amp / amp.max(), 'r', label='dmfit') + ax[0].plot(f, a / a.max(), 'k', label='mrsimulator') + ax[0].legend() + ax[1].plot(f, (amp / amp.max())[::4] - a / a.max()) + plt.show() + + +if __name__ == '__main__': + _dmfit() diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 000000000..c3b4a89c1 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,59 @@ +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = No + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = No + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = YES + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO diff --git a/docs/Makefile b/docs/Makefile index 4dcb0ec2e..515162637 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -18,4 +18,4 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -E diff --git a/docs/_static/13C_mas_1kHz.png b/docs/_static/13C_mas_1kHz.png new file mode 100644 index 000000000..f763bd537 Binary files /dev/null and b/docs/_static/13C_mas_1kHz.png differ diff --git a/docs/_static/13C_static.png b/docs/_static/13C_static.png new file mode 100644 index 000000000..85924f189 Binary files /dev/null and b/docs/_static/13C_static.png differ diff --git a/docs/_static/1H_example.png b/docs/_static/1H_example.png new file mode 100644 index 000000000..6ac0cf9e6 Binary files /dev/null and b/docs/_static/1H_example.png differ diff --git a/docs/_static/1H_mas_2kHz.png b/docs/_static/1H_mas_2kHz.png new file mode 100644 index 000000000..a9cdd6c05 Binary files /dev/null and b/docs/_static/1H_mas_2kHz.png differ diff --git a/docs/_static/1H_mas_2khz_90deg.png b/docs/_static/1H_mas_2khz_90deg.png new file mode 100644 index 000000000..791b94f8a Binary files /dev/null and b/docs/_static/1H_mas_2khz_90deg.png differ diff --git a/docs/_static/29Si_mas_1kHz.png b/docs/_static/29Si_mas_1kHz.png new file mode 100644 index 000000000..566eefab3 Binary files /dev/null and b/docs/_static/29Si_mas_1kHz.png differ diff --git a/docs/_static/copybutton.js b/docs/_static/copybutton.js new file mode 100644 index 000000000..cbd5eee67 --- /dev/null +++ b/docs/_static/copybutton.js @@ -0,0 +1,61 @@ +$(document).ready(function() { + /* Add a [>>>] button on the top-right corner of code samples to hide + * the >>> and ... prompts and the output and thus make the code + * copyable. */ + var div = $('.highlight-python .highlight,' + + '.highlight-python3 .highlight') + var pre = div.find('pre'); + + // get the styles from the current theme + pre.parent().parent().css('position', 'relative'); + var hide_text = 'Hide the prompts and output'; + var show_text = 'Show the prompts and output'; + var border_width = pre.css('border-top-width'); + var border_style = pre.css('border-top-style'); + var border_color = pre.css('border-top-color'); + var button_styles = { + 'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0', + 'border-color': border_color, 'border-style': border_style, + 'border-width': border_width, 'color': border_color, 'text-size': '75%', + 'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em', + 'border-radius': '0 3px 0 0' + } + + // create and add the button to all the code blocks that contain >>> + div.each(function(index) { + var jthis = $(this); + if (jthis.find('.gp').length > 0) { + var button = $('>>>'); + button.css(button_styles) + button.attr('title', hide_text); + button.data('hidden', 'false'); + jthis.prepend(button); + } + // tracebacks (.gt) contain bare text elements that need to be + // wrapped in a span to work with .nextUntil() (see later) + jthis.find('pre:has(.gt)').contents().filter(function() { + return ((this.nodeType == 3) && (this.data.trim().length > 0)); + }).wrap(''); + }); + + // define the behavior of the button when it's clicked + $('.copybutton').click(function(e){ + e.preventDefault(); + var button = $(this); + if (button.data('hidden') === 'false') { + // hide the code output + button.parent().find('.go, .gp, .gt').hide(); + button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden'); + button.css('text-decoration', 'line-through'); + button.attr('title', show_text); + button.data('hidden', 'true'); + } else { + // show the code output + button.parent().find('.go, .gp, .gt').show(); + button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible'); + button.css('text-decoration', 'none'); + button.attr('title', hide_text); + button.data('hidden', 'false'); + } + }); +}); diff --git a/docs/_static/style.css b/docs/_static/style.css new file mode 100644 index 000000000..be726d17b --- /dev/null +++ b/docs/_static/style.css @@ -0,0 +1,17 @@ +.wy-nav-content { + padding: 1.618em 3.236em; + height: 100%; + max-width: 800px; + margin: auto; +} + +/* override table width restrictions */ +.wy-table-responsive table td, .wy-table-responsive table th { + white-space: normal; +} + +.wy-table-responsive { + margin-bottom: 24px; + max-width: 100%; + overflow: visible; +} diff --git a/docs/_static/test_output.png b/docs/_static/test_output.png new file mode 100644 index 000000000..7eb7fc4cc Binary files /dev/null and b/docs/_static/test_output.png differ diff --git a/docs/api_py/hamiltonian.rst b/docs/api_py/hamiltonian.rst new file mode 100644 index 000000000..a9c34f1e8 --- /dev/null +++ b/docs/api_py/hamiltonian.rst @@ -0,0 +1,9 @@ +.. _hamiltonian_api: + +=========== +Hamiltonian +=========== + +.. automodule:: mrsimulator.python.Hamiltonian + :members: + :undoc-members: diff --git a/docs/api/api.rst b/docs/api_py/py_api.rst similarity index 56% rename from docs/api/api.rst rename to docs/api_py/py_api.rst index 11ff2ec03..0bd4fd467 100644 --- a/docs/api/api.rst +++ b/docs/api_py/py_api.rst @@ -1,13 +1,16 @@ -================= -API documentation -================= +===================== +Python-API References +===================== .. toctree:: :maxdepth: 2 :caption: Contents: simulator + sandbox + hamiltonian + transition_functions .. parameterized_tensor .. interaction diff --git a/docs/api_py/sandbox.rst b/docs/api_py/sandbox.rst new file mode 100644 index 000000000..24a9634eb --- /dev/null +++ b/docs/api_py/sandbox.rst @@ -0,0 +1,8 @@ +=========== +Sandbox api +=========== + +.. automodule:: mrsimulator.sandbox + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/simulator.rst b/docs/api_py/simulator.rst similarity index 100% rename from docs/api/simulator.rst rename to docs/api_py/simulator.rst diff --git a/docs/api_py/transition_functions.rst b/docs/api_py/transition_functions.rst new file mode 100644 index 000000000..940713dc2 --- /dev/null +++ b/docs/api_py/transition_functions.rst @@ -0,0 +1,10 @@ + +.. _transition_fn_api: + +==================== +Transition functions +==================== + +.. automodule:: mrsimulator.python.transition_function + :members: + :undoc-members: diff --git a/docs/conf.py b/docs/conf.py index ab12a725e..6cf8fc2f5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,14 +12,15 @@ # import os import sys - -from mrsimulator.__version__ import __version__ +import textwrap sys.path.insert(0, os.path.abspath("../..")) -sys.setrecursionlimit(1500) +# sys.path.insert(0, os.path.dirname(os.path.abspath("."))) +# sys.path.insert(0, os.path.abspath("../..")) + +# curr_dir = os.path.abspath(os.path.dirname(__file__)) +# path_to_static = os.path.join(curr_dir, "_build", "_static") -curr_dir = os.path.abspath(os.path.dirname(__file__)) -path_to_static = os.path.join(curr_dir, "_build", "html", "_static") # -- Project information ----------------------------------------------------- @@ -28,16 +29,15 @@ author = "Deepansh J. Srivastava" # The short X.Y version -version = __version__ +version = "0.1" # The full version, including alpha/beta/rc tags -release = "0.1.0" +release = "0.1.1a0" # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' +needs_sphinx = "2.0" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -49,8 +49,51 @@ "sphinx.ext.mathjax", "sphinx.ext.githubpages", "sphinx.ext.autosummary", + "sphinx.ext.viewcode", + # "autoapi.extension", + "breathe", + "exhale", + "sphinxjp.themes.basicstrap", ] +# Setup the breathe extension +breathe_projects = {"My Project": "./doxyoutput/xml"} +breathe_default_project = "My Project" +breathe_domain_by_extension = {"h": "c", "py": "py"} +breathe_use_project_refids = True + +# Setup the exhale extension +exhale_args = { + # These arguments are required + "containmentFolder": "./api_c", + "rootFileName": "c_api.rst", + "rootFileTitle": "C-API References", + "afterTitleDescription": textwrap.dedent( + """ + .. note:: + + The following documentation presents the C-API. The Python API + generally mirrors the C-API, but some methods may not be available in + Python or may perform different actions. + """ + ), + "doxygenStripFromPath": "..", + # Suggested optional arguments + "createTreeView": True, + # TIP: if using the sphinx-bootstrap-theme, you need + # "treeViewIsBootstrap": True, + "exhaleExecutesDoxygen": True, + "exhaleDoxygenStdin": "INPUT = ../mrsimulator/scr/include", +} + +# Tell sphinx what the primary language being documented is. +primary_domain = "py" + +# Tell sphinx what the pygments highlight language should be. +highlight_language = "c" + +# autoapi_dirs = ["../mrsimulator/"] + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -84,41 +127,97 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = "sphinx_rtd_theme" +# Some html_theme options are 'alabaster', 'bootstrap', 'sphinx_rtd_theme', +# 'classic', 'basicstrap' +html_theme = "basicstrap" + # 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 = { - # 'canonical_url': '', - # 'analytics_id': 'UA-XXXXXXX-1', # Provided by Google in your dashboard - "logo_only": False, - "display_version": True, - "prev_next_buttons_location": "bottom", - "style_external_links": True, - "style_nav_header_background": "black", - # Toc options - "collapse_navigation": True, - "sticky_navigation": True, - "navigation_depth": 4, - "includehidden": False, - "titles_only": False, + # Set the lang attribute of the html tag. Defaults to 'en' + "lang": "en", + # Disable showing the sidebar. Defaults to 'false' + "nosidebar": False, + # Show header searchbox. Defaults to false. works only "nosidebar=True", + "header_searchbox": True, + # Put the sidebar on the right side. Defaults to false. + "rightsidebar": False, + # Set the width of the sidebar. Defaults to 3 + "sidebar_span": 3, + # Fix navbar to top of screen. Defaults to true + "nav_fixed_top": True, + # Fix the width of the sidebar. Defaults to false + "nav_fixed": False, + # Set the width of the sidebar. Defaults to '900px' + "nav_width": "900px", + # Fix the width of the content area. Defaults to false + "content_fixed": False, + # Set the width of the content area. Defaults to '900px' + "content_width": "900px", + # Fix the width of the row. Defaults to false + "row_fixed": True, + # Disable the responsive design. Defaults to false + "noresponsive": False, + # Disable the responsive footer relbar. Defaults to false + "noresponsiverelbar": False, + # Disable flat design. Defaults to false. + # Works only "bootstrap_version = 3" + "noflatdesign": False, + # Enable Google Web Font. Defaults to false + "googlewebfont": True, + # Set the URL of Google Web Font's CSS. + # Defaults to 'http://fonts.googleapis.com/css?family=Text+Me+One' + # "googlewebfont_url": "http://fonts.googleapis.com/css?family=Lily+Script+One", # NOQA + # Set the Style of Google Web Font's CSS. + # Defaults to "font-family: 'Text Me One', sans-serif;" + "googlewebfont_style": u"font-family: 'Roboto' Regular 24;", + # Set 'navbar-inverse' attribute to header navbar. Defaults to false. + "header_inverse": True, + # Set 'navbar-inverse' attribute to relbar navbar. Defaults to false. + "relbar_inverse": True, + # Enable inner theme by Bootswatch. Defaults to false + "inner_theme": False, + # Set the name of inner theme. Defaults to 'bootswatch-simplex' + "inner_theme_name": "bootswatch-simplex", + # Select Twitter bootstrap version 2 or 3. Defaults to '3' + "bootstrap_version": "3", + # Show "theme preview" button in header navbar. Defaults to false. + "theme_preview": True, + # Set the Size of Heading text. Defaults to None + # "h1_size": "3.0em", + # "h2_size": "2.6em", + # "h3_size": "2.2em", + # "h4_size": "1.8em", + # "h5_size": "1.4em", + # "h6_size": "1.1em", } -# 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"] +# Theme options +html_logo = "_static/csdmpy.png" html_context = { - "css_files": ["_static/style.css"], "display_github": True, "github_user": "DeepanshS", "github_repo": "mrsimulator", "github_version": "master/docs/", + "css_files": [ + "_static/button.css", + # "_static/theme_overrides.css", # override wide tables in RTD theme + # "_static/style.css", + # "_static/custom.css", + # "_static/bootstrap-toc.css", + ], } +# 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. # @@ -141,16 +240,16 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # - # 'papersize': 'letterpaper', + "papersize": "letterpaper", # The font size ('10pt', '11pt' or '12pt'). # - # 'pointsize': '10pt', + "pointsize": "10pt", # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # - # 'figure_align': 'htbp', + "figure_align": "htbp", } # Grouping the document tree into LaTeX files. List of tuples @@ -171,9 +270,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, "mrsimulator", "mrsimulator Documentation", [author], 1) -] +man_pages = [(master_doc, "mrsimulator", "mrsimulator Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -215,6 +312,6 @@ # -- Extension configuration ------------------------------------------------- -def setup(app): - app.add_javascript("_static/copybutton.js") - app.add_javascript("_static/jquery.js") +# def setup(app): +# app.add_javascript("_static/copybutton.js") +# app.add_javascript("_static/jquery.js") diff --git a/docs/examples.rst b/docs/examples.rst index 314ccbc6e..d5f414327 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -23,15 +23,15 @@ isotopomers from a JSON serialized isotopomers file. >>> filename = 'https://raw.githubusercontent.com/DeepanshS/mrsimulator-test/master/isotopomers_ppm.json' >>> sim.load_isotopomers(filename) - Downloading '/DeepanshS/mrsimulator-test/master/isotopomers_ppm.json' from 'raw.githubusercontent.com' to file 'isotopomers.json'. - [█████████████████████████████████████████████████████████████████████████] + Downloading '/DeepanshS/mrsimulator-test/master/isotopomers_ppm.json' from 'raw.githubusercontent.com' to file 'isotopomers_ppm.json'. + [████████████████████████████████████████████████████████████████████] .. testcleanup:: import os os.remove('isotopomers_ppm.json') -Use the :attr:`~mrsimulator.Simulator.isotope_list` attribute of the ``sim`` +Use the attr:`~mrsimulator.Simulator.isotope_list` attribute of the ``sim`` instance to get the list of unique isotopes from the list of isotopomers. .. doctest:: @@ -53,7 +53,7 @@ object, >>> sim.spectrum = { ... "direct_dimension": { - ... "nucleus": "13C", + ... "isotope": "13C", ... "magnetic_flux_density": "9.4 T", ... "rotor_frequency": "0 kHz", ... "rotor_angle": "54.735 deg", @@ -67,7 +67,7 @@ The above spectrum object is set to simulate a :math:`^{13}\mathrm{C}` static spectrum at 9.4 T magnetic field over 5 kHz frequency-bandwidth using 8192 points. -Now, generate the lineshape with the :meth:`~mrsimulator.Simulator.run` +Now, generate the lineshape with the meth:`~mrsimulator.Simulator.run` method as .. doctest:: @@ -83,15 +83,15 @@ method as 13C site 0 from isotopomer 0 @ 100.0% abundance ----------------------------------------------- - isotropic chemical shift = 1.0 ppm - chemical shift anisotropy = -3.89 ppm - chemical shift asymmetry = 0.25 + Isotropic chemical shift = 1.0 ppm + Shielding anisotropy = -3.89 ppm + Shielding asymmetry = 0.25 13C site 0 from isotopomer 1 @ 100.0% abundance ----------------------------------------------- - isotropic chemical shift = 1.0 ppm - chemical shift anisotropy = 8.2 ppm - chemical shift asymmetry = 0.0 + Isotropic chemical shift = 1.0 ppm + Shielding anisotropy = 8.2 ppm + Shielding asymmetry = 0.0 The simulator goes through every isotopomer in the list and simulates the lineshape corresponding to the :math:`^{13}\mathrm{C}` isotopes. In this @@ -127,7 +127,7 @@ for the ``rotor_frequency`` which is set to 100 Hz. >>> sim.spectrum = { ... "direct_dimension": { - ... "nucleus": "13C", + ... "isotope": "13C", ... "magnetic_flux_density": "9.4 T", ... "rotor_frequency": "100 Hz", ... "rotor_angle": "54.735 deg", @@ -152,15 +152,15 @@ Now compute the lineshape as before. 13C site 0 from isotopomer 0 @ 100.0% abundance ----------------------------------------------- - isotropic chemical shift = 1.0 ppm - chemical shift anisotropy = -3.89 ppm - chemical shift asymmetry = 0.25 + Isotropic chemical shift = 1.0 ppm + Shielding anisotropy = -3.89 ppm + Shielding asymmetry = 0.25 13C site 0 from isotopomer 1 @ 100.0% abundance ----------------------------------------------- - isotropic chemical shift = 1.0 ppm - chemical shift anisotropy = 8.2 ppm - chemical shift asymmetry = 0.0 + Isotropic chemical shift = 1.0 ppm + Shielding anisotropy = 8.2 ppm + Shielding asymmetry = 0.0 .. doctest:: @@ -174,7 +174,7 @@ Switch to a different isotope ----------------------------- Generate a new :ref:`spectrum` object with a different isotope. The isotope -is specified with the `nucleus` key, as shown below. In the following +is specified with the `isotope` key, as shown below. In the following example, a :math:`^1\mathrm{H}` spectrum is simulated at 9.4 T field, spinning at the magic angle at 2 kHz frequency, and sampled over 100 kHz frequency bandwidth with 8192 points. @@ -183,7 +183,7 @@ bandwidth with 8192 points. >>> sim.spectrum = { ... "direct_dimension": { - ... "nucleus": "1H", + ... "isotope": "1H", ... "magnetic_flux_density": "9.4 T", ... "rotor_frequency": "2 kHz", ... "rotor_angle": "54.735 deg", @@ -208,15 +208,15 @@ Now compute the lineshape. 1H site 0 from isotopomer 2 @ 100.0% abundance ---------------------------------------------- - isotropic chemical shift = 3.0 ppm - chemical shift anisotropy = 23.2 ppm - chemical shift asymmetry = 0.0 + Isotropic chemical shift = 3.0 ppm + Shielding anisotropy = 23.2 ppm + Shielding asymmetry = 0.0 1H site 0 from isotopomer 6 @ 100.0% abundance ---------------------------------------------- - isotropic chemical shift = 5.6 ppm - chemical shift anisotropy = 13.2 ppm - chemical shift asymmetry = 0.0 + Isotropic chemical shift = 5.6 ppm + Shielding anisotropy = 13.2 ppm + Shielding asymmetry = 0.0 .. doctest:: @@ -232,7 +232,7 @@ isotope. >>> sim.spectrum = { ... "direct_dimension": { - ... "nucleus": "29Si", + ... "isotope": "29Si", ... "magnetic_flux_density": "9.4 T", ... "rotor_frequency": "1 kHz", ... "rotor_angle": "54.735 deg", @@ -257,21 +257,21 @@ The simulated lineshape. 29Si site 0 from isotopomer 3 @ 100.0% abundance ------------------------------------------------ - isotropic chemical shift = -100.0 ppm - chemical shift anisotropy = 1.36 ppm - chemical shift asymmetry = 0.0 + Isotropic chemical shift = -100.0 ppm + Shielding anisotropy = 1.36 ppm + Shielding asymmetry = 0.0 29Si site 0 from isotopomer 4 @ 100.0% abundance ------------------------------------------------ - isotropic chemical shift = -100.0 ppm - chemical shift anisotropy = 70.36 ppm - chemical shift asymmetry = 0.0 + Isotropic chemical shift = -100.0 ppm + Shielding anisotropy = 70.36 ppm + Shielding asymmetry = 0.0 29Si site 0 from isotopomer 5 @ 100.0% abundance ------------------------------------------------ - isotropic chemical shift = -90.0 ppm - chemical shift anisotropy = 80.36 ppm - chemical shift asymmetry = 0.5 + Isotropic chemical shift = -90.0 ppm + Shielding anisotropy = 80.36 ppm + Shielding asymmetry = 0.5 .. doctest:: @@ -293,7 +293,7 @@ object is the same are from the previous example, except the >>> sim.spectrum = { ... "direct_dimension": { - ... "nucleus": "1H", + ... "isotope": "1H", ... "magnetic_flux_density": "9.4 T", ... "rotor_frequency": "2 kHz", ... "rotor_angle": "90 deg", @@ -318,15 +318,15 @@ The simulated lineshape. 1H site 0 from isotopomer 2 @ 100.0% abundance ---------------------------------------------- - isotropic chemical shift = 3.0 ppm - chemical shift anisotropy = 23.2 ppm - chemical shift asymmetry = 0.0 + Isotropic chemical shift = 3.0 ppm + Shielding anisotropy = 23.2 ppm + Shielding asymmetry = 0.0 1H site 0 from isotopomer 6 @ 100.0% abundance ---------------------------------------------- - isotropic chemical shift = 5.6 ppm - chemical shift anisotropy = 13.2 ppm - chemical shift asymmetry = 0.0 + Isotropic chemical shift = 5.6 ppm + Shielding anisotropy = 13.2 ppm + Shielding asymmetry = 0.0 .. doctest:: diff --git a/docs/getting_started.rst b/docs/getting_started.rst index eee5d52d2..b8c9bbf17 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -21,8 +21,8 @@ and create an instance, Here, ``sim1`` is an instance of the :ref:`simulator_api` class. The two often used attributes of this instance are -:attr:`~mrsimulator.Simulator.isotopomers` and -:attr:`~mrsimulator.Simulator.spectrum`. +attr:`~mrsimulator.Simulator.isotopomers` and +attr:`~mrsimulator.Simulator.spectrum`. The default value of these attributes is .. doctest:: @@ -56,13 +56,13 @@ command. >>> from pprint import pprint >>> pprint(sim1.isotopomers) [{'abundance': '100 %', - 'sites': [{'isotope_symbol': '1H', + 'sites': [{'isotope': '1H', 'isotropic_chemical_shift': '0 ppm', 'shielding_symmetric': {'anisotropy': '13.89 ppm', 'asymmetry': 0.25}}]}] >>> pprint(sim1.spectrum) {'direct_dimension': {'magnetic_flux_density': '9.4 T', - 'nucleus': '1H', + 'isotope': '1H', 'number_of_points': 2048, 'reference_offset': '0 Hz', 'rotor_angle': '54.735 deg', @@ -71,7 +71,7 @@ command. In general, the isotopomers contain the metadata on the spin system while the spectrum contains metadata required to simulate the lineshapes. -A lineshape is simulated using the :meth:`~mrsimulator.Simulator.run` method +A lineshape is simulated using the meth:`~mrsimulator.Simulator.run` method of the :ref:`simulator_api` instance based on the NMR method. In version 0.1, we provide `one_d_spectrum` method for simulating one dimensional NMR lineshapes. Import this method using @@ -95,11 +95,11 @@ and run the simulation. 1H site 0 from isotopomer 0 @ 100.0% abundance ---------------------------------------------- - isotropic chemical shift = 0.0 ppm - chemical shift anisotropy = 13.89 ppm - chemical shift asymmetry = 0.25 + Isotropic chemical shift = 0.0 ppm + Shielding anisotropy = 13.89 ppm + Shielding asymmetry = 0.25 -In the above code, the ``freq`` and ``amp`` are the frequency in ppm and the +In the above code, the ``freq`` and ``amp`` are the frequency in Hz and the corresponding amplitude of the spectrum. The following is a figure of the above lineshape plotted using the matplotlib library. diff --git a/docs/index.rst b/docs/index.rst index b6f4917f2..f68ed0545 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,7 +17,7 @@ simulation of single spin :math:`I=\frac{1}{2}` nuclei static, magic angle spinning (MAS), and variable angle spinning (VSA) lineshapes. .. toctree:: - :maxdepth: 2 + :maxdepth: 7 :caption: Contents: installation @@ -25,7 +25,8 @@ magic angle spinning (MAS), and variable angle spinning (VSA) lineshapes. mr_objects load_isotopomers examples - api/api + api_py/py_api + api_c/c_api Indices and tables ================== diff --git a/docs/installation.rst b/docs/installation.rst index 5a7464827..1fc93379b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -2,55 +2,68 @@ .. _shielding_tensor_api: -============ -Installation -============ +================================ +Installing `mrsimulator` package +================================ -Installing requirements -+++++++++++++++++++++++ +We recommend installing `anaconda `_ +distribution for python version 3.6 or higher. The anaconda distribution +ships with numerous packages and modules including Numpy, Scipy, and Matplotlib +which are useful packages for scientific datasets. In addition, +conda provides `mkl optimized `_ +for numerical libraries such as Numpy, Scipy. -The `mrsimulator` package requires `fftw3 `_ -C routines. Download and install the fftw3 routines by typing -the following in the terminal +.. If you have opted for the ``nomkl``, we suggest you create a new conda +.. environment before proceeding. You can read about creating new conda +.. environment `here `_. -.. code-block:: text - conda install -c eumetsat fftw3 -In addition, `mrsimulator` also requires `NumPy>=1.13.3 `_ -and intel `mkl `_ and -`mkl_include `_ C routines to build and -install the `mrsimulator` package. Download and install these C routines using -.. code-block:: text +Installing dependencies +^^^^^^^^^^^^^^^^^^^^^^^ + +Clone and download the `mrsimulator` package, + +.. code-block:: shell + + $ git clone git://github.com/DeepanshS/mrsimulator.git + $ cd mrsimulator + - pip install "numpy>=1.13.1" mkl mkl-include +.. and install the dependencies using -Some additional package dependencies are +.. .. code-block:: shell - - `astropy>=3.0 `_ for the units library, - - `matplotlib>=3.0.2 `_ for figures and visualization, +.. $ cd mrsimulator +.. $ conda install --file requirements.txt -and, - - `plotly>=3.6 `_ - - `dash>=0.40 `_ - - `dash_daq>=0.1 `_ +Installing dependencies +^^^^^^^^^^^^^^^^^^^^^^^ -for the web-face interface. +In Anaconda versions 2.5 and later, intel MKL is freely available by default. +To build and link ``mrsimulator`` with the intel-mkl libraries follow, +.. code-block:: shell -Installing mrsimulator -++++++++++++++++++++++ + $ conda install mkl mkl-include --file requirements.txt + +If you, however, wish to opt out of MKL and instead use +`openBLAS `_, execute the following lines. + +.. code-block:: shell + + $ conda install -c anaconda nomkl openblas --file requirements.txt A c compiler is required to successful compile and build the ``mrsimulator`` package. -On linux +.. On linux, you can get the gcc compiler. -.. code-block:: text +.. .. code-block:: text - sudo apt install gcc +.. $ sudo apt install gcc To install the ``mrsimulator`` package, type the following @@ -58,18 +71,20 @@ in the terminal. .. code-block:: text - pip install git+https://github.com/DeepanshS/mrsimulator.git@master + python setup.py install +.. pip install git+https://github.com/DeepanshS/mrsimulator.git@master -Test -++++ -If the installation is successful, you should be able to run the following -in the terminal. +Check your build +++++++++++++++++ + +If the installation is successful, you should be able to run the following test +file in your terminal. Download the test file `here `_. .. code-block:: text - python -c "import mrsimulator; mrsimulator.run_test()" + python mrsimulator_quick_test.py This will display the following message on the screen @@ -85,9 +100,9 @@ This will display the following message on the screen 1H site 0 from isotopomer 0 @ 100.0% abundance ---------------------------------------------- - isotropic chemical shift = 0.0 ppm - chemical shift anisotropy = 13.89 ppm - chemical shift asymmetry = 0.25 + Isotropic chemical shift = 0.0 ppm + Shielding anisotropy = 13.89 ppm + Shielding asymmetry = 0.25 Setting up the virtual NMR spectrometer --------------------------------------- Adjusting the magnetic flux density to 9.4 T. @@ -98,9 +113,9 @@ This will display the following message on the screen 1H site 0 from isotopomer 0 @ 100.0% abundance ---------------------------------------------- - isotropic chemical shift = 0.0 ppm - chemical shift anisotropy = 13.89 ppm - chemical shift asymmetry = 0.25 + Isotropic chemical shift = 0.0 ppm + Shielding anisotropy = 13.89 ppm + Shielding asymmetry = 0.25 and the corresponding plot shown below. diff --git a/docs/isotopomers_object.rst b/docs/isotopomers_object.rst index 493fb844b..6fb6e1b57 100644 --- a/docs/isotopomers_object.rst +++ b/docs/isotopomers_object.rst @@ -20,7 +20,7 @@ abundance of each isotopomer. ... { ... "sites": [ ... { - ... "isotope_symbol": "13C", + ... "isotope": "13C", ... "isotropic_chemical_shift": "1 ppm", ... "shielding_symmetric": { ... "anisotropy": "-3.89 µHz/Hz", @@ -33,7 +33,7 @@ abundance of each isotopomer. ... { ... "sites": [ ... { - ... "isotope_symbol": "29Si", + ... "isotope": "29Si", ... "isotropic_chemical_shift": "-0.1 mHz/Hz", ... "shielding_symmetric": { ... "anisotropy": "8.89 ppm", diff --git a/docs/load_isotopomers.rst b/docs/load_isotopomers.rst index 173fd9419..1a3603ec8 100644 --- a/docs/load_isotopomers.rst +++ b/docs/load_isotopomers.rst @@ -17,7 +17,7 @@ objects or directly imported from a JSON serialized isotopomers file. ... { ... "sites": [ ... { - ... "isotope_symbol": "13C", + ... "isotope": "13C", ... "isotropic_chemical_shift": "1 ppm", ... "shielding_symmetric": { ... "anisotropy": "-3.89 ppm", @@ -30,7 +30,7 @@ objects or directly imported from a JSON serialized isotopomers file. ... { ... "sites": [ ... { - ... "isotope_symbol": "1H", + ... "isotope": "1H", ... "isotropic_chemical_shift": "1 ppm", ... "shielding_symmetric": { ... "anisotropy": "8.2 ppm", @@ -42,7 +42,7 @@ objects or directly imported from a JSON serialized isotopomers file. ... { ... "sites": [ ... { - ... "isotope_symbol": "1H", + ... "isotope": "1H", ... "isotropic_chemical_shift": "1 ppm", ... "shielding_symmetric": { ... "anisotropy": "8.2 ppm", @@ -54,7 +54,7 @@ objects or directly imported from a JSON serialized isotopomers file. ... { ... "sites": [ ... { - ... "isotope_symbol": "1H", + ... "isotope": "1H", ... "isotropic_chemical_shift": "3 ppm", ... "shielding_symmetric": { ... "anisotropy": "23.2 ppm", @@ -66,7 +66,7 @@ objects or directly imported from a JSON serialized isotopomers file. ... { ... "sites": [ ... { - ... "isotope_symbol": "29Si", + ... "isotope": "29Si", ... "isotropic_chemical_shift": "-90 ppm", ... "shielding_symmetric": { ... "anisotropy": "1 mHz/Hz", @@ -79,7 +79,7 @@ objects or directly imported from a JSON serialized isotopomers file. ... { ... "sites": [ ... { - ... "isotope_symbol": "29Si", + ... "isotope": "29Si", ... "isotropic_chemical_shift": "-100 ppm", ... "shielding_symmetric": { ... "anisotropy": "80.36 µHz/Hz", @@ -120,7 +120,7 @@ The list of isotopomers may directly be assigned to an instance of a In the following example, we load an example `JSON `_ serialized isotopomers file. For this, we make use of the -:meth:`~mrsimulator.Simulator.load_isotopomers` method as follows, +meth:`~mrsimulator.Simulator.load_isotopomers` method as follows, .. doctest:: @@ -128,8 +128,8 @@ serialized isotopomers file. For this, we make use of the >>> filename = 'https://raw.githubusercontent.com/DeepanshS/mrsimulator-test/master/isotopomers_ppm.json' >>> st2 = Simulator() >>> st2.load_isotopomers(filename) - Downloading '/DeepanshS/mrsimulator-test/master/isotopomers_ppm.json' from 'raw.githubusercontent.com' to file 'isotopomers_ppm.json'. - [█████████████████████████████████████████████████████████████████████████] + Downloading '/DeepanshS/mrsimulator-test/master/isotopomers_ppm.json' from 'raw.githubusercontent.com' to file 'isotopomers_ppm_0.json'. + [████████████████████████████████████████████████████████████████████] .. testcleanup:: @@ -143,31 +143,31 @@ The list of isotopomers from this file are >>> from pprint import pprint >>> pprint(st2.isotopomers) [{'abundance': '100 %', - 'sites': [{'isotope_symbol': '13C', + 'sites': [{'isotope': '13C', 'isotropic_chemical_shift': '1 ppm', 'shielding_symmetric': {'anisotropy': '-3.89 ppm', 'asymmetry': 0.25}}]}, - {'sites': [{'isotope_symbol': '13C', + {'sites': [{'isotope': '13C', 'isotropic_chemical_shift': '1 ppm', 'shielding_symmetric': {'anisotropy': '8.2 ppm', 'asymmetry': 0.0}}]}, - {'sites': [{'isotope_symbol': '1H', + {'sites': [{'isotope': '1H', 'isotropic_chemical_shift': '3 ppm', 'shielding_symmetric': {'anisotropy': '23.2 ppm', 'asymmetry': 0.0}}]}, - {'sites': [{'isotope_symbol': '29Si', + {'sites': [{'isotope': '29Si', 'isotropic_chemical_shift': '-100 ppm', 'shielding_symmetric': {'anisotropy': '1.36 ppm', 'asymmetry': 0.0}}]}, - {'sites': [{'isotope_symbol': '29Si', + {'sites': [{'isotope': '29Si', 'isotropic_chemical_shift': '-100 ppm', 'shielding_symmetric': {'anisotropy': '70.36 ppm', 'asymmetry': 0.0}}]}, - {'sites': [{'isotope_symbol': '29Si', + {'sites': [{'isotope': '29Si', 'isotropic_chemical_shift': '-90 ppm', 'shielding_symmetric': {'anisotropy': '80.36 ppm', 'asymmetry': 0.5}}]}, - {'sites': [{'isotope_symbol': '1H', + {'sites': [{'isotope': '1H', 'isotropic_chemical_shift': '5.6 ppm', 'shielding_symmetric': {'anisotropy': '13.2 ppm', 'asymmetry': 0.0}}]}] diff --git a/docs/mr_objects.rst b/docs/mr_objects.rst index 164dc87e3..5209fa07d 100644 --- a/docs/mr_objects.rst +++ b/docs/mr_objects.rst @@ -8,10 +8,10 @@ Version 0.1 of `mrsimulator` utilizes python dict and list to construct various objects required in simulating an NMR spectrum. Below is a detail description of these objects. - .. toctree:: - :maxdepth: 2 - :caption: Contents: +.. toctree:: + :maxdepth: 2 + :caption: Contents: - objects - isotopomers_object - spectrum_object + objects + isotopomers_object + spectrum_object diff --git a/docs/objects.rst b/docs/objects.rst index bbc65d954..3d85d16c4 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -41,10 +41,7 @@ Orientation *An example of Orientation object.* -.. doctest:: - :skipif: None is None - - >>> { + >>> orientation_object = { ... "alpha": "0.5 rad", ... "beta": "0.23 rad", ... "gamma": "2.54 rad" @@ -94,10 +91,7 @@ SymmetricTensor *An example of SymmetricTensor object.* -.. doctest:: - :skipif: None is None - - >>> { + >>> symmetric_tensor_object = { ... "anisotropy": "10.3 ppm", ... "asymmetry": 0.5, ... "orientation": { @@ -127,7 +121,7 @@ Site * - key - value type - value description - * - ``isotope_symbol`` + * - ``isotope`` - A `string `__ - The NMR active isotope symbol, for example, '13C'. This is a required key. @@ -143,11 +137,8 @@ Site *An example of Site object.* -.. doctest:: - :skipif: None is None - - >>> { - ... "isotope_symbol": "13C", + >>> site_object = { + ... "isotope": "13C", ... "isotropic_chemical_shift": "15 ppm", ... "shielding_symmetric": { ... "anisotropy": "10.3 ppm", @@ -201,13 +192,10 @@ Isotopomer *An example of Isotopomer object.* -.. doctest:: - :skipif: None is None - - >>> { + >>> isotopomer_object = { ... "sites": [ ... { - ... "isotope_symbol": "13C", + ... "isotope": "13C", ... "isotropic_chemical_shift": "15 ppm", ... "shielding_symmetric": { ... "anisotropy": "10.3 ppm", @@ -242,7 +230,7 @@ DirectDimension * - key - value type - value description - * - ``nucleus`` + * - ``isotope`` - A `string `__ - The isotope symbol of the nuclei. The recorded spectrum a histogram of frequencies corresponding to this nuclear isotope. An example may diff --git a/docs/requirements.rst b/docs/requirements.rst new file mode 100644 index 000000000..64f7ed8e6 --- /dev/null +++ b/docs/requirements.rst @@ -0,0 +1,33 @@ + + +==================== +Package dependencies +==================== + +The required and optional dependencies of `mrsimulator` are + +**Required packages** + +- `NumPy>=1.13.3 `_ +- `intel mkl>=2019 `_ +- `mkl_include>=2019 `_ +- `astropy>=3.0 `_ for the units library +- `requests>=2.21.0 `_ +- setuptools>=27.3 +- cython>=0.29.11 +- `matplotlib>=3.0.2 `_ for figures and visualization, + +**Other packages** + +- pytest>=4.5.0 for unit tests. +- pre-commit for code formatting +- sphinx>=2.0 for generating the documentation +- sphinxjp.themes.basicstrap for documentation. +- breathe +- exhale + +**Optional packages** + +- `plotly>=3.6 `_ +- `dash>=0.40 `_ +- `dash_daq>=0.1 `_ diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..885184703 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ + +sphinx>=2.0 +sphinxjp.themes.basicstrap +breathe +exhale diff --git a/docs/spectrum_object.rst b/docs/spectrum_object.rst index 7b539b34e..5c597bb4a 100644 --- a/docs/spectrum_object.rst +++ b/docs/spectrum_object.rst @@ -17,7 +17,7 @@ object with a single key, `direct_dimension`, whose value is a >>> spectrum_object = { ... "direct_dimension": { - ... "nucleus": "13C", + ... "isotope": "13C", ... "magnetic_flux_density": "9.4 T", ... "rotor_frequency": "5 kHz", ... "rotor_angle": "54.735 deg", diff --git a/package.json b/package.json new file mode 100644 index 000000000..b8779bcb7 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "mrsimulator", + "description": "A python toolbox for simulation NMR lineshapes.", + "version": "0.1.1", + "repository": { + "type": "git", + "url": "https://github.com/deepanshS/mrsimulator.git" + }, + "keywords": [ + "NMR simulator", + "NMR lineshape", + "amorphous solids" + ], + "author": "Deepansh J. Srivastava", + "license": "BSD-3-Clause", + "bugs": { + "url": "https://github.com/deepanshS/mrsimulator/issues" + }, + "homepage": "https://github.com/deepanshS/mrsimulator" +} diff --git a/pyproject.toml b/pyproject.toml index dfab0250e..fed6c9759 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.black] -line-length = 79 +line-length = 88 include = '\.pyi?$' exclude = ''' /( diff --git a/requirements-dev.txt b/requirements-dev.txt index 6892e6ac5..07e722bd9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,6 @@ black pre-commit -pytest +## test required +sympy +pytest diff --git a/requirements-optional.txt b/requirements-optional.txt index 117b8b057..45beda037 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -1,4 +1,4 @@ matplotlib>=3.0.2 plotly>=3.6 dash>=0.40 -dash_daq>=0.1 \ No newline at end of file +dash_daq>=0.1 diff --git a/requirements-ui.txt b/requirements-ui.txt new file mode 100644 index 000000000..735dbfe01 --- /dev/null +++ b/requirements-ui.txt @@ -0,0 +1,6 @@ + + +flask>=1.1.0 +plotly>=3.6 +dash>=0.40 +dash_daq>=0.1 diff --git a/requirements.txt b/requirements.txt index 8d328bd15..6a2734b05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,8 @@ -cython>=0.29.6 -mkl>=2019.0 -mkl-include>=2019.0 -numpy>=1.15.4 -pydantic==0.28 -astropy==3.2.1 -requests==2.21.0 -monty==2.0.4 -pydash==4.7.5 \ No newline at end of file + +numpy>=1.13.3 +setuptools>=27.3 +cython>=0.29.11 +astropy>=3.0 +requests>=2.21.0 +matplotlib>=3.0.2 +fftw>=3.3.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..d8212573e --- /dev/null +++ b/setup.cfg @@ -0,0 +1,18 @@ +[aliases] +test=pytest + +[tool:pytest] +addopts = --verbose +python_files = tests/*test*.py + +# --doctest-glob='docs/*.rst' --doctest-modules + +[nosetests] +verbose=1 +detailed-errors=1 +nocapture=1 +with-coverage=1 +cover-package=mrsimulator +# debug=nose.loader +# pdb=1 +# pdb-failures=1 diff --git a/setup.py b/setup.py index 29bb105ea..98ef7c776 100644 --- a/setup.py +++ b/setup.py @@ -1,66 +1,161 @@ # -*- coding: utf-8 -*- - -import os -import numpy -import numpy.distutils.system_info as sysinfo -from setuptools import setup, find_packages -from setuptools.extension import Extension -from Cython.Build import cythonize - -module_dir = os.path.dirname(os.path.abspath(__file__)) - -mkl_info = sysinfo.get_info("mkl") - -include_dirs = ["src/c_lib/include", numpy.get_include()] - -if "include_dirs" in mkl_info: - include_dirs += mkl_info["include_dirs"] - -extra_compile_args = "-O1 -std=c11" - -ext_modules = [ - Extension( - name="mrsimulator.methods", - sources=[ - "src/c_lib/lib/c_array.c", - "src/c_lib/lib/MRAngularMomentum.c", - "src/c_lib/mrmethods/spinning_sidebands.c", - "src/c_lib/mrmethods/powder_setup.c", - "src/c_lib/mrmethods/nmr_methods.pyx", - ], - include_dirs=include_dirs, - language="c", - libraries=["fftw3","mkl_intel_lp64","mkl_core","mkl_intel_thread","pthread","iomp5","m","dl"], - extra_compile_args=extra_compile_args.split(), - ) -] - -setup( - name="mrsimulator", - version="0.1.0", - description="A python toolbox for simulating NMR spectra", - long_description=open(os.path.join(module_dir, "README.md")).read(), - author="Deepansh J. Srivastava", - author_email="srivastava.89@osu.edu", - python_requires=">=3.0", - url="https://github.com/DeepanshS/MRsimulator/", - packages=find_packages("src"), - package_dir={"": "src"}, - install_requires=[ - "numpy>=1.13.3", - "astropy>=3.0", - "pydantic==0.28", - "requests>=2.21.0", - "monty==2.0.4", - "mkl==2019.0", - "mkl-include==2019.0", - ], - tests_require=["pytest"], - ext_modules=cythonize(ext_modules), - classifiers=[ - # Trove classifiers - # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers - "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 3", - ], -) + +from setuptools import Extension +from setuptools import find_packages +from setuptools import setup + +from Cython.Build import cythonize + +from os.path import join +from os.path import abspath +from os.path import dirname +import platform + +import numpy as np +import numpy.distutils.system_info as sysinfo + +import json + +module_dir = dirname(abspath(__file__)) + +include_dirs = [] +library_dirs = [] +libraries = [] +openblas_info = sysinfo.get_info("openblas") +fftw3_info = sysinfo.get_info("fftw3") +# mkl_info = sysinfo.get_info("mkl") + +# if mkl_info != {}: +# name = "mkl" +# include_dirs += mkl_info["include_dirs"] +# library_dirs += mkl_info["library_dirs"] +# libraries += mkl_info["libraries"] +if openblas_info != {}: + name = "openblas" + library_dirs += openblas_info["library_dirs"] + libraries += openblas_info["libraries"] + libraries += ["pthread"] +# else: +# raise Exception("mkl blas or openblas library not found.") + +include_dirs += fftw3_info["include_dirs"] +library_dirs += fftw3_info["library_dirs"] +libraries += fftw3_info["libraries"] + +include_dirs = list(set(include_dirs)) +library_dirs = list(set(library_dirs)) +libraries = list(set(libraries)) + +blas_info = { + "name": name, + "library_dirs": library_dirs, + "include_dirs": include_dirs, + "libraries": libraries, +} + +print(blas_info) + +with open("src/mrsimulator/__config__.json", "w", encoding="utf8") as outfile: + json.dump(blas_info, outfile, ensure_ascii=True, indent=2) + +# other include paths +include_dirs.append("src/c_lib/include") +include_dirs.append(np.get_include()) + +extra_link_args = [] +extra_compile_args = [] + +# system = platform.system() +# arch = platform.architecture()[0] +# compiler = platform.python_compiler() +# if system == 'Linux': +# extra_link_args += ["-lm", "-ldl"] +# if arch == '64bit': +# extra_compile_args += ["-m64", "-DMKL_ILP64"] +# if arch == '32bit': +# extra_compile_args += ["-m32"] +# if system == 'Darwin': +# extra_link_args += ["-Wl", "-lm", "-ldl"] +# extra_compile_args += ["-m64"] + +print(extra_compile_args) +print(extra_link_args) +ext_modules = [ + Extension( + name="mrsimulator.methods", + sources=[ + "src/c_lib/lib/angular_momentum.c", + "src/c_lib/lib/interpolation.c", + "src/c_lib/lib/mrsimulator.c", + "src/c_lib/lib/octahedron.c", + "src/c_lib/lib/spinning_sidebands.c", + "src/c_lib/lib/powder_setup.c", + "src/c_lib/mrmethods/nmr_methods.pyx", + ], + include_dirs=include_dirs, + language="c", + libraries=libraries, + library_dirs=library_dirs, + extra_compile_args=["--std=c99", "-g", "-O3"] + extra_compile_args, + extra_link_args=extra_link_args, + ) +] + +# Sandbox + +ext_modules += [ + Extension( + name="mrsimulator.sandbox", + sources=[ + "src/c_lib/lib/angular_momentum.c", + "src/c_lib/lib/interpolation.c", + "src/c_lib/lib/mrsimulator.c", + "src/c_lib/lib/octahedron.c", + "src/c_lib/lib/spinning_sidebands.c", + "src/c_lib/lib/powder_setup.c", + "src/c_lib/sandbox/sandbox.pyx", + ], + include_dirs=include_dirs, + language="c", + libraries=libraries, + library_dirs=library_dirs, + extra_compile_args=["--std=c99", "-g", "-O3"] + extra_compile_args, + extra_link_args=extra_link_args, + ) +] + + +setup( + name="mrsimulator", + version="0.1.0", + description="A python toolbox for simulating NMR spectra", + long_description=open(join(module_dir, "README.md")).read(), + author="Deepansh J. Srivastava", + author_email="deepansh2012@gmail.com", + python_requires=">=3.6", + url="https://github.com/DeepanshS/MRsimulator/", + packages=find_packages("src"), + package_dir={"": "src"}, + setup_requires=["numpy>=1.13.3", "setuptools>=27.3", "cython>=0.29.11"], + install_requires=[ + "numpy>=1.13.3", + "setuptools>=27.3", + "cython>=0.29.11", + "astropy>=3.0", + "pydantic==0.28", + "requests>=2.21.0", + "monty==2.0.4", + "matplotlib>=3.0.2", + ], + extras_require={"fancy feature": ["plotly>=3.6", "dash>=0.40", "dash_daq>=0.1"]}, + ext_modules=cythonize(ext_modules, language_level=3), + include_package_data=True, + zip_safe=False, + license="BSD-3-Clause", + classifiers=[ + # Trove classifiers + # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers + "License :: OSI Approved :: BSD License", + "Programming Language :: Python :: 3", + ], +) diff --git a/src/c_lib/include/Hamiltonian.h b/src/c_lib/include/Hamiltonian.h new file mode 100644 index 000000000..d618cf906 --- /dev/null +++ b/src/c_lib/include/Hamiltonian.h @@ -0,0 +1,146 @@ +// +// Hamiltonian.h +// +// Created by Deepansh J. Srivastava, Apr 11, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include "mrsimulator.h" +#include "transition_function.h" + +/* +=============================================================================== + First order Nuclear shielding Hamiltonian in the PAS. +------------------------------------------------------------------------------- +The Hamiltonian includes the product of second rank tensor and the +spin transition functions. +*/ +static inline void get_nuclear_shielding_hamiltonian_to_first_order( + double *R0, complex128 *R2, double iso, double zeta, double eta, + double *transition) { + // Spin transition contribution + double transition_fn = p(transition[1], transition[0]); + + // Scaled R00 + R0[0] = iso * transition_fn; + + /* + Scaled R2m containing the components of the quad second rank tensor in + its principal axis frame. + */ + double temp = -0.4082482905 * (zeta * eta) * transition_fn; + complex128_add_inplace(R2[0], temp); // R2-2 + complex128_add_inplace(R2[1], 0.0); // R2-1 + complex128_add_inplace(R2[2], zeta * transition_fn); // R2 0 + complex128_add_inplace(R2[3], 0.0); // R2 1 + complex128_add_inplace(R2[4], temp); // R2 2 +} + +/* +=============================================================================== + First order Quadrupolar Hamiltonian in the PAS. +------------------------------------------------------------------------------- +The Hamiltonian includes the product of second rank tensor and the +spin transition functions. +*/ +static inline void +get_quadrupole_hamiltonian_to_first_order(double *R0, complex128 *R2, + double spin, double Cq, double eta, + double *transition) { + // Spin transition contribution + double transition_fn = d(transition[1], transition[0]); + + // Scaled R00 + R0[0] += 0.0; + + /* vq is the Quadrupolar coupling constant given as vq = 3*Cq/(2I(2I-1)), + where `I` is the spin quantum number. */ + double vq = 3.0 * Cq; + double denominator = 2.0 * spin * (2.0 * spin - 1.0); + vq /= denominator; + + /* Scaled R2m containing the components of the quad second rank tensor in + its principal axis frame. */ + double temp = -0.1666666667 * (vq * eta) * transition_fn; + complex128_add_inplace(R2[0], temp); // R2-2 + complex128_add_inplace(R2[1], 0.0); // R2-1 + complex128_add_inplace(R2[2], 0.4082482905 * vq * transition_fn); // R2 0 + complex128_add_inplace(R2[3], 0.0); // R2 1 + complex128_add_inplace(R2[4], temp); // R2 2 +} + +/* +=============================================================================== + Second order Quadrupolar Hamiltonian in the PAS. +------------------------------------------------------------------------------- +The Hamiltonian includes the product of second rank tensor and the +spin transition functions. +*/ +static inline void get_quadrupole_hamiltonian_to_second_order( + double *R0, complex128 *R2, complex128 *R4, double spin, double Cq, + double eta, double *transition, double vo, + int remove_second_order_quad_iso) { + // Spin transition contribution + double c0, c2, c4; + quad_ci(&c0, &c2, &c4, transition[1], transition[0], spin); + + /* vq is the Quadrupolar coupling constant given as vq = 3*Cq/(2I(2I-1)), + where `I` is the spin quantum number. */ + double vq = 3.0 * Cq; + double denominator = 2.0 * spin * (2.0 * spin - 1.0); + vq /= denominator; + + double scale = vq * vq / vo; + double eta2 = eta * eta; + + // Scaled R00 + if (remove_second_order_quad_iso == 0) { + R0[0] += (eta2 * 0.33333333333 + 1.0) * 0.07453559925 * scale * c0; + } + + /* Scaled R2m containing the components of the quad second rank tensor in + its principal axis frame. */ + double temp = -eta * 0.07273929675 * scale * c2; + double temp2 = 0.08908708064 * (eta2 * 0.33333333333 - 1.0) * scale * c2; + complex128_add_inplace(R2[0], temp); // R2-2 + complex128_add_inplace(R2[1], 0.0); // R2-1 + complex128_add_inplace(R2[2], temp2); // R2 0 + complex128_add_inplace(R2[3], 0.0); // R2 1 + complex128_add_inplace(R2[4], temp); // R2 2 + + /* Scaled R4m containing the components of the quad second rank tensor in + its principal axis frame. */ + temp = eta2 * 0.02777777778 * scale * c4; + temp2 = -0.06299407883 * eta * scale * c4; + double temp4 = 0.1195228609 * (eta2 * 0.05555555556 + 1.0) * scale * c4; + complex128_add_inplace(R4[0], temp); // R4-4 + complex128_add_inplace(R4[2], temp2); // R4-2 + complex128_add_inplace(R4[4], temp4); // R4 0 + complex128_add_inplace(R4[6], temp2); // R4 2 + complex128_add_inplace(R4[8], temp); // R4 4 +} + +/* +=============================================================================== + First order Weakly coupled Magnetic Dipole Hamiltonian in the PAS. +------------------------------------------------------------------------------- +The Hamiltonian includes the product of second rank tensor and the +spin transition functions in the weak coupling limit. +*/ +static inline void get_weakly_coupled_direct_dipole_hamiltonian_to_first_order( + double *R0, complex128 *R2, double D, double *transition) { + // Spin transition contribution + double transition_fn = dIS(transition[0], transition[1], 0.5, 0.5); + + // Scaled R00 + R0[0] += 0.0; + + /* Scaled R2m containing the components of the magnetic dipole second rank + tensor in its principal axis frame. */ + complex128_add_inplace(R2[0], 0.0); // R2-2 + complex128_add_inplace(R2[1], 0.0); // R2-1 + complex128_add_inplace(R2[2], 2.0 * D * transition_fn); // R2 0 + complex128_add_inplace(R2[3], 0.0); // R2 1 + complex128_add_inplace(R2[4], 0.0); // R2 2 +} diff --git a/src/c_lib/include/MRAngularMomentum.h b/src/c_lib/include/MRAngularMomentum.h deleted file mode 100644 index 45d72f557..000000000 --- a/src/c_lib/include/MRAngularMomentum.h +++ /dev/null @@ -1,24 +0,0 @@ - -#include -#define MKL_Complex16 double complex - -#include "mkl.h" -#include -#include - -extern void full_DLM(double complex *wigner, int l, double *omega); - -extern double wigner_d(int l, int m1, int m2, double beta); - -// extern double complex DLM(int l, int m1, int m2, OCEulerAngle omega); - -void full_DLM_trig(double complex *wigner, int l, double cosAlpha, - double sinAlpha, double cosBeta, double sinBeta); - -void get_even_DLM_4_from_2(double complex *wigner, double cosBeta); - -double wigner_d_trig(int l, int m1, int m2, double cx, double sx); - -// extern double wigner4(double beta, int m1, int m2); - -extern void wigner_d_matrix(double *wigner, int l, double *value, int trig); diff --git a/src/c_lib/include/angular_momentum.h b/src/c_lib/include/angular_momentum.h new file mode 100644 index 000000000..ee12e45c6 --- /dev/null +++ b/src/c_lib/include/angular_momentum.h @@ -0,0 +1,82 @@ +// +// angular_momentum.c +// +// Created by Philip Grandinetti on 4/12/17. +// Copyright © 2017 Philip Grandinetti. All rights reserved. +// Contribution: Deepansh J. Srivatava. contact: srivastava.89@osu.edu +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com + +#include "mrsimulator.h" + +extern void full_DLM(complex128 *wigner, int l, double *omega); + +extern double wigner_d(int l, int m1, int m2, double beta); + +// extern complex128 DLM(int l, int m1, int m2, OCEulerAngle omega); + +void full_DLM_trig(complex128 *wigner, int l, double cosAlpha, double sinAlpha, + double cosBeta, double sinBeta); + +void get_even_DLM_4_from_2(complex128 *wigner, double cosBeta); + +double wigner_d_trig(int l, int m1, int m2, double cx, double sx); + +/** + * @brief Evaluates `n` wigner-d matrices of rank `l` at every given angles + * @f$\beta@f$ (in radians). + * + * Each wigner-d matrix is `(2l+1) x (2l+1)` in dimension, where @p l is the + * rank. When evaluating @p n wigner-d matrices, the matrices are stored such + * that the wigner-d matrix corresponding to the angle `alpha[i]` starts at the + * index `i*(2*l+1)*(2*l+1)`. + * + * @param l The rank of the wigner-d matrix. + * @param n The number of wigner-d matrix to evaluate. + * @param angle A pointer to a 1D-array of angles @f$\beta@f$ of length @p n, + * given in radians. + * @param wigner A pointer to the start of wigner-d matrices. + */ +extern void AMT_wigner_d_matrix(int l, int n, double *angle, double *wigner); + +/** + * @brief Evaluates @p n wigner-d matrices of rank @p l at every given + * **cosine** of angles, @f$\beta@f$. + * + * Each wigner-d matrix is `(2l+1) x (2l+1)` in dimension, where @p l is the + * rank. When evaluating @p n wigner-d matrices, the matrices are stored such + * that the wigner-d matrix corresponding to the angle `alpha[i]` starts at the + * index `i*(2*l+1)*(2*l+1)`. + * + * @param l The rank of the wigner-d matrix. + * @param n The number of wigner-d matrix to evaluate. + * @param cos_angle A pointer to a 1D-array of cosine of angles @f$\beta@f$ of + * length @p n. + * @param wigner A pointer to the start of wigner-d matrices. + */ +extern void __wigner_d_matrix_cosine(const int l, const int n, + const double *cos_angle, double *wigner); + +/** + * @brief Rotate @p R_in of length @p l using wigner-d matrices of rank @p l. + * wigner matrices. The result is a stack of output vector of size `nxl` +evaluated + * at all `n` wigner matrices. + * @param l The rank of the wigner-d matrices. + * @param n The number of cosine alpha angles and wigner-lj matrices. + * @param cos_alpha A pointer to the 1d array of @f$\cos\alphas@f$. + * @param wigner A pointer to nx(2l+1)x(2l+1) wigner-d matrices of rank @p l. + * The wigner matrices are stacked in a row major order. + * @param R_in A pointer to a 1D-array of initial vector of length `2l+1`. + * @param R_out A pointer to a 1D-array of final vectors after rotation. The + * length of this vector is `n*(2*l+1)`, where the vector of index + * `i*(2*l+1)` is rotated with the wigner-lj matrix at index + * `i*(2*l+1)*(2*l+1)`. + */ +extern void __wigner_rotation(int l, int n, double *wigner, double *cos_alpha, + complex128 *R_in, complex128 *R_out); + +extern void __wigner_rotation_2(const int l, const int n, const double *wigner, + const complex128 *exp_Im_alpha, + const complex128 *R_in, complex128 *R_out); + +extern void __wigner_dm0_vector(const int l, const double beta, double *R_out); diff --git a/src/c_lib/include/array.h b/src/c_lib/include/array.h new file mode 100644 index 000000000..ff41df9e8 --- /dev/null +++ b/src/c_lib/include/array.h @@ -0,0 +1,16 @@ + +// +// array.h +// +// Created by Deepansh J. Srivastava, Apr 11, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include "mrsimulator.h" + +// allocate memory for array of size m for a given type. +#define malloc_complex128(m) (complex128 *)malloc(m * sizeof(complex128)) +#define malloc_complex64(m) (complex64 *)malloc(m * sizeof(complex64)) +#define malloc_float(m) (float *)malloc(m * sizeof(float)) +#define malloc_double(m) (double *)malloc(m * sizeof(double)) diff --git a/src/c_lib/include/c_array.h b/src/c_lib/include/c_array.h deleted file mode 100644 index 49e2508f3..000000000 --- a/src/c_lib/include/c_array.h +++ /dev/null @@ -1,41 +0,0 @@ - -// -// c_array.h -// -// Created by Deepansh J. Srivastava, Apr 11, 2019 -// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. -// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com -// - -#include "complex.h" -// to use calloc, malloc, and free methods -#include - -extern float *createFloat1DArray(int m); -extern void destroyFloat1DArray(float *arr); - -extern float complex *createFloatComplex1DArray(int m); -extern void destroyFloatComplex1DArray(float complex *arr); - -extern double *createDouble1DArray(int m); -extern void destroyDouble1DArray(double *arr); - -extern double complex *createDoubleComplex1DArray(int m); -extern void destroyDoubleComplex1DArray(double complex *arr); - -extern float **createFloat2DMatrix(int m, int n); -extern void destroyFloat2DMatrix(float **arr); - -extern double **createDouble2DMatrix(int n, int m); -extern void destroyDouble2DMatrix(double **arr); - -extern double ***createDouble3DArray(int n, int m, int o); -extern void destroyDouble3DArray(double ***arr); - -// extern OCPolarAngleTrig** create2DOCPolarAngleTrigArray(int m, int n); -// extern void destroy2DOCPolarAngleTrigArray(OCPolarAngleTrig** arr); - -// extern OCDirectionCosineSquare** create2DOCDirectionCosineSquareArray(int m, -// int n); -// extern void destroy2DOCDirectionCosineSquareArray(OCDirectionCosineSquare** -// arr); diff --git a/src/c_lib/include/config.h b/src/c_lib/include/config.h new file mode 100644 index 000000000..00de88262 --- /dev/null +++ b/src/c_lib/include/config.h @@ -0,0 +1,85 @@ + + +// +// config.h +// +// Created by Deepansh J. Srivastava, Aug 10, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#ifndef __config__ + +#define __config__ + +#include "vm_common.h" +#include + +#if __STDC_VERSION__ >= 199901L +#define complex128 double _Complex +#define MKL_Complex16 double _Complex +#define complex64 _Complex +#define MKL_Complex6 _Complex +#define complex128_add_inplace(a, b) (a += b) +// similarly for other operations + +#else // not C99 +typedef struct complex128_ { + double real; + double imag; +} complex128; +typedef struct complex64_ { + float real; + float imag; +} complex64; +#define restrict __restrict +inline complex128 complex128_add_inplace(complex128 a, double b) { + a.real += b; + a.imag += b; + return a; +} +#endif + +// library definition +#if __has_include("mkl.h") +#include "mkl.h" +#define __blas_activate +#include "vm_mkl.h" + +#elif __has_include("cblas.h") +#include "cblas.h" +#define __blas_activate +#include "vm.h" +#endif + +// user definition +#define PI2 6.2831853072 +#define PI2I PI2 *I + +// #ifdef __APPLE__ +// #include +// #define __blas_activate +// #include "vm.h" +// #include "mkl.h" +// #include "vm_mkl.h" +// #endif + +// #ifdef linux +// #include "mkl.h" +// #include "vm_mkl.h" +// // mkl_set_threading_layer(MKL_THREADING_INTEL); +// // int max_threads = mkl_get_max_threads(); +// // mkl_set_num_threads(max_threads); +// // printf("Using upto %d threads for simulation.\n", max_threads); +// #endif + +// #ifdef _WIN32 +// #include "mkl.h" +// #include "vm_mkl.h" +// // mkl_set_threading_layer(MKL_THREADING_INTEL); +// // int max_threads = mkl_get_max_threads(); +// // mkl_set_num_threads(max_threads); +// // printf("Using upto %d threads for simulation.\n", max_threads); +// #endif + +#endif diff --git a/src/c_lib/include/interpolation.h b/src/c_lib/include/interpolation.h new file mode 100644 index 000000000..7dd34bfa7 --- /dev/null +++ b/src/c_lib/include/interpolation.h @@ -0,0 +1,13 @@ + +// +// interpolation.h +// +// Created by Deepansh J. Srivastava, Apr 11, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include "mrsimulator.h" + +extern int triangle_interpolation(double *freq1, double *freq2, double *freq3, + double *amp, double *spec, int *points); diff --git a/src/c_lib/include/isotopomer_ravel.h b/src/c_lib/include/isotopomer_ravel.h new file mode 100644 index 000000000..790851bb1 --- /dev/null +++ b/src/c_lib/include/isotopomer_ravel.h @@ -0,0 +1,37 @@ +// +// isotopomer_ravel.h +// +// Created by Deepansh J. Srivastava, Apr 11, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#ifndef isotopomer_ravel_h +#define isotopomer_ravel_h + +// isotopomer like structure +struct __isotopomer_ravel { + int number_of_sites; /* Number of sites */ + float spin; /* The spin quantum number */ + double larmor_frequency; /* Larmor frequency (MHz) */ + double *isotropic_chemical_shift_in_Hz; /* Isotropic chemical shift (Hz) */ + double *shielding_anisotropy_in_Hz; /* Nuclear shielding anisotropy (Hz) */ + double *shielding_asymmetry; /* Nuclear shielding asymmetry parameter */ + double *shielding_orientation; /* Nuclear shielding PAS to CRS euler angles + (rad.) */ + double *quadrupolar_constant_in_Hz; /* Quadrupolar coupling constant (Hz) */ + double *quadrupolar_asymmetry; /* Quadrupolar asymmetry parameter */ + double + *quadrupolar_orientation; /* Quadrupolar PAS to CRS euler angles (rad.) */ + double *dipolar_couplings; /* dipolar coupling stored as list of lists */ +}; + +typedef struct __isotopomer_ravel isotopomer_ravel; + +struct __isotopomers_list { + isotopomer_ravel *isotopomers; +}; + +typedef struct __isotopomers_list isotopomers_list; + +#endif /* isotopomer_h */ diff --git a/src/c_lib/include/mrsimulator.h b/src/c_lib/include/mrsimulator.h new file mode 100644 index 000000000..00136257c --- /dev/null +++ b/src/c_lib/include/mrsimulator.h @@ -0,0 +1,323 @@ +// +// mrsimulator.h +// +// Created by Deepansh J. Srivastava, Jun 30, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#ifndef mrsimulator_h +#define mrsimulator_h + +#include "vm_common.h" +#include + +#if __STDC_VERSION__ >= 199901L +#define complex128 double _Complex +#define MKL_Complex16 double _Complex +#define complex64 float _Complex +#define MKL_Complex8 float _Complex +#define complex128_add_inplace(a, b) (a += b) +#define complex64_add_inplace(a, b) (a += b) +// similarly for other operations + +#else // not C99 +typedef struct complex128_ { + double real; + double imag; +} complex128; +typedef struct complex64_ { + float real; + float imag; +} complex64; +#define restrict __restrict +inline complex128 complex128_add_inplace(complex128 a, double b) { + a.real += b; + a.imag += b; + return a; +} +inline complex64 complex128_add_inplace(complex64 a, float b) { + a.real += b; + a.imag += b; + return a; +} +#endif + +// library definition +// #if __has_include("mkl.h") +// #include "mkl.h" +// #define __blas_activate +// #include "vm_mkl.h" + +#if __has_include("cblas.h") +#include "cblas.h" +#define __blas_activate +#include "vm.h" +#endif + +// user definition +#define PI2 6.2831853072 +#define PI2I PI2 *I + +// #ifdef __APPLE__ +// #include +// #define __blas_activate +// #include "vm.h" +// #include "mkl.h" +// #include "vm_mkl.h" +// #endif + +// #ifdef linux +// #include "mkl.h" +// #include "vm_mkl.h" +// // mkl_set_threading_layer(MKL_THREADING_INTEL); +// // int max_threads = mkl_get_max_threads(); +// // mkl_set_num_threads(max_threads); +// // printf("Using upto %d threads for simulation.\n", max_threads); +// #endif + +// #ifdef _WIN32 +// #include "mkl.h" +// #include "vm_mkl.h" +// // mkl_set_threading_layer(MKL_THREADING_INTEL); +// // int max_threads = mkl_get_max_threads(); +// // mkl_set_num_threads(max_threads); +// // printf("Using upto %d threads for simulation.\n", max_threads); +// #endif + +#include +#include +#include // to use calloc, malloc, and free methods +#include + +#include "Hamiltonian.h" +#include "angular_momentum.h" +#include "array.h" +#include "fftw3.h" +#include "interpolation.h" +#include "isotopomer_ravel.h" +#include "octahedron.h" +#include "powder_setup.h" + +/** + * @struct MRS_plan_t + * @brief Create a mrsimulator plan for faster lineshape simulation. + */ +struct MRS_plan_t { + /** + * The number of triangles along the edge of octahedron. This value is a + * positive integer which represents the frequency of class I geodesic + * polyhedra. These polyhedra may be used in calculating the spherical + * average. Currently, we only use octahedral as the frequency 1 polyhedra. As + * the frequency of the geodesic polyhedron increases, the polyhedra approach + * to a sphere geometry. For line-shape simulation, a higher geodesic + * polyhedron frequency will result in a better spherical averaging. The + * default value is 72. Read more on the Geodesic + * polyhedron. + */ + unsigned short geodesic_polyhedron_frequency; + + int number_of_sidebands; /**< The number of sidebands to compute. */ + + double sample_rotation_frequency_in_Hz; /**< The sample rotation frequency in + Hz. */ + + /** + * The polar angle, in radians, describing the axis of rotation of the sample + * with respect to the lab-frame z-axis. + */ + double rotor_angle_in_rad; + + bool allow_fourth_rank; /**< Allow buffer for fourth rank tensors. */ + + /** + * The sideband frequency ratio stored in the fft output order. The sideband + * frequency ratio is defined as the ratio - + * @f[\frac{n \omega_r}{n_i}@f] + * where `n` is an integer, @f$\omega_r@f$ is the spinning frequency frequency + * in Hz, and @f$n_i@f$ is the `increment` along the spectroscopic grid + * dimension. + */ + double *vr_freq; + + /** The isotropic frequency offset ratio. The ratio is similarly defined as + * before. */ + double isotropic_offset; + + /** The buffer to hold the sideband amplitudes as stride 2 array after + * mrsimulator processing. + */ + fftw_complex *vector; + + /* private attributes */ + + fftw_plan the_fftw_plan; // The plan for fftw routine. + unsigned int n_orientations; // number of unique orientations. + unsigned int size; // number of orientations * number of sizebands. + double *amplitudes; // array of amplitude scaling per orientation. + double *norm_amplitudes; // array of normalized amplitudes per orientation. + complex128 *exp_Im_alpha; // array of cos_alpha per orientation. + complex128 *w2; // buffer for 2nd rank frequency calculation. + complex128 *w4; // buffer for 4nd rank frequency calculation. + double *wigner_2j_matrices; // wigner-d 2j matrix per orientation. + double *wigner_d2m0_vector; // wigner-2j dm0 vector, n ∈ [-2, 2]. + double *wigner_4j_matrices; // wigner-d 4j matrix per orientation. + double *wigner_d4m0_vector; // wigner-4j dm0 vector, n ∈ [-4, 4]. + complex128 *pre_phase_2; // buffer for 2nk rank sideband phase calculation. + complex128 *pre_phase_4; // buffer for 4th rank sideband phase calculation. + double *local_frequency; // buffer for local frequencies. + double *freq_offset; // buffer for local + sideband frequencies. + complex128 one; // holds complex value 1. + complex128 zero; // hold complex value 0. + double buffer; // buffer for temporary storage. +}; + +typedef struct MRS_plan_t MRS_plan; + +typedef struct MRS_dimension_t { + int count; /**< the number of coordinates along the dimension. */ + double coordinates_offset; /**< starting coordinate of the dimension. */ + double increment; /**< increment of coordinates along the dimension. */ + + /* private attributes */ + double normalize_offset; // fixed value = 0.5 - coordinate_offset/increment + double inverse_increment; +} MRS_dimension; + +MRS_dimension *MRS_create_dimension(int count, double coordinates_offset, + double increment); + +/** + * @brief Release the allocated memory from a mrsimulator plan. + * + * @param MRS_plan *plan The pointer to the mrsimulator plan to be freed. + */ +void MRS_free_plan(MRS_plan *plan); + +/** + * @brief Create a new mrsimulator plan. + * + * @param geodesic_polyhedron_frequency The number of triangles along the + * edge of the octahedron. + * @param number_of_sidebands The number of sideband to compute. + * @param sample_rotation_frequency_in_Hz The sample rotation frequency in Hz. + * @param rotor_angle_in_rad The polar angle in radians with respect to z-axis + * describing the axis of rotation. + * @param increment The increment along the spectroscopic dimension. + * @param allow_fourth_rank When true, the plan calculates matrices for + * processing the fourth rank tensor. + */ +MRS_plan *MRS_create_plan(unsigned int geodesic_polyhedron_frequency, + int number_of_sidebands, + double sample_rotation_frequency_in_Hz, + double rotor_angle_in_rad, double increment, + bool allow_fourth_rank); + +/** + * @brief Free memory allocated for spherical averaging scheme. + * + * @param plan The plan from which the memory allocated to spherical + * averaging scheme is freed. + */ +void MRS_plan_free_averaging_scheme(MRS_plan *plan); + +/** + * @brief Update the plan with a new spherical averaging scheme. + * + * @param geodesic_polyhedron_frequency The number of triangles along the + * edge of the octahedron. + * @param allow_fourth_rank When true, the plan calculates matrices for + * processing the fourth rank tensor. + */ +void MRS_plan_update_averaging_scheme( + MRS_plan *plan, unsigned int geodesic_polyhedron_frequency, + bool allow_fourth_rank); + +/* Free the memory from the mrsimulator plan associated with the wigner + * d^l_{m,0}(rotor_angle_in_rad) vectors. Here, l=2 or 4. + * */ +void MRS_plan_free_rotor_angle_in_rad(MRS_plan *plan); + +/* Update the MRS plan for the given rotor angle in radians. */ +void MRS_plan_update_rotor_angle_in_rad(MRS_plan *plan, + double rotor_angle_in_rad, + bool allow_fourth_rank); +/** + * @brief Return a copy of the mrsimulator plan. + * + * @param *plan The pointer to the plan to be copied. + * @return MRS_plan = A pointer to the copied plan. + * This function is incomplete. + */ +MRS_plan *MRS_copy_plan(MRS_plan *plan); + +/** + * @brief Process the plan for the amplitudes at every orientation. + * + * The method takes the arguments @p R2 and @p R4 vectors defined in a crystal / + * commmon frame and evaluates the amplitudes corresponding to the @p R2 and @p + * R4 vectors in the lab frame. The transformation from the crystal / commmon + * frame to the lab frame is done using the wigner 2j and 4j rotation matrices + * over all orientations. The sideband amplitudes are evaluated using equation + * [39] of the reference https://doi.org/10.1006/jmre.1998.1427. + * + * @param plan A pointer to the mrsimulator plan of type MRS_plan. + * @param R2 A pointer to the product of the spatial part coefficients of the + * second rank tensor and the spin transition functions. The vector + * @p R2 is a complex128 array of length 5 with the first element + * corresponding to the product of the spin transition function and + * the coefficient of the @f$T_{2,-2}@f$ spatial irreducible tensor. + * @param R4 A pointer to the product of the spatial part coefficients of the + * fourth rank tensor and the spin transition functions. The vector + * @p R4 is a complex128 array of length 9 with the first element + * corresponding to the product of the spin transition function and + * the coefficient of the @f$T_{4,-4}@f$ spatial irreducible tensor. + */ +void MRS_get_amplitudes_from_plan(MRS_plan *plan, complex128 *R2, + complex128 *R4); + +/** + * @brief Process the plan for normalized frequencies at every orientation. + * + * @param plan + * @param dim + * @param R0 + */ +void MRS_get_normalized_frequencies_from_plan(MRS_plan *plan, + MRS_dimension *dim, double R0); + +/** + * @brief Return a vector ordered according to the fft output order. + * + * @params n The number of points. + * @params increment The increment along the dimension axis (sampling interval). + * @returns values A pointer to the fft output order vector of size @p p. + */ +static inline double *__get_frequency_in_FFT_order(int n, double increment) { + double *vr_freq = malloc_double(n); + int i = 0, m, positive_limit, negative_limit; + + if (n % 2 == 0) { + negative_limit = (int)(-n / 2); + positive_limit = -negative_limit - 1; + } else { + negative_limit = (int)(-(n - 1) / 2); + positive_limit = -negative_limit; + } + + for (m = 0; m <= positive_limit; m++) { + vr_freq[i] = (double)m * increment; + i++; + } + for (m = negative_limit; m < 0; m++) { + vr_freq[i] = (double)m * increment; + i++; + } + return vr_freq; +}; + +extern void __get_components(int number_of_sidebands, double spin_frequency, + complex128 *pre_phase); + +#endif /* mrsimulator_h */ diff --git a/src/c_lib/include/octahedron.h b/src/c_lib/include/octahedron.h new file mode 100644 index 000000000..e5a815f0c --- /dev/null +++ b/src/c_lib/include/octahedron.h @@ -0,0 +1,20 @@ + +// +// powder_setup.h +// +// Created by Deepansh J. Srivastava, Apr 11, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include "mrsimulator.h" + +extern void octahedronGetDirectionCosineSquareOverOctantAndWeights( + int nt, double *xr, double *yr, double *zr, double *amp); + +extern void octahedronGetPolarAngleTrigOverAnOctant(int nt, double *cos_alpha, + double *cos_beta, + double *amp); + +extern void octahedronInterpolation(double *spec, double *freq, int nt, + double *amp, int stride, int m); diff --git a/src/c_lib/include/powder_setup.h b/src/c_lib/include/powder_setup.h index 022020cf9..ef0e7b54b 100644 --- a/src/c_lib/include/powder_setup.h +++ b/src/c_lib/include/powder_setup.h @@ -7,62 +7,9 @@ // Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com // -#include "c_array.h" -#include "mkl.h" -#include -#include -#include +#include "mrsimulator.h" -extern void getDirectionCosineSquareOverOctantAndWeights2(int nt, double *xr, - double *yr, - double *zr, - double *amp); - -// extern void getDirectionCosineSquareOverOctantAndWeights(int nt, double **xr, -// double **yr, -// double **zr, -// double **rrr); - -// extern OCAveragingSchemeDirectionCosineSquareForTenting -// powderDirectionCosineSquare(int nt); - -extern void getDirectionCosineSquareOverHemishpereAndWeights( - int nt, double **xr, double **yr, double **zr, double **amp); - -// extern void powderDirectionCosineSquare(int nt, double **xr, double **yr, -// double **zr, double **rrr); - -extern void getPolarAngleTrigOverHemisphere(int nt, double *cosAlpha, - double *sinAlpha, double *cosBeta, - double *sinBeta, double **amp); - -void getPolarAngleTrigOverAnOctant(int nt, double *cosAlpha, double *cosBeta, - double *amp); - -// void tent(double freq1, double freq2, double freq3, double amp, double *spec, -// int points, double fstart, double finc); - -extern int triangle_interpolation(double *freq1, double *freq2, double *freq3, - double *offset, double *amp, double *spec, - int *points); - -// int tent_amp(double *freq1, double *freq2, double *freq3, double *offset, -// double *amp1, double *amp2, double *amp3, double *spec, -// int points); - -extern void powderAverageWithTentingSchemeOverOctant(double *spec, double *freq, - int nt, double *amp, - double *offset, int m); - -// extern void powderAverageWithTentingSchemeOverOctant(double *spec, -// double **powfreq, int -// nt, double **amp, double -// *offset, int m); - -extern void powderAverageWithTentingSchemeOverHemisphere(double *spec, - double **powfreq, - int nt, double **amp, - double *offset, int m); - -extern void rasterization(double *grid, double *v0, double *v1, double *v2, - int rows, int columns); +extern void __powder_averaging_setup( + int nt, double *cos_alpha, double *cos_beta, double *amp, + int space // 1 for octant, 2 for hemisphere and 4 for sphere +); diff --git a/src/c_lib/include/spinning_sidebands.h b/src/c_lib/include/spinning_sidebands.h index 5402b9254..2c08441b7 100644 --- a/src/c_lib/include/spinning_sidebands.h +++ b/src/c_lib/include/spinning_sidebands.h @@ -6,78 +6,32 @@ // Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com // -#define PI2 6.2831853072 -#define PI2I PI2 *I +#include "mrsimulator.h" -#include "MRAngularMomentum.h" -#include "c_array.h" -#include "fftw/fftw3.h" -#include "fftw/fftw3_mkl.h" -#include "math.h" -#include "mkl.h" -#include "powder_setup.h" -#include -#include - -#define MKL_Complex16 double complex - -struct directionCosines { - double cosAlpha; - double cosBeta; -}; - -extern void __powder_averaging_setup( - int nt, double *cosAlpha, double *cosBeta, double *amp, - int space // 1 for octant, 2 for hemisphere and 4 for sphere -); - -// Return a vector ordered according to the fft output order. // -// @params int n - The number of points // -// @params double increment - The increment (sampling interval) // -// @returns *double values = The pointer to the fft output order vector // -extern inline double *__get_frequency_in_FFT_order(int n, double increment); +// headerdoc extern void spinning_sideband_core( // spectrum information and related amplitude double *spec, // The amplitude of the spectrum. - double *cpu_time_, // Execution time double spectral_start, // The start of the frequency spectrum. - double spectral_increment, // The bandwidth of the frequency spectrum. + double spectral_increment, // The increment of the frequency spectrum. int number_of_points, // Number of points on the frequency spectrum. - double spin_quantum_number, // Spin quantum numbers - double larmor_frequency, // Larmor frequency + isotopomer_ravel *ravel_isotopomer, // isotopomer structure - // Pointer to the array of CSA tensor information in the PAS. - double *iso_n, // The isotropic chemical shift. - double *aniso_n, // The chemical shielding anisotropic. - double *eta_n, // Chemical shielding asymmetry - - // Pointer to the array of quadrupole tensor information in the PAS. - double *Cq_e, // The Cq of the quadrupole center. - double *eta_e, // The asymmetry term of the tensor. - int quadSecondOrder, // Quad theory for second order, - - // Pointer to the array of dipolar tensor information in the PAS. - double *D, // The dipolar coupling constant. + int quadSecondOrder, // Quad theory for second order, + int remove_second_order_quad_iso, // remove the isotropic contribution from + // the second order quad Hamiltonian. // spin rate, spin angle and number spinning sidebands - int ph_step, // The number of spinning sidebands to evaluate - double spin_frequency, // The rotor spin frequency - double rotor_angle, // The rotor angle relative to lab-frame z-axis + int number_of_sidebands, // The number of sidebands + double sample_rotation_frequency_in_Hz, // The rotor spin frequency + double rotor_angle_in_rad, // The rotor angle relative to lab-frame z-axis - double *transition, // The transition as transition[0] = mi and - // transition[1] = mf - - // The principal to molecular frame transformation euler angles. - // double * omega_PM, + // Pointer to the transitions. transition[0] = mi and transition[1] = mf + double *transition, // powder orientation average - unsigned int n_orientations, // number of orientations - double *cosAlpha, // array of cosAlpha of orientations - double *cosBeta, // array of cosBeta of orientations - double *amp, // array of amplitude of orientations - int nt, // number of triangles along the edge of the octahedral face - - unsigned int number_of_sites // number of sites in the isotopomer + int geodesic_polyhedron_frequency // The number of triangle along + // the edge of octahedron ); diff --git a/src/c_lib/include/transition_function.h b/src/c_lib/include/transition_function.h new file mode 100644 index 000000000..c413a0e42 --- /dev/null +++ b/src/c_lib/include/transition_function.h @@ -0,0 +1,116 @@ +// +// transition_function.h +// +// Created by Deepansh J. Srivastava, Apr 11, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include "mrsimulator.h" + +// Definning pi^{2,2}_{L,J} as piLJ // +// #define pi01 = 0.3577708764 +// #define pi21 = 0.1069044968 +// #define pi41 = -0.1434274331 + +// #define pi03 = 0.8485281374 +// #define pi23 = -1.0141851057 +// #define pi43 = -1.2850792082 +// --------------------------------- // + +/** + * @brief The @f$\mathbb{p}@f$ spin symmetry transition function. + * + * The transition symmetry function from irreducible 1st rank tensor, given as + * @f[ + * \mathbb{p}(m_f, m_i) &= \left< m_f | T_{10} | m_f \right> - + * \left< m_i | T_{10} | m_i \right> \\ + * &= m_f - m_i + * @f] + * where @f$T_{10}@f$ is the irreducible 1st rank tensor operator in the + * rotating tilted frame. + * + * @param mf The quantum number associated with the final energy state. + * @param mi The quantum number associated with the initial energy state. + * @return The spin transition symmetry function @f$\mathbb{p}@f$. + */ +static inline double p(double mf, double mi) { return (mf - mi); } + +/** + * @brief The @f$\mathbb{d}@f$ spin transition symmetry function. + * + * The transition symmetry function from irreducible 2nd rank tensor, given as + * @f[ + * \mathbb{d}(m_f, m_i) &= \left< m_f | T_{20} | m_f \right> - + * \left< m_i | T_{20} | m_i \right> \\ + * &= \sqrt{\frac{3}{2}} \left(m_f^2 - m_i^2 \right) + * @f] + * where @f$T_{20}@f$ is the irreducible 2nd rank tensor operator in the + * rotating tilted frame. + * + * @param mf The quantum number associated with the final energy state. + * @param mi The quantum number associated with the initial energy state. + * @return The spin transition symmetry function @f$\mathbb{d}@f$. + */ +static inline double d(double mf, double mi) { + return 1.2247448714 * (mf * mf - mi * mi); +} + +/** + * @brief The @f$\mathbb{f}@f$ spin transition symmetry function. + * + * The transition symmetry function from irreducible 3rd rank tensor, given as + * @f[ + * \mathbb{f}(m_f, m_i) &= \left< m_f | T_{30} | m_f \right> - + * \left< m_i | T_{30} | m_i \right> \\ + * &= \frac{1}{\sqrt{10}} [5(m_f^3 - m_i^3) + (1 - 3I(I+1))(m_f-m_i)] + * @f] + * where @f$T_{30}@f$ is the irreducible 3rd rank tensor operator in the + * rotating tilted frame. + * + * @param mf The quantum number associated with the final energy state. + * @param mi The quantum number associated with the initial energy state. + * @return The spin transition symmetry function @f$\mathbb{f}@f$. + */ +static inline double f(double mf, double mi, double spin) { + double f_ = 1.0 - 3.0 * spin * (spin + 1.0); + f_ *= (mf - mi); + f_ += 5.0 * (mf * mf * mf - mi * mi * mi); + f_ *= 0.316227766; + return f_; +} + +/** + * @brief The @f$\mathbb{d_{IS}}@f$ spin transition symmetry function. + * + * The transition symmetry function from irreducible tensors for a coupled + * spin system is defined as + * @f[ + * \mathbb{d}_{IS}(m_{If}, m_{Sf}, m_{Ii}, m_{Si}) &= + * \left< m_{If} m_{Sf} | T_{10}(I)~T_{10}(S) | m_{If} m_{Sf} \right> \\ + * &~~- \left< m_{Ii} m_{Si} | T_{10}(I)~T_{10}(S) | m_{Ii} m_{Si} \right> \\ + * &= m_{If} m_{Sf} - m_{Ii} m_{Si} + * @f] + * where @f$T_{10}(I)@f$ and @f$T_{10}(S)@f$ are the irreducible 1st rank tensor + * operators in the rotating tilted frame for spin I and S, respectively. + * + * @param mIi The quantum number associated with the initial state of spin I. + * @param mIf The quantum number associated with the final state of spin I. + * @param mSi The quantum number associated with the initial state of spin S. + * @param mSf The quantum number associated with the final state of spin S. + * @return The spin transition symmetry function @f$\mathbb{d_{IS}}@f$. + */ +static inline double dIS(double mIf, double mIi, double mSf, double mSi) { + return mIf * mSf - mIi * mSi; +} + +static inline void quad_ci(double *c0, double *c2, double *c4, double mf, + double mi, double spin) { + double f_ = f(mf, mi, spin); + double p_ = p(mf, mi); + + double temp = spin * (spin + 1.0) - 0.75; + c0[0] = 0.3577708764 * temp * p_ + 0.8485281374 * f_; + c2[0] = 0.1069044968 * temp * p_ + -1.0141851057 * f_; + c4[0] = -0.1434274331 * temp * p_ + -1.2850792082 * f_; +} diff --git a/src/c_lib/include/vm.h b/src/c_lib/include/vm.h new file mode 100644 index 000000000..d3ec92241 --- /dev/null +++ b/src/c_lib/include/vm.h @@ -0,0 +1,449 @@ +// +// vm.h +// +// Created by Deepansh J. Srivastava, Jul 26, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +/** Arithmetic suit ======================================================== */ + +/** + * Add the elements of vector x and y and store in res of type double. + * res = x + y + */ +static inline void vm_double_add(int count, const double *restrict x, + const double *restrict y, + double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = *x++ + *y++; + // x += stride_x; + // y += stride_y; + // res += stride_res; + } +} + +/** + * Add the elements of vector y inplace with the elements from vector x. + * y += x + */ +static inline void vm_double_add_inplace(int count, const double *restrict x, + double *restrict y) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + while (count-- > 0) { + *y++ += *x++; + // x += stride_x; + // y += stride_y; + } +} + +/** + * Subtract the elements of vector x from y and store in res of type double. + * res = x - y + */ +static inline void vm_double_subtract(int count, const double *restrict x, + const double *restrict y, + double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = *x++ - *y++; + // x += stride_x; + // y += stride_y; + // res += stride_res; + } +} + +/** + * Subtract the elements of vector y inplace with the elements from vector x. + * y -= x + */ +static inline void vm_double_subtract_inplace(int count, + const double *restrict x, + double *restrict y) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + while (count-- > 0) { + *y++ -= *x++; + // x += stride_x; + // y += stride_y; + } +} + +/** + * Multiply the elements of vector x and y and store in res of type double. + * res = x * y + */ +static inline void vm_double_multiply(int count, const double *restrict x, + const double *restrict y, + double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = *x++ * *y++; + // x += stride_x; + // y += stride_y; + // res += stride_res; + } +} + +/** + * Multiply the elements of vector y inplace with the elements from vector x. + * y *= x + */ +static inline void vm_double_multiply_inplace(int count, + const double *restrict x, + double *restrict y) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + while (count-- > 0) { + *y++ *= *x++; + // x += stride_x; + // y += stride_y; + } +} + +/** + * Divide the elements of vector x by y and store in res of type double. + * res = x / y + */ +static inline void vm_double_divide(int count, const double *restrict x, + const double *restrict y, + double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = *x++ / *y++; + // x += stride_x; + // y += stride_y; + // res += stride_res; + } +} + +/** + * Divide the elements of vector y inplace with the elements from vector x. + * y /= x + */ +static inline void vm_double_divide_inplace(int count, const double *restrict x, + double *restrict y) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + while (count-- > 0) { + *y++ /= *x++; + // x += stride_x; + // y += stride_y; + } +} + +/** + * Square the elements of vector x and store in res of type double. + * res = x * x + */ +static inline void vm_double_square(int count, const double *restrict x, + double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = *x * *x; + x++; + } +} + +/** + * Square the elements of vector y inplace. + * y /= x + */ +static inline void vm_double_square_inplace(int count, double *restrict x) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + while (count-- > 0) { + *x *= *x; + x++; + // x += stride_x; + // y += stride_y; + } +} + +/** Power and roots suit =================================================== */ +/** + * Square root of the elements of vector x stored in res of type double. + * res = sqrt(x) + */ +static inline void vm_double_square_root(int count, const double *restrict x, + double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = sqrt(*x++); + } +} + +/** + * Square root of the elements of vector x inplace. + * x = sqrt(x) + */ +static inline void vm_double_square_root_inplace(int count, + double *restrict x) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *x = sqrt(*x); + x++; + } +} + +/** + * Multiply the elements of vector x and y and store in res of type double + * complex. + * res = x * y + */ +static inline void vm_double_complex_multiply(int count, + const complex128 *restrict x, + const complex128 *restrict y, + complex128 *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + // res = __builtin_assume_aligned(res, 32); + // double *res_ = (double *)res; + // double *x_ = (double *)x; + // double *y_ = (double *)y; + // int count_ = 2 * count; + + while (count-- > 0) { + *res++ = *x++ * *y++; + } +} + +// Trignometry + +/** + * Cosine of the elements of vector x stored in res of type double. + * res = cos(x) + */ +static inline void vm_double_cosine(int count, const double *restrict x, + double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = cos(*x++); + // x += stride_x; + // res += stride_res; + } +} + +/** + * Sine of the elements of vector x stored in res of type double. + * res = sin(x) + */ +static inline void vm_double_sine(int count, const double *restrict x, + double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = sin(*x++); + // x += stride_x; + // res += stride_res; + } +} + +/** + * Cosine + I Sine of the elements of vector x in rad and stored in + * res of type complex128. + * res = cos(x) + I sin(x) + */ +static inline void vm_cosine_I_sine(int count, const double *restrict x, + complex128 *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + double *res_ = (double *)res; + while (count-- > 0) { + *res_++ = cos(*x); + *res_++ = sin(*x++); + } +} + +// Exponent + +static inline double my_exp(double x) { + x /= 1024.0; + x += 1.0; + x *= x; // 1 + x *= x; // 2 + x *= x; // 3 + x *= x; // 4 + x *= x; // 5 + x *= x; // 6 + x *= x; // 7 + x *= x; // 8 + x *= x; // 9 + x *= x; // 10 + return x; +} +/** + * Exponent of the elements of vector x stored in res of type double. + * res = exp(x) + */ +static inline void vm_dexp(int count, double *restrict x, + double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = my_exp(*x++); + } +} + +/** + * Exponent of the elements of vector x stored in res of type complex128. + * res = exp(x) + */ +static inline void vm_double_complex_exp(int count, + const complex128 *restrict x, + complex128 *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + // double *x_ = (double *)x; + // double *res_ = (double *)res; + // double *res_1 = (double *)res + 1; + // int count_ = 2 * count; + // int i = 1; + // double factor, num_; + + // // double temp; + while (count-- > 0) { + // i = 1; + // factor = *++x_; + // num_ = factor; + // *res_ = 1; + // *res_1 = factor; + // while (i < 10) + // { + // factor *= num_ / ++i; + // *res_ -= factor; + // factor *= num_ / ++i; + // *res_1 -= factor; + // factor *= num_ / ++i; + // *res_ += factor; + // factor *= num_ / ++i; + // *res_1 += factor; + // } + // res_ += 2; + // res_1 += 2; + // x_++; + + *res++ = cexp(*x++); + // temp = my_exp(*x_++); + // *res_++ = cos(*++x_) * temp; + // *res_++ = sin(*x_++) * temp; + } +} + +#ifndef __blas_activate +//========================================================================== // +// Wrapper for blas and blas like functions // +//========================================================================== // + +/** + * Scale the elements of vector x by a of type double. + * x *= a + * Equivalent to cblas_dscal. + */ +static inline void cblas_dscal(int count, const double a, double *restrict x, + const int stride_x) { + // x = __builtin_assume_aligned(x, 32); + while (count-- > 0) { + *x *= a; + x += stride_x; + } +} + +/** + * Scale the elements of complex128 vector x by a double a. + * x *= a + * Equivalent to cblas_zdscal. + */ +static inline void cblas_zdscal(int count, const double a, + complex128 *restrict x, const int stride_x) { + // x = __builtin_assume_aligned(x, 32); + while (count-- > 0) { + *x *= a; + x += stride_x; + } +} + +/** + * Copy elements of vector x to vector y of type double. + * y = x + * Equivalent to cblas_dcopy. + */ +static inline void cblas_dcopy(int count, const double *restrict x, + const int stride_x, double *restrict y, + const int stride_y) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + while (count-- > 0) { + *y = *x; + x += stride_x; + y += stride_y; + } +} + +/** + * Copy elements of vector x to vector y of type complex128. + * y = x + * Equivalent to cblas_zcopy. + */ +static inline void cblas_zcopy(int count, const complex128 *restrict x, + const int stride_x, complex128 *restrict y, + const int stride_y) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + while (count-- > 0) { + *y = *x; + x += stride_x; + y += stride_y; + } +} + +/** + * Compute the following + * y = a*x + y + * Equivalent to cblas_daxpy. + */ +static inline void cblas_daxpy(int count, const double a, + const double *restrict x, const int stride_x, + double *restrict y, const int stride_y) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + while (count-- > 0) { + *y += a * *x; + x += stride_x; + y += stride_y; + } +} + +/** + * Compute the following + * y = a*x + b*y + * Equivalent to catlas_daxpby. + */ +static inline void catlas_daxpby(int count, const double a, + const double *restrict x, const int stride_x, + const double b, double *restrict y, + const int stride_y) { + // x = __builtin_assume_aligned(x, 32); + // y = __builtin_assume_aligned(y, 32); + while (count-- > 0) { + *y *= b; + *y += a * *x; + x += stride_x; + y += stride_y; + } +} +#endif diff --git a/src/c_lib/include/vm_common.h b/src/c_lib/include/vm_common.h new file mode 100644 index 000000000..9429a952b --- /dev/null +++ b/src/c_lib/include/vm_common.h @@ -0,0 +1,63 @@ +// +// vm_common.h +// +// Created by Deepansh J. Srivastava, Jul 26, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include +#include + +/** + * Multiply a vector of type double by `scale` and add an `offset` to its + * elements. res = scale*x + offset + */ +static inline void vm_double_ramp(int count, const double *restrict x, + const double scale, const double offset, + double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = scale * *x++ + offset; + } +} + +/** + * Create a vector x = [0 .. count-1] + * res = 0 .. count-1 + */ +static inline void vm_double_arange(int count, double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + double i = 0.0; + while (count-- > 0) { + *res++ = i++; + } +} + +/** + * Create a vector of length count with all zero entries + * res = [0.0, 0.0, 0.0, ... ] + */ +static inline void vm_double_zeros(int count, double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = 0.0; + } + // memset(res, 0, count * sizeof(double)); +} + +/** + * Create a vector of length count with all one entries + * res = [1.0, 1.0, 1.0, ... ] + */ +static inline void vm_double_ones(int count, double *restrict res) { + // x = __builtin_assume_aligned(x, 32); + // res = __builtin_assume_aligned(res, 32); + while (count-- > 0) { + *res++ = 1.0; + } + // memset(res, 0, count * sizeof(double)); +} diff --git a/src/c_lib/include/vm_mkl.h b/src/c_lib/include/vm_mkl.h new file mode 100644 index 000000000..bbb72545c --- /dev/null +++ b/src/c_lib/include/vm_mkl.h @@ -0,0 +1,129 @@ +// +// vm_mkl.h +// +// Created by Deepansh J. Srivastava, Jul 26, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +// static inline complex128 cmult(complex128 x, complex128 y) { +// complex128 res; +// res.real = x.real * y.real - x.imag * y.imag; +// res.imag = x.real * y.imag + x.imag * y.real; +// return res; +// } + +/** + * Add the elements of vector x and y and store in res of type double. + * res = x + y + */ +static inline void vm_double_add(int count, const double *x, const double *y, + double *res) { + vdAdd(count, x, y, res); +} + +/** + * Subtract the elements of vector x from y and store in res of type double. + * res = x - y + */ +static inline void vm_double_subtract(int count, const double *x, + const double *y, double *res) { + vdSub(count, x, y, res); +} + +/** + * Multiply the elements of vector x and y and store in res of type double. + * res = x * y + */ +static inline void vm_double_multiply(int count, const double *x, + const double *y, double *res) { + vdMul(count, x, y, res); +} + +/** + * Divide the elements of vector x by y and store in res of type double. + * res = x / y + */ +static inline void vm_double_divide(int count, const double *x, const double *y, + double *res) { + vdDiv(count, x, y, res); +} + +/** + * Square the elements of vector x and store in res of type double. + * res = x * x + */ +static inline void vm_double_square(int count, const double *x, double *res) { + vdSqr(count, x, res); +} + +/** + * Square root of the elements of vector x stored in res of type double. + * res = sqrt(x) + */ +static inline void vm_double_square_root(int count, const double *x, + double *res) { + vdSqrt(count, x, res); +} + +/** + * Multiply the elements of vector x and y and store in res of type double + * complex. + * res = x * y + */ +static inline void vm_double_complex_multiply(int count, const complex128 *x, + const complex128 *y, + complex128 *res) { + vzMul(count, x, y, res); +} + +// Trignometry + +/** + * Cosine of the elements of vector x stored in res of type double. + * res = cos(x) + */ +static inline void vm_double_cosine(int count, const double *x, double *res) { + vdCos(count, x, res); +} + +/** + * Sine of the elements of vector x stored in res of type double. + * res = sin(x) + */ +static inline void vm_double_sine(int count, const double *x, double *res) { + vdSin(count, x, res); +} + +/** + * Cosine + I Sine of the elements of vector x in rad and stored in + * res of type complex128. + * res = cos(x) + I sin(x) + */ +static inline void vm_cosine_I_sine(int count, const double *x, + complex128 *res) { + vzCIS(count, x, res); +} + +static inline void vm_dlinear(int count, double *x, double scale, double offset, + double *res) { + vdLinearFrac(count, x, x, scale, offset, 0.0, 1.0, res); +} +// Exponent + +/** + * Exponent of the elements of vector x stored in res of type double. + * res = exp(x) + */ +static inline void vm_dexp(int count, const double *x, double *res) { + vdExp(count, x, res); +} + +/** + * Exponent of the elements of vector x stored in res of type complex128. + * res = exp(x) + */ +static inline void vm_double_complex_exp(int count, const complex128 *x, + complex128 *res) { + vmzExp(count, x, res, VML_EP); +} diff --git a/src/c_lib/lib/MRAngularMomentum.c b/src/c_lib/lib/angular_momentum.c similarity index 64% rename from src/c_lib/lib/MRAngularMomentum.c rename to src/c_lib/lib/angular_momentum.c index 8af40a255..344348ebd 100644 --- a/src/c_lib/lib/MRAngularMomentum.c +++ b/src/c_lib/lib/angular_momentum.c @@ -1,12 +1,12 @@ // -// MRAngularMomentum.c +// angular_momentum.c // // Created by Philip Grandinetti on 4/12/17. // Copyright © 2017 Philip Grandinetti. All rights reserved. // Contribution: Deepansh J. Srivatava. contact: srivastava.89@osu.edu // Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com -#include "MRAngularMomentum.h" +#include "angular_momentum.h" /* calculate Wigner rotation matrices */ @@ -17,7 +17,6 @@ double fac(double x) { int ix; if (x < 0) { - fprintf(stderr, "illegal argument x = %g in factorial...\n", x); exit(1); } ix = (int)x; @@ -28,7 +27,6 @@ double fac(double x) { } /* power function */ - double mypow(double x, int n) { double temp; if (n == 0) { @@ -41,208 +39,387 @@ double mypow(double x, int n) { return (temp); } -void wigner_d_matrix(double *wigner, int l, double *value, int trig) { - double cx; - if (trig == 0) { - cx = cos(value[0]); - } else { - cx = value[0]; - } - if (l == 2) { - double complex cx2 = cx * cx; - double complex sx = sqrt(1. - cx2); - - double complex t1 = (1. + cx); - double complex temp = -sx * t1 / 2.; - wigner[19] = temp; // 2, 1 // 19 - wigner[5] = -temp; // -2, -1 // 5 - wigner[23] = -temp; // 1, 2 // 23 - wigner[1] = temp; // -1, -2 // 1 - - temp = t1 * t1 / 4.; - wigner[24] = temp; // 2, 2 // 24 - wigner[0] = temp; // -2, -2 // 0 - - t1 = (1. - cx); - temp = -sx * t1 / 2.; - wigner[9] = temp; // 2, -1 // 9 - wigner[15] = -temp; // -2, 1 // 15 - wigner[3] = temp; // 1, -2 // 3 - wigner[21] = -temp; // -1, 2 // 21 - - temp = t1 * t1 / 4.; - wigner[4] = temp; // 2, -2 // 4 - wigner[20] = temp; // -2, 2 // 20 - - temp = 0.6123724355 * sx * sx; - wigner[14] = temp; // 2, 0 // 14 - wigner[10] = temp; // -2, 0 // 10 - wigner[22] = temp; // 0, 2 // 22 - wigner[2] = temp; // 0, -2 // 2 - - temp = 1.224744871 * sx * cx; - wigner[13] = -temp; // 1, 0 // 13 - wigner[17] = temp; // 0, 1 // 17 - wigner[7] = -temp; // 0, -1 // 7 - wigner[11] = temp; // -1, 0 // 11 - - temp = (2.0 * cx2 + cx - 1.) / 2.; - wigner[18] = temp; // 1, 1 // 18 - wigner[6] = temp; // -1, -1 // 6 - - temp = -(2.0 * cx2 - cx - 1.) / 2.; - wigner[8] = temp; // 1, -1 // 8 - wigner[16] = temp; // -1, 1 // 16 +void __wigner_d_matrix(int l, int n, double *angle, double *wigner) { + vm_double_cosine(n, &angle[0], &angle[0]); + __wigner_d_matrix_cosine(l, n, &angle[0], &wigner[0]); +} - wigner[12] = 1.5 * cx2 - .5; // 0, 0 // 12 +void __wigner_d_matrix_cosine(const int l, const int n, const double *cos_angle, + double *wigner) { + double cx, cx2, sx, temp; + int i; + if (l == 2) { + double t1; + for (i = 0; i < n; i++) { + cx = *cos_angle++; + cx2 = cx * cx; + sx = sqrt(1. - cx2); + + t1 = (1. + cx); + temp = -sx * t1 / 2.; + wigner[19] = temp; // 2, 1 // 19 + wigner[5] = -temp; // -2, -1 // 5 + wigner[23] = -temp; // 1, 2 // 23 + wigner[1] = temp; // -1, -2 // 1 + + temp = t1 * t1 / 4.; + wigner[24] = temp; // 2, 2 // 24 + wigner[0] = temp; // -2, -2 // 0 + + t1 = (1. - cx); + temp = -sx * t1 / 2.; + wigner[9] = temp; // 2, -1 // 9 + wigner[15] = -temp; // -2, 1 // 15 + wigner[3] = temp; // 1, -2 // 3 + wigner[21] = -temp; // -1, 2 // 21 + + temp = t1 * t1 / 4.; + wigner[4] = temp; // 2, -2 // 4 + wigner[20] = temp; // -2, 2 // 20 + + temp = 0.6123724355 * sx * sx; + wigner[14] = temp; // 2, 0 // 14 + wigner[10] = temp; // -2, 0 // 10 + wigner[22] = temp; // 0, 2 // 22 + wigner[2] = temp; // 0, -2 // 2 + + temp = 1.224744871 * sx * cx; + wigner[13] = -temp; // 1, 0 // 13 + wigner[17] = temp; // 0, 1 // 17 + wigner[7] = -temp; // 0, -1 // 7 + wigner[11] = temp; // -1, 0 // 11 + + temp = (2.0 * cx2 + cx - 1.) / 2.; + wigner[18] = temp; // 1, 1 // 18 + wigner[6] = temp; // -1, -1 // 6 + + temp = -(2.0 * cx2 - cx - 1.) / 2.; + wigner[8] = temp; // 1, -1 // 8 + wigner[16] = temp; // -1, 1 // 16 + + wigner[12] = 1.5 * cx2 - .5; // 0, 0 // 12 + + wigner += 25; + } } if (l == 4) { - double complex cx2 = cx * cx; - double complex sx = sqrt(1. - cx2); - double complex sx2 = sx * sx, sx3 = sx2 * sx; - - double complex cxp1 = (1. + cx), cxm1 = (1. - cx); - double complex cxp12 = cxp1 * cxp1, cxm12 = cxm1 * cxm1; - double complex cxm13 = cxm12 * cxm1; - double complex cxp13 = cxp12 * cxp1; - - // index = (gamma+4)*9 + (alpha+4) - double complex temp = 0.0625 * cxp12 * cxp12; - wigner[0] = temp; // -4, -4 // 0 - wigner[80] = temp; // 4, 4 // 80 - - temp = 0.0625 * cxm12 * cxm12; - wigner[72] = temp; // -4, 4 // 72 - wigner[8] = temp; // 4, -4 // 8 - - temp = -0.1767766953 * cxp13 * sx; - wigner[1] = temp; // -3, -4 // 1 - wigner[9] = -temp; // -4, -3 // 9 - wigner[79] = -temp; // 3, 4 // 79 - wigner[71] = temp; // 4, 3 // 9 - - temp = -0.1767766953 * cxm13 * sx; - wigner[7] = temp; // 3, -4 // 7 - wigner[63] = -temp; // -4, 3 // 63 - wigner[73] = -temp; // -3, 4 // 73 - wigner[17] = temp; // 4, -3 // 17 - - temp = -0.4677071733 * cxp1 * sx3; - wigner[53] = temp; // 4, 1 // 53 - wigner[27] = -temp; // -4, -1 // 27 - wigner[77] = -temp; // 1, 4 // 77 - wigner[3] = temp; // -1, -4 // 3 - - temp = -0.4677071733 * cxm1 * sx3; - wigner[35] = temp; // 4, -1 // 35 - wigner[45] = -temp; // -4, 1 // 45 - wigner[75] = -temp; // -1, 4 // 75 - wigner[5] = temp; // 1, -4 // 5 - - temp = 0.5229125166 * sx3 * sx; - wigner[44] = temp; // 4, 0 // 44 - wigner[36] = temp; // -4, 0 // 36 - wigner[76] = temp; // 0, 4 // 76 - wigner[4] = temp; // 0, -4 // 4 - - temp = -1.4790199458 * sx3 * cx; - wigner[43] = temp; // 3, 0 // 43 - wigner[37] = -temp; // -3, 0 // 37 - wigner[67] = -temp; // 0, 3 // 67 - wigner[13] = temp; // 0, -3 // 13 - - temp = 0.3307189139 * sx2 * cxp12; - wigner[78] = temp; // 2, 4 // 78 - wigner[2] = temp; // -2, -4 // 2 - wigner[62] = temp; // 4, 2 // 62 - wigner[18] = temp; // -4, -2 // 18 - - temp = 0.3307189139 * sx2 * cxm12; - wigner[6] = temp; // 2, -4 // 6 - wigner[74] = temp; // -2, 4 // 74 - wigner[54] = temp; // -4, 2 // 54 - wigner[26] = temp; // 4, -2 // 26 - - temp = 0.4677071733 * cxp12 * sx * (2. * cx - 1.); - wigner[69] = temp; // 2, 3 // 69 - wigner[11] = -temp; // -2, -3 // 11 - wigner[61] = -temp; // 3, 2 // 61 - wigner[19] = temp; // -3, -2 // 19 - - temp = 0.4677071733 * cxm12 * sx * (-2. * cx - 1.); - wigner[15] = temp; // 2, -3 // 15 - wigner[65] = -temp; // -2, 3 // 65 - wigner[55] = -temp; // -3, 2 // 55 - wigner[25] = temp; // 3, -2 // 25 + double sx2, sx3, cxp1, cxm1, cxp12, cxm12, cxm13, cxp13; + for (i = 0; i < n; i++) { + cx = *cos_angle++; + cx2 = cx * cx; + sx = sqrt(1. - cx2); + + sx2 = sx * sx; + sx3 = sx2 * sx; + + cxp1 = (1. + cx); + cxm1 = (1. - cx); + cxp12 = cxp1 * cxp1; + cxm12 = cxm1 * cxm1; + cxm13 = cxm12 * cxm1; + cxp13 = cxp12 * cxp1; + + temp = 0.0625 * cxp12 * cxp12; + wigner[0] = temp; // -4, -4 // 0 + wigner[80] = temp; // 4, 4 // 80 + + temp = 0.0625 * cxm12 * cxm12; + wigner[72] = temp; // -4, 4 // 72 + wigner[8] = temp; // 4, -4 // 8 + + temp = -0.1767766953 * cxp13 * sx; + wigner[1] = temp; // -3, -4 // 1 + wigner[9] = -temp; // -4, -3 // 9 + wigner[79] = -temp; // 3, 4 // 79 + wigner[71] = temp; // 4, 3 // 9 + + temp = -0.1767766953 * cxm13 * sx; + wigner[7] = temp; // 3, -4 // 7 + wigner[63] = -temp; // -4, 3 // 63 + wigner[73] = -temp; // -3, 4 // 73 + wigner[17] = temp; // 4, -3 // 17 + + temp = -0.4677071733 * cxp1 * sx3; + wigner[53] = temp; // 4, 1 // 53 + wigner[27] = -temp; // -4, -1 // 27 + wigner[77] = -temp; // 1, 4 // 77 + wigner[3] = temp; // -1, -4 // 3 + + temp = -0.4677071733 * cxm1 * sx3; + wigner[35] = temp; // 4, -1 // 35 + wigner[45] = -temp; // -4, 1 // 45 + wigner[75] = -temp; // -1, 4 // 75 + wigner[5] = temp; // 1, -4 // 5 + + temp = 0.5229125166 * sx3 * sx; + wigner[44] = temp; // 4, 0 // 44 + wigner[36] = temp; // -4, 0 // 36 + wigner[76] = temp; // 0, 4 // 76 + wigner[4] = temp; // 0, -4 // 4 + + temp = -1.4790199458 * sx3 * cx; + wigner[43] = temp; // 3, 0 // 43 + wigner[37] = -temp; // -3, 0 // 37 + wigner[67] = -temp; // 0, 3 // 67 + wigner[13] = temp; // 0, -3 // 13 + + temp = 0.3307189139 * sx2 * cxp12; + wigner[78] = temp; // 2, 4 // 78 + wigner[2] = temp; // -2, -4 // 2 + wigner[62] = temp; // 4, 2 // 62 + wigner[18] = temp; // -4, -2 // 18 + + temp = 0.3307189139 * sx2 * cxm12; + wigner[6] = temp; // 2, -4 // 6 + wigner[74] = temp; // -2, 4 // 74 + wigner[54] = temp; // -4, 2 // 54 + wigner[26] = temp; // 4, -2 // 26 + + temp = 0.4677071733 * cxp12 * sx * (2. * cx - 1.); + wigner[69] = temp; // 2, 3 // 69 + wigner[11] = -temp; // -2, -3 // 11 + wigner[61] = -temp; // 3, 2 // 61 + wigner[19] = temp; // -3, -2 // 19 + + temp = 0.4677071733 * cxm12 * sx * (-2. * cx - 1.); + wigner[15] = temp; // 2, -3 // 15 + wigner[65] = -temp; // -2, 3 // 65 + wigner[55] = -temp; // -3, 2 // 55 + wigner[25] = temp; // 3, -2 // 25 + + temp = 0.25 * cxp12 * (1. - 7. * cxm1 + 7. * cxm12); + wigner[60] = temp; // 2, 2 // 60 + wigner[20] = temp; // -2, -2 // 20 + + temp = 0.25 * cxm12 * (1. - 7. * cxp1 + 7. * cxp12); + wigner[56] = temp; // -2, 2 // 56 + wigner[24] = temp; // 2, -2 // 24 + + temp = 0.3952847075 * sx2 * (7. * cx2 - 1); + wigner[42] = temp; // 2, 0 // 42 + wigner[38] = temp; // -2, 0 // 38 + wigner[58] = temp; // 0, 2 // 58 + wigner[22] = temp; // 0, -2 // 22 + + temp = 0.125 * cxp13 * (-3. + 4. * cx); + wigner[10] = temp; // -3, -3 // 10 + wigner[70] = temp; // 3, 3 // 70 + + temp = 0.125 * cxm13 * (3. + 4. * cx); + wigner[64] = temp; // -3, 3 // 64 + wigner[16] = temp; // 3, -3 // 16 + + temp = 0.3307189139 * cxm1 * cxp12 * (-1. + 4. * cx); + wigner[12] = temp; // -1, -3 // 12 + wigner[28] = temp; // -3, -1 // 28 + wigner[68] = temp; // 1, 3 // 68 + wigner[52] = temp; // 3, 1 // 52 + + temp = 0.3307189139 * cxm12 * cxp1 * (1. + 4. * cx); + wigner[14] = temp; // 1, -3 // 14 + wigner[46] = temp; // -3, 1 // 46 + wigner[66] = temp; // -1, 3 // 66 + wigner[34] = temp; // 3, -1 // 34 + + temp = -0.5590169944 * (4. - 18. * cxm1 + 21. * cxm12 - 7. * cxm13) * sx; + wigner[41] = temp; // 1, 0 // 41 + wigner[39] = -temp; // -1, 0 // 39 + wigner[49] = -temp; // 0, 1 // 49 + wigner[31] = temp; // 0, -1 // 31 + + temp = -0.3535533906 * (3. - 10.5 * cxm1 + 7. * cxm12) * sx * cxp1; + wigner[51] = temp; // 2, 1 // 51 + wigner[29] = -temp; // -2, -1 // 29 + wigner[59] = -temp; // 1, 2 // 59 + wigner[21] = temp; // -1, -2 // 21 + + temp = -0.3535533906 * (10. - 17.5 * cxm1 + 7. * cxm12) * sx * cxm1; + wigner[23] = temp; // 1, -2 // 23 + wigner[57] = -temp; // -1, 2 // 57 + wigner[47] = -temp; // -2, 1 // 47 + wigner[33] = temp; // 2, -1 // 33 + + temp = 0.5 * (1. - 9. * cxm1 + 15.75 * cxm12 - 7. * cxm13) * cxp1; + wigner[30] = temp; // -1, -1 // 30 + wigner[50] = temp; // 1, 1 // 50 + + temp = 0.5 * (10. - 30. * cxm1 + 26.25 * cxm12 - 7. * cxm13) * cxm1; + wigner[32] = temp; // 1, -1 // 32 + wigner[48] = temp; // -1, 1 // 48 + + temp = 0.125 * (3. - 30. * cx2 + 35 * cx2 * cx2); + wigner[40] = temp; // 0, 0 // 40 + + wigner += 81; + } + } +} - temp = 0.25 * cxp12 * (1. - 7. * cxm1 + 7. * cxm12); - wigner[60] = temp; // 2, 2 // 60 - wigner[20] = temp; // -2, -2 // 20 +void __wigner_rotation(int l, int n, double *wigner, double *cos_alpha, + complex128 *R_in, complex128 *R_out) { + int orientation; + int n1 = 2 * l + 1, m, i = 0, mp; + complex128 temp_initial_vector[n1], ph2, pha; + complex128 *final_vector; + int n2 = n1 * n1; + + // #pragma omp parallel for + // // private(orientation, pha, ph2, m, temp_initial_vector, + // // final_vector, i), + // // shared(n, cos_alpha, n1, R_in, l, R_out, wigner, n2) + for (orientation = 0; orientation < n; orientation++) { + + // calculate the alpha phase, exp(-I m alpha). + pha = cos_alpha[orientation] - + I * sqrt(1.0 - cos_alpha[orientation] * cos_alpha[orientation]); + + // note to self: it is important to copy the pha to temporary variable, ph2. + ph2 = pha; + + // copy the initial vector + for (m = 0; m < n1; m++) { + temp_initial_vector[m] = R_in[m]; + } - temp = 0.25 * cxm12 * (1. - 7. * cxp1 + 7. * cxp12); - wigner[56] = temp; // -2, 2 // 56 - wigner[24] = temp; // 2, -2 // 24 + // scale the temp initial vector with exp[-I m alpha] + for (m = 1; m <= l; m++) { + temp_initial_vector[l + m] *= ph2; + temp_initial_vector[l - m] *= conj(ph2); + ph2 *= pha; + } - temp = 0.3952847075 * sx2 * (7. * cx2 - 1); - wigner[42] = temp; // 2, 0 // 42 - wigner[38] = temp; // -2, 0 // 38 - wigner[58] = temp; // 0, 2 // 58 - wigner[22] = temp; // 0, -2 // 22 + final_vector = &R_out[orientation * n1]; + i = orientation * n2; + // Apply wigner rotation to the temp inital vector + for (m = 0; m < n1; m++) { + final_vector[m] = 0.; + for (mp = 0; mp < n1; mp++) { + final_vector[m] += wigner[i++] * temp_initial_vector[mp]; + } + } - temp = 0.125 * cxp13 * (-3. + 4. * cx); - wigner[10] = temp; // -3, -3 // 10 - wigner[70] = temp; // 3, 3 // 70 + // The following blas library is slower than the c for-loops + // probably because over head in calling blas library is more than the + // computation of a 5 x 5 matrix-vector multiplication. + // c for-loops --- 1.19 ms ± 10.4 µs + // blas --- 4.19 ms ± 14.8 µs + + // final_vector = &R_out[orientation * n1]; + // cblas_dgemv(CblasRowMajor, CblasNoTrans, n1, n1, 1.0, &wigner[i], n1, + // (double *)&temp_initial_vector[0], 2, 0.0, + // (double *)&final_vector[0], 2); + // cblas_dgemv(CblasRowMajor, CblasNoTrans, n1, n1, 1.0, &wigner[i], n1, + // (double *)&temp_initial_vector[0] + 1, 2, 0.0, + // (double *)&final_vector[0] + 1, 2); + // i += n2; + } +} - temp = 0.125 * cxm13 * (3. + 4. * cx); - wigner[64] = temp; // -3, 3 // 64 - wigner[16] = temp; // 3, -3 // 16 +#if !(__STDC_VERSION__ >= 199901L) +static inline void self_cmult(complex128 a, complex128 b) { + a.real = a.real * b.real - a.imag * b.imag; + a.imag = a.real * b.imag + b.real * a.imag; +} - temp = 0.3307189139 * cxm1 * cxp12 * (-1. + 4. * cx); - wigner[12] = temp; // -1, -3 // 12 - wigner[28] = temp; // -3, -1 // 28 - wigner[68] = temp; // 1, 3 // 68 - wigner[52] = temp; // 3, 1 // 52 +void __wigner_rotation_2(const int l, const int n, const double *wigner, + const complex128 *exp_Im_alpha, const complex128 *R_in, + complex128 *R_out) { + int orientation; + int n1 = 2 * l + 1, m, i = 0, mp; + complex128 *temp_initial_vector = malloc_complex128(n1); + complex128 *final_vector, temp; - temp = 0.3307189139 * cxm12 * cxp1 * (1. + 4. * cx); - wigner[14] = temp; // 1, -3 // 14 - wigner[46] = temp; // -3, 1 // 46 - wigner[66] = temp; // -1, 3 // 66 - wigner[34] = temp; // 3, -1 // 34 + for (orientation = 0; orientation < n; orientation++) { - temp = -0.5590169944 * (4. - 18. * cxm1 + 21. * cxm12 - 7. * cxm13) * sx; - wigner[41] = temp; // 1, 0 // 41 - wigner[39] = -temp; // -1, 0 // 39 - wigner[49] = -temp; // 0, 1 // 49 - wigner[31] = temp; // 0, -1 // 31 + // copy the initial vector + for (m = 0; m < n1; m++) { + temp_initial_vector[m] = R_in[m]; + } - temp = -0.3535533906 * (3. - 10.5 * cxm1 + 7. * cxm12) * sx * cxp1; - wigner[51] = temp; // 2, 1 // 51 - wigner[29] = -temp; // -2, -1 // 29 - wigner[59] = -temp; // 1, 2 // 59 - wigner[21] = temp; // -1, -2 // 21 + // scale the temp initial vector with exp[-I m alpha] + for (m = 1; m <= l; m++) { + temp = exp_Im_alpha[(4 - m) * n + orientation]; - temp = -0.3535533906 * (10. - 17.5 * cxm1 + 7. * cxm12) * sx * cxm1; - wigner[23] = temp; // 1, -2 // 23 - wigner[57] = -temp; // -1, 2 // 57 - wigner[47] = -temp; // -2, 1 // 47 - wigner[33] = temp; // 2, -1 // 33 + self_cmult(temp_initial_vector[l - m], temp); + self_cmult(temp_initial_vector[l + m], conj(temp)); + } - temp = 0.5 * (1. - 9. * cxm1 + 15.75 * cxm12 - 7. * cxm13) * cxp1; - wigner[30] = temp; // -1, -1 // 30 - wigner[50] = temp; // 1, 1 // 50 + final_vector = &R_out[orientation * n1]; + // Apply wigner rotation to the temp inital vector + for (m = 0; m < n1; m++) { + final_vector[m] = 0.; + for (mp = 0; mp < n1; mp++) { + final_vector[m].real += wigner[i] * temp_initial_vector[mp].real; + final_vector[m].imag += wigner[i++] * temp_initial_vector[mp].imag; + } + } + } +} +#else +void __wigner_rotation_2(const int l, const int n, const double *wigner, + const complex128 *exp_Im_alpha, const complex128 *R_in, + complex128 *R_out) { + int orientation; + int n1 = 2 * l + 1, m, mp; + complex128 *temp_initial_vector = malloc_complex128(n1); + complex128 temp; + + // #pragma omp parallel for + // private(orientation, pha, ph2, m, temp_initial_vector, + // final_vector, i), + // shared(n, cos_alpha, n1, R_in, l, R_out, wigner, n2) + for (orientation = 0; orientation < n; orientation++) { + + // copy the initial vector + cblas_zcopy(n1, R_in, 1, temp_initial_vector, 1); + + // scale the temp initial vector with exp[-I m alpha] + for (m = 1; m <= l; m++) { + temp = exp_Im_alpha[(4 - m) * n + orientation]; + temp_initial_vector[l - m] *= temp; + temp_initial_vector[l + m] *= conj(temp); + } - temp = 0.5 * (10. - 30. * cxm1 + 26.25 * cxm12 - 7. * cxm13) * cxm1; - wigner[32] = temp; // 1, -1 // 32 - wigner[48] = temp; // -1, 1 // 48 + // Apply wigner rotation to the temp inital vector + for (m = 0; m < n1; m++) { + *R_out = 0.0; + for (mp = 0; mp < n1; mp++) { + *R_out += *wigner++ * temp_initial_vector[mp]; + } + R_out++; + } + } +} +#endif - temp = 0.125 * (3. - 30. * cx2 + 35 * cx2 * cx2); - wigner[40] = temp; // 0, 0 // 40 +void __wigner_dm0_vector(const int l, const double beta, double *R_out) { + double cx = cos(beta), sx = sin(beta); + if (l == 2) { + R_out[0] = 0.6123724355 * sx * sx; + R_out[1] = 1.224744871 * sx * cx; + R_out[2] = 1.5 * cx * cx - 0.5; + R_out[3] = -R_out[1]; + R_out[4] = R_out[0]; + } + if (l == 4) { + double sx2 = sx * sx, sx3 = sx2 * sx, cx2 = 1.0 - sx2; + double cxm1 = 1.0 - cx, cxm12 = cxm1 * cxm1; + double temp = 4. - 18. * cxm1 + 21. * cxm12 - 7. * cxm12 * cxm1; + R_out[0] = 0.5229125166 * sx3 * sx; + R_out[1] = 1.4790199458 * sx3 * cx; + R_out[2] = 0.3952847075 * sx2 * (7. * cx2 - 1); + R_out[3] = 0.5590169944 * temp * sx; + R_out[4] = 0.125 * (3. - 30. * cx2 + 35 * cx2 * cx2); + R_out[5] = -R_out[3]; + R_out[6] = R_out[2]; + R_out[7] = -R_out[1]; + R_out[8] = R_out[0]; } } -void full_DLM(double complex *wigner, int l, double *omega) { +void full_DLM(complex128 *wigner, int l, double *omega) { if (l == 2) { - double complex pha[5], phg[5]; + complex128 pha[5], phg[5]; int m; for (m = -2; m <= 2; m++) { pha[m + 2] = cexp(-I * m * omega[0]); @@ -297,7 +474,7 @@ void full_DLM(double complex *wigner, int l, double *omega) { wigner[12] = 1.5 * cx * cx - .5 * pha[2] * phg[2]; // 0, 0 // 12 } if (l == 4) { - double complex pha[9], phg[9]; + complex128 pha[9], phg[9]; int m; for (m = -4; m <= 4; m++) { pha[m + 4] = cexp(-I * m * omega[0]); @@ -442,23 +619,23 @@ void full_DLM(double complex *wigner, int l, double *omega) { wigner[32] = temp * pha[5]; // 1, -1 // 32 wigner[48] = temp * pha[3]; // -1, 1 // 48 - temp = 0.125 * (3. - 30. * cx2 + 35 * cx2 * cx2); + temp = 0.125 * (3. - 30. * cx2 + 35. * cx2 * cx2); wigner[40] = temp * pha[4]; // 0, 0 // 40 } } // @parameters // int l: The angular momentum quantum number -// double complex *wigner: a pointer to (2l+1)*(2l+1) size matrix of full Wigner -// matrix -void full_DLM_trig(double complex *wigner, int l, double cosAlpha, - double sinAlpha, double cosBeta, double sinBeta) { +// complex128 *wigner: a pointer to (2l+1)*(2l+1) size matrix of full +// Wigner matrix +void full_DLM_trig(complex128 *wigner, int l, double cosAlpha, double sinAlpha, + double cosBeta, double sinBeta) { // double cosGamma, // double sinGamma){ if (l == 2) { // pha[m+2] holds the value of exp(-I m alpha) // phg[m+2] holds the value of exp(-I m beta) - double complex pha[5]; //, phg[5]; + complex128 pha[5]; //, phg[5]; pha[2] = 1.0; // phg[2] = 1.0; @@ -543,7 +720,7 @@ void full_DLM_trig(double complex *wigner, int l, double cosAlpha, } if (l == 4) { - double complex pha[9]; //, phg[5]; + complex128 pha[9]; //, phg[5]; pha[4] = 1.0; pha[5] = cosAlpha - I * sinAlpha; pha[3] = cosAlpha + I * sinAlpha; @@ -698,7 +875,7 @@ void full_DLM_trig(double complex *wigner, int l, double cosAlpha, } double wigner_d(int l, int m1, int m2, double beta) { - if (l == 5) { + if (l == 2) { if (m1 == 2) { if (m2 == 2) { double cx = cos(beta); diff --git a/src/c_lib/lib/c_array.c b/src/c_lib/lib/c_array.c deleted file mode 100644 index 1516638fc..000000000 --- a/src/c_lib/lib/c_array.c +++ /dev/null @@ -1,91 +0,0 @@ -// -// c_array.c -// -// Created by Deepansh J. Srivastava, Apr 11, 2019 -// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. -// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com -// - -#include "c_array.h" -#include - -// float 1d array -float *createFloat1DArray(int m) { - float *values = calloc(m, sizeof(float)); - return values; -} - -void destroyFloat1DArray(float *arr) { free(arr); } - -// complex float 1d array -float complex *createFloatComplex1DArray(int m) { - float complex *values = calloc(m, sizeof(float complex)); - return values; -} - -void destroyFloatComplex1DArray(float complex *arr) { free(arr); } - -// double 1d array -double *createDouble1DArray(int m) { - double *values = calloc(m, sizeof(double)); - return values; -} - -void destroyDouble1DArray(double *arr) { free(arr); } - -// complex double 1d array -double complex *createDoubleComplex1DArray(int m) { - double complex *values = calloc(m, sizeof(double complex)); - return values; -} - -void destroyDoubleComplex1DArray(double complex *arr) { free(arr); } - -// float 2d matrix -float **createFloat2DMatrix(int n, int m) { - float *values = calloc(m * n, sizeof(float)); - float **rows = malloc(n * sizeof(float *)); - for (int i = 0; i < n; ++i) { - rows[i] = values + i * m; - } - return rows; -} - -void destroyFloat2DMatrix(float **arr) { - free(*arr); - free(arr); -} - -// double array -double **createDouble2DMatrix(int n, int m) { - double *values = calloc(m * n, sizeof(double)); - double **rows = malloc(n * sizeof(double *)); - for (int i = 0; i < n; ++i) { - rows[i] = values + i * m; - } - return rows; -} - -void destroyDouble2DMatrix(double **arr) { - free(*arr); - free(arr); -} - -// double 3d array -double ***createDouble3DArray(int n, int m, int o) { - double ***p = malloc(n * sizeof(double ***)); - for (int i = 0; i < n; i++) { - p[i] = (double **)malloc(m * sizeof(double *)); - - for (int j = 0; j < m; j++) { - p[i][j] = (double *)malloc(o * sizeof(double)); - } - } - return p; -} - -void destroyDouble3DArray(double ***arr) { - free(**arr); - free(*arr); - free(arr); -} diff --git a/src/c_lib/lib/interpolation.c b/src/c_lib/lib/interpolation.c new file mode 100644 index 000000000..f83562c8b --- /dev/null +++ b/src/c_lib/lib/interpolation.c @@ -0,0 +1,280 @@ + +// +// interpolation.c +// +// Created by Deepansh J. Srivastava, Apr 11, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include "interpolation.h" + +// triangle_interpolation is an optimized version of tent. Still plenty of +// room for optimization. + +int triangle_interpolation(double *freq1, double *freq2, double *freq3, + double *amp, double *spec, int *points) { + + double df1, df2, top = 0.0, t, diff, f10 = 0.0, f21 = 0.0, temp; + int p, pmid, pmax, i, j; + int clip_right1 = 0, clip_left1 = 0, clip_right2 = 0, clip_left2 = 0; + + p = (int)(freq1[0]); + if ((int)freq1[0] == (int)freq2[0] && (int)freq1[0] == (int)freq3[0]) { + if (p >= points[0] || p < 0) { + return 0; + } + spec[p] += amp[0]; + return 0; + } + + double f[3] = {freq1[0], freq2[0], freq3[0]}; + + for (j = 1; j <= 2; j++) { + t = f[j]; + i = j - 1; + while (i >= 0 && f[i] > t) { + f[i + 1] = f[i]; + i--; + } + f[i + 1] = t; + } + + p = (int)f[0]; + if (p > points[0]) { + return 0; + } + + pmax = (int)f[2]; + if (pmax < 0) { + return 0; + } + + pmid = (int)f[1]; + if (pmid >= points[0]) { + pmid = points[0]; + clip_right1 = 1; + } + + if (pmax >= points[0]) { + pmax = points[0]; + clip_right2 = 1; + } + + if (p < 0) { + p = 0; + clip_left1 = 1; + } + + if (pmid < 0) { + pmid = 0; + clip_left2 = 1; + } + + top += (amp[0] * 2.0 / (f[2] - f[0])); + f10 += f[1] - f[0]; + f21 += f[2] - f[1]; + + if (p != pmid) { + df1 = top / f10; + diff = (double)p + 1. - f[0]; + if (clip_left1 == 0) { + spec[p++] += 0.5 * diff * diff * df1; + } else { + spec[p++] += (diff - 0.5) * df1; + } + diff -= 0.5; + diff *= df1; + while (p != pmid) { + diff += df1; + spec[p++] += diff; + } + if (clip_right1 == 0) { + spec[p] += (f[1] - (double)p) * (f10 + ((double)p - f[0])) * 0.5 * df1; + } + } else { + if (clip_right1 == 0 && clip_left1 == 0) { + spec[p] += f10 * top * 0.5; + } + } + + if (p != pmax) { + df2 = top / f21; + diff = f[2] - (double)p - 1.; + + if (clip_left2 == 0) { + spec[p++] += (f21 - diff) * (diff + f21) * 0.5 * df2; + } else { + spec[p++] += (diff + 0.5) * df2; + } + diff += 0.5; + diff *= df2; + while (p != pmax) { + diff -= df2; + spec[p++] += diff; + } + if (clip_right2 == 0) { + temp = (f[2] - (double)p); + spec[p] += temp * temp * 0.5 * df2; + } + } else { + if (clip_right2 == 0) { + spec[p] += f21 * top * 0.5; + } + } + return 0; +} + +void rasterization(double *grid, double *v0, double *v1, double *v2, int rows, + int columns) { + + double A12, B12, C12, A20, B20, C20, A01, B01, C01; + double minX, minY, maxX, maxY, w0, w1, w2; + double w0_row, w1_row, w2_row; + int i, j, i_, minX_, minY_, maxX_, maxY_; + + minX = fmin(fmin(v0[0], v1[0]), v2[0]); + minY = fmin(fmin(v0[1], v1[1]), v2[1]); + maxX = fmax(fmax(v0[0], v1[0]), v2[0]); + maxY = fmax(fmax(v0[1], v1[1]), v2[1]); + + // clip against screen bounds + minX_ = (int)fmax(minX, 0.); + minY_ = (int)fmax(minY, 0.); + maxX_ = (int)fmin(maxX, (double)rows - 1.); + maxY_ = (int)fmin(maxY, (double)columns - 1.); + + A12 = (v2[0] - v1[0]); + B12 = (v2[1] - v1[1]); + C12 = -A12 * v1[1] + B12 * v1[0]; + A20 = (v0[0] - v2[0]); + B20 = (v0[1] - v2[1]); + C20 = -A20 * v2[1] + B20 * v2[0]; + A01 = (v1[0] - v0[0]); + B01 = (v1[1] - v0[1]); + C01 = -A01 * v0[1] + B01 * v0[0]; + + w0_row = A12 * minY - B12 * minX + C12; + w1_row = A20 * minY - B20 * minX + C20; + w2_row = A01 * minY - B01 * minX + C01; + + // Rasterize + for (i = minY_; i <= maxY_; i++) { + + // Determine barycentric coordinates + w0 = w0_row; + w1 = w1_row; + w2 = w2_row; + + i_ = rows * i; + for (j = minX_; j <= maxX_; j++) { + // If p is on or inside all edges, render pixel. + if ((int)w0 >= 0 && (int)w1 >= 0 && (int)w2 >= 0) { + grid[i_ + j] += 1.; //(w0+w1+w2); + } + if ((int)w0 <= 0 && (int)w1 <= 0 && (int)w2 <= 0) { + grid[i_ + j] += -1.; //(w0+w1+w2); + } + // i_++; + + w0 -= B12; + w1 -= B20; + w2 -= B01; + } + w0_row += A12; + w1_row += A20; + w2_row += A01; + } +} + +// static inline void get_KL( +// double *v0, +// double *v1, +// double *x, +// double *y) +// { +// double K[2] = +// } +// def get_KL(self, x0, y0, x1, y1, eps = 1e-5): +// self.x0, self.y0, self.x1, self.y1 = x0, y0, x1, y1 +// Kx, Ky, Lx, Ly = 0, 0, 0, 0 +// for sec in self.clip(0, 1, 1, 0): +// v1 = Point(sec[0], sec[1]) +// v0 = Point(sec[2], sec[3]) +// if abs(v0.x - 1) < eps and abs(v1.x - 1) < eps \ +// or abs(v0.y - 1) < eps and abs(v1.y - 1) < eps: +// continue + +// Kx += 1./4 * (v0.y - v1.y) +// Ky += 1./4 * (v1.x - v0.x) +// Lx += 1./8 * (v0.y-v1.y) * (v0.x+v1.x) +// Ly += 1./8 * (v1.x-v0.x) * (v0.y+v1.y) +// return Point(Kx, Ky), Point(Lx, Ly) + +// static inline void clip( +// double left, +// double right, +// double bottom, +// double top, +// double *p0, +// double *p1, +// double *clipped_lines +// ) +// { +// int edge, i; +// double t0=0, t1=1, r; +// double delta_x = p1[0] - p0[0], delta_y=p1[1]-p0[1], p, q; + +// for(edge=0; edge<4; edge++){ +// if(edge ==0){ +// p = -delta_x; +// q = -(left - p0[0]); +// } +// else if(edge ==1){ +// p = delta_x; +// q = (right - p0[0]); +// } +// else if(edge ==2){ +// p = delta_y; +// q = (bottom - p0[1]); +// } +// else if(edge ==3){ +// p = -delta_y; +// q = -(top - p0[1]); +// } +// if(p == 0 && q < 0) return 0; +// if (p < 0){ +// r = q / (double)p; +// if(r > t1) return 0; +// if(r > t0) t0 = r; // line is clipped! +// } +// else if(p > 0){ +// r = q / (double)p; +// if(r < t0) return 0; +// if(r < t1) t1 = r; // line is clipped! +// } +// } +// } + +// def clip(self, left, right, bottom, top): +// t0, t1 = 0, 1 +// xdelta = self.x1 - self.x0 +// ydelta = self.y1 - self.y0 +// for edge in range(4): #traverse through left, right, bottom, top +// edges. +// if edge == 0: p, q = -xdelta, -(left-self.x0) +// elif edge == 1: p, q = xdelta, (right-self.x0) +// elif edge == 2: p, q = ydelta, (bottom-self.y0) +// elif edge == 3: p, q = -ydelta, -(top-self.y0) +// if p == 0 and q < 0: return [] +// if p < 0: +// r = q / float(p) +// if r > t1: return [] +// elif r > t0: t0 = r # line is clipped! +// elif p > 0: +// r = q / float(p) +// if r < t0: return [] +// elif r < t1: t1 = r # line is clipped! +// clipped_line = (self.x0 + t0*xdelta, self.y0 + t0*ydelta, +// self.x0 + t1*xdelta, self.y0 + t1*ydelta) +// return [clipped_line] diff --git a/src/c_lib/lib/mrsimulator.c b/src/c_lib/lib/mrsimulator.c new file mode 100644 index 000000000..f70e410ae --- /dev/null +++ b/src/c_lib/lib/mrsimulator.c @@ -0,0 +1,566 @@ +// +// mrsimulator.c +// +// Created by Deepansh J. Srivastava, Jun 9, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include "mrsimulator.h" + +/* free the buffer and pre-calculated tables from the mrsimulator plan. */ +void MRS_free_plan(MRS_plan *the_plan) { + fftw_destroy_plan(the_plan->the_fftw_plan); + fftw_free(the_plan->vector); + free(the_plan->vr_freq); + free(the_plan->wigner_d2m0_vector); + free(the_plan->wigner_d4m0_vector); + free(the_plan->freq_offset); + free(the_plan->amplitudes); + free(the_plan->local_frequency); + free(the_plan->wigner_2j_matrices); + free(the_plan->wigner_4j_matrices); + free(the_plan->w2); + free(the_plan->w4); + free(the_plan->pre_phase_2); + free(the_plan->pre_phase_4); +} + +/* Create a new mrsimulator plan. + * A plan for mrsimulator contains buffers and tabulated values to produce + * faster simulation. The plan includes, + * 1) calculating an array of orientations over the surface of a sphere. Each + * orientation is described by an azimuthal angle, (α), a polar angle, (β), + * and a weighting factor describing the spherical average. + * 2) calculating wigner-2j(β) and wigner-4j(β) matrices at every orientation + * angle β, + * 3) pre-calculating the exponent of the sideband order phase, + * exp(-Imα), at every orientation angle α, + * 4) creating the fftw plan, + * 4) allocating buffer for storing the evaluated frequencies and their + * respective amplitudes. + */ + +MRS_plan *MRS_create_plan(unsigned int geodesic_polyhedron_frequency, + int number_of_sidebands, + double sample_rotation_frequency_in_Hz, + double rotor_angle_in_rad, double increment, + bool allow_fourth_rank) { + + unsigned int j, i, size_2, size_4; + + MRS_plan *plan = malloc(sizeof(MRS_plan)); + plan->number_of_sidebands = number_of_sidebands; + plan->sample_rotation_frequency_in_Hz = sample_rotation_frequency_in_Hz; + + plan->allow_fourth_rank = allow_fourth_rank; + plan->one = 1.0; + plan->zero = 0.0; + // plan->one.real = 1.0; + // plan->one.imag = 0.0; + // plan->zero.real = 0.0; + // plan->zero.imag = 0.0; + + /** + * Update the mrsimulator plan with the given spherical averaging scheme. We + * create the coordinates on the surface of the unit sphere by projecting the + * points on the face of the octahedron to a unit sphere. + * Usually, before updating the averaging scheme, the memory allocated by the + * previous scheme must be freed. Since, we are creating the scheme for this + * plan for the very first time, there is no need to call + * MRS_free_averaging_plan() method. + */ + MRS_plan_update_averaging_scheme(plan, geodesic_polyhedron_frequency, + allow_fourth_rank); + + /** + * Update the mrsimulator plan with the given rotor angle in radian. + * This method updates the wigner d^l_{m,0}(rotor_angle_in_rad) vectors used + * in tranforming the l-rank tensors from the rotor frame to lab frame. Here l + * is either 2 or 4. + */ + MRS_plan_update_rotor_angle_in_rad(plan, rotor_angle_in_rad, + allow_fourth_rank); + + /* sideband order frequency in fft output order. */ + double increment_inverse = 1.0 / increment; + plan->vr_freq = __get_frequency_in_FFT_order(number_of_sidebands, + sample_rotation_frequency_in_Hz); + cblas_dscal(number_of_sidebands, increment_inverse, plan->vr_freq, 1); + + /** + * calculating the sideband phase multiplier. + * pre_phase(m,t) = I 2π [(exp(I m wr t) - 1)/(I m wr)]. + * @see __get_components() + */ + size_4 = 9 * number_of_sidebands; + complex128 *pre_phase = malloc_complex128(size_4); + __get_components(number_of_sidebands, sample_rotation_frequency_in_Hz, + pre_phase); + + size_2 = 5 * number_of_sidebands; + plan->pre_phase_2 = malloc_complex128(size_2); + + /* multiplying the wigner 2j d^2_{m,0}(rotor_angle_in_rad) vector to the + * sideband phase multiplier. This multiplication absorbs the rotation of the + * second rank tensors from the rotor frame to the lab frame, thereby, + * reducing the number of calculations involved per site. This step assumes + * that the Euler angles invloved in the rotation of the 2nd rank tensors to + * the lab frame is (0, rotor_angle_in_rad, 0). + */ + cblas_zcopy(size_2, &pre_phase[2 * number_of_sidebands], 1, plan->pre_phase_2, + 1); + j = 0; + for (i = 0; i < 5; i++) { + cblas_zdscal(number_of_sidebands, plan->wigner_d2m0_vector[i], + &plan->pre_phase_2[j], 1); + j += number_of_sidebands; + } + + plan->pre_phase_4 = NULL; + + /* Setup for processing the fourth rank tensors. */ + if (allow_fourth_rank) { + + /* multiplying the wigner 4j d^4_{m,0} vector to the sideband phase + * multiplier. This multiplication absorbs the rotation of the fourth rank + * tensors from the rotor frame to the lab frame, thereby, reducing the + * number of calculations involved per site. This step assumes that the + * Euler angles involved in the rotation of the 4th rank tensors to the lab + * frame is (0, rotor_angle_in_rad, 0). + */ + plan->pre_phase_4 = pre_phase; + j = 0; + for (i = 0; i < 9; i++) { + cblas_zdscal(number_of_sidebands, plan->wigner_d4m0_vector[i], + &plan->pre_phase_4[j], 1); + j += number_of_sidebands; + } + } else { + free(pre_phase); + } + return plan; +} + +/* Free the memory from the mrsimulator plan associated with the wigner + * d^l_{m,0}(rotor_angle_in_rad) vectors. Here, l=2 or 4. + * */ +void MRS_plan_free_rotor_angle_in_rad(MRS_plan *plan) { + free(plan->wigner_d2m0_vector); + free(plan->wigner_d4m0_vector); +} + +/* Update the MRS plan for the given rotor angle in radians. */ +void MRS_plan_update_rotor_angle_in_rad(MRS_plan *plan, + double rotor_angle_in_rad, + bool allow_fourth_rank) { + plan->rotor_angle_in_rad = rotor_angle_in_rad; + /** + * calculating wigner 2j d^2_{m,0} vector where m ∈ [-2, 2]. This vector is + * used to rotate the second tank tensors from the rotor frame to the lab + * frame. + * @see __wigner_dm0_vector() + */ + plan->wigner_d2m0_vector = malloc_double(5); + __wigner_dm0_vector(2, rotor_angle_in_rad, plan->wigner_d2m0_vector); + + plan->wigner_d4m0_vector = NULL; + if (allow_fourth_rank) { + /* calculating wigner 4j d^4_{m,0} vector where m ∈ [-4, 4]. This vector is + * used to rotate the fourth tank tensors from the rotor frame to the lab + * frame.*/ + plan->wigner_d4m0_vector = malloc_double(9); + __wigner_dm0_vector(4, rotor_angle_in_rad, plan->wigner_d4m0_vector); + } +} + +/* Free the memory from the mrsimulator plan associated with the spherical + * averaging scheme */ +void MRS_plan_free_averaging_scheme(MRS_plan *plan) { + free(plan->amplitudes); + free(plan->norm_amplitudes); + free(plan->exp_Im_alpha); + free(plan->w2); + free(plan->wigner_2j_matrices); + free(plan->w4); + free(plan->wigner_4j_matrices); + free(plan->local_frequency); + free(plan->freq_offset); + free(plan->vector); +} + +/* Update the spherical averaging scheme of the mrsimulator plan. */ +void MRS_plan_update_averaging_scheme( + MRS_plan *plan, unsigned int geodesic_polyhedron_frequency, + bool allow_fourth_rank) { + + unsigned int nt = geodesic_polyhedron_frequency, i; + unsigned int n_orientations = (nt + 1) * (nt + 2) / 2; + plan->n_orientations = n_orientations; + plan->geodesic_polyhedron_frequency = nt; + plan->allow_fourth_rank = allow_fourth_rank; + + /* orientation setup. Calculate α, β, and weights. */ + double *cos_alpha = malloc_double(n_orientations); + double *cos_beta = malloc_double(n_orientations); + plan->amplitudes = malloc_double(n_orientations); + plan->norm_amplitudes = malloc_double(n_orientations); + __powder_averaging_setup(geodesic_polyhedron_frequency, cos_alpha, cos_beta, + plan->amplitudes, 1); + + /* normalizing amplitudes from the spherical averaging scheme by the number + * of sidebands + */ + cblas_dcopy(n_orientations, plan->amplitudes, 1, plan->norm_amplitudes, 1); + double number_of_sideband_inverse = (1.0 / (double)plan->number_of_sidebands); + cblas_dscal(n_orientations, number_of_sideband_inverse, plan->norm_amplitudes, + 1); + + double *sin_alpha = malloc_double(n_orientations); + for (i = 0; i < n_orientations; i++) { + sin_alpha[i] = sqrt(1.0 - pow(cos_alpha[i], 2)); + } + + /* calculating exp(-Imα) at every orientation angle α and for m=-4 to -1. */ + plan->exp_Im_alpha = malloc_complex128(4 * n_orientations); + int s_2 = 2 * n_orientations, s_3 = 3 * n_orientations; + int s_0 = 0 * n_orientations, s_1 = 1 * n_orientations; + + cblas_dcopy(n_orientations, cos_alpha, 1, (double *)&plan->exp_Im_alpha[s_3], + 2); + cblas_dcopy(n_orientations, sin_alpha, 1, + (double *)&plan->exp_Im_alpha[s_3] + 1, 2); + + vm_double_complex_multiply(n_orientations, &plan->exp_Im_alpha[s_3], + &plan->exp_Im_alpha[s_3], + &plan->exp_Im_alpha[s_2]); + + if (allow_fourth_rank) { + vm_double_complex_multiply(n_orientations, &plan->exp_Im_alpha[s_2], + &plan->exp_Im_alpha[s_3], + &plan->exp_Im_alpha[s_1]); + vm_double_complex_multiply(n_orientations, &plan->exp_Im_alpha[s_1], + &plan->exp_Im_alpha[s_3], + &plan->exp_Im_alpha[s_0]); + } + + /* Setup for processing the second rank tensors. */ + /* w2 is the buffer for storing calculated frequencies after processing the + * second rank tensors. */ + plan->w2 = malloc_complex128(5 * n_orientations); + + /* calculating wigner 2j matrices at every β orientation. */ + plan->wigner_2j_matrices = malloc_double(25 * n_orientations); + __wigner_d_matrix_cosine(2, n_orientations, cos_beta, + plan->wigner_2j_matrices); + + plan->w4 = NULL; + plan->wigner_4j_matrices = NULL; + + if (allow_fourth_rank) { + /* w4 is the buffer for storing calculated frequencies after processing + * the fourth rank tensors. */ + plan->w4 = malloc_complex128(9 * n_orientations); + + /* calculating wigner 4j matrices at every β orientation. */ + plan->wigner_4j_matrices = malloc_double(81 * n_orientations); + __wigner_d_matrix_cosine(4, n_orientations, cos_beta, + plan->wigner_4j_matrices); + } + free(cos_beta); + free(cos_alpha); + free(sin_alpha); + + /* buffer to hold the local frequencies and frequency offset. The buffer * + * is useful when the rotor angle is off magic angle (54.735 deg). */ + plan->local_frequency = malloc_double(n_orientations); + plan->freq_offset = malloc_double(n_orientations); + + // setup the fftw routine + plan->size = n_orientations * plan->number_of_sidebands; + plan->vector = malloc_complex128(plan->size); + // gettimeofday(&fft_setup_time, NULL); + plan->the_fftw_plan = fftw_plan_many_dft( + 1, &plan->number_of_sidebands, plan->n_orientations, plan->vector, NULL, + plan->n_orientations, 1, plan->vector, NULL, plan->n_orientations, 1, + FFTW_FORWARD, FFTW_ESTIMATE); + + // char *filename = "128_sidebands.wisdom"; + // int status = fftw_export_wisdom_to_filename(filename); + // printf("file save status %i \n", status); +} + +/* Return a copy of thr mrsimulator plan. */ +MRS_plan *MRS_copy_plan(MRS_plan *plan) {} + +/** + * @func MRS_get_amplitudes_from_plan + * + * The function evaluates the amplitudes at every orientation and at every + * sideband per orientation. This is done in two steps. + * 1) Rotate R2 and R4, given in the crystal or common frame to w2 and w4 in + * the lab frame using wigner 2j and 4j rotation matrices, respectively, + * at all orientations. + * 2) Evalute the sideband amplitudes using equation [39] of the reference + * https://doi.org/10.1006/jmre.1998.1427. + */ +void MRS_get_amplitudes_from_plan(MRS_plan *plan, complex128 *R2, + complex128 *R4) { + + /* Wigner second rank rotation from crystal/common frame to rotor frame. */ + __wigner_rotation_2(2, plan->n_orientations, plan->wigner_2j_matrices, + plan->exp_Im_alpha, R2, plan->w2); + + /* Evaluating the exponent of the sideband phase w.r.t the second rank + * tensors. The exponent is given as, + * w2(Θ) * d^2_{m, 0}(rotor_angle_in_rad) * I 2π [(exp(I m ωr t) - 1)/(I m + * ωr)] + * |-lab frame 2nd rank tensors--| + * |---------------------- pre_phase_2 ------------------------| + * where `pre_phase_2` is pre-calculated and stored in the plan. The result + * is stored in the plan as a complex double array under the variable + * `vector`, which is interpreted as a row major matrix of shape + * `number_of_sidebands` x `n_orientations` with `n_orientations` as the + * leading dimension. */ + cblas_zgemm(CblasRowMajor, CblasTrans, CblasTrans, plan->number_of_sidebands, + plan->n_orientations, 5, &plan->one, plan->pre_phase_2, + plan->number_of_sidebands, plan->w2, 5, &plan->zero, plan->vector, + plan->n_orientations); + + if (plan->allow_fourth_rank) { + /* Wigner fourth rank rotation from crystal/common frame to rotor frame. + */ + __wigner_rotation_2(4, plan->n_orientations, plan->wigner_4j_matrices, + plan->exp_Im_alpha, R4, plan->w4); + + /* Evaluating the exponent of the sideband phase w.r.t the fourth rank + * tensors. The exponent is given as, + * w4(Θ) * d^4_{m, 0}(rotor_angle_in_rad) * I 2π [(exp(I m ωr t) - 1)/(I m + * ωr)] + * |-lab frame 4th rank tensors--| + * |--------------------- pre_phase_4 -------------------------| + * where `pre_phase_4` is pre-calculated and also stored in the plan. This + * operation updates the variable `vector`. */ + cblas_zgemm(CblasRowMajor, CblasTrans, CblasTrans, + plan->number_of_sidebands, plan->n_orientations, 9, &plan->one, + plan->pre_phase_4, plan->number_of_sidebands, plan->w4, 9, + &plan->one, plan->vector, plan->n_orientations); + } + + /* Evaluating the sideband phase exp(vector) */ + vm_double_complex_exp(plan->size, plan->vector, plan->vector); + + /* Evaluate the Fourier transform of vector, fft(vector). The fft operation + * updates the value of the array, `vector` */ + fftw_execute(plan->the_fftw_plan); + + /* Taking the absolute value square of the vector array. The absolute value + * square is stores as the real part of the `vector` array. The imaginary + * part is garbage. This method avoids creating new arrays. */ + + // cblas_dsbmv(CblasRowMajor, CblasUpper, 2 * plan->size, 0, 1.0, + // (double *)plan->vector, 1, (double *)plan->vector, 1, 0.0, + // (double *)plan->vector, 1); + + vm_double_square(2 * plan->size, (double *)plan->vector, + (double *)plan->vector); + cblas_daxpy(plan->size, 1.0, (double *)plan->vector + 1, 2, + (double *)plan->vector, 2); + + /* Scaling the absolute value square with the powder scheme weights. Only + * the real part is scaled and the imaginary part is left as is. + */ + for (int orientation = 0; orientation < plan->n_orientations; orientation++) { + cblas_dscal(plan->number_of_sidebands, plan->norm_amplitudes[orientation], + (double *)&plan->vector[orientation], 2 * plan->n_orientations); + } +} + +/** + * @func MRS_get_frequencies_from_plan + * + * The complex128 w2 and w4 vectors are accessed from the plan as plan->w2 + * and plan->w4 + */ +void MRS_get_frequencies_from_plan(MRS_plan *plan, double R0) { + /* Setting isotropic frequency contribution from the zeroth rank tensor. */ + plan->isotropic_offset = R0; + + /* Calculating the local anisotropic frequency contributions from the * + * second rank tensor. */ + plan->buffer = plan->wigner_d2m0_vector[2]; + cblas_daxpby(plan->n_orientations, plan->buffer, (double *)&plan->w2[2], 10, + 0.0, plan->local_frequency, 1); + + if (plan->allow_fourth_rank) { + /* Calculating the local anisotropic frequency contributions from the * + * fourth rank tensor. */ + plan->buffer = plan->wigner_d4m0_vector[4]; + cblas_daxpy(plan->n_orientations, plan->buffer, (double *)&plan->w4[4], 18, + plan->local_frequency, 1); + } +} + +void MRS_get_normalized_frequencies_from_plan(MRS_plan *plan, + MRS_dimension *dim, double R0) { + /* Calculating the normalized isotropic frequency contribution from the * + * zeroth rank tensor. */ + plan->isotropic_offset = dim->normalize_offset + R0 * dim->inverse_increment; + + /* Calculating the normalized local anisotropic frequency contributions * + * from the second rank tensor. */ + plan->buffer = dim->inverse_increment * plan->wigner_d2m0_vector[2]; + cblas_daxpby(plan->n_orientations, plan->buffer, (double *)&plan->w2[2], 10, + 0.0, plan->local_frequency, 1); + + if (plan->allow_fourth_rank) { + /* Calculating the normalized local anisotropic frequency contributions * + * from the fourth rank tensor. */ + plan->buffer = dim->inverse_increment * plan->wigner_d4m0_vector[4]; + cblas_daxpy(plan->n_orientations, plan->buffer, (double *)&plan->w4[4], 18, + plan->local_frequency, 1); + } +} + +/** + * The function calculates the following. + * pre_phase(m, t) = I 2π [(exp(I m ωr t) - 1)/(I m ωr)] + * = (2π / m ωr) (exp(I m ωr t) - 1) + * |--scale--| + * = scale (exp(I m ωr t) - 1) + * = scale [[cos(m ωr t) -1] +Isin(m ωr t)] + * where ωr is the sample spinning frequency in Hz, m goes from -4 to 4, and + * t is a vector of length `number_of_sidebands` given as + * t = [0, 1, ... number_of_sidebands-1]/(ωr*number_of_sidebands) + * `pre_phase` is a matrix of shape, `9 x number_of_sidebands`. + * + * Also, + * pre_phase(-m, t) = (-2π / m ωr) (exp(-I m ωr t) - 1) + * = -scale [[cos(m ωr t) -1] -Isin(m ωr t)] + * = scale [-[cos(m ωr t) -1] +Isin(m ωr t)] + * That is pre_phase[-m] = -Re(pre_phase[m]) + Im(pre_phase[m]) + */ +void __get_components_2(int number_of_sidebands, + double sample_rotation_frequency_in_Hz, + complex128 *pre_phase) { + int m, i; + double spin_angular_freq, tau, scale; + + // memset(pre_phase, 0, 2 * number_of_sidebands * sizeof(double)); + + double *input = malloc_double(number_of_sidebands); + double *ones = malloc_double(number_of_sidebands); + double *phase = malloc_double(number_of_sidebands); + + vm_double_ones(number_of_sidebands, ones); + // for (i = 0; i < number_of_sidebands; i++) { + // printf("%f", ones[i]); + // } + vm_double_arange(number_of_sidebands, input); + + // Calculate the spin angular frequency + spin_angular_freq = sample_rotation_frequency_in_Hz * PI2; + + // Calculate tau, where tau = (rotor period / number of phase steps) + tau = 1.0 / ((double)number_of_sidebands * sample_rotation_frequency_in_Hz); + + // pre-calculate the m omega spinning frequencies + double m_wr[9] = {-4., -3., -2., -1., 0., 1., 2., 3., 4.}; + cblas_dscal(9, spin_angular_freq, m_wr, 1); + + for (m = 0; m <= 3; m++) { + /** + * evaluate pre_phase = scale * (cexp(I * phase) - 1.0) + * where phase = m_wr[m] * tau * [0 .. number_of_sidebands-1] + * and scale = 2π/m_wr[m]. + */ + i = m * number_of_sidebands; + scale = PI2 / m_wr[m]; + + // step 1. calculate phase + vm_double_ramp(number_of_sidebands, input, m_wr[m] * tau, 0.0, phase); + + // step 2. evaluate cexp(I * phase) = cos(phase) + I sin(phase) + vm_cosine_I_sine(number_of_sidebands, phase, &pre_phase[i]); + + // step 3. subtract 1.0 from pre_phase + cblas_daxpy(number_of_sidebands, -1.0, ones, 1, (double *)&pre_phase[i], 2); + + // step 4. scale pre_phase with factor `scale` + cblas_zdscal(number_of_sidebands, scale, &pre_phase[i], 1); + + /* The expression pre_phase[m] = scale * (cexp(I * phase) - 1.0) given + * above from m is related to -m as pre_phase[-m] = -Re(pre_phase[m]) + + * Im(pre_phase[m]) + */ + + cblas_zcopy(number_of_sidebands, &pre_phase[i], 1, + &pre_phase[(8 - m) * number_of_sidebands], 1); + cblas_dscal(number_of_sidebands, -1.0, (double *)&pre_phase[i], 2); + } + vm_double_zeros(2 * number_of_sidebands, + (double *)&pre_phase[4 * number_of_sidebands]); + // memset((double *)&pre_phase[4 * number_of_sidebands], 0, + // 2 * number_of_sidebands * sizeof(double)); + + free(input); + free(phase); + free(ones); +} + +/** + * The function calculates the following. + * pre_phase(m, t) = I 2π [(exp(I m ωr t) - 1)/(I m ωr)] + * = (2π / m ωr) (exp(I m ωr t) - 1) + * |--scale--| + * = scale * (exp(I m ωr t) - 1) + * where ωr is the sample spinning frequency in Hz, m goes from -4 to 4, and + * t is a vector of length `number_of_sidebands` given as + * t = [0, 1, ... number_of_sidebands-1]/(ωr*number_of_sidebands) + * `pre_phase` is a matrix of shape, `9 x number_of_sidebands`. + */ +void __get_components(int number_of_sidebands, double sample_rotation_frequency, + complex128 *pre_phase) { + double spin_angular_freq, tau, wrt, pht, scale; + int step, i, m; + + // Calculate the spin angular frequency + spin_angular_freq = sample_rotation_frequency * PI2; + + // Calculate tau increments, where tau = (rotor period / number of phase + // steps) + tau = 1.0 / ((double)number_of_sidebands * sample_rotation_frequency); + + // pre-calculate the m omega spinning frequencies + double m_wr[9] = {-4., -3., -2., -1., 0., 1., 2., 3., 4.}; + cblas_dscal(9, spin_angular_freq, m_wr, 1); + + i = 0; + for (m = 0; m <= 8; m++) { + if (m != 4) { + wrt = m_wr[m] * tau; + pht = 0.0; + scale = PI2 / m_wr[m]; + for (step = 0; step < number_of_sidebands; step++) { + pre_phase[i++] = scale * (cexp(I * pht) - 1.0); + pht += wrt; + } + } else { + for (step = 0; step < number_of_sidebands; step++) { + pre_phase[i++] = 0.0; + } + } + } +} + +MRS_dimension *MRS_create_dimension(int count, double coordinates_offset, + double increment) { + MRS_dimension *dim = malloc(sizeof(MRS_dimension)); + dim->count = count; + dim->coordinates_offset = coordinates_offset; + dim->increment = increment; + + dim->inverse_increment = 1.0 / increment; + dim->normalize_offset = 0.5 - (coordinates_offset * dim->inverse_increment); + return dim; +} diff --git a/src/c_lib/lib/octahedron.c b/src/c_lib/lib/octahedron.c new file mode 100644 index 000000000..bb37978f9 --- /dev/null +++ b/src/c_lib/lib/octahedron.c @@ -0,0 +1,174 @@ + +// +// octahedron.c +// +// Created by Deepansh J. Srivastava, Apr 11, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include "octahedron.h" + +void octahedronGetDirectionCosineSquareOverOctantAndWeights(int nt, double *xr, + double *yr, + double *zr, + double *amp) { + + int i, j, k = 0; + double x2, y2, z2, r2, scale = 1.0; + + /* Do the (x + y + z = nt) face of the octahedron + !z -> 0 to nt-1 + !y -> 0 to nt-z + !x -> nt - y - z + !*/ + + for (j = 0; j <= nt - 1; j++) { + for (i = 0; i <= nt - j; i++) { + // x = nt-i-j; + // y = i; + // z = j; + x2 = pow(nt - i - j, 2); + y2 = pow(i, 2); + z2 = pow(j, 2); + r2 = x2 + y2 + z2; + xr[k] = x2 / r2; + yr[k] = y2 / r2; + zr[k] = z2 / r2; + amp[k] = scale / (r2 * sqrt(r2)); + k++; + } + } + + xr[k] = 0.0; + yr[k] = 0.0; + zr[k] = 1.0; + r2 = (double)nt; + amp[k] = scale / (r2 * r2 * r2); +} + +void octahedronGetPolarAngleTrigOverAnOctant(int nt, double *cos_alpha, + double *cos_beta, double *amp) { + + int points = (nt + 1) * (nt + 2) / 2; + double *xr = malloc_double(points); + double *yr = malloc_double(points); + double *zr = malloc_double(points); + double *sin_beta = malloc_double(points); + + // The values xr = x^2, yr = y^2, zr = z^2, where x, y, and z are the + // direction cosines. + octahedronGetDirectionCosineSquareOverOctantAndWeights(nt, xr, yr, zr, amp); + + // Evaluate sqrt of zr to get cos(beta) ---> cos(beta) = sqrt(z^2) + vm_double_square_root(points, zr, cos_beta); + + // Evaluate zr = xr + yr ---> sin^2(beta) = x^2 + y^2. Here zr is sin^2(beta). + vm_double_add(points, xr, yr, zr); + + // Take sqrt of zr to get sin(beta) ---> sin(beta) = sqrt(x^2 + y^2) + vm_double_square_root(points, zr, sin_beta); + + // Evaluate sqrt of xr and set the value to zr, zr = sqrt(x^2) = sqrt(xr) + vm_double_square_root(points, xr, zr); + + // Evaluate sqrt of xr + // vdSqrt(points, &yr[0], &yr[0]); + + // Evaluate cos_alpha = x/sqrt(x^2 + y^2) = zr/sin_beta + vm_double_divide(points - 1, zr, sin_beta, cos_alpha); + // vdDiv(points-1, yr, sinBeta, sinAlpha ); + + cos_alpha[points - 1] = 1.0; + // sinAlpha[points-1] = 0.0; + + free(xr); + free(yr); + free(zr); + free(sin_beta); +} +void octahedronGetPolarAngleCosineAzimuthalAnglePhaseOverOctant( + int nt, complex128 *exp_I_alpha, double *cos_beta, double *amp) { + int points = (nt + 1) * (nt + 2) / 2; + double *xr = malloc_double(points); + double *yr = malloc_double(points); + double *zr = malloc_double(points); + double *sin_beta = malloc_double(points); + + // The values xr = x^2, yr = y^2, zr = z^2, where x, y, and z are the + // direction cosines. + octahedronGetDirectionCosineSquareOverOctantAndWeights(nt, xr, yr, zr, amp); + + // Evaluate sqrt of zr to get cos(beta) ---> cos(beta) = sqrt(z^2) + vm_double_square_root(points, zr, cos_beta); + + // Evaluate zr = xr + yr ---> sin^2(beta) = x^2 + y^2 + vm_double_add(points, xr, yr, zr); + // Take sqrt of zr to get sin(beta) ---> sin(beta) = sqrt(x^2 + y^2) + vm_double_square_root(points, zr, sin_beta); + + // Evaluate sqrt of xr ---> value of xr is updated to sqrt(x^2) + vm_double_square_root(points, xr, xr); + + // Evaluate sqrt of yr ---> value of yr is updated to sqrt(y^2) + vm_double_square_root(points, yr, yr); + + // Evaluate cos_alpha = x/sqrt(x^2 + y^2) = xr/sin_beta + // value of xr is updated to hold cos_alpha + vm_double_divide(points - 1, xr, sin_beta, xr); + + // Evaluate sin_alpha = y/sqrt(x^2 + y^2) = yr/sin_beta + // value of yr is updated to hold sin_alpha + vm_double_divide(points - 1, yr, sin_beta, yr); + + xr[points - 1] = 1.0; + yr[points - 1] = 0.0; + + // copy cos_alpha and sin_alpha to alternating address of exp_I_alpha + // to emulate a complex array. + cblas_dcopy(points, xr, 1, (double *)&exp_I_alpha[0], 2); + cblas_dcopy(points, yr, 1, (double *)&exp_I_alpha[0] + 1, 2); + + free(xr); + free(yr); + free(zr); + free(sin_beta); +} + +void octahedronInterpolation(double *spec, double *freq, int nt, double *amp, + int stride, int m) { + + int i = 0, j = 0, local_index, n_pts = (nt + 1) * (nt + 2) / 2; + unsigned int int_i_stride, int_j_stride; + double amp1 = 0.0, temp; + double *amp_address, *freq_address; + + /* Interpolate between 1d points by setting up triangles of unit area */ + + local_index = nt - 1; + amp_address = &[(nt + 1) * stride]; + freq_address = &freq[nt + 1]; + + while (i < n_pts - 1) { + int_i_stride = i * stride; + int_j_stride = j * stride; + temp = amp[int_i_stride + stride] + amp_address[int_j_stride]; + amp1 = temp; + amp1 += amp[int_i_stride]; + + triangle_interpolation(&freq[i], &freq[i + 1], &freq_address[j], &1, + spec, &m); + + if (i < local_index) { + amp1 = temp; + amp1 += amp_address[int_j_stride + stride]; + triangle_interpolation(&freq[i + 1], &freq_address[j], + &freq_address[j + 1], &1, spec, &m); + } else { + local_index = j + nt; + i++; + } + i++; + j++; + } +} diff --git a/src/c_lib/lib/powder_setup.c b/src/c_lib/lib/powder_setup.c new file mode 100644 index 000000000..dfbc45c20 --- /dev/null +++ b/src/c_lib/lib/powder_setup.c @@ -0,0 +1,21 @@ + +// +// powder_setup.c +// +// Created by Deepansh J. Srivastava, Apr 11, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include "powder_setup.h" +#include "octahedron.h" + +void __powder_averaging_setup( + int nt, double *cos_alpha, double *cos_beta, double *amp, + int space // 1 for octant, 2 for hemisphere and 4 for sphere +) { + // unsigned int n_orientations = 1; + if (space == 1) { // octant + octahedronGetPolarAngleTrigOverAnOctant(nt, cos_alpha, cos_beta, amp); + } +} diff --git a/src/c_lib/lib/spinning_sidebands.c b/src/c_lib/lib/spinning_sidebands.c new file mode 100644 index 000000000..28db4060d --- /dev/null +++ b/src/c_lib/lib/spinning_sidebands.c @@ -0,0 +1,208 @@ + +// +// sideband_simulator.c +// +// Created by Deepansh J. Srivastava, Apr 11, 2019 +// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. +// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com +// + +#include "spinning_sidebands.h" + +static inline void __zero_components(double *R0, complex128 *R2, + complex128 *R4) { + R0[0] = 0.0; + vm_double_zeros(10, (double *)R2); + vm_double_zeros(18, (double *)R4); +} + +static inline void __spinning_sideband_core( + // spectrum information and related amplitude + double *spec, // amplitude vector representing the spectrum. + + isotopomer_ravel *ravel_isotopomer, // isotopomer structure + + int remove_second_order_quad_iso, // remove the isotropic contribution from + // the second order quad Hamiltonian. + + // Pointer to the transitions. transition[0] = mi and transition[1] = mf + double *transition, MRS_plan *plan, MRS_dimension *dimension) { + + /* + The computation of the spinning sidebands is based on the method described by + Eden and Levitt et. al. + `Computation of Orientational Averages in Solid-State NMR by Gaussian + Spherical Quadrature` + JMR, 132, 1998. https://doi.org/10.1006/jmre.1998.1427 + */ + + int i, site; + double iso_n_, zeta_n_, eta_n_, Cq_e_, eta_e_, d_, offset; + double R0 = 0.0; + complex128 *R2 = malloc_complex128(5); + complex128 *R4 = malloc_complex128(9); + + int spec_site; + double *spec_site_ptr; + + // Per site base calculation + for (site = 0; site < ravel_isotopomer->number_of_sites; site++) { + // gettimeofday(&start_site_time, NULL); + + spec_site = site * dimension->count; + spec_site_ptr = &spec[spec_site]; + + /* Nuclear shielding terms */ + iso_n_ = ravel_isotopomer->isotropic_chemical_shift_in_Hz[site]; + zeta_n_ = ravel_isotopomer->shielding_anisotropy_in_Hz[site]; + eta_n_ = ravel_isotopomer->shielding_asymmetry[site]; + + /* Electric quadrupolar terms */ + Cq_e_ = ravel_isotopomer->quadrupolar_constant_in_Hz[site]; + eta_e_ = ravel_isotopomer->quadrupolar_asymmetry[site]; + + /* Magnetic dipole terms */ + d_ = ravel_isotopomer->dipolar_couplings[site]; + + /* The following codeblock populates the product of spatial part, Rlm, of + * the tensor and the spin transition function, T(mf, mi) for + * zeroth rank, R0 = [ R00 ] * T(mf, mi) + * second rank, R2 = [ R2m ] * T(mf, mi) where m ∈ [-2, 2]. + * fourth rank, R4 = [ R4m ] * T(mf, mi) where m ∈ [-4, 4]. + * Here, mf, mi are the spin quantum numbers of the final and initial + * energy state of the spin transition. The term `Rlm` is the coefficient + * of the irreducible spherical tensor of rank `l` and order `m`. For more + * information, see reference + * Symmetry pathways in solid-state NMR. PNMRS 2011 59(2):12 1-96. + * https://doi.org/10.1016/j.pnmrs.2010.11.003 */ + + /* Initialize with zeroing all spatial components */ + __zero_components(&R0, R2, R4); + + /* get nuclear shielding components upto first order */ + get_nuclear_shielding_hamiltonian_to_first_order(&R0, R2, iso_n_, zeta_n_, + eta_n_, transition); + + /* get weakly coupled direct dipole components upto first order */ + get_weakly_coupled_direct_dipole_hamiltonian_to_first_order(&R0, R2, d_, + transition); + + if (ravel_isotopomer->spin > 0.5) { + /* get electric quadrupolar components upto first order */ + get_quadrupole_hamiltonian_to_first_order(&R0, R2, ravel_isotopomer->spin, + Cq_e_, eta_e_, transition); + + /* get electric quadrupolar components upto second order */ + if (plan->allow_fourth_rank) { + get_quadrupole_hamiltonian_to_second_order( + &R0, R2, R4, ravel_isotopomer->spin, Cq_e_, eta_e_, transition, + ravel_isotopomer->larmor_frequency, remove_second_order_quad_iso); + } + } + + /* */ + MRS_get_amplitudes_from_plan(plan, R2, R4); + MRS_get_normalized_frequencies_from_plan(plan, dimension, R0); + + // --------------------------------------------------------------------- + // Calculating the tent for every sideband + // Allowing only sidebands that are within the spectral bandwidth + // + for (i = 0; i < plan->number_of_sidebands; i++) { + offset = plan->vr_freq[i] + plan->isotropic_offset; + if ((int)offset >= 0 && (int)offset <= dimension->count) { + + vm_double_ramp(plan->n_orientations, plan->local_frequency, 1.0, offset, + plan->freq_offset); + octahedronInterpolation( + spec_site_ptr, plan->freq_offset, + plan->geodesic_polyhedron_frequency, + (double *)&plan->vector[i * plan->n_orientations], 2, + dimension->count); + } + } + // gettimeofday(&end_site_time, NULL); + // clock_time = + // (double)(end_site_time.tv_usec - start_site_time.tv_usec) / 1000000. + // + (double)(end_site_time.tv_sec - start_site_time.tv_sec); + // printf("Total time per site %f \n", clock_time); + } +} + +void spinning_sideband_core( + // spectrum information and related amplitude + double *spec, // The amplitude of the spectrum. + double coordinates_offset, // The start of the frequency spectrum. + double increment, // The increment of the frequency spectrum. + int count, // Number of points on the frequency spectrum. + + isotopomer_ravel *ravel_isotopomer, // isotopomer structure + + int quadSecondOrder, // Quad theory for second order, + int remove_second_order_quad_iso, // remove the isotropic contribution + // from the second order quad + // Hamiltonian. + + // spin rate, spin angle and number spinning sidebands + int number_of_sidebands, // The number of sidebands + double sample_rotation_frequency_in_Hz, // The rotor spin frequency + double rotor_angle_in_rad, // The rotor angle relative to lab-frame z-axis + + // Pointer to the transitions. transition[0] = mi and transition[1] = mf + double *transition, + + // powder orientation average + int geodesic_polyhedron_frequency // The number of triangle along the edge + // of octahedron +) { + + // int num_process = openblas_get_num_procs(); + // printf("%d processors", num_process); + // int parallel = openblas_get_parallel(); + // printf("%d parallel", parallel); + + bool allow_fourth_rank = false; + if (ravel_isotopomer[0].spin > 0.5 && quadSecondOrder == 1) { + allow_fourth_rank = true; + } + + // check for spinning speed + if (sample_rotation_frequency_in_Hz < 1.0e-3) { + sample_rotation_frequency_in_Hz = 1.0e9; + rotor_angle_in_rad = 0.0; + number_of_sidebands = 1; + } + + MRS_dimension *dimension = + MRS_create_dimension(count, coordinates_offset, increment); + + // gettimeofday(&begin, NULL); + MRS_plan *plan = + MRS_create_plan(geodesic_polyhedron_frequency, number_of_sidebands, + sample_rotation_frequency_in_Hz, rotor_angle_in_rad, + increment, allow_fourth_rank); + + // gettimeofday(&all_site_time, NULL); + __spinning_sideband_core( + // spectrum information and related amplitude + spec, // The amplitude of the spectrum. + + ravel_isotopomer, // isotopomer structure + + remove_second_order_quad_iso, // remove the isotropic contribution + // from the second order quad + // Hamiltonian. + + // Pointer to the transitions. transition[0] = mi and transition[1] = mf + transition, + + plan, dimension); + + // gettimeofday(&end, NULL); + // clock_time = (double)(end.tv_usec - begin.tv_usec) / 1000000. + + // (double)(end.tv_sec - begin.tv_sec); + // printf("time %f s\n", clock_time); + // cpu_time_[0] += clock_time; + + MRS_free_plan(plan); /* clean up */ +} diff --git a/src/c_lib/mrmethods/nmr_methods.pxd b/src/c_lib/mrmethods/nmr_methods.pxd index 0854a3a0c..62cf641b1 100644 --- a/src/c_lib/mrmethods/nmr_methods.pxd +++ b/src/c_lib/mrmethods/nmr_methods.pxd @@ -1,54 +1,47 @@ +cdef extern from "angular_momentum.h": + void __wigner_d_matrix_cosine(int l, int n, double *cos_angle, + double *wigner) + +cdef extern from "powder_setup.h": + void __powder_averaging_setup(int nt, double *cosAlpha, double *cosBeta, + double *amp, int space) + +cdef extern from "isotopomer_ravel.h": + ctypedef struct isotopomer_ravel: + int number_of_sites; # Number of sites + float spin; # The spin quantum number + double larmor_frequency; # Larmor frequency (MHz) + double *isotropic_chemical_shift_in_Hz; # Isotropic chemical shift (Hz) + double *shielding_anisotropy_in_Hz; # Nuclear shielding anisotropy (Hz) + double *shielding_asymmetry; # Nuclear shielding asymmetry parameter + double *shielding_orientation; # Nuclear shielding PAS to CRS euler angles (rad.) + double *quadrupolar_constant_in_Hz; # Quadrupolar coupling constant (Hz) + double *quadrupolar_asymmetry; # Quadrupolar asymmetry parameter + double *quadrupolar_orientation; # Quadrupolar PAS to CRS euler angles (rad.) + double *dipolar_couplings; # dipolar coupling stored as list of lists + + ctypedef struct isotopomers_list: + isotopomer_ravel *isotopomers cdef extern from "spinning_sidebands.h": - void __powder_averaging_setup( - int nt, - double *cosAlpha, - double *cosBeta, - double *amp, - int space) # 1 for octant, 2 for hemisphere and 4 for sphere - void spinning_sideband_core( # spectrum information and related amplitude double * spec, - double * cpu_time_, double spectral_start, double spectral_increment, int number_of_points, - # Pointer to spin quantum numbers for every spin. - double spin_quantum_number, - double larmor_frequency, - - # Pointer to the array of CSA tensor information in the PAS for every spin. - double *iso, - double *aniso, - double *eta, + isotopomer_ravel *ravel_isotopomer, - # Pointer to the array of quadrupole tensor information in the PAS for every spin. - double *Cq_e, # The Cq of the quadrupole center. - double *eta_e, # The asymmetry term of the tensor. int quadSecondOrder, # Quad theory for second order, - - # Pointer to the array of dipolar tensor information in the PAS. - double *D, # The dipolar coupling constant. + int remove_second_order_quad_iso, # remove the isotropic contribution from the + # second order quad Hamiltonian. # spin rate, spin angle and number spinning sidebands - int ph_step, - double spin_frequency, - double rotor_angle, + int number_of_sidebands, + double sample_rotation_frequency_in_Hz, + double rotor_angle_in_rad, # The transition as transition[0] = mi and transition[1] = mf double *transition, - - # Euler angle -> principal to molecular frame - # double *omega_PM, - - # Euler angles for powder averaging scheme - # powder orientation average - unsigned int n_orientations, - double *cosAlpha, - double *cosBeta, - double *amp, - int nt, - - unsigned int number_of_site) + int geodesic_polyhedron_frequency) diff --git a/src/c_lib/mrmethods/nmr_methods.pyx b/src/c_lib/mrmethods/nmr_methods.pyx index 64a225988..69f7aef22 100644 --- a/src/c_lib/mrmethods/nmr_methods.pyx +++ b/src/c_lib/mrmethods/nmr_methods.pyx @@ -1,13 +1,8 @@ cimport nmr_methods as clib -from nmr_methods cimport ( - __powder_averaging_setup -) -cimport numpy as np +from numpy cimport ndarray import numpy as np import cython - - __author__ = "Deepansh J. Srivastava" __email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] @@ -37,15 +32,15 @@ def one_d_spectrum(dict spectrum, geodesic polyhedra. These polyhedra are used in calculating the spherical average. Presently we only use octahedral as the frequency1 polyhedra. As the frequency of the geodesic polyhedron increases, the - polyhedra approach a sphere geometry. In lineshape simulation, a higher + polyhedra approach a sphere geometry. For line-shape simulation, a higher frequency will result in a better powder averaging. The default value is 72. Read more on the `Geodesic polyhedron `_. """ # --------------------------------------------------------------------- # spectrum ________________________________________________________ - cdef double sample_rotation_frequency = spectrum['rotor_frequency'] - cdef double rotor_angle = spectrum['rotor_angle'] + cdef double sample_rotation_frequency_in_Hz = spectrum['rotor_frequency'] + cdef double rotor_angle_in_rad = spectrum['rotor_angle'] B0 = spectrum['magnetic_flux_density'] if verbose in [1, 11]: @@ -54,23 +49,26 @@ def one_d_spectrum(dict spectrum, print (f'Adjusting the magnetic flux density to {B0} T.') _angle = spectrum['rotor_angle'] print (f'Setting rotation angle to {_angle} rad.') - print (f'Setting rotation frequency to {sample_rotation_frequency} Hz.') + print (f'Setting rotation frequency to {sample_rotation_frequency_in_Hz} Hz.') # --------------------------------------------------------------------- # spin observed _______________________________________________________ - # obs_dict = __get_spin_attribute__[detect] isotope = spectrum['isotope'] # spin quantum number of the observed spin - cdef double spin_quantum_number = spectrum['spin'] + cdef double spin_quantum_number = spectrum['spin']/2.0 # transitions of the observed spin - number_of_energy_levels = int(2*spin_quantum_number+1) - cdef int number_of_transitions = number_of_energy_levels-1 - energy_states = np.arange(number_of_energy_levels) - spin_quantum_number - transitions = [ [energy_states[i], energy_states[i+1]] for i in range(number_of_transitions)] - # print(transitions) - cdef np.ndarray[double, ndim=1] transition_array = np.asarray(transitions).ravel() + cdef ndarray[double, ndim=1] transition_array + cdef int number_of_transitions + transition_array = np.asarray([-0.5, 0.5]) + number_of_transitions = int(transition_array.size/2) + # else: + # energy_level_count = int(2*spin_quantum_number+1) + # number_of_transitions = energy_level_count-1 + # energy_states = np.arange(energy_level_count) - spin_quantum_number + # transitions = [ [energy_states[i], energy_states[i+1]] for i in range(number_of_transitions)] + # transition_array = np.asarray(transitions).ravel() # gyromagnetic ratio cdef double larmor_frequency = spectrum['gyromagnetic_ratio']*B0 @@ -93,36 +91,30 @@ def one_d_spectrum(dict spectrum, # CSA cdef int number_of_sites - cdef np.ndarray[double] iso_n - cdef np.ndarray[double] aniso_n - cdef np.ndarray[double] eta_n + cdef ndarray[double] iso_n + cdef ndarray[double] zeta_n + cdef ndarray[double] eta_n + cdef ndarray[double] ori_n # quad - cdef np.ndarray[double] Cq_e - cdef np.ndarray[double] eta_e + cdef ndarray[double] Cq_e + cdef ndarray[double] eta_e + cdef ndarray[double] ori_e - cdef np.ndarray[double] D_c + cdef ndarray[double] D_c cdef int i, trans__ - cdef np.ndarray[double, ndim=1] amp - cdef double cpu_time_ = 0.0 + cdef ndarray[double, ndim=1] amp amp1 = np.zeros(number_of_points, dtype=np.float64) - # octahedral power orientation averaging - cdef unsigned int n_orientations = int((geodesic_polyhedron_frequency+1) * (geodesic_polyhedron_frequency+2)/2) - cdef np.ndarray[double] cosAlpha = np.zeros(n_orientations, dtype=np.float64) - cdef np.ndarray[double] cosBeta = np.zeros(n_orientations, dtype=np.float64) - cdef np.ndarray[double] amp_orientation = np.zeros(n_orientations, dtype=np.float64) - - __powder_averaging_setup(geodesic_polyhedron_frequency, &cosAlpha[0], - &cosBeta[0], &_orientation[0], 1) - amp_orientation*=(1./number_of_sidebands) + cdef clib.isotopomer_ravel isotopomer_struct + # cdef clib.isotopomers_list *isotopomers_list_c list_index_isotopomer = [] # --------------------------------------------------------------------- # sample _______________________________________________________________ for index_isotopomer, isotopomer in enumerate(isotopomers): - abundance = isotopomer['abundance']/100 + abundance = isotopomer['abundance'] sub_sites = [site for site in isotopomer['sites'] if site['isotope'] == isotope] number_of_sites= len(sub_sites) @@ -130,12 +122,14 @@ def one_d_spectrum(dict spectrum, # site specification # CSA iso_n = np.empty(number_of_sites, dtype=np.float64) - aniso_n = np.empty(number_of_sites, dtype=np.float64) + zeta_n = np.empty(number_of_sites, dtype=np.float64) eta_n = np.empty(number_of_sites, dtype=np.float64) + ori_n = np.zeros(3*number_of_sites, dtype=np.float64) # quad Cq_e = np.zeros(number_of_sites, dtype=np.float64) eta_e = np.zeros(number_of_sites, dtype=np.float64) + ori_e = np.zeros(3*number_of_sites, dtype=np.float64) D_c = np.zeros(number_of_sites, dtype=np.float64) @@ -148,9 +142,9 @@ def one_d_spectrum(dict spectrum, site = sub_sites[i] # CSA tensor - iso_n[i] = site['isotropic_chemical_shift'] - aniso_n[i] = site['shielding_symmetric']['anisotropy'] - eta_n[i] = site['shielding_symmetric']['asymmetry'] + iso = site['isotropic_chemical_shift'] + aniso = site['shielding_symmetric']['anisotropy'] + eta = site['shielding_symmetric']['asymmetry'] if verbose in [1, 11]: text = (( @@ -160,11 +154,43 @@ def one_d_spectrum(dict spectrum, len_ = len(text) print(text) print(f"{'-'*(len_-1)}") - print(f'isotropic chemical shift = {str(iso_n[i])}') - print(f'chemical shift anisotropy = {str(aniso_n[i])}') - print(f'chemical shift asymmetry = {eta_n[i]}') + print(f'Isotropic chemical shift = {str(iso)}') + print(f'Shielding anisotropy = {str(aniso)}') + print(f'Shielding asymmetry = {eta}') + + # CSA tensor in Hz + iso_n[i] = iso + zeta_n[i] = aniso + eta_n[i] = eta + + # quad tensor + if spin_quantum_number > 0.5: + Cq_e[i] = site['quadrupolar']['anisotropy'] + eta_e[i] = site['quadrupolar']['asymmetry'] + if verbose in [1, 11]: + print(f'Quadrupolar coupling constant = {Cq_e[i]/1e6} MHz') + print(f'Quadrupolar asymmetry = {eta}') + # print(transition_array) + # print(number_of_transitions) + + isotopomer_struct.number_of_sites = number_of_sites + isotopomer_struct.spin = spin_quantum_number + isotopomer_struct.larmor_frequency = larmor_frequency*1.0e6 + + isotopomer_struct.isotropic_chemical_shift_in_Hz = &iso_n[0] + isotopomer_struct.shielding_anisotropy_in_Hz = &zeta_n[0] + isotopomer_struct.shielding_asymmetry = &eta_n[0] + isotopomer_struct.shielding_orientation = &ori_n[0] + + isotopomer_struct.quadrupolar_constant_in_Hz = &Cq_e[0] + isotopomer_struct.quadrupolar_asymmetry = &eta_e[0] + isotopomer_struct.quadrupolar_orientation = &ori_e[0] + + isotopomer_struct.dipolar_couplings = &D_c[0] + + # isotopomers_list_c[i] = isotopomer_struct for trans__ in range(number_of_transitions): # spin transitions # m_initial = transition_array[i][0] @@ -172,262 +198,25 @@ def one_d_spectrum(dict spectrum, clib.spinning_sideband_core( # spectrum information and related amplitude &[0], - &cpu_time_, reference_offset, increment, number_of_points, - spin_quantum_number, - larmor_frequency, - - # CSA tensor information - &iso_n[0], - &aniso_n[0], - &eta_n[0], + &isotopomer_struct, - # quad tensor information - &Cq_e[0], - &eta_e[0], 1, # quad_second_order_c, - - # Dipolar couplings - &D_c[0], + 0, # turn off quad second order isotropic contribution # spin rate, spin angle and number spinning sidebands number_of_sidebands, - sample_rotation_frequency, - rotor_angle, + sample_rotation_frequency_in_Hz, + rotor_angle_in_rad, &transition_array[2*trans__], - # Euler angle -> principal to molecular frame - # &omega_PM_c[0], - - # Euler angles for powder averaging scheme - - # powder orientation averager - n_orientations, - &cosAlpha[0], - &cosBeta[0], - &_orientation[0], - geodesic_polyhedron_frequency, - - number_of_sites - ) + geodesic_polyhedron_frequency) amp1 += amp.reshape(number_of_sites, number_of_points).sum(axis=0)*abundance - if verbose in [11]: - print(f'\nExecution time {cpu_time_} s') freq = (np.arange(number_of_points)*increment + reference_offset) return freq, amp1, larmor_frequency, list_index_isotopomer - - - - -@cython.boundscheck(False) -@cython.wraparound(False) -def _one_d_simulator( - # spectrum information - double reference_offset, - double increment, - int number_of_points, - - float quantum_number = 0.5, - float larmor_frequency = 0.0, - - # CSA tensor information - isotropic_chemical_shift = None, - chemical_shift_anisotropy = None, - chemical_shift_asymmetry = None, - - # quad tensor information - quadrupolar_coupling_constant = None, - quadrupolar_asymmetry = None, - second_order_quad = 1, - - # dipolar coupling - D = None, - - # spin rate, spin angle and number spinning sidebands - int number_of_sidebands = 96, - double sample_rotation_frequency = 0.0, - rotor_angle = None, - - m_final = 0.5, - m_initial = -0.5, - - # Euler angle -> principal to molecular frame - # omega_PM=None, - - # Euler angles for powder averaging scheme - int averaging_scheme=0, - int averaging_size=64): - - - if isotropic_chemical_shift is None: - isotropic_chemical_shift = 0 - isotropic_chemical_shift = np.asarray([isotropic_chemical_shift], dtype=np.float64).ravel() - cdef number_of_sites = isotropic_chemical_shift.size - cdef np.ndarray[double, ndim=1] isotropic_chemical_shift_c = isotropic_chemical_shift - - if quantum_number > 0.5 and larmor_frequency == 0.0: - raise Exception("'larmor_frequency' is required for quadrupole spins.") - - # Shielding anisotropic values - if chemical_shift_anisotropy is None: - chemical_shift_anisotropy = np.ones(number_of_sites, dtype=np.float64).ravel() #*1e-4*increment - else: - chemical_shift_anisotropy = np.asarray([chemical_shift_anisotropy], dtype=np.float64).ravel() - # chemical_shift_anisotropy[np.where(chemical_shift_anisotropy==0.)] = 1e-4*increment - if chemical_shift_anisotropy.size != number_of_sites: - raise Exception("Number of shielding anisotropies are not consistent with the number of spins.") - cdef np.ndarray[double, ndim=1] chemical_shift_anisotropy_c = chemical_shift_anisotropy - - # Shielding asymmetry values - if chemical_shift_asymmetry is None: - chemical_shift_asymmetry = np.zeros(number_of_sites, dtype=np.float64).ravel() - else: - chemical_shift_asymmetry = np.asarray([chemical_shift_asymmetry], dtype=np.float64).ravel() - if chemical_shift_asymmetry.size != number_of_sites: - raise Exception("Number of shielding asymmetry are not consistent with the number of spins.") - cdef np.ndarray[double, ndim=1] chemical_shift_asymmetry_c = chemical_shift_asymmetry - - # Quad coupling constant - if quadrupolar_coupling_constant is None: - quadrupolar_coupling_constant = np.zeros(number_of_sites, dtype=np.float64).ravel() - else: - quadrupolar_coupling_constant = np.asarray([quadrupolar_coupling_constant], dtype=np.float64).ravel() - if quadrupolar_coupling_constant.size != number_of_sites: - raise Exception("Number of quad coupling constants are not consistent with the number of spins.") - cdef np.ndarray[double, ndim=1] quadrupolar_coupling_constant_c = quadrupolar_coupling_constant - - # Quad asymmetry value - if quadrupolar_asymmetry is None: - quadrupolar_asymmetry = np.zeros(number_of_sites, dtype=np.float64).ravel() - else: - quadrupolar_asymmetry = np.asarray([quadrupolar_asymmetry], dtype=np.float64).ravel() - if quadrupolar_asymmetry.size != number_of_sites: - raise Exception("Number of quad asymmetry are not consistent with the number of spins.") - cdef np.ndarray[double, ndim=1] quadrupolar_asymmetry_c = quadrupolar_asymmetry - - - # Dipolar coupling constant - if D is None: - D = np.zeros(number_of_sites, dtype=np.float64).ravel() - else: - D = np.asarray([D], dtype=np.float64).ravel() - if D.size != number_of_sites: - raise Exception("Number of dipolar coupling are not consistent with the number of spins.") - cdef np.ndarray[double, ndim=1] D_c = D - - - # if omega_PM is None: - # omega_PM = np.zeros(3) - # cdef np.ndarray[double, ndim=1] omega_PM_c = np.asarray(omega_PM, dtype=np.float64).ravel() - - if rotor_angle is None: - rotor_angle = 54.735 - cdef double rotor_angle_c = np.pi*rotor_angle/180. - - # if sample_rotation_frequency == 0: - # rotor_angle_c = 0 - # sample_rotation_frequency = 1000000 - print(rotor_angle_c, sample_rotation_frequency) - - cdef second_order_quad_c = second_order_quad - # if quad_second_order: - # quad_second_order_c = 1 - - cdef np.ndarray[double, ndim=1] transition_c = np.asarray([m_initial, m_final], dtype=np.float64) - - cdef np.ndarray[double, ndim=1] amp = np.zeros(number_of_points * number_of_sites) - cdef double cpu_time_ - # # print('rotor_angle in rad', rotor_angle_in_rad) - # # print('transition', transition_c) - - # octahedral power orientation averaging - cdef unsigned int n_orientations = int((averaging_size+1) * (averaging_size+2)/2) - cdef np.ndarray[double, ndim=1] cosAlpha = np.empty(n_orientations, dtype=np.float64) - cdef np.ndarray[double, ndim=1] cosBeta = np.empty(n_orientations, dtype=np.float64) - cdef np.ndarray[double, ndim=1] amp_orientation = np.empty(n_orientations, dtype=np.float64) - __powder_averaging_setup(averaging_size, &cosAlpha[0], - &cosBeta[0], &_orientation[0], 1) - - - if averaging_scheme == 0: - clib.spinning_sideband_core( - # spectrum information and related amplitude - &[0], - &cpu_time_, - reference_offset, - increment, - number_of_points, - - quantum_number, - larmor_frequency, - - # CSA tensor information - &isotropic_chemical_shift_c[0], - &chemical_shift_anisotropy_c[0], - &chemical_shift_asymmetry_c[0], - - # quad tensor information - &quadrupolar_coupling_constant_c[0], - &quadrupolar_asymmetry_c[0], - second_order_quad_c, - - # Dipolar couplings - &D_c[0], - - # spin rate, spin angle and number spinning sidebands - number_of_sidebands, - sample_rotation_frequency, - rotor_angle_c, - - &transition_c[0], - # Euler angle -> principal to molecular frame - # &omega_PM_c[0], - - # Euler angles for powder averaging scheme - - # powder orientation averager - n_orientations, - &cosAlpha[0], - &cosBeta[0], - &_orientation[0], - averaging_size, - - number_of_sites - ) - - # else: - # clib.lineshape_cas_spinning_sideband_cython_angles( - # # spectrum information and related amplitude - # &[0], - # &cpu_time_, - # spectral_start, - # spectral_increment, - # number_of_points, - - # # CSA tensor information - # iso, - # aniso, - # eta, - - # # spin rate, spin angle and number spinning sidebands - # ph_step, - # spin_frequency, - # rotor_angle_c, - - # # Euler angle -> principal to molecular frame - # &omega_PM_c[0], - - # # Euler angles for powder averaging scheme - # averaging_scheme, - # averaging_size - # ) - - freq = np.arange(number_of_points)*increment + reference_offset - - return freq, amp, cpu_time_ diff --git a/src/c_lib/mrmethods/powder_setup.c b/src/c_lib/mrmethods/powder_setup.c deleted file mode 100644 index 81c375896..000000000 --- a/src/c_lib/mrmethods/powder_setup.c +++ /dev/null @@ -1,673 +0,0 @@ - -// -// powder_setup.c -// -// Created by Deepansh J. Srivastava, Apr 11, 2019 -// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. -// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com -// - -#include "powder_setup.h" - -void getDirectionCosineSquareOverOctantAndWeights(int nt, double *xr, - double *yr, double *zr, - double *amp) { - - int i, j, k = 0; - double x2, y2, z2, r2, scale = 1.0; // /((double)(nt+1)*(nt+2)/2); - - /* Do the (x + y + z = nt) face of the octahedron - !z -> 0 to nt-1 - !y -> 0 to nt-z - !x -> nt - y - z - !*/ - - for (j = 0; j <= nt - 1; j++) { - for (i = 0; i <= nt - j; i++) { - // x = nt-i-j; - // y = i; - // z = j; - x2 = pow(nt - i - j, 2); - y2 = pow(i, 2); - z2 = pow(j, 2); - r2 = x2 + y2 + z2; - xr[k] = x2 / r2; - yr[k] = y2 / r2; - zr[k] = z2 / r2; - amp[k] = scale / (r2 * sqrt(r2)); - k++; - } - } - - xr[k] = 0.0; - yr[k] = 0.0; - zr[k] = 1.0; - r2 = nt; - amp[k] = scale / (r2 * r2 * r2); -} - -void getDirectionCosineSquareOverHemishpereAndWeights(int nt, double **xr, - double **yr, double **zr, - double **amp) { - - int i, j; - double x2, y2, z2, r2; - - /* Do the (x + y + z = nt) face of the octahedron - !z -> 0 to nt-1 - !y -> 0 to nt-z - !x -> nt - y - z - !*/ - - for (j = 0; j <= nt - 1; j++) { - for (i = 0; i <= nt - j; i++) { - // x = nt-i-j; - // y = i; - // z = j; - x2 = pow(nt - i - j, 2); - y2 = pow(i, 2); - z2 = pow(j, 2); - r2 = x2 + y2 + z2; - xr[i][j] = x2 / r2; - yr[i][j] = y2 / r2; - zr[i][j] = z2 / r2; - amp[i][j] = 1.0 / (r2 * sqrt(r2)); - } - - /* Do the (-x + y + z = nt) face of the octahedron - !z -> 0 to nt-1 - !y -> 0 to nt-z - !x -> -nt + y + z - !*/ - for (i = nt - j + 1; i <= nt; i++) { - // x = nt-i-j; - // y = nt-j; - // z = nt-i; - x2 = pow(nt - i - j, 2); - y2 = pow(nt - j, 2); - z2 = pow(nt - i, 2); - r2 = x2 + y2 + z2; - xr[i][j] = x2 / r2; - yr[i][j] = y2 / r2; - zr[i][j] = z2 / r2; - amp[i][j] = 1.0 / (r2 * sqrt(r2)); - } - } - - /* Do the (-x - y + z = nt) face of the octahedron - !*/ - - for (j = nt; j < 2 * nt; j++) { - for (i = j - nt + 1; i < nt; i++) { - // x = -nt-i+j; - // y = nt-j; - // z = nt-i; - x2 = pow(-nt - i + j, 2); // x*x; - y2 = pow(nt - j, 2); // y*y; - z2 = pow(nt - i, 2); // z*z; - r2 = x2 + y2 + z2; - xr[i][j] = x2 / r2; - yr[i][j] = y2 / r2; - zr[i][j] = z2 / r2; - amp[i][j] = 1.0 / (r2 * sqrt(r2)); - } - - /* Do the (x - y + z = nt) face of the octahedron - !*/ - - for (i = 1; i <= j - nt; i++) { - // x = -nt-i+j; - // y = -i; - // z = 2*nt-j; - x2 = pow(-nt - i + j, 2); // x*x; - y2 = pow(-i, 2); // y*y; - z2 = pow(2 * nt - j, 2); // z*z; - r2 = x2 + y2 + z2; - xr[i][j] = x2 / r2; - yr[i][j] = y2 / r2; - zr[i][j] = z2 / r2; - amp[i][j] = 1.0 / (r2 * sqrt(r2)); - } - } - - xr[0][nt] = 0.0; - yr[0][nt] = 0.0; - zr[0][nt] = 1.0; - r2 = nt; - amp[0][nt] = 1.0 / (r2 * r2 * r2); - - for (j = 0; j < nt; j++) { - i = 2 * nt - j; - xr[0][i] = xr[0][j]; - yr[0][i] = yr[0][j]; - zr[0][i] = zr[0][j]; - amp[0][i] = amp[0][j]; - } - - for (i = 0; i <= nt; i++) { - xr[nt][nt + i] = xr[i][0]; - yr[nt][nt + i] = yr[i][0]; - zr[nt][nt + i] = zr[i][0]; - amp[nt][nt + i] = amp[i][0]; - } - - i = 2 * nt; - for (j = 1; j < nt; j++) { - xr[nt - j][i] = xr[nt][j]; - yr[nt - j][i] = yr[nt][j]; - zr[nt - j][i] = zr[nt][j]; - amp[nt - j][i] = amp[nt][j]; - } -} - -void getPolarAngleTrigOverAnOctant(int nt, double *cosAlpha, - // double* sinAlpha, - double *cosBeta, - // double* sinBeta, - double *amp) { - - // int i, j; - - int points = (nt + 1) * (nt + 2) / 2; - double *xr = createDouble1DArray(points); - double *yr = createDouble1DArray(points); - double *zr = createDouble1DArray(points); - double *sinBeta = createDouble1DArray(points); - - getDirectionCosineSquareOverOctantAndWeights(nt, xr, yr, zr, amp); - - // // OCPolarAngleTrig **trigs = create2DOCPolarAngleTrigArray(nt+1, 2*nt+1); - - // Evaluate sqrt of zr to get cos(beta) - vdSqrt(points, &zr[0], &cosBeta[0]); - - // exaluate A = x + y - vdAdd(points, &xr[0], &yr[0], &sinBeta[0]); - // Take sqrt of A to get sin(beta) - vdSqrt(points, &sinBeta[0], &sinBeta[0]); - - // Evaluate sqrt of xr - vdSqrt(points, &xr[0], &xr[0]); - - // Evaluate sqrt of xr - // vdSqrt(points, &yr[0], &yr[0]); - - vdDiv(points - 1, xr, sinBeta, cosAlpha); - // vdDiv(points-1, yr, sinBeta, sinAlpha ); - - cosAlpha[points - 1] = 1.0; - // sinAlpha[points-1] = 0.0; - - // int ii=0; - // for( i = 0; i < points; i++) { - // if (sinBeta[i] != 0.){ - // cosAlpha[i] = xr[i] / sinBeta[i]; - // sinAlpha[i] = yr[i] / sinBeta[i]; - // } - // else{ - // cosAlpha[i] = 1.0; - // sinAlpha[i] = 0.0; - // } - // } - destroyDouble1DArray(xr); - destroyDouble1DArray(yr); - destroyDouble1DArray(zr); - destroyDouble1DArray(sinBeta); -} - -void getPolarAngleTrigOverHemisphere(int nt, double *cosAlpha, double *sinAlpha, - double *cosBeta, double *sinBeta, - double **amp) { - - int i, j; - - double points = (nt + 1) * (2 * nt + 1); - double **xr = createDouble2DMatrix((nt + 1), (2 * nt + 1)); - double **yr = createDouble2DMatrix((nt + 1), (2 * nt + 1)); - double **zr = createDouble2DMatrix((nt + 1), (2 * nt + 1)); - - getDirectionCosineSquareOverHemishpereAndWeights(nt, xr, yr, zr, amp); - - // Evaluate sqrt of zr to get cos(beta) - vdSqrt(points, &zr[0][0], &cosBeta[0]); - - // evaluate A = x + y - vdAdd(points, &xr[0][0], &yr[0][0], &sinBeta[0]); - // Take sqrt of A to get sin(beta) - vdSqrt(points, &sinBeta[0], &sinBeta[0]); - - // Evaluate sqrt of xr - vdSqrt(points, &xr[0][0], &xr[0][0]); - - // Evaluate sqrt of xr - vdSqrt(points, &yr[0][0], &yr[0][0]); - - int ii = 0; - for (i = 0; i <= nt; i++) { - for (j = 0; j <= 2 * nt; j++) { - // cosBeta[i][j] = sqrt(zr[i][j]); - // sinBeta[i][j] = sqrt(xr[i][j] + yr[i][j]); - if (sinBeta[ii] != 0.) { - cosAlpha[ii] = xr[i][j] / sinBeta[ii]; - sinAlpha[ii] = yr[i][j] / sinBeta[ii]; - ii++; - } else { - cosAlpha[ii] = 1.0; - sinAlpha[ii] = 0.0; - ii++; - } - } - } - destroyDouble2DMatrix(xr); - destroyDouble2DMatrix(yr); - destroyDouble2DMatrix(zr); -} - -// triangle_interpolation is an optimized version of tent. -int triangle_interpolation(double *freq1, double *freq2, double *freq3, - double *offset, double *amp, double *spec, - int *points) { - - double df1, df2, top = 0.0, t, diff, f10 = 0.0, f21 = 0.0; - int p, pmid, pmax, i, j, clip_right = 0, clip_left = 0; - - // off = (int) offset[0]; - p = (int)(freq1[0] + offset[0]); - if ((int)freq1[0] == (int)freq2[0] && (int)freq1[0] == (int)freq3[0]) { - if (p >= points[0] || p < 0) { - return 0; - } - spec[p] += amp[0]; - return 0; - } - - double f[3]; //= {0.0, 0.0, 0.0}; - - f[0] = freq1[0] + offset[0]; - f[1] = freq2[0] + offset[0]; - f[2] = freq3[0] + offset[0]; - - for (j = 1; j <= 2; j++) { - t = f[j]; - i = j - 1; - while (i >= 0 && f[i] > t) { - f[i + 1] = f[i]; - i--; - } - f[i + 1] = t; - } - - top += (amp[0] * 2.0 / (f[2] - f[0])); - p = (int)f[0]; // floor( f[0] ); - pmid = (int)f[1]; // floor( f[1] ); - pmax = (int)f[2]; // floor( f[2] ); - f10 += f[1] - f[0]; - f21 += f[2] - f[1]; - - // if((pmax >= points[0]) || (p < 0)) return 0; - - if (pmax < 0) { - return 0; - } - if (p > points[0]) { - return 0; - } - - if (pmax >= points[0]) { - pmax = points[0]; - clip_right = 1; - } - - if (pmid >= points[0]) { - pmid = points[0]; - clip_right = 1; - } - - if (p < 0) { - p = 0; - clip_left = 1; - } - - if (pmid < 0) { - pmid = 0; - clip_left = 1; - } - - if (p != pmid) { - df1 = top / f10; - diff = (double)p + 1. - f[0]; - if (clip_left == 0) { - spec[p++] += 0.5 * diff * diff * df1; - } else { - spec[p++] += (diff - 0.5) * df1; - } - // diff = (diff - 0.5) * df1; - diff -= 0.5; - diff *= df1; - while (p != pmid) { - diff += df1; - spec[p++] += diff; - } - if (clip_right == 0) { - spec[p] += (f[1] - (double)p) * (f10 + ((double)p - f[0])) * 0.5 * df1; - } - } else { - if (clip_right == 0 && clip_left == 0) { - spec[p] += f10 * top * 0.5; - } - } - - if (p != pmax) { - df2 = top / f21; - diff = f[2] - (double)p - 1.; - - if (clip_left == 0) { - spec[p++] += (f21 - diff) * (diff + f21) * 0.5 * df2; - } else { - spec[p++] += (diff + 0.5) * df2; - } - // diff = (diff + 0.5) * df2; - diff += 0.5; - diff *= df2; - while (p != pmax) { - diff -= df2; - spec[p++] += diff; - } - if (clip_right == 0) { - spec[p] += pow((f[2] - (double)p), 2) * 0.5 * df2; - } - } else { - if (clip_right == 0) { - spec[p] += f21 * top * 0.5; - } - } - return 0; -} - -void powderAverageWithTentingSchemeOverOctant(double *spec, double *freq, - int nt, double *amp, - double *offset, int m) { - - int i = 0, j = 0, local_index, n_pts = (nt + 1) * (nt + 2) / 2; - double amp1 = 0.0, temp; - double *amp_address, *freq_address; - - /* Interpolate between frequencies by setting up tents */ - - local_index = nt - 1; - amp_address = &[nt + 1]; - freq_address = &freq[nt + 1]; - - while (i < n_pts - 1) { - temp = amp[i + 1] + amp_address[j]; - amp1 = temp; - amp1 += amp[i]; - - triangle_interpolation(&freq[i], &freq[i + 1], &freq_address[j], offset, - &1, spec, &m); - - if (i < local_index) { - amp1 = temp; - amp1 += amp_address[j + 1]; - triangle_interpolation(&freq[i + 1], &freq_address[j], - &freq_address[j + 1], offset, &1, spec, &m); - } else { - local_index = j + nt; - i++; - } - i++; - j++; - } -} - -void powderAverageWithTentingSchemeOverHemisphere2(double *spec, double *freq, - int nt, double *amp, - double *offset, int m) { - - int i = 0, j = 0, local_index, n_pts = (nt + 1) * (nt + 2) / 2; - double amp1 = 0.0, temp; - double *amp_address, *freq_address; - - /* Interpolate between frequencies by setting up tents */ - int l = nt; - local_index = nt - 1; - amp_address = &[4 * nt]; - freq_address = &freq[4 * nt]; - - while (i < n_pts - 1) { - temp = amp[i + 1] + amp_address[j]; - amp1 = temp; - amp1 += amp[i]; - - triangle_interpolation(&freq[i], &freq[i + 1], &freq_address[j], offset, - &1, spec, &m); - - if (i < local_index) { - amp1 = temp; - if (j == 4 * l) { - amp1 += amp_address[j - 4 * l]; - triangle_interpolation(&freq[i + 1], &freq_address[j], &freq_address[0], - offset, &1, spec, &m); - } else - amp1 += amp_address[j + 1]; - triangle_interpolation(&freq[i + 1], &freq_address[j], - &freq_address[j + 1], offset, &1, spec, &m); - i++; - j++; - } else { - local_index = i + nt; - i++; - } - } -} - -void powderAverageWithTentingSchemeOverHemisphere(double *spec, - double **powfreq, int nt, - double **amp, double *offset, - int m) { - - int i, j; - double amp1, amp2, temp; - - /* Interpolate between frequencies by setting up tents */ - - for (i = 0; i <= nt - 1; i++) { - for (j = 0; j <= nt - 1; j++) { - temp = amp[i][j + 1] + amp[i + 1][j]; - amp1 = amp[i][j] + temp; - amp2 = temp + amp[i + 1][j + 1]; - - triangle_interpolation(&powfreq[i + 1][j], &powfreq[i][j + 1], - &powfreq[i][j], offset, &1, spec, &m); - triangle_interpolation(&powfreq[i + 1][j], &powfreq[i][j + 1], - &powfreq[i + 1][j + 1], offset, &2, spec, &m); - - // tent_amp(&powfreq[i+1][j], &powfreq[i][j+1], &powfreq[i][j], offset, \ - // &[i+1][j], &[i][j+1], &[i][j], spec, m); - // tent_amp(&powfreq[i+1][j], &powfreq[i][j+1], &powfreq[i+1][j+1], offset, \ - // &[i+1][j], &[i][j+1], &[i+1][j+1], spec, m); - } - } - - // if (octa == 0){ - for (i = 0; i <= nt - 1; i++) { - for (j = nt; j <= 2 * nt - 1; j++) { - temp = amp[i][j] + amp[i + 1][j + 1]; - amp1 = temp + amp[i + 1][j]; - amp2 = temp + amp[i][j + 1]; - - triangle_interpolation(&powfreq[i][j], &powfreq[i + 1][j + 1], - &powfreq[i + 1][j], offset, &1, spec, &m); - triangle_interpolation(&powfreq[i][j], &powfreq[i + 1][j + 1], - &powfreq[i][j + 1], offset, &2, spec, &m); - - // tent_amp(&powfreq[i][j], &powfreq[i+1][j+1], &powfreq[i][j+1], offset, \ - // &[i][j], &[i+1][j+1], &[i][j+1], spec, m); - // tent_amp(&powfreq[i][j], &powfreq[i+1][j+1], &powfreq[i][j+1], offset, \ - // &[i][j], &[i+1][j+1], &[i][j+1], spec, m); - } - } - // } -}; - -// static inline double two_times_triangle_area( -// double *a, -// double *b, -// double *c) -// { -// return ((b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0])); -// } - -void rasterization(double *grid, double *v0, double *v1, double *v2, int rows, - int columns) { - - double A12, B12, C12, A20, B20, C20, A01, B01, C01; - double minX, minY, maxX, maxY, w0, w1, w2; - double w0_row, w1_row, w2_row; - int i, j, i_, minX_, minY_, maxX_, maxY_; - - minX = fmin(fmin(v0[0], v1[0]), v2[0]); - minY = fmin(fmin(v0[1], v1[1]), v2[1]); - maxX = fmax(fmax(v0[0], v1[0]), v2[0]); - maxY = fmax(fmax(v0[1], v1[1]), v2[1]); - - // clip against screen bounds - minX_ = (int)fmax(minX, 0.); - minY_ = (int)fmax(minY, 0.); - maxX_ = (int)fmin(maxX, (double)rows - 1.); - maxY_ = (int)fmin(maxY, (double)columns - 1.); - - A12 = (v2[0] - v1[0]); - B12 = (v2[1] - v1[1]); - C12 = -A12 * v1[1] + B12 * v1[0]; - A20 = (v0[0] - v2[0]); - B20 = (v0[1] - v2[1]); - C20 = -A20 * v2[1] + B20 * v2[0]; - A01 = (v1[0] - v0[0]); - B01 = (v1[1] - v0[1]); - C01 = -A01 * v0[1] + B01 * v0[0]; - - w0_row = A12 * minY - B12 * minX + C12; - w1_row = A20 * minY - B20 * minX + C20; - w2_row = A01 * minY - B01 * minX + C01; - - // Rasterize - for (i = minY_; i <= maxY_; i++) { - - // Determine barycentric coordinates - w0 = w0_row; - w1 = w1_row; - w2 = w2_row; - - i_ = rows * i; - for (j = minX_; j <= maxX_; j++) { - // If p is on or inside all edges, render pixel. - if ((int)w0 >= 0 && (int)w1 >= 0 && (int)w2 >= 0) { - grid[i_ + j] += 1.; //(w0+w1+w2); - } - if ((int)w0 <= 0 && (int)w1 <= 0 && (int)w2 <= 0) { - grid[i_ + j] += -1.; //(w0+w1+w2); - } - // i_++; - - w0 -= B12; - w1 -= B20; - w2 -= B01; - } - w0_row += A12; - w1_row += A20; - w2_row += A01; - } -} - -// static inline void get_KL( -// double *v0, -// double *v1, -// double *x, -// double *y) -// { -// double K[2] = -// } -// def get_KL(self, x0, y0, x1, y1, eps = 1e-5): -// self.x0, self.y0, self.x1, self.y1 = x0, y0, x1, y1 -// Kx, Ky, Lx, Ly = 0, 0, 0, 0 -// for sec in self.clip(0, 1, 1, 0): -// v1 = Point(sec[0], sec[1]) -// v0 = Point(sec[2], sec[3]) -// if abs(v0.x - 1) < eps and abs(v1.x - 1) < eps \ -// or abs(v0.y - 1) < eps and abs(v1.y - 1) < eps: -// continue - -// Kx += 1./4 * (v0.y - v1.y) -// Ky += 1./4 * (v1.x - v0.x) -// Lx += 1./8 * (v0.y-v1.y) * (v0.x+v1.x) -// Ly += 1./8 * (v1.x-v0.x) * (v0.y+v1.y) -// return Point(Kx, Ky), Point(Lx, Ly) - -// static inline void clip( -// double left, -// double right, -// double bottom, -// double top, -// double *p0, -// double *p1, -// double *clipped_lines -// ) -// { -// int edge, i; -// double t0=0, t1=1, r; -// double delta_x = p1[0] - p0[0], delta_y=p1[1]-p0[1], p, q; - -// for(edge=0; edge<4; edge++){ -// if(edge ==0){ -// p = -delta_x; -// q = -(left - p0[0]); -// } -// else if(edge ==1){ -// p = delta_x; -// q = (right - p0[0]); -// } -// else if(edge ==2){ -// p = delta_y; -// q = (bottom - p0[1]); -// } -// else if(edge ==3){ -// p = -delta_y; -// q = -(top - p0[1]); -// } -// if(p == 0 && q < 0) return 0; -// if (p < 0){ -// r = q / (double)p; -// if(r > t1) return 0; -// if(r > t0) t0 = r; // line is clipped! -// } -// else if(p > 0){ -// r = q / (double)p; -// if(r < t0) return 0; -// if(r < t1) t1 = r; // line is clipped! -// } -// } -// } - -// def clip(self, left, right, bottom, top): -// t0, t1 = 0, 1 -// xdelta = self.x1 - self.x0 -// ydelta = self.y1 - self.y0 -// for edge in range(4): #traverse through left, right, bottom, top -// edges. -// if edge == 0: p, q = -xdelta, -(left-self.x0) -// elif edge == 1: p, q = xdelta, (right-self.x0) -// elif edge == 2: p, q = ydelta, (bottom-self.y0) -// elif edge == 3: p, q = -ydelta, -(top-self.y0) -// if p == 0 and q < 0: return [] -// if p < 0: -// r = q / float(p) -// if r > t1: return [] -// elif r > t0: t0 = r # line is clipped! -// elif p > 0: -// r = q / float(p) -// if r < t0: return [] -// elif r < t1: t1 = r # line is clipped! -// clipped_line = (self.x0 + t0*xdelta, self.y0 + t0*ydelta, -// self.x0 + t1*xdelta, self.y0 + t1*ydelta) -// return [clipped_line] diff --git a/src/c_lib/mrmethods/spinning_sidebands.c b/src/c_lib/mrmethods/spinning_sidebands.c deleted file mode 100644 index 9f83813e3..000000000 --- a/src/c_lib/mrmethods/spinning_sidebands.c +++ /dev/null @@ -1,701 +0,0 @@ - -// -// sideband_simulator.c -// -// Created by Deepansh J. Srivastava, Apr 11, 2019 -// Copyright © 2019 Deepansh J. Srivastava. All rights reserved. -// Contact email = srivastava.89@osu.edu, deepansh2012@gmail.com -// - -// Definning pi^{2,2}_{L,J} as piLJ // -#define pi01 = 0.3577708764 -#define pi21 = 0.1069044968 -#define pi41 = -0.1434274331 - -#define pi03 = 0.8485281374 -#define pi23 = -1.0141851057 -#define pi43 = -1.2850792082 -// --------------------------------- // - -#include "spinning_sidebands.h" - -// Return a vector ordered according to the fft output order. // -// @params int n - The number of points // -// @params double increment - The increment (sampling interval) // -// @returns *double values = The pointer to the fft output order vector // -inline double *__get_frequency_in_FFT_order(int n, double increment) { - double *vr_freq = createDouble1DArray(n); - int i = 0, m, positive_limit, negative_limit; - - if (n % 2 == 0) { - negative_limit = (int)(-n / 2); - positive_limit = -negative_limit - 1; - } else { - negative_limit = (int)(-(n - 1) / 2); - positive_limit = -negative_limit; - } - - for (m = 0; m <= positive_limit; m++) { - vr_freq[i] = (double)m * increment; - i++; - } - for (m = negative_limit; m < 0; m++) { - vr_freq[i] = (double)m * increment; - i++; - } - return vr_freq; -} - -// Return the p(mi, mf) transition element // -// The expression follows // -// p(mf, mi) = < mf | T10 | mf > - < mi | T10 | mi > // -// = mf - mi // -// // -// @params double mi = quantum number of the initial state // -// @params double mf = quantum number of the final state // -// @returns double p = The value // -static inline double __p__(double mf, double mi) { return (mf - mi); } - -// Return the d(mi, mf) transition element // -// The expression follows // -// d(mf, mi) = < mf | T20 | mf > - < mi | T20 | mi > // -// = sqrt(3/2)*(mf^2 - mi^2) // -// // -// @params double mi = quantum number of the initial state // -// @params double mf = quantum number of the final state // -// @returns double d = The value // -static inline double __d__(double mf, double mi) { - return 1.2247448714 * (mf * mf - mi * mi); -} - -/* - Return the dIS(mIi, mIf, mSi, mSf) transition element - The expression follows - d(mf, mi) = < mIf mSf | T10(I) T10(S) | mIf mSf > - - < mIi mSi | T10(I) T10(S) | mTi mSi > - = (mIf * mSf - mIi * mSi) - - @params double mIi = quantum number of the initial state of spin I - @params double mIf = quantum number of the final state of spin I - @params double mSi = quantum number of the initial state of spin S - @params double mSf = quantum number of the final state of spin S - @returns double dIS = The value -*/ -static inline double __dIS__(double mIf, double mIi, double mSf, double mSi) { - return mIf * mSf - mIi * mSi; -} - -// Return the f(mi, mf) transition element // -// The expression follows // -// f(mf, mi) = < mf | T30 | mf > - < mi | T30 | mi > // -// = sqrt(1/10)*[5(mf^3 - mi^3)+(1-3I(I+1))(mf-mi) // -// // -// @params double mi = quantum number of the initial state // -// @params double mf = quantum number of the final state // -// @returns double f = The value // -static inline double __f__(double mf, double mi, double spin) { - double f = 1.0 - 3.0 * spin * (spin + 1.0); - f *= (mf - mi); - f += 5.0 * (mf * mf * mf - mi * mi * mi); - f *= 0.316227766; - return f; -} - -static inline void __get_quad_ci__(double *c0, double *c2, double *c4, - double mf, double mi, double spin) { - double f = __f__(mf, mi, spin); - double p = __p__(mf, mi); - - double temp = spin * (spin + 1.0) - 0.75; - c0[0] = 0.3577708764 * temp * p + 0.8485281374 * f; - c2[0] = 0.1069044968 * temp * p + -1.0141851057 * f; - c4[0] = -0.1434274331 * temp * p + -1.2850792082 * f; -} - -// ========================================================================= // -// First order Nuclear shielding Hamiltonian in the PAS. // -// ------------------------------------------------------------------------- // -// The Hamiltonian includes the product of second rank tensor and the // -// spin transition functions. // -// ------------------------------------------------------------------------- // -static inline void getNuclearShieldingHamiltonianUptoFirstOrder( - double complex *R0, double complex *R2, double iso, double aniso, - double eta, double *transition) { - // Spin transition contribution - double scale = __p__(transition[1], transition[0]); - // printf("\n Entering CSA"); - // printf("\n Transition element %f \n", scale); - // Scaled R00 - R0[0] = iso * scale; - - // Scaled R2m containing the components of the CSA second rank tensor in // - // its principal axis frame. // - double temp = -0.4082482905 * (aniso * eta) * scale; - R2[0] += temp; // R2-2 - R2[1] += 0.0; // R2-1 - R2[2] += aniso * scale; // R2 0 - R2[3] += 0.0; // R2 1 - R2[4] += temp; // R2 2 - - // printf("R00 %f \n", creal(R0[0]) ); - // printf("R2-2 %f \n", creal(R2[0]) ); - // printf("R2-1 %f \n", creal(R2[1]) ); - // printf("R20 %f \n", creal(R2[2]) ); - // printf("R21 %f \n", creal(R2[3]) ); - // printf("R22 %f \n", creal(R2[4]) ); -} -// ========================================================================= // - -// ========================================================================= // -// First order Quadrupolar Hamiltonian in the PAS. // -// ------------------------------------------------------------------------- // -// The Hamiltonian includes the product of second rank tensor and the // -// spin transition functions. // -// ------------------------------------------------------------------------- // -static inline void getQuadrupoleHamiltonianUptoFirstOrder(double complex *R0, - double complex *R2, - double spin, - double Cq, double eta, - double *transition) { - // Spin transition contribution - double transition_d_ = __d__(transition[1], transition[0]); - - // printf("\n Entering Quad"); - // printf("\n Transition element %f \n", transition_d_); - - // Scaled R00 - R0[0] += 0.0; - - // vq is the Quadrupolar coupling constant // - // vq = 3*Cq/(2I(2I-1)), where I is the spin quantum number // - double vq = 3.0 * Cq; - double denominator = 2.0 * spin * (2.0 * spin - 1.0); - vq /= denominator; - // printf("quad coupling constant, vq %f \n", vq); - - // Scaled R2m containing the components of the quad second rank tensor in // - // its principal axis frame. // - double temp = -0.1666666667 * (vq * eta) * transition_d_; - R2[0] += temp; // R2-2 - R2[1] += 0.0; // R2-1 - R2[2] += 0.4082482905 * vq * transition_d_; // R20 - R2[3] += 0.0; // R2 1 - R2[4] += temp; // R2 2 - - // printf("R00 %f \n", creal(R0[0]) ); - // printf("R2-2 %f \n", creal(R2[0]) ); - // printf("R2-1 %f \n", creal(R2[1]) ); - // printf("R20 %f \n", creal(R2[2]) ); - // printf("R21 %f \n", creal(R2[3]) ); - // printf("R22 %f \n", creal(R2[4]) ); -} -// ========================================================================= // - -// ========================================================================= // -// Second order Quadrupolar Hamiltonian in the PAS. // -// ------------------------------------------------------------------------- // -// The Hamiltonian includes the product of second rank tensor and the // -// spin transition functions. // -// ------------------------------------------------------------------------- // -static inline void getQuadrupoleHamiltonianUptoSecondOrder( - double complex *R0, double complex *R2, double complex *R4, double spin, - double Cq, double eta, double *transition, double vo) { - // Spin transition contribution - double c0, c2, c4; - __get_quad_ci__(&c0, &c2, &c4, transition[1], transition[0], spin); - - // printf("\n Entering Quad"); - // printf("\n Transition element c0=%f, c2=%f, c4=%f \n", c0, c2, c4); - - // vq is the Quadrupolar coupling constant // - // vq = 3*Cq/(2I(2I-1)), where I is the spin quantum number // - double vq = 3.0 * Cq; - double denominator = 2.0 * spin * (2.0 * spin - 1.0); - vq /= denominator; - // printf("quad coupling constant, vq %f \n", vq); - - double scale = vq * vq / vo; - double eta2 = eta * eta; - - // Scaled R00 - R0[0] += (eta2 * 0.33333333333 + 1.0) * 0.07453559925 * scale * c0; - - // Scaled R2m containing the components of the quad second rank tensor in // - // its principal axis frame. // - double temp = -eta * 0.07273929675 * scale * c2; - R2[0] += temp; // R2-2 - R2[1] += 0.0; // R2-1 - R2[2] += 0.08908708064 * (eta2 * 0.33333333333 - 1.0) * scale * c2; // R2 0 - R2[3] += 0.0; // R2 1 - R2[4] += temp; // R2 2 - - // Scaled R2m containing the components of the quad fourth rank tensor in // - // its principal axis frame. // - temp = eta2 * 0.02777777778 * scale * c4; - double temp2 = -0.06299407883 * eta * scale * c4; - R4[0] += temp; // R4-4 - R4[2] += temp2; // R4-2 - R4[4] += 0.1195228609 * (eta2 * 0.05555555556 + 1.0) * scale * c4; // R4 0 - R4[6] += temp2; // R4 2 - R4[8] += temp; // R4 4 - - // printf("R00 %f \n", creal(R0[0]) ); - - // printf("R2-2 %f \n", creal(R2[0]) ); - // printf("R2-1 %f \n", creal(R2[1]) ); - // printf("R20 %f \n", creal(R2[2]) ); - // printf("R21 %f \n", creal(R2[3]) ); - // printf("R22 %f \n", creal(R2[4]) ); - - // printf("R4-4 %f \n", creal(R4[0]) ); - // printf("R4-2 %f \n", creal(R4[2]) ); - // printf("R40 %f \n", creal(R4[4]) ); - // printf("R42 %f \n", creal(R4[6]) ); - // printf("R44 %f \n", creal(R4[8]) ); -} -// ========================================================================= // - -// ========================================================================= // -// First order Weakly coupled Magnetic Dipole Hamiltonian in the PAS. // -// ------------------------------------------------------------------------- // -// The Hamiltonian includes the product of second rank tensor and the // -// spin transition functions in the weak coupling limit. // -// ------------------------------------------------------------------------- // -static inline void getWeaklyCoupledMagneticDipoleHamiltonianUptoFirstOrder( - double complex *R0, double complex *R2, double D, double *transition) { - // Spin transition contribution - double transition_dIS_ = __dIS__(transition[0], transition[1], 0.5, 0.5); - - // printf("\n Entering Quad"); - // printf("\n Transition element %f \n", transition_dIS_); - - // Scaled R00 - R0[0] += 0.0; - - // Scaled R2m containing the components of the magnetic dipole second rank // - // tensor in its principal axis frame. // - R2[0] += 0.0; // R2-2 - R2[1] += 0.0; // R2-1 - R2[2] += 2.0 * D * transition_dIS_; // R20 - R2[3] += 0.0; // R2 1 - R2[4] += 0.0; // R2 2 - - // printf("R00 %f \n", creal(R0[0]) ); - // printf("R2-2 %f \n", creal(R2[0]) ); - // printf("R2-1 %f \n", creal(R2[1]) ); - // printf("R20 %f \n", creal(R2[2]) ); - // printf("R21 %f \n", creal(R2[3]) ); - // printf("R22 %f \n", creal(R2[4]) ); -} -// ========================================================================= // - -static inline void wigner_rotation(int l, double *wigner, double *cos_alpha, - double *cos_gamma, double complex *scalex, - double complex *initial_vector, - double complex *final_vector) { - int i = 0, m, mp, ll = 2 * l; - double complex pha = - cos_alpha[0] - I * sqrt(1.0 - cos_alpha[0] * cos_alpha[0]); - double complex ph2 = pha; - double complex temp_inital_vector[ll + 1]; - - // copy the initial vector - for (m = 0; m <= ll; m++) { - temp_inital_vector[m] = initial_vector[m]; - } - - // scale the temp initial vector with exp[-I m alpha] - for (m = 1; m <= l; m++) { - temp_inital_vector[l + m] *= ph2; - temp_inital_vector[l - m] *= conj(ph2); - ph2 *= pha; - } - - // Apply wigner rotation to the temp inital vector - for (m = 0; m <= ll; m++) { - final_vector[m] *= scalex[0]; - for (mp = 0; mp <= ll; mp++) { - final_vector[m] += wigner[i++] * temp_inital_vector[mp]; - } - // final_vector[ll-m] = creal(final_vector[m]) - I*cimag(final_vector[m]); - // if(m%2!=0) final_vector[ll-m]*=-1.0; - } - - // for(mp=0; mp<=ll; mp++){ - // final_vector[l] += wigner[i++]*initial_vector[mp]; - // } - // cblas_zgemv(CblasRowMajor, CblasNoTrans, ll, ll, &one, wigner, l, - // &temp_inital_vector[0], 1, scalex, &final_vector[0], 1); -} - -static inline void __zero_components(double complex *R0, double complex *R2, - double complex *R4) { - int i; - R0[0] = 0.0; - for (i = 0; i <= 4; i++) { - R2[i] = 0.0; - } - for (i = 0; i <= 8; i++) { - R4[i] = 0.0; - } -} - -void __powder_averaging_setup( - int nt, double *cosAlpha, double *cosBeta, double *amp, - int space // 1 for octant, 2 for hemisphere and 4 for sphere -) { - // unsigned int n_orientations = 1; - if (space == 1) { // octant - getPolarAngleTrigOverAnOctant(nt, cosAlpha, cosBeta, amp); - } -} - -void spinning_sideband_core( - // spectrum information and related amplitude - double *spec, // The amplitude of the spectrum. - double *cpu_time_, // Execution time - double spectral_start, // The start of the frequency spectrum. - double spectral_increment, // The increment of the frequency spectrum. - int number_of_points, // Number of points on the frequency spectrum. - - double spin_quantum_number, // Spin quantum numbers - double larmor_frequency, // Larmor frequency - - // Pointer to the array of CSA tensor information in the PAS. - double *iso_n, // The isotropic chemical shift. - double *aniso_n, // The chemical shielding anisotropic. - double *eta_n, // Chemical shielding asymmetry - - // Pointer to the array of quadrupole tensor information in the PAS. - double *Cq_e, // The Cq of the quadrupole center. - double *eta_e, // The asymmetry term of the tensor. - int quadSecondOrder, // Quad theory for second order, - - // Pointer to the array of dipolar tensor information in the PAS. - double *D, // The dipolar coupling constant. - - // spin rate, spin angle and number spinning sidebands - int number_of_sidebands, // The number of spinning sidebands to evaluate - double spin_frequency, // The rotor spin frequency - double rotor_angle, // The rotor angle relative to lab-frame z-axis - - // Pointer to the transitions. transition[0] = mi and transition[1] = mf - double *transition, - - // The principal to molecular frame transformation euler angles. - // double * omega_PM, - - // powder orientation average - unsigned int n_orientations, // number of orientations - double *cosAlpha, // array of cosAlpha of orientations - double *cosBeta, // array of cosBeta of orientations - double *amp, // array of amplitude of orientations - int nt, // number of triangles along the edge of the octahedral face - unsigned int number_of_sites // number of sites in the isotopomer -) { - - // The computation of the spinning sidebands is based on the method - // described by Eden and Levitt et. al. - // `Computation of Orientational Averages in Solid-State NMR by - // Gaussian Spherical Quadrature` - // JMR, 132, 1998. https://doi.org/10.1006/jmre.1998.1427 - - // Time it - clock_t start, end; - start = clock(); - // mkl_set_num_threads( 4 ); - // fftw_init_threads(); - - // Sampled over an octant - // unsigned int n_orientations = (nt+1) * (nt+2)/2, orientation; - unsigned int ji, ii, orientation; - unsigned int size = n_orientations * number_of_sidebands; - unsigned int site; - int min_bound; - - // double* cosBeta = createDouble1DArray( n_orientations ); - // double* sinBeta = createDouble1DArray( n_orientations ); - // double* cosAlpha = createDouble1DArray( n_orientations ); - // double* sinAlpha = createDouble1DArray( n_orientations ); - - // double* amp = createDouble1DArray(n_orientations); - double *amp_temp = createDouble1DArray(n_orientations); - - int m, mp, step, i, allow_second_order_quad = 0; - double tau, wrt, pht, spin_angular_freq, scale; - // double number_of_sidebands_inverse = 1.0/((double) (number_of_sidebands)); - double spectral_increment_inverse = 1.0 / spectral_increment; - double iso_n_, aniso_n_, eta_n_, Cq_e_, eta_e_, d_; - - double complex *pre_phase = - createDoubleComplex1DArray(number_of_sidebands * 9); - double complex *amp1; - double *MR_full_DLM_2 = createDouble1DArray(25); - double *MR_full_DLM_4 = createDouble1DArray(81); - - // set the rotor angle to 0 degree if the sample spin frequency is - // less than 1 mHz - if (spin_frequency < 1e-3) { - spin_frequency = 1e6; - rotor_angle = 0.0; - } - - // Angle setup - // getPolarAngleTrigOverAnOctant(nt, cosAlpha, cosBeta, amp); - - // __powder_averaging_setup(nt, &cosAlpha[0], &cosBeta[0], &[0], 1); // 1 - // for octant, 2 for hemisphere and 4 for sphere Normalize the amplitudes - // for(ii=0; ii 0.5) { - getQuadrupoleHamiltonianUptoFirstOrder( - &R0[0], &R2[0], spin_quantum_number, Cq_e_, eta_e_, transition); - if (quadSecondOrder == 1) { - allow_second_order_quad = 1; - getQuadrupoleHamiltonianUptoSecondOrder( - &R0[0], &R2[0], &R4[0], spin_quantum_number, Cq_e_, eta_e_, - transition, larmor_frequency); - } - } - - // Equation [39] in the refernce above. - // - // w_cs^{m}(O_MR) = iso delta(m,0) + sum_{m', m" =-2}^{2} A[m"] - // D^2_{m"m'}(O_PM) D^2_{m'm}(O_MR) d^2_{m'm}(b_RL) - // - - for (orientation = 0; orientation < n_orientations; orientation++) { - double *sideband_amplitude_f = - &sideband_amplitude[number_of_sidebands * orientation]; - - wigner_d_matrix(MR_full_DLM_2, 2, &cosBeta[orientation], 1); - - // ------------------------------------------------------------------- // - // Computing wigner rotation upto lab frame // - - // Second rank wigner rotation to rotor frame - wigner_rotation(2, MR_full_DLM_2, &cosAlpha[orientation], &zero_f, &zero, - &R2[0], &w_cs_2[0]); - - // Second rank wigner rotation to lab frame cosidering alpha=gamma=0 - // vzMul( 5, &w_cs_2[0], &rotor_lab_2[0], &w_cs_2[0] ); - for (i = 0; i < 5; i++) { - w_cs_2[i] *= rotor_lab_2[i]; - } - - // Fourth rank Wigner Rotation - if (allow_second_order_quad) { - - wigner_d_matrix(MR_full_DLM_4, 4, &cosBeta[orientation], 1); - wigner_rotation(4, MR_full_DLM_4, &cosAlpha[orientation], &zero_f, - &zero, R4, w_cs_4); - - // vzMul( 9, &w_cs_4[0], &rotor_lab_4[0], &w_cs_4[0] ); - for (i = 0; i < 9; i++) { - w_cs_4[i] *= rotor_lab_4[i]; - } - } - - // ------------------------------------------------------------------- // - // Computing phi = w_cs * I 2pi [(exp(i m wr t) - 1)/(i m wr)] // - // -------------- pre_phase------------ // - // The pre_phase is calculated before. // - - cblas_zgemv(CblasRowMajor, CblasTrans, 5, number_of_sidebands, &one, - &pre_phase[2 * number_of_sidebands], number_of_sidebands, - &w_cs_2[0], 1, &zero, phi, 1); - - if (allow_second_order_quad) { - cblas_zgemv(CblasRowMajor, CblasTrans, 9, number_of_sidebands, &one, - pre_phase, number_of_sidebands, &w_cs_4[0], 1, &one, phi, - 1); - } - - // Computing exp(phi) ------------------------------------------------ // - vzExp(number_of_sidebands, phi, phi); - - // Compute the fft --------------------------------------------------- // - fftw_execute(plan); - - // ------------------------------------------------------------------- // - // Taking the square of the the fft ampitudes - for (m = 0; m < number_of_sidebands; m++) { - amp1 = &side_band[m]; - sideband_amplitude_f[m] = - creal(amp1[0]) * creal(amp1[0]) + cimag(amp1[0]) * cimag(amp1[0]); - } - - // Multiplying the square amplitudes with the power scheme weights. -- // - // And Normalizing with the number of phase steps squared ------------ // - cblas_dscal(number_of_sidebands, amp[orientation], sideband_amplitude_f, - 1); - - // adding the w_cs[0] term to the sideband frequencies before binning the - // spectrum. - local_frequency[orientation] = - shift + - (creal(w_cs_2[2]) + creal(w_cs_4[4]) + R0[0] - spectral_start) / - spectral_increment; - } - - // --------------------------------------------------------------------- // - // Calculating the tent for every sideband // - // Allowing only sidebands that are within the spectral bandwidth ------ // - for (i = 0; i < number_of_sidebands; i++) { - min_bound = (int)(vr_freq[i] + iso_n[site] / spectral_increment); - if (min_bound >= -number_of_points / 2 - 1 && - min_bound < number_of_points / 2 + 1) { - ii = 0; - ji = i; - while (ii < n_orientations) { - // ptr_ptr[ii] = &sideband_amplitude[ji]; - amp_temp[ii] = sideband_amplitude[ji]; - ii++; - ji += number_of_sidebands; - }; - - // cblas_dcopy(n_orientations, &sideband_amplitude[0], - // number_of_sidebands, &[0][0], 1); - powderAverageWithTentingSchemeOverOctant(spec_site_ptr, local_frequency, - nt, amp_temp, &vr_freq[i], - number_of_points); - } - } - } - - // clean up ------------------------------- // - fftw_destroy_plan(plan); - fftw_free(phi); - fftw_free(side_band); - destroyDoubleComplex1DArray(pre_phase); - destroyDouble1DArray(MR_full_DLM_2); - destroyDouble1DArray(MR_full_DLM_4); - destroyDouble1DArray(vr_freq); - destroyDouble1DArray(local_frequency); - destroyDouble1DArray(sideband_amplitude); - destroyDouble1DArray(amp_temp); - - end = clock(); - cpu_time_[0] += ((double)(end - start)) / (double)CLOCKS_PER_SEC; -} diff --git a/src/c_lib/sandbox/sandbox.pxd b/src/c_lib/sandbox/sandbox.pxd new file mode 100644 index 000000000..4ce80d81b --- /dev/null +++ b/src/c_lib/sandbox/sandbox.pxd @@ -0,0 +1,95 @@ +from libcpp cimport bool as bool_t + +cdef extern from "angular_momentum.h": + void __wigner_d_matrix(int l, int n, double *angle, double *wigner) + + void __wigner_d_matrix_cosine(int l, int n, double *cos_angle, + double *wigner) + + void __wigner_rotation(int l, int n, double *wigner, double *cos_alpha, + double complex *R_in, double complex *R_out) + + void __wigner_dm0_vector(int l, double beta, double *R_out) + + +cdef extern from "powder_setup.h": + void __powder_averaging_setup( + int nt, + double *cosAlpha, + double *cosBeta, + double *amp, + int space) # 1 for octant, 2 for hemisphere and 4 for sphere + +cdef extern from "interpolation.h": + void triangle_interpolation( + double *freq1, + double *freq2, + double *freq3, + double *amp, + double *spec, + int *points) + +cdef extern from "octahedron.h": + void octahedronInterpolation( + double *spec, + double *freq, + int nt, + double *amp, + int stride, + int m) + +cdef extern from "mrsimulator.h": + void __get_components( + int number_of_sidebands, + double spin_frequency, + double complex *pre_phase) + + ctypedef struct MRS_plan + + MRS_plan *MRS_create_plan( + unsigned int geodesic_polyhedron_frequency, + int number_of_sidebands, + double sample_rotation_frequency_in_Hz, + double rotor_angle_in_rad, double increment, + bool_t allow_fourth_rank) + + +cdef extern from "isotopomer_ravel.h": + ctypedef struct isotopomer_ravel: + int number_of_sites; # Number of sites + float spin; # The spin quantum number + double larmor_frequency; # Larmor frequency (MHz) + double *isotropic_chemical_shift_in_Hz; # Isotropic chemical shift (Hz) + double *shielding_anisotropy_in_Hz; # Nuclear shielding anisotropy (Hz) + double *shielding_asymmetry; # Nuclear shielding asymmetry parameter + double *shielding_orientation; # Nuclear shielding PAS to CRS euler angles (rad.) + double *quadrupolar_constant_in_Hz; # Quadrupolar coupling constant (Hz) + double *quadrupolar_asymmetry; # Quadrupolar asymmetry parameter + double *quadrupolar_orientation; # Quadrupolar PAS to CRS euler angles (rad.) + double *dipolar_couplings; # dipolar coupling stored as list of lists + + ctypedef struct isotopomers_list: + isotopomer_ravel *isotopomers + +cdef extern from "spinning_sidebands.h": + void spinning_sideband_core( + # spectrum information and related amplitude + double * spec, + double spectral_start, + double spectral_increment, + int number_of_points, + + isotopomer_ravel *ravel_isotopomer, + + int quadSecondOrder, # Quad theory for second order, + int remove_second_order_quad_iso, # remove the isotropic contribution from the + # second order quad Hamiltonian. + + # spin rate, spin angle and number spinning sidebands + int number_of_sidebands, + double sample_rotation_frequency_in_Hz, + double rotor_angle_in_rad, + + # The transition as transition[0] = mi and transition[1] = mf + double *transition, + int geodesic_polyhedron_frequency) diff --git a/src/c_lib/sandbox/sandbox.pyx b/src/c_lib/sandbox/sandbox.pyx new file mode 100644 index 000000000..87ea118be --- /dev/null +++ b/src/c_lib/sandbox/sandbox.pyx @@ -0,0 +1,327 @@ +cimport sandbox as clib + +cimport numpy as np +import numpy as np +import cython + +__author__ = "Deepansh J. Srivastava" +__email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] + +# @cython.boundscheck(False) +# @cython.wraparound(False) +# def MRS_plan(int geodesic_polyhedron_frequency, +# int number_of_sidebands) + +@cython.boundscheck(False) +@cython.wraparound(False) +def pre_phase_components(int number_of_sidebands, double sample_rotation_frequency_in_Hz): + r""" + + """ + cdef int n1 = 9 * number_of_sidebands + cdef np.ndarray[complex] pre_phase = np.zeros(n1, dtype=np.complex128) + clib.__get_components(number_of_sidebands, sample_rotation_frequency_in_Hz, &pre_phase[0]) + return pre_phase.reshape(9, number_of_sidebands) + + +@cython.boundscheck(False) +@cython.wraparound(False) +def wigner_dm0_vector(int l, double beta): + r""" + + """ + cdef int n1 = (2 * l + 1) + cdef np.ndarray[double] R_out = np.zeros(n1, dtype=np.float64) + clib.__wigner_dm0_vector(l, beta, &R_out[0]) + return R_out + + +@cython.boundscheck(False) +@cython.wraparound(False) +def wigner_rotation(int l, np.ndarray[complex] R_in, + cos_alpha = None, cos_beta = None, + wigner_matrix=None, phase_alpha=None): + r""" + + """ + cdef int n1 = 2 * l + 1 + cdef np.ndarray[double, ndim=1] wigner, cos_alpha_c, cos_beta_c + cos_alpha_c = np.asarray(cos_alpha, dtype=np.float64) + + if wigner_matrix is None: + n = cos_beta.size + wigner = np.empty(n1**2 * n) + cos_beta_c = np.asarray(cos_beta, dtype=np.float64) + clib.__wigner_d_matrix_cosine(l, n, &cos_beta_c[0], &wigner[0]) + else: + n = wigner_matrix.shape[0] + wigner = np.asarray(wigner_matrix.ravel(), dtype=np.float64) + + # if phase_alpha is None: + # alpha = np.arccos(cos_alpha) + # temp = np.tile(np.arange(9)-4.0, n).reshape(n,9) + # phase_alpha = np.exp(-1j*temp*alpha[:, np.newaxis]) + + # cdef np.ndarray[complex] phase_alpha_c = np.asarray(phase_alpha.ravel(), dtype=np.complex128) + + cdef np.ndarray[complex] R_out = np.zeros(n1*n, dtype=np.complex128) + + clib.__wigner_rotation(l, n, &wigner[0], + &cos_alpha_c[0], &R_in[0], &R_out[0]) + return R_out.reshape(n, n1) + + +@cython.boundscheck(False) +@cython.wraparound(False) +def wigner_d_matrix_cosines(int l, np.ndarray[double] cos_beta): + r""" + Returns a :math:`(2l+1) \times (2l+1)` wigner-d(cos_beta) matrix for rank $l$ at + a given `cos_beta`. Currently only rank l=2 and l=4 is supported. + + If `cos_beta` is a 1D-numpy array of size n, a + `n x (2l+1) x (2l+1)` matrix is returned instead. + + :ivar l: The angular momentum quantum number. + :ivar cos_beta: An 1D numpy array or a scalar representing the cosine of $\beta$ angles. + """ + n1 = (2 * l + 1) + cdef int n = cos_beta.size + cdef np.ndarray[double, ndim=1] wigner = np.empty(n * n1**2) + clib.__wigner_d_matrix_cosine(l, n, &cos_beta[0], &wigner[0]) + return wigner.reshape(n, n1, n1) + + +@cython.boundscheck(False) +@cython.wraparound(False) +def cosine_of_polar_angles_and_amplitudes(int geodesic_polyhedron_frequency=72): + r""" + Calculate the direction cosines and the related amplitudes for + the positive quadrant of the sphere. The direction cosines corresponds to + angle $\alpha$ and $\beta$, where $\alpha$ is the azimuthal angle and + $\beta$ is the polar angle. The amplitudes are evaluated as + + `amp = 1/r**3` + + where `r` is the distance from the origin to the face of the unit + octahedron in the positive quadrant along the line given by the values of + $\alpha$ and $\beta$. + + :ivar geodesic_polyhedron_frequency: + The value is an integer which represents the frequency of class I + geodesic polyhedra. These polyhedra are used in calculating the + spherical average. Presently we only use octahedral as the frequency1 + polyhedra. As the frequency of the geodesic polyhedron increases, the + polyhedra approach a sphere geometry. For line-shape simulation, a higher + frequency will result in a better powder averaging. + The default value is 72. + Read more on the `Geodesic polyhedron + `_. + + :return cos_alpha: The cosine of the azimuthal angle. + :return cos_beta: The cosine of the polar angle. + :return amp: The amplitude at the given $\alpha$ and $\beta$. + """ + nt = geodesic_polyhedron_frequency + cdef unsigned int n_orientations = int((nt+1) * (nt+2)/2) + + cdef np.ndarray[double] cos_alpha = np.empty(n_orientations, dtype=np.float64) + cdef np.ndarray[double] cos_beta = np.empty(n_orientations, dtype=np.float64) + cdef np.ndarray[double] amp = np.empty(n_orientations, dtype=np.float64) + + clib.__powder_averaging_setup(nt, &cos_alpha[0], &cos_beta[0], &[0], 1) + + return cos_alpha, cos_beta, amp + + +@cython.boundscheck(False) +@cython.wraparound(False) +def octahedronInterpolation(np.ndarray[double] spec, np.ndarray[double, ndim=2] freq, int nt, np.ndarray[double, ndim=2] amp, int stride=1): + cdef int i + cdef int number_of_sidebands = amp.shape[0] + for i in range(number_of_sidebands): + clib.octahedronInterpolation(&spec[0], &freq[i,0], nt, &[i,0], stride, spec.size) + + +@cython.boundscheck(False) +@cython.wraparound(False) +def triangle_interpolation(vector, np.ndarray[double, ndim=1] spectrum_amp, + double amp=1): + r""" + Given a vector of three points, this method interpolates the + between the points to form a triangle. The height of the triangle is given + as `2.0/(f[2]-f[1])` where `f` is the array `vector` sorted in an ascending + order. + + :ivar vector: 1-D array of three points. + :ivar spectrum_amp: A numpy array of amplitudes. This array is updated. + :ivar offset: A float specifying the offset. The points from array `vector` + are incremented or decremented based in this values. The + default value is 0. + :ivar amp: A float specifying the offset. The points from array `vector` + are incremented or decremented based in this values. The + default value is 0. + """ + cdef np.ndarray[int, ndim=1] points = np.asarray([spectrum_amp.size], dtype=np.int32) + cdef np.ndarray[double, ndim=1] f_vector = np.asarray(vector, dtype=np.float64) + + cdef double *f1 = &f_vector[0] + cdef double *f2 = &f_vector[1] + cdef double *f3 = &f_vector[2] + + cdef np.ndarray[double, ndim=1] amp_ = np.asarray([amp]) + + clib.triangle_interpolation(f1, f2, f3, &_[0], &spectrum_amp[0], &points[0]) + + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _one_d_simulator( + # spectrum information + double reference_offset, + double increment, + int number_of_points, + + float spin_quantum_number = 0.5, + float larmor_frequency = 0.0, + + # CSA tensor information + isotropic_chemical_shift = None, + chemical_shift_anisotropy = None, + chemical_shift_asymmetry = None, + + # quad tensor information + quadrupolar_coupling_constant = None, + quadrupolar_asymmetry = None, + second_order_quad = 1, + remove_second_order_quad_iso = 0, + + # dipolar coupling + D = None, + + # spin rate, spin angle and number spinning sidebands + int number_of_sidebands = 128, + double sample_rotation_frequency_in_Hz = 0.0, + rotor_angle = None, + + m_final = 0.5, + m_initial = -0.5, + + # Euler angle -> principal to molecular frame + # omega_PM=None, + + # Euler angles for powder averaging scheme + int geodesic_polyhedron_frequency=90): + + nt = geodesic_polyhedron_frequency + if isotropic_chemical_shift is None: + isotropic_chemical_shift = 0 + isotropic_chemical_shift = np.asarray([isotropic_chemical_shift], dtype=np.float64).ravel() + cdef number_of_sites = isotropic_chemical_shift.size + cdef np.ndarray[double, ndim=1] isotropic_chemical_shift_c = isotropic_chemical_shift + + if spin_quantum_number > 0.5 and larmor_frequency == 0.0: + raise Exception("'larmor_frequency' is required for quadrupole spins.") + + # Shielding anisotropic values + if chemical_shift_anisotropy is None: + chemical_shift_anisotropy = np.ones(number_of_sites, dtype=np.float64).ravel() #*1e-4*increment + else: + chemical_shift_anisotropy = np.asarray([chemical_shift_anisotropy], dtype=np.float64).ravel() + # chemical_shift_anisotropy[np.where(chemical_shift_anisotropy==0.)] = 1e-4*increment + if chemical_shift_anisotropy.size != number_of_sites: + raise Exception("Number of shielding anisotropies are not consistent with the number of spins.") + cdef np.ndarray[double, ndim=1] chemical_shift_anisotropy_c = chemical_shift_anisotropy + + # Shielding asymmetry values + if chemical_shift_asymmetry is None: + chemical_shift_asymmetry = np.zeros(number_of_sites, dtype=np.float64).ravel() + else: + chemical_shift_asymmetry = np.asarray([chemical_shift_asymmetry], dtype=np.float64).ravel() + if chemical_shift_asymmetry.size != number_of_sites: + raise Exception("Number of shielding asymmetry are not consistent with the number of spins.") + cdef np.ndarray[double, ndim=1] chemical_shift_asymmetry_c = chemical_shift_asymmetry + + # Quad coupling constant + if quadrupolar_coupling_constant is None: + quadrupolar_coupling_constant = np.zeros(number_of_sites, dtype=np.float64).ravel() + else: + quadrupolar_coupling_constant = np.asarray([quadrupolar_coupling_constant], dtype=np.float64).ravel() + if quadrupolar_coupling_constant.size != number_of_sites: + raise Exception("Number of quad coupling constants are not consistent with the number of spins.") + cdef np.ndarray[double, ndim=1] quadrupolar_coupling_constant_c = quadrupolar_coupling_constant + + # Quad asymmetry value + if quadrupolar_asymmetry is None: + quadrupolar_asymmetry = np.zeros(number_of_sites, dtype=np.float64).ravel() + else: + quadrupolar_asymmetry = np.asarray([quadrupolar_asymmetry], dtype=np.float64).ravel() + if quadrupolar_asymmetry.size != number_of_sites: + raise Exception("Number of quad asymmetry are not consistent with the number of spins.") + cdef np.ndarray[double, ndim=1] quadrupolar_asymmetry_c = quadrupolar_asymmetry + + + # Dipolar coupling constant + if D is None: + D = np.zeros(number_of_sites, dtype=np.float64).ravel() + else: + D = np.asarray([D], dtype=np.float64).ravel() + if D.size != number_of_sites: + raise Exception("Number of dipolar coupling are not consistent with the number of spins.") + cdef np.ndarray[double, ndim=1] D_c = D + + if rotor_angle is None: + rotor_angle = 54.735 + cdef double rotor_angle_in_rad_c = np.pi*rotor_angle/180. + + cdef second_order_quad_c = second_order_quad + + cdef np.ndarray[double, ndim=1] transition_c = np.asarray([m_initial, m_final], dtype=np.float64) + + cdef np.ndarray[double, ndim=1] amp = np.zeros(number_of_points * number_of_sites) + + cdef np.ndarray[double] ori_n = np.zeros(3*number_of_sites, dtype=np.float64) + cdef np.ndarray[double] ori_e = np.zeros(3*number_of_sites, dtype=np.float64) + + cdef clib.isotopomer_ravel isotopomer_struct + + isotopomer_struct.number_of_sites = number_of_sites + isotopomer_struct.spin = spin_quantum_number + isotopomer_struct.larmor_frequency = larmor_frequency + + isotopomer_struct.isotropic_chemical_shift_in_Hz = &isotropic_chemical_shift_c[0] + isotopomer_struct.shielding_anisotropy_in_Hz = &chemical_shift_anisotropy_c[0] + isotopomer_struct.shielding_asymmetry = &chemical_shift_asymmetry_c[0] + isotopomer_struct.shielding_orientation = &ori_n[0] + + isotopomer_struct.quadrupolar_constant_in_Hz = &quadrupolar_coupling_constant_c[0] + isotopomer_struct.quadrupolar_asymmetry = &quadrupolar_asymmetry_c[0] + isotopomer_struct.quadrupolar_orientation = &ori_e[0] + + isotopomer_struct.dipolar_couplings = &D_c[0] + + cdef int remove_second_order_quad_iso_c = remove_second_order_quad_iso + clib.spinning_sideband_core( + # spectrum information and related amplitude + &[0], + reference_offset, + increment, + number_of_points, + + &isotopomer_struct, + + second_order_quad_c, + remove_second_order_quad_iso_c, + + # spin rate, spin angle and number spinning sidebands + number_of_sidebands, + sample_rotation_frequency_in_Hz, + rotor_angle_in_rad_c, + + &transition_c[0], + geodesic_polyhedron_frequency) + + + freq = np.arange(number_of_points)*increment + reference_offset + + return freq, amp diff --git a/src/mrsimulator/__main__.py b/src/mrsimulator/__main__.py new file mode 100644 index 000000000..a4e8a42e7 --- /dev/null +++ b/src/mrsimulator/__main__.py @@ -0,0 +1,495 @@ +# -*- coding: utf-8 -*- +import base64 +import datetime +import io +import json +import os + +import dash_core_components as dcc +import dash_html_components as html +import flask +import numpy as np +import plotly.graph_objs as go +from dash import Dash +from dash.dependencies import Input +from dash.dependencies import Output +from dash.dependencies import State + +from mrsimulator import Simulator +from mrsimulator.methods import one_d_spectrum +from mrsimulator.widgets import direct_dimension_setup +from mrsimulator.widgets import display_isotopomers +from mrsimulator.widgets import plot_object_widget +from mrsimulator.widgets import spectrum_object_widget +from mrsimulator.widgets import top_bar +import csdmpy as cp + + +__author__ = "Deepansh J. Srivastava" +__email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] + + +external_stylesheets = [ + ( + "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/" + "materialize.min.css" + ) +] + +external_scripts = [ + ( + "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/" + "materialize.min.js" + ) +] +colors = {"background": "#e2e2e2", "text": "#585858"} + +__title__ = "mrsimulator" +__sub_title__ = "A web application framework for NMR lineshape simulation." + + +class mrsimulatorWebApp: + __slots__ = "_filename" + + def __init__(self, app, simulator): + self._filename = "" + app.layout = html.Div( + className="container", + style={ + "color": "#585858", + "background-image": "linear-gradient(#fdfefe, #f4f6f7, #fbfcfc)", + "width": "200%", + }, + children=[ + dcc.ConfirmDialog( + id="confirm", message="Cannot process the current request." + ), + html.H1( + children=__title__, + style={"textAlign": "center", "color": colors["text"]}, + ), + html.Div( + children=__sub_title__, + style={"textAlign": "center", "color": colors["text"]}, + ), + html.Hr(), + *top_bar(), + html.Hr(), + html.Div( + className="row", + children=[ + html.Div( + className="col s12 m12 l7", children=plot_object_widget() + ), + html.Div( + className="col s12 m12 l5", + children=spectrum_object_widget(direct_dimension_setup()), + ), + ], + # style={"width": "100%", "height": "100%"}, + ), + html.Hr(), + html.Div(id="isotopomer_computed_log"), + ], + ) + + @app.callback( + [Output("download_csv", "href"), Output("download_csdf", "href")], + [Input("nmr_spectrum", "figure")], + [State("filename_dataset", "children"), State("spectrum_id", "children")], + ) + def update_link(value, filename_dataset, spectrum_id): + """Update the csv download link when the plot is refreshed.""" + name_ = os.path.splitext(str(filename_dataset))[0] + return [ + "/dash/urlToDownload?name={0}&nuclei={1}&id=csv".format( + name_, spectrum_id + ), + "/dash/urlToDownload?name={0}&nuclei={1}&id=csdf".format( + name_, spectrum_id + ), + ] + + @app.server.route("/dash/urlToDownload") + def download(): + # creating a dynamic csv or file here using `StringIO` + file_ = flask.request.args.get("name") + nuclei = flask.request.args.get("nuclei") + id_ = flask.request.args.get("id") + str_io = io.StringIO() + + print("id", id_) + + if id_ == "csv": + writer = np.asarray([simulator._freq.value, simulator._amp]).T + _header_ = ( + ("\n {0} from file {1}.json\nfrequency / kHz, amplitudes\n") + ).format(nuclei, file_) + + # save file as csv + np.savetxt(str_io, writer, fmt="%f", delimiter=",", header=_header_) + + mem = io.BytesIO() + mem.write(str_io.getvalue().encode("utf-8")) + mem.seek(0) + str_io.close() + name_ = "_".join([file_, nuclei]) + return flask.send_file( + mem, + mimetype="text/csv", + attachment_filename=f"{name_}.csv", + as_attachment=True, + ) + print(simulator.spectrum) + if id_ == "csdf": + new = cp.new() + dimension = { + "type": "linear", + "count": simulator._spectrum_c["number_of_points"], + "increment": "{0} Hz".format( + simulator._spectrum_c["spectral_width"] + / simulator._spectrum_c["number_of_points"] + ), + "coordinates_offset": "{0} Hz".format( + simulator._spectrum_c["reference_offset"] + ), + "complex_fft": True, + } + dependent_variable = { + "type": "internal", + "quantity_type": "scalar", + "numeric_type": "float64", + "components": [simulator._amp], + } + new.add_dimension(dimension) + new.add_dependent_variable(dependent_variable) + new.dependent_variables[0].encoding = "base64" + + new.save(output_device=str_io) + + mem = io.BytesIO() + mem.write(str_io.getvalue().encode("utf-8")) + mem.seek(0) + str_io.close() + name_ = "_".join([file_, nuclei]) + return flask.send_file( + mem, + mimetype="json", + attachment_filename=f"{name_}.csdf", + as_attachment=True, + ) + + @app.callback( + Output("isotopomer_computed_log", "children"), + [Input("isotope_id", "value")], + ) + def _update_isotopomers_table_log(value): + return display_isotopomers(value, simulator.isotopomers) + + # @app.callback( + # Output('tabs_content', 'children'), + # [Input('tabs', 'value')] + # ) + # def update_tab(value): + # if value == 'direct_dimension': + # return direct_dimension_setup() + + @app.callback( + [Output("nmr_spectrum", "figure")], + [ + # Input('confirm', 'submit_n_clicks'), + Input("spinning_frequency_in_kHz_coarse", "value"), + Input("spinning_frequency_in_kHz_fine", "value"), + Input("number_of_points", "value"), + Input("frequency_bandwidth_coarse", "value"), + Input("frequency_bandwidth_fine", "value"), + Input("reference_offset_coarse", "value"), + Input("reference_offset_fine", "value"), + Input("magnetic_flux_density", "value"), + # Input('MAS_switch', 'value'), + Input("isotope_id", "value"), + ], + ) + def _update_plot( + # submit_n_clicks, + spinning_frequency_in_kHz_coarse, + spinning_frequency_in_kHz_fine, + number_of_points, + frequency_bandwidth_coarse, + frequency_bandwidth_fine, + reference_offset_coarse, + reference_offset_fine, + magnetic_flux_density, + # MAS_switch, + isotope_id, + ): + """ + The method creates a new spectrum dictionary based on the inputs + and re-computes the NMR lineshape. + """ + + # calculating frequency_bandwidth + frequency_bandwidth = frequency_bandwidth_coarse + frequency_bandwidth_fine + + # exit when the following conditions are True + if ( + number_of_points == 0 + or frequency_bandwidth == 0 + or isotope_id in ["", None] + ): + return empty_plot() + + # calculating spin_frequency + spin_frequency = ( + spinning_frequency_in_kHz_coarse + spinning_frequency_in_kHz_fine + ) + + reference_offset = reference_offset_coarse + reference_offset_fine + + # if MAS_switch: + rotor_angle_in_degree = 54.735 + # else: + # rotor_angle_in_degree = 0 + + simulator.spectrum = { + "direct_dimension": { + "isotope": isotope_id, + "magnetic_flux_density": str(magnetic_flux_density) + " T", + "rotor_frequency": str(spin_frequency) + " kHz", + "rotor_angle": str(rotor_angle_in_degree) + " deg", + "number_of_points": 2 ** number_of_points, + "spectral_width": str(frequency_bandwidth) + " kHz", + "reference_offset": str(reference_offset) + " kHz", + } + } + + freq, amp = simulator.run(one_d_spectrum) + freq = freq.to("kHz") + data_spinning = go.Scatter( + x=freq, y=amp / amp.max(), mode="lines", opacity=1.0, name=isotope_id + ) + + x_label = str(isotope_id + f" frequency / {freq.unit}") + + return [ + { + "data": [data_spinning], + "layout": go.Layout( + xaxis={ + "type": "linear", + "title": x_label, + "ticks": "outside", + "showline": True, + "autorange": True, + }, + yaxis={ + "type": "linear", + "title": "arbitrary unit", + "ticks": "outside", + "showline": True, + "autorange": True, + }, + autosize=True, + # 'easing': 'quad-in-out'}, + transition={"duration": 500}, + margin={"l": 50, "b": 40, "t": 5, "r": 5}, + # legend={'x': 0, 'y': 1}, + hovermode="closest", + ), + } + ] + + @app.callback( + [ + Output("filename_dataset", "children"), + Output("data_time", "children"), + Output("error_message", "children"), + Output("isotope_widget_id", "children"), + ], + [Input("upload_data", "contents")], + [State("upload_data", "filename"), State("upload_data", "last_modified")], + ) + def _update_isotopomers(content, filename, date): + """Update the isotopomers when a new file is imported.""" + # FIRST = False + children, success = parse_contents(simulator, content, filename, date) + + if success: + nuclei = [ + {"label": site_iso, "value": site_iso} + for site_iso in simulator.isotope_list + ] + + if len(simulator.isotope_list) >= 1: + value = simulator.isotope_list[0] + else: + value = "" + isotope_id = [ + dcc.Dropdown( + id="isotope_id", + options=nuclei, + value=value, + style={ + "display": "block", + "margin-left": "auto", + "margin-right": "auto", + "width": "auto", + }, + ) + ] + else: + simulator.isotopomers = [] + isotope_id = [ + dcc.Dropdown( + id="isotope_id", + style={ + "display": "block", + "margin-left": "auto", + "margin-right": "auto", + "width": "auto", + }, + ) + ] + return [children[0], children[1], children[2], isotope_id] + + @app.callback( + Output("Magnetic_flux_density_output_container", "children"), + [Input("magnetic_flux_density", "value")], + ) + def _update_magnetic_flux_density(value): + """Update the value of magnetic flux density.""" + return "Magnetic flux density {0} T @ {1} MHz".format( + value, "{0:.2f}".format(42.57747892 * value) + ) + + @app.callback(Output("spectrum_id", "children"), [Input("isotope_id", "value")]) + def _update_spectrum_title(value): + """Update the title of the plot.""" + if value is None: + return "Spectrum" + return "{} spectrum".format(value) + + @app.callback( + Output("number_of_points_output_container", "children"), + [Input("number_of_points", "value")], + ) + def _update_number_of_points(value): + """Update the number of points.""" + return "Number of points {}".format(2 ** value) + + @app.callback( + Output("spinning_frequency_output_container", "children"), + [ + Input("spinning_frequency_in_kHz_fine", "value"), + Input("spinning_frequency_in_kHz_coarse", "value"), + ], + ) + def _update_rotor_frequency(value1, value2): + """Update the rotor spin frequency.""" + return "Magic angle spinning frequency {} kHz".format(value1 + value2) + + @app.callback( + Output("reference_offset_output_container", "children"), + [ + Input("reference_offset_fine", "value"), + Input("reference_offset_coarse", "value"), + ], + ) + def _update_reference_offset(value1, value2): + """Update the reference offset.""" + return "Reference offset {} kHz".format(value1 + value2) + + @app.callback( + Output("frequency_bandwidth_output_container", "children"), + [ + Input("frequency_bandwidth_fine", "value"), + Input("frequency_bandwidth_coarse", "value"), + ], + ) + def _update_frequency_bandwidth(value1, value2): + """Update the spectral width.""" + return "Spectral width {} kHz".format(value1 + value2) + + +# ['linear', 'quad', 'cubic', 'sin', 'exp', 'circle', +# 'elastic', 'back', 'bounce', 'linear-in', 'quad-in', +# 'cubic-in', 'sin-in', 'exp-in', 'circle-in', 'elastic-in', +# 'back-in', 'bounce-in', 'linear-out', 'quad-out', +# 'cubic-out', 'sin-out', 'exp-out', 'circle-out', +# 'elastic-out', 'back-out', 'bounce-out', 'linear-in-out', +# 'quad-in-out', 'cubic-in-out', 'sin-in-out', 'exp-in-out', +# 'circle-in-out', 'elastic-in-out', 'back-in-out', +# 'bounce-in-out'] + + +def empty_plot(): + data = go.Scatter(x=[-1, 1], y=[0, 0], text="", mode="lines", opacity=1.0) + return [ + { + "data": [data], + "layout": go.Layout( + xaxis={ + "type": "linear", + "title": "frequency / kHz", + "ticks": "outside", + "showline": True, + "autorange": True, + }, + yaxis={ + "type": "linear", + "title": "arbitrary unit", + "ticks": "outside", + "showline": True, + "autorange": True, + }, + autosize=True, + transition={"duration": 500}, + margin={"l": 50, "b": 40, "t": 5, "r": 5}, + # legend={'x': 0, 'y': 1}, + hovermode="closest", + ), + } + ] + + +def parse_contents(simulator, contents, filename, date): + try: + if "json" in filename: + content_string = contents.split(",")[1] + decoded = base64.b64decode(content_string) + parse = json.loads(str(decoded, encoding="UTF-8"))["isotopomers"] + # print(parse) + simulator.isotopomers = parse + return ( + [ + filename, + datetime.datetime.fromtimestamp(date), + "Select a JSON serialized isotopomers file.", + ], + True, + ) + + else: + return ( + ["", "", "A JSON file with valid list of isotopomers is required."], + False, + ) + + except Exception: + if FIRST: + return (["", "", "Select a JSON serialized isotopomers file."], False) + else: + return ["", "", "There was an error reading the file."], False + + +if __name__ == "__main__": + app = Dash(__name__) + for css in external_stylesheets: + app.css.append_css({"external_url": css}) + for js in external_scripts: + app.scripts.append_script({"external_url": js}) + + FIRST = True + simulator = Simulator() + mrsimulatorWebApp(app, simulator) + app.run_server(debug=True) diff --git a/test_files/mas.json b/src/mrsimulator/examples/mas.json similarity index 90% rename from test_files/mas.json rename to src/mrsimulator/examples/mas.json index 18149bcc9..02689f810 100644 --- a/test_files/mas.json +++ b/src/mrsimulator/examples/mas.json @@ -3,7 +3,7 @@ { "sites": [ { - "isotope_symbol": "1H", + "isotope": "1H", "isotropic_chemical_shift": "0 ppm", "shielding_symmetric": { "anisotropy": "13.89 ppm", @@ -22,7 +22,7 @@ "number_of_points": 2048, "spectral_width": "25 kHz", "reference_offset": "0 Hz", - "nucleus": "1H" + "isotope": "1H" } } -} \ No newline at end of file +} diff --git a/test_files/static.json b/src/mrsimulator/examples/static.json similarity index 90% rename from test_files/static.json rename to src/mrsimulator/examples/static.json index 6720b6c17..235c88831 100644 --- a/test_files/static.json +++ b/src/mrsimulator/examples/static.json @@ -3,7 +3,7 @@ { "sites": [ { - "isotope_symbol": "1H", + "isotope": "1H", "isotropic_chemical_shift": "0 ppm", "shielding_symmetric": { "anisotropy": "13.89 ppm", @@ -22,7 +22,7 @@ "number_of_points": 2048, "spectral_width": "25 kHz", "reference_offset": "0 Hz", - "nucleus": "1H" + "isotope": "1H" } } -} \ No newline at end of file +} diff --git a/src/mrsimulator/importer.py b/src/mrsimulator/importer.py new file mode 100644 index 000000000..aca113bd1 --- /dev/null +++ b/src/mrsimulator/importer.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +"""Utility functions for the csdfpy module.""" +import sys +from os import path +from urllib.parse import urlparse +import json +import requests + + +__author__ = "Deepansh J. Srivastava" +__email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] + + +def download_file_from_url(url): + res = urlparse(url) + filename = path.split(res[2])[1] + name, extension = path.splitext(filename) + original_name = name + i = 0 + while path.isfile(filename): + filename = "{0}_{1}{2}".format(original_name, str(i), extension) + i += 1 + + with open(filename, "wb") as f: + response = requests.get(url, stream=True) + total = response.headers.get("content-length") + + if total is None: + f.write(response.content) + else: + downloaded = 0 + total = int(total) + sys.stdout.write( + "Downloading '{0}' from '{1}' to file '{2}'.".format( + res[2], res[1], filename + ) + ) + for data in response.iter_content( + chunk_size=max(int(total / 1000), 1024 * 1024) + ): + downloaded += len(data) + f.write(data) + done = int(8 * downloaded / total) + sys.stdout.write("\n[{}{}]".format("█" * done, "." * (8 - done))) + sys.stdout.flush() + + sys.stdout.write("\n") + + return filename + + +def import_json(filename): + res = urlparse(filename) + if res[0] not in ["file", ""]: + filename = download_file_from_url(filename) + with open(filename, "rb") as f: + content = f.read() + return json.loads(str(content, encoding="UTF-8")) diff --git a/src/mrsimulator/isotope_data.json b/src/mrsimulator/isotope_data.json index eb8e22e5e..7c4b98f42 100644 --- a/src/mrsimulator/isotope_data.json +++ b/src/mrsimulator/isotope_data.json @@ -95,4 +95,4 @@ "natural_abundance": 26.44, "gyromagnetic_ratio": 11.777 } -} \ No newline at end of file +} diff --git a/src/mrsimulator/isotopomer.py b/src/mrsimulator/isotopomer.py index 673ff6682..a54c1ccbd 100644 --- a/src/mrsimulator/isotopomer.py +++ b/src/mrsimulator/isotopomer.py @@ -32,11 +32,13 @@ def parse_json_with_units(cls, json_dict): def to_freq_dict(self, larmor_frequency): """ - Enforces units of Hz by multiplying any ppm values by the Larmor frequency in MHz - MHz*ppm -> Hz + Enforces units of Hz by multiplying any ppm values by the Larmor frequency in + MHz, MHz*ppm -> Hz """ temp_dict = self.dict() - temp_dict["sites"] = [site.to_freq_dict(larmor_frequency) for site in self.sites] + temp_dict["sites"] = [ + site.to_freq_dict(larmor_frequency) for site in self.sites + ] - return temp_dict \ No newline at end of file + return temp_dict diff --git a/src/mrsimulator/miscellaneous.py b/src/mrsimulator/miscellaneous.py new file mode 100644 index 000000000..1ec555c15 --- /dev/null +++ b/src/mrsimulator/miscellaneous.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +import json +import os +import numpy as np + +__author__ = "Deepansh J. Srivastava" +__email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] + + +def import_json(filename): + with open(filename, "rb") as f: + content = f.read() + return json.loads(str(content, encoding="UTF-8")) + + +def _get_header_and_footer(source_file): + """ + Return the number of rows in the header and footer. + This assumes that the data is listed in-between lines + with keywords 'DATA' and 'END' respectively. + """ + f = open(source_file) + skip_header = 0 + total_lines = 0 + data = {} + for line_ in f: + if "NP" in line_: + data["count"] = int(line_.split("=")[1]) + if "X0" in line_: + data["zero_index_coordinate"] = float(line_.split("=")[1]) + if "SW" in line_: + data["increment"] = float(line_.split("=")[1]) / data["count"] + skip_header += 1 + total_lines += 1 + if "DATA" in line_: + total_lines += 1 + skip_footer = 1 + break + f.close() + if total_lines == skip_header: + skip_header = 0 + skip_footer = 0 + data["skip_header"] = skip_header + data["skip_footer"] = skip_footer + return data + + +def read_dmfit_files(filename): + """Load a dmfit output file""" + # source data + # data_object = import_json(filename) + + # test_data_object = data_object["test_data"] + + # source_file = test_data_object["filename"] + path, extension = os.path.split(filename) + # path_ = path.split(path_)[0] + # source_file = path.join(path_, filename) + + data = _get_header_and_footer(filename) + print(data) + x, y = np.genfromtxt( + filename, + skip_header=data["skip_header"], + # delimiter='tab', + skip_footer=data["skip_footer"], + unpack=True, + ) + print(x) + data_source = x + 1j * y + data_source = data_source[::-1] + data_source = np.roll(data_source, 1) + freq = np.arange(data["count"]) * data["increment"] - data["zero_index_coordinate"] + return freq, data_source diff --git a/src/mrsimulator/parseable.py b/src/mrsimulator/parseable.py index 1842aff08..02072de4f 100644 --- a/src/mrsimulator/parseable.py +++ b/src/mrsimulator/parseable.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from pydantic import BaseModel from mrsimulator.unit import string_to_quantity from typing import ClassVar, Dict @@ -33,9 +34,7 @@ def parse_json_with_units(cls, json_dict): if prop in json_dict: # If we have a single option - if isinstance(required_type, str) and isinstance( - default_unit, str - ): + if isinstance(required_type, str) and isinstance(default_unit, str): try: json_dict[prop] = enforce_units( json_dict[prop], required_type, default_unit @@ -48,9 +47,7 @@ def parse_json_with_units(cls, json_dict): ) # If there are multiple type/unit combinations - elif isinstance(required_type, list) and isinstance( - default_unit, list - ): + elif isinstance(required_type, list) and isinstance(default_unit, list): pos_values = [ enforce_units( json_dict[prop], r_type, d_unit, throw_error=False @@ -59,24 +56,20 @@ def parse_json_with_units(cls, json_dict): ] # If none of the units were enforceable, error # else choose the first good one - if not ([val != None for val in pos_values]): - raise Exception( - f"Could not enforce any units on {prop}" - ) + if not ([val is not None for val in pos_values]): + raise Exception(f"Could not enforce any units on {prop}") else: json_dict[prop], property_units[prop] = [ - d for d in zip(pos_values, default_unit) if d[0] != None - ][0] - return cls(**json_dict,property_units=property_units) + d for d in zip(pos_values, default_unit) if d[0] is not None + ][0] + return cls(**json_dict, property_units=property_units) -def enforce_units( - value: str, required_type: str, default_unit: str, throw_error=True -): +def enforce_units(value: str, required_type: str, default_unit: str, throw_error=True): """ Enforces a required type and default unit on the value - value + value """ try: value = string_to_quantity(value) diff --git a/src/mrsimulator/python/Hamiltonian.py b/src/mrsimulator/python/Hamiltonian.py new file mode 100644 index 000000000..560633366 --- /dev/null +++ b/src/mrsimulator/python/Hamiltonian.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +import numpy as np + +# from . import transition_function as tf + +__author__ = "Deepansh J. Srivastava" +__email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] + +__all__ = ["nuclear_shielding"] + + +def nuclear_shielding(iso, zeta, eta, order=1): + # scale = tf.p(transition[1], transition[0]) + R0 = iso # * scale + + R2 = np.zeros(5) + temp = -0.4082482905 * (zeta * eta) # * scale + R2[0] += temp # R2-2 + R2[1] += 0.0 # R2-1 + R2[2] += zeta # * scale # R2 0 + R2[3] += 0.0 # R2 1 + R2[4] += temp # R2 2 + + return R0, R2.astype(np.complex128) diff --git a/src/mrsimulator/python/angular_momentum.py b/src/mrsimulator/python/angular_momentum.py new file mode 100644 index 000000000..6c7b495b6 --- /dev/null +++ b/src/mrsimulator/python/angular_momentum.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +import numpy as np + +__author__ = "Deepansh J. Srivastava" +__email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] + + +def wigner_rotation( + l, R_in, cos_alpha=None, cos_beta=None, wigner_matrix=None, phase_alpha=None +): + n = 2 * l + 1 + if wigner_matrix is None: + n_orientation = cos_beta.size + wigner = wigner_d_matrix_cosines(l, cos_beta) + else: + wigner = wigner_matrix + n_orientation = wigner.shape[0] + + pha = cos_alpha - 1j * np.sqrt(1.0 - cos_alpha ** 2) + ph2 = np.copy(pha) + + R_vec = np.tile(R_in, n_orientation).reshape(n_orientation, n) + + for m in range(1, l + 1): + R_vec[:, l + m] *= ph2 + R_vec[:, l - m] *= ph2.conj() + ph2 *= pha + + R_out = wigner.ravel() * np.tile(R_vec, (1, n)).ravel() + return R_out.reshape(n_orientation, n, n).sum(axis=-1) + + +def wigner_d_matrix(l, beta): + """ + Evaluates a wigner matrix of rank `l` ev + """ + return wigner_d_matrix_cosines(l, np.cos(beta)) + + +def wigner_d_matrix_cosines(l, cos_beta): + r""" + Returns a $(2l+1) \times (2l+1)$ wigner-d(cos_beta) matrix for rank $l$ at + a given `cos_beta`. Currently only rank l=2 and l=4 is supported. + + If `cos_beta` is a 1D-numpy array of size n, a + `n x (2l+1) x (2l+1)` matrix is returned instead. + + :ivar l: The angular momentum quantum number. + :ivar cos_beta: An 1D numpy array or a scalar representing the cosine of + $\beta$ angles. + """ + if not isinstance(cos_beta, np.ndarray): + cos_beta = np.asarray([cos_beta]).ravel() + + cx = cos_beta + cx2 = cx * cx + sx = np.sqrt(1.0 - cx2) + + if l == 2: + wigner = np.empty((25, cx.size), dtype=np.float64) + + t1 = 1.0 + cx + temp = -sx * t1 / 2.0 + wigner[19] = temp # 2, 1 # 19 + wigner[5] = -temp # -2, -1 # 5 + wigner[23] = -temp # 1, 2 # 23 + wigner[1] = temp # -1, -2 # 1 + + temp = t1 * t1 / 4.0 + wigner[24] = temp # 2, 2 # 24 + wigner[0] = temp # -2, -2 # 0 + + t1 = 1.0 - cx + temp = -sx * t1 / 2.0 + wigner[9] = temp # 2, -1 # 9 + wigner[15] = -temp # -2, 1 # 15 + wigner[3] = temp # 1, -2 # 3 + wigner[21] = -temp # -1, 2 # 21 + + temp = t1 * t1 / 4.0 + wigner[4] = temp # 2, -2 # 4 + wigner[20] = temp # -2, 2 # 20 + + temp = 0.6123724355 * sx * sx + wigner[14] = temp # 2, 0 # 14 + wigner[10] = temp # -2, 0 # 10 + wigner[22] = temp # 0, 2 # 22 + wigner[2] = temp # 0, -2 # 2 + + temp = 1.224744871 * sx * cx + wigner[13] = -temp # 1, 0 # 13 + wigner[17] = temp # 0, 1 # 17 + wigner[7] = -temp # 0, -1 # 7 + wigner[11] = temp # -1, 0 # 11 + + temp = (2.0 * cx2 + cx - 1.0) / 2.0 + wigner[18] = temp # 1, 1 # 18 + wigner[6] = temp # -1, -1 # 6 + + temp = -(2.0 * cx2 - cx - 1.0) / 2.0 + wigner[8] = temp # 1, -1 # 8 + wigner[16] = temp # -1, 1 # 16 + + wigner[12] = 1.5 * cx2 - 0.5 # 0, 0 # 12 + + wigner = wigner.reshape(5, 5, cx.size) + return np.moveaxis(wigner, -1, 0) + + if l == 4: + wigner = np.empty((81, cx.size), dtype=np.float64) + + sx2 = sx * sx + sx3 = sx2 * sx + + cxp1 = 1.0 + cx + cxm1 = 1.0 - cx + cxp12 = cxp1 * cxp1 + cxm12 = cxm1 * cxm1 + cxm13 = cxm12 * cxm1 + cxp13 = cxp12 * cxp1 + + temp = 0.0625 * cxp12 * cxp12 + wigner[0] = temp # -4, -4 # 0 + wigner[80] = temp # 4, 4 # 80 + + temp = 0.0625 * cxm12 * cxm12 + wigner[72] = temp # -4, 4 # 72 + wigner[8] = temp # 4, -4 # 8 + + temp = -0.1767766953 * cxp13 * sx + wigner[1] = temp # -3, -4 # 1 + wigner[9] = -temp # -4, -3 # 9 + wigner[79] = -temp # 3, 4 # 79 + wigner[71] = temp # 4, 3 # 9 + + temp = -0.1767766953 * cxm13 * sx + wigner[7] = temp # 3, -4 # 7 + wigner[63] = -temp # -4, 3 # 63 + wigner[73] = -temp # -3, 4 # 73 + wigner[17] = temp # 4, -3 # 17 + + temp = -0.4677071733 * cxp1 * sx3 + wigner[53] = temp # 4, 1 # 53 + wigner[27] = -temp # -4, -1 # 27 + wigner[77] = -temp # 1, 4 # 77 + wigner[3] = temp # -1, -4 # 3 + + temp = -0.4677071733 * cxm1 * sx3 + wigner[35] = temp # 4, -1 # 35 + wigner[45] = -temp # -4, 1 # 45 + wigner[75] = -temp # -1, 4 # 75 + wigner[5] = temp # 1, -4 # 5 + + temp = 0.5229125166 * sx3 * sx + wigner[44] = temp # 4, 0 # 44 + wigner[36] = temp # -4, 0 # 36 + wigner[76] = temp # 0, 4 # 76 + wigner[4] = temp # 0, -4 # 4 + + temp = -1.4790199458 * sx3 * cx + wigner[43] = temp # 3, 0 # 43 + wigner[37] = -temp # -3, 0 # 37 + wigner[67] = -temp # 0, 3 # 67 + wigner[13] = temp # 0, -3 # 13 + + temp = 0.3307189139 * sx2 * cxp12 + wigner[78] = temp # 2, 4 # 78 + wigner[2] = temp # -2, -4 # 2 + wigner[62] = temp # 4, 2 # 62 + wigner[18] = temp # -4, -2 # 18 + + temp = 0.3307189139 * sx2 * cxm12 + wigner[6] = temp # 2, -4 # 6 + wigner[74] = temp # -2, 4 # 74 + wigner[54] = temp # -4, 2 # 54 + wigner[26] = temp # 4, -2 # 26 + + temp = 0.4677071733 * cxp12 * sx * (2.0 * cx - 1.0) + wigner[69] = temp # 2, 3 # 69 + wigner[11] = -temp # -2, -3 # 11 + wigner[61] = -temp # 3, 2 # 61 + wigner[19] = temp # -3, -2 # 19 + + temp = 0.4677071733 * cxm12 * sx * (-2.0 * cx - 1.0) + wigner[15] = temp # 2, -3 # 15 + wigner[65] = -temp # -2, 3 # 65 + wigner[55] = -temp # -3, 2 # 55 + wigner[25] = temp # 3, -2 # 25 + + temp = 0.25 * cxp12 * (1.0 - 7.0 * cxm1 + 7.0 * cxm12) + wigner[60] = temp # 2, 2 # 60 + wigner[20] = temp # -2, -2 # 20 + + temp = 0.25 * cxm12 * (1.0 - 7.0 * cxp1 + 7.0 * cxp12) + wigner[56] = temp # -2, 2 # 56 + wigner[24] = temp # 2, -2 # 24 + + temp = 0.3952847075 * sx2 * (7.0 * cx2 - 1) + wigner[42] = temp # 2, 0 # 42 + wigner[38] = temp # -2, 0 # 38 + wigner[58] = temp # 0, 2 # 58 + wigner[22] = temp # 0, -2 # 22 + + temp = 0.125 * cxp13 * (-3.0 + 4.0 * cx) + wigner[10] = temp # -3, -3 # 10 + wigner[70] = temp # 3, 3 # 70 + + temp = 0.125 * cxm13 * (3.0 + 4.0 * cx) + wigner[64] = temp # -3, 3 # 64 + wigner[16] = temp # 3, -3 # 16 + + temp = 0.3307189139 * cxm1 * cxp12 * (-1.0 + 4.0 * cx) + wigner[12] = temp # -1, -3 # 12 + wigner[28] = temp # -3, -1 # 28 + wigner[68] = temp # 1, 3 # 68 + wigner[52] = temp # 3, 1 # 52 + + temp = 0.3307189139 * cxm12 * cxp1 * (1.0 + 4.0 * cx) + wigner[14] = temp # 1, -3 # 14 + wigner[46] = temp # -3, 1 # 46 + wigner[66] = temp # -1, 3 # 66 + wigner[34] = temp # 3, -1 # 34 + + temp = -0.5590169944 * (4.0 - 18.0 * cxm1 + 21.0 * cxm12 - 7.0 * cxm13) * sx + wigner[41] = temp # 1, 0 # 41 + wigner[39] = -temp # -1, 0 # 39 + wigner[49] = -temp # 0, 1 # 49 + wigner[31] = temp # 0, -1 # 31 + + temp = -0.3535533906 * (3.0 - 10.5 * cxm1 + 7.0 * cxm12) * sx * cxp1 + wigner[51] = temp # 2, 1 # 51 + wigner[29] = -temp # -2, -1 # 29 + wigner[59] = -temp # 1, 2 # 59 + wigner[21] = temp # -1, -2 # 21 + + temp = -0.3535533906 * (10.0 - 17.5 * cxm1 + 7.0 * cxm12) * sx * cxm1 + wigner[23] = temp # 1, -2 # 23 + wigner[57] = -temp # -1, 2 # 57 + wigner[47] = -temp # -2, 1 # 47 + wigner[33] = temp # 2, -1 # 33 + + temp = 0.5 * (1.0 - 9.0 * cxm1 + 15.75 * cxm12 - 7.0 * cxm13) * cxp1 + wigner[30] = temp # -1, -1 # 30 + wigner[50] = temp # 1, 1 # 50 + + temp = 0.5 * (10.0 - 30.0 * cxm1 + 26.25 * cxm12 - 7.0 * cxm13) * cxm1 + wigner[32] = temp # 1, -1 # 32 + wigner[48] = temp # -1, 1 # 48 + + temp = 0.125 * (3.0 - 30.0 * cx2 + 35.0 * cx2 * cx2) + wigner[40] = temp # 0, 0 # 40 + + # factor = (np.arange(9) - 4.)*(cos_alpha - + # 1j * np.sqrt(1.0 - cos_alpha ** 2)) + wigner = wigner.reshape(9, 9, cx.size) + # wigner *= factor[np.newaxis, :, np.newaxis] + return np.moveaxis(wigner, -1, 0) + + +def wigner_dm0_vector(l: int, angle): + R_out = np.empty(2 * l + 1, dtype=np.float64) + sx = np.sin(angle) + cx = np.cos(angle) + if l == 2: + R_out[0] = 0.6123724355 * sx * sx + R_out[1] = 1.224744871 * sx * cx + R_out[2] = 1.5 * cx * cx - 0.5 + R_out[3] = -R_out[1] * 1 + R_out[4] = R_out[0] * 1 + return R_out + + if l == 4: + sx2 = sx * sx + sx3 = sx2 * sx + cx2 = 1.0 - sx2 + cxm1 = 1.0 - cx + cxm12 = cxm1 * cxm1 + temp = 4.0 - 18.0 * cxm1 + 21.0 * cxm12 - 7.0 * cxm12 * cxm1 + R_out[0] = 0.5229125166 * sx3 * sx + R_out[1] = 1.4790199458 * sx3 * cx + R_out[2] = 0.3952847075 * sx2 * (7.0 * cx2 - 1) + R_out[3] = 0.5590169944 * temp * sx + R_out[4] = 0.125 * (3.0 - 30.0 * cx2 + 35 * cx2 * cx2) + R_out[5] = -R_out[3] + R_out[6] = R_out[2] + R_out[7] = -R_out[1] + R_out[8] = R_out[0] + return R_out diff --git a/src/mrsimulator/python/orientation.py b/src/mrsimulator/python/orientation.py new file mode 100644 index 000000000..911fc58a3 --- /dev/null +++ b/src/mrsimulator/python/orientation.py @@ -0,0 +1,462 @@ +# -*- coding: utf-8 -*- +import numpy as np + +__author__ = "Deepansh J. Srivastava" +__email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] + + +def octahedral_coordinate(nt: int): + + # Do the (x + y + z = nt) face of the octahedron + # z -> 0 to nt-1 + # y -> 0 to nt-z + # x -> nt - y - z + + n = int((nt + 1) * (nt + 2) / 2) + x = np.empty(n, dtype=np.float64) + y = np.empty(n, dtype=np.float64) + z = np.empty(n, dtype=np.float64) + + k = 0 + for j in range(nt): + for i in range(nt - j + 1): + # x = nt-i-j; + # y = i; + # z = j; + x[k] = nt - i - j + y[k] = i + z[k] = j + k += 1 + + x[-1] = 0.0 + y[-1] = 0.0 + z[-1] = 1.0 + + return x, y, z + + +def octahedral_direction_cosine_squares_and_amplitudes(nt: int): + + # Do the (x + y + z = nt) face of the octahedron + # z -> 0 to nt-1 + # y -> 0 to nt-z + # x -> nt - y - z + + n = int((nt + 1) * (nt + 2) / 2) + x = np.empty(n, dtype=np.float64) + y = np.empty(n, dtype=np.float64) + z = np.empty(n, dtype=np.float64) + amp = np.empty(n, dtype=np.float64) + + k = 0 + for j in range(nt): + for i in range(nt - j + 1): + # x = nt-i-j; + # y = i; + # z = j; + x[k] = nt - i - j + y[k] = i + z[k] = j + k += 1 + + x *= x + y *= y + z *= z + r2 = x + y + z + x /= r2 + y /= r2 + z /= r2 + amp = 1.0 / (r2 * np.sqrt(r2)) + + x[-1] = 0.0 + y[-1] = 0.0 + z[-1] = 1.0 + amp[-1] = 1.0 / (nt * nt * nt) + + return x, y, z, amp + + +def cosine_of_polar_angles_and_amplitudes(geodesic_polyhedron_frequency: int = 72): + r""" + Calculates and return the direction cosines and the related amplitudes for + the positive quadrant of the sphere. The direction cosines corresponds to + angle $\alpha$ and $\beta$, where $\alpha$ is the azimuthal angle and + $\beta$ is the polar angle. The amplitudes are evaluated as $\frac{1}{r^3}$ + where $r$ is the distance from the origin to the face of the unit + octahedron in the positive quadrant along the line given by the values of + $\alpha$ and $\beta$. + + :ivar geodesic_polyhedron_frequency: + The value is an integer which represents the frequency of class I + geodesic polyhedra. These polyhedra are used in calculating the + spherical average. Presently we only use octahedral as the frequency1 + polyhedra. As the frequency of the geodesic polyhedron increases, the + polyhedra approach a sphere geometry. For line-shape simulation, a + higher frequency will result in a better powder averaging. + The default value is 72. + Read more on the `Geodesic polyhedron + `_. + + :return cos_alpha: The cosine of the azimuthal angle. + :return cos_beta: The cosine of the polar angle. + :return amp: The amplitude at the given $\alpha$ and $\beta$. + """ + nt = geodesic_polyhedron_frequency + xr, yr, zr, amp = octahedral_direction_cosine_squares_and_amplitudes(nt) + + cos_beta = np.sqrt(zr) + cos_alpha = np.zeros(xr.size, dtype=np.float64) + cos_alpha[:-1] = np.sqrt(xr[:-1] / (xr[:-1] + yr[:-1])) + cos_alpha[-1] = 1.0 + return cos_alpha, cos_beta, amp + + +def triangle_interpolation(f, spec, amp=1.0): + points = spec.size + f = np.asarray(f, dtype=np.float64) + + clip_right1 = clip_right2 = False + clip_left1 = clip_left2 = False + + p = int(f[0]) + # fint = f.astype(int) + if int(f[0]) == int(f[1]) == int(f[2]): + if p >= points or p < 0: + return + spec[p] += amp + return + + f = np.sort(f) + + top = amp * 2.0 / (f[2] - f[0]) + + p = int(f[0]) + pmid = int(f[1]) + pmax = int(f[2]) + + f10 = f[1] - f[0] + f21 = f[2] - f[1] + + if pmax < 0: + return + + if p > points: + return + + if pmid >= points: + pmid = points + clip_right1 = True + + if pmax >= points: + pmax = points + clip_right2 = True + + if p < 0: + p = 0 + clip_left1 = True + + if pmid < 0: + pmid = 0 + clip_left2 = True + + if p != pmid: + df1 = top / f10 + diff = p + 1.0 - f[0] + if not clip_left1: + spec[p] += 0.5 * diff * diff * df1 + p += 1 + else: + spec[p] += (diff - 0.5) * df1 + p += 1 + + diff -= 0.5 + diff *= df1 + while p != pmid: + diff += df1 + spec[p] += diff + p += 1 + # spec[p:pmid] += diff + (np.arange(pmid - p, dtype=np.float64) + 1) * df1 + p = pmid + if not clip_right1: + spec[p] += (f[1] - p) * (f10 + p - f[0]) * 0.5 * df1 + + else: + if not clip_right1 and not clip_left1: + spec[p] += f10 * top * 0.5 + if p != pmax: + df2 = top / f21 + diff = f[2] - p - 1.0 + + if not clip_left2: + spec[p] += (f21 - diff) * (diff + f21) * 0.5 * df2 + p += 1 + else: + spec[p] += (diff + 0.5) * df2 + p += 1 + + diff += 0.5 + diff *= df2 + while p != pmax: + diff -= df2 + spec[p] += diff + p += 1 + # spec[p:pmax] += diff - (np.arange(pmax - p, dtype=np.float64) + 1) * df2 + p = pmax + if not clip_right2: + spec[p] += (f[2] - p) ** 2 * 0.5 * df2 + else: + if not clip_right2: + spec[p] += f21 * top * 0.5 + + +# def triangle_interpolation_2(f, spec, amp=1.0): +# points = spec.size +# size = f.shape[0] +# f = np.asarray(f, dtype=np.float64) + +# spec_temp = np.zeros((size, points)) + +# p = f.astype(np.int64) + +# f = np.sort(f, axis=-1) +# top = amp * 2.0 / (f[:, 2] - f[:, 0]) +# p = f[:, 0].astype(np.int32) +# pmid = f[:, 1].astype(np.int32) +# pmax = f[:, 2].astype(np.int32) + +# f10 = f[:, 1] - f[:, 0] +# f21 = f[:, 2] - f[:, 1] + +# region_of_interest = np.intersect1d(np.where(pmax >= 0), +# np.where(p <= points)) + +# # clip right 1 +# index = np.where(pmid >= points) +# pmid[index] = points +# clip_right1 = np.zeros(size) == 1 +# clip_right1[index] = True + +# # clip right 2 +# index = np.where(pmax >= points) +# pmax[index] = points +# clip_right2 = np.zeros(size) == 1 +# clip_right2[index] = True + +# # clip left 1 +# index = np.where(p < 0) +# p[index] = 0 +# clip_left1 = np.zeros(size) == 1 +# clip_left1[index] = True + +# # clip left 2 +# index = np.where(pmid < 0) +# pmid[index] = 0 +# clip_left2 = np.zeros(size) == 1 +# clip_left2[index] = True + +# p_equal_pmid = np.where(p == pmid) +# p_equal_pmax = np.where(p == pmax) + +# p_not_equal_pmid = np.intersect1d(np.where(p != pmid), +# np.where(p < pmid)) + +# # first part +# df1 = top / f10 +# diff = p + 1.0 - f[:, 0] + +# index = np.where(clip_left1 == True) +# index = np.intersect1d(np.intersect1d(index, p_not_equal_pmid), +# region_of_interest) +# spec_temp[index, p[index]] += (diff[index] - 0.5) * df1[index] + +# index = np.where(clip_left1 == False) +# index = np.intersect1d(np.intersect1d(index, p_not_equal_pmid), +# region_of_interest) +# spec_temp[index, p[index]] += 0.5 * diff[index] ** 2 * df1[index] + +# p += 1 +# diff -= 0.5 +# diff *= df1 +# for i in np.intersect1d(p_not_equal_pmid, region_of_interest): +# spec_temp[i, p[i] : pmid[i]] += ( +# diff[i] + (np.arange(pmid[i] - p[i], dtype=np.float64) + 1.0) * df1[i] +# ) +# p = pmid.copy() + +# index = np.where(clip_right1 == False) +# index = np.intersect1d(np.intersect1d(index, p_not_equal_pmid), +# region_of_interest) +# spec_temp[index, p[index]] += ( +# (f[index, 1] - p[index]) +# * (f10[index] + p[index] - f[index, 0]) +# * 0.5 +# * df1[index] +# ) + +# index = np.intersect1d( +# np.where(clip_left1 == False), np.where(clip_right1 == False) +# ) +# index = np.intersect1d(np.intersect1d(index, p_equal_pmid), +# region_of_interest) +# spec_temp[index, p[index]] += f10[index] * top[index] * 0.5 + +# # second part +# p_not_equal_pmax = np.intersect1d(np.where(p != pmax), np.where(p >= pmid)) + +# df2 = top / f21 +# diff = f[:, 2] - p - 1.0 + +# index = np.where(clip_left2 == False) +# index = np.intersect1d(np.intersect1d(index, p_not_equal_pmax), +# region_of_interest) +# spec_temp[index, p[index]] += ( +# (f21[index] - diff[index]) * (diff[index] + f21[index]) * 0.5 * df2[index] +# ) + +# index = np.where(clip_left2 == True) +# index = np.intersect1d(np.intersect1d(index, p_not_equal_pmax), +# region_of_interest) +# spec_temp[index, p[index]] += (diff[index] + 0.5) * df2[index] + +# p += 1 +# diff += 0.5 +# diff *= df2 +# for i in np.intersect1d(p_not_equal_pmax, region_of_interest): +# spec_temp[i, p[i] : pmax[i]] += ( +# diff[i] - (np.arange(pmax[i] - p[i], dtype=np.float64) + 1) * df2[i] +# ) + +# p = pmax.copy() +# index2 = np.where(clip_right2 == False) +# index = np.intersect1d(np.intersect1d(index2, p_not_equal_pmax), +# region_of_interest) +# spec_temp[index, p[index]] += (f[index, 2] - p[index]) ** 2 * 0.5 * df2[index] + +# index = np.intersect1d(np.intersect1d(index2, p_equal_pmax), +# region_of_interest) +# spec_temp[index, p[index]] += f21[index] * top[index] * 0.5 +# spec += spec_temp.sum(axis=0) + + +# def average_over_octant_2(spec, freq, nt, amp): + +# n_pts = (nt + 1) * (nt + 2) / 2 + +# # Interpolate between frequencies by setting up tents + +# local_index = nt + 1 +# i0 = 0 +# j0 = nt + 1 +# j_last = j0 + local_index - 1 +# while i0 < n_pts - 1: +# i = np.arange(i0, j0 - 1) +# j = np.arange(j0, j_last) +# amp1 = amp[i + 1] + amp[j] + amp[i] +# freq_3 = np.asarray([freq[i + 1], freq[j], freq[i]]).T +# triangle_interpolation_2(freq_3, spec, amp1) + +# i = i[:-1] +# j = j[:-1] +# amp1 = amp[i + 1] + amp[j] + amp[j + 1] +# freq_3 = np.asarray([freq[i + 1], freq[j], freq[j + 1]]).T +# triangle_interpolation_2(freq_3, spec, amp1) + +# local_index -= 1 +# i0 = j0 +# j0 = j_last +# j_last += local_index - 1 + + +def average_over_octant(spec, freq, nt, amp): + + n_pts = (nt + 1) * (nt + 2) / 2 + + # Interpolate between frequencies by setting up tents + + local_index = nt - 1 + i = 0 + j = 0 + while i < n_pts - 1: + temp = amp[i + 1] + amp[nt + 1 + j] + amp1 = temp + amp1 += amp[i] + + freq_3 = [freq[i], freq[i + 1], freq[nt + 1 + j]] + triangle_interpolation(freq_3, spec, amp1) + + if i < local_index: + amp1 = temp + amp1 += amp[nt + 1 + j + 1] + freq_3 = [freq[i + 1], freq[nt + 1 + j], freq[nt + 1 + j + 1]] + triangle_interpolation(freq_3, spec, amp1) + else: + local_index = j + nt + i += 1 + i += 1 + j += 1 + + +# def average_over_octant_2(spec, freq, nt, amp): + +# n_pts = (nt + 1) * (nt + 2) / 2 + +# # Interpolate between frequencies by setting up tents + +# local_index = nt - 1 +# i = 0 +# j = 0 +# lst_i = np.arange(n_pts) +# lst_j = +# amp_1 = amp[lst1 +1 ] + amp[lst1+nt+] + +# while i < n_pts - 1: +# temp = amp[i + 1] + amp[nt + 1 + j] +# amp1 = temp +# amp1 += amp[i] + +# freq_3 = [freq[i], freq[i + 1], freq[nt + 1 + j]] +# triangle_interpolation(freq_3, spec, amp1) + +# if i < local_index: +# amp1 = temp +# amp1 += amp[nt + 1 + j + 1] +# freq_3 = [freq[i + 1], freq[nt + 1 + j], freq[nt + 1 + j + 1]] +# triangle_interpolation(freq_3, spec, amp1) +# else: +# local_index = j + nt +# i += 1 +# i += 1 +# j += 1 + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + # import numpy as np + + f = [ # [20,14.3,74], + [20, 64.2, 74], + # [20, 129.2, 45], + # [-20, -50, -4.56], + # [-23.4, -50.2, 167.13], + # [40, 17.45, 122.4], + # [23, 122, 156.3], + # [-32, 121.3, 129.3], + # [123,194, 129], + # [-50.38, 56, 124], + # [40, 40.2, 40.2], + # [12.2, 12.21, 12.2], + ] + + spec1 = np.zeros(100, dtype=np.float64) + spec2 = np.zeros(100, dtype=np.float64) + # triangle_interpolation_2(np.asarray(f) + 0.5, spec1) + triangle_interpolation(np.asarray(f).ravel() + 0.5, spec2) + + f_sort = np.sort(f[0]) + h = 2.0 / (f_sort[2] - f_sort[0]) + + plt.plot(spec1, "r") + plt.plot(spec2, "k") + plt.plot(f_sort, [0, h, 0], "--", marker="*", markersize=15) + plt.show() diff --git a/src/mrsimulator/python/simulator.py b/src/mrsimulator/python/simulator.py new file mode 100644 index 000000000..1ff1e7287 --- /dev/null +++ b/src/mrsimulator/python/simulator.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +from numpy.fft import fft, fftfreq +import numpy as np +from numpy import dot, exp + +from mrsimulator.python.angular_momentum import ( + wigner_d_matrix_cosines as wigner_matrices, +) +from mrsimulator.python.angular_momentum import wigner_dm0_vector as rotation_lab +from mrsimulator.python.angular_momentum import wigner_rotation as rotation + + +from mrsimulator.python.Hamiltonian import nuclear_shielding as NS +from mrsimulator.python.orientation import ( + cosine_of_polar_angles_and_amplitudes as polar_coordinates, +) + +from mrsimulator.python.orientation import average_over_octant as averager +from mrsimulator.python.utils import pre_phase_components +import mrsimulator.python.transition_function as tf + +# from timeit import default_timer +# import matplotlib.pyplot as plt + +__author__ = "Deepansh J. Srivastava" +__email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] + + +def simulator( + spectrum, isotopomers, transitions=[[-0.5, 0.5]], nt=90, number_of_sidebands=128 +): + + B0 = spectrum["magnetic_flux_density"] + spin_frequency = spectrum["rotor_frequency"] + rotor_angle = spectrum["rotor_angle"] + + frequency_scaling_factor = spectrum["gyromagnetic_ratio"] * B0 + + number_of_points = spectrum["number_of_points"] + spectral_width = spectrum["spectral_width"] + reference_offset = spectrum["reference_offset"] + frequency = (np.arange(number_of_points) / number_of_points) - 0.5 + frequency *= spectral_width + frequency += reference_offset + increment = frequency[1] - frequency[0] + + if spin_frequency < 1.0e-3: + spin_frequency = 1.0e9 + rotor_angle = 0.0 + number_of_sidebands = 1 + + shift_half_bin = 0.5 + + # orientations + cos_alpha, cos_beta, orientation_amp = polar_coordinates(nt) + orientation_amp /= np.float64(number_of_sidebands) + n_orientation = cos_beta.size + + # sideband freq + vr_freq = fftfreq(number_of_sidebands, d=1.0 / number_of_sidebands) + vr_freq *= spin_frequency / increment + + # wigner matrix + wigner_2 = wigner_matrices(2, cos_beta) + + # rotor to lab frame transformation + lab_vector_2 = rotation_lab(2, rotor_angle) + + # pre phase + pre_phase = pre_phase_components(number_of_sidebands, spin_frequency) + + # allocate memory for calculations + R2_out = np.empty((n_orientation, 5), dtype=np.complex128) + spectrum = np.zeros(number_of_points) + + shape = (n_orientation, number_of_sidebands) + temp = np.empty(shape, dtype=np.complex128) + sideband_amplitude = np.empty(shape, dtype=np.float64) + local_frequency = np.empty(n_orientation, dtype=np.float64) + freq_offset = np.empty(n_orientation, dtype=np.float64) + offset = np.empty(number_of_sidebands, dtype=np.float64) + + # start calculating the spectrum for every site in every isotopomer. + for isotopomer in isotopomers: + + sites = isotopomer["sites"] + spec = np.zeros(number_of_points) + + for transition in transitions: + for site in sites: + iso = site["isotropic_chemical_shift"] + if iso.unit.physical_type == "dimensionless": + iso = iso.value * frequency_scaling_factor + else: + iso = iso.value + + zeta = site["shielding_symmetric"]["anisotropy"] + if zeta.unit.physical_type == "dimensionless": + zeta = zeta.value * frequency_scaling_factor + else: + zeta = zeta.value + + eta = site["shielding_symmetric"]["asymmetry"] + + # Hailtonian + # nuclear shielding + R0, R2 = NS(iso, zeta, eta) + scale = tf.p(transition[1], transition[0]) + R0 *= scale + R2 *= scale + + local_frequency_offset = ( + shift_half_bin + (R0 - frequency[0]) / increment + ) + + # rotation from PAS to Rotor frame over all orientations + R2_out = rotation(2, R2, wigner_matrix=wigner_2, cos_alpha=cos_alpha) + # rotation from rotor to lab frame over all orientations + R2_out *= lab_vector_2 + + # calculating side-band amplitudes + temp[:] = fft(exp(dot(R2_out, pre_phase[2:7])), axis=-1) + sideband_amplitude[:] = temp.real ** 2 + temp.imag ** 2 + sideband_amplitude *= orientation_amp[:, np.newaxis] + + # calculating local frequencies + local_frequency[:] = R2_out[:, 2].real / increment + + # interpolate in-between the frequencies to generate a smooth spectrum. + offset[:] = vr_freq + local_frequency_offset + + # print("before", default_timer() - start0) + for j, shift in enumerate(offset): + if int(shift) >= 0 and int(shift) <= number_of_points: + freq_offset[:] = shift + local_frequency + # This is the slowest part of the code. + averager(spec, freq_offset, nt, sideband_amplitude[:, j]) + # np.vectorize(averager(spec, freq_offset, + # nt, sideband_amplitude[:, j])) + + # print("time for computing site", default_timer() - start0) + # average over all spins + spectrum += spec * isotopomer["abundance"] + + return frequency, spectrum diff --git a/src/mrsimulator/python/transition_function.py b/src/mrsimulator/python/transition_function.py new file mode 100644 index 000000000..3e1620d5e --- /dev/null +++ b/src/mrsimulator/python/transition_function.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +"""Transition symmetry functions from irreducible tensors. + +.. seealso:: + + `Grandinetti et. al. `_ + **Symmetry pathways in solid-state NMR.** +""" + +__all__ = ["p", "d", "dIS", "f", "quad_ci"] + + +def p(mf, mi): + r""" + Transition symmetry function from irreducible 1st rank tensor. + The function evaluates, + + .. math:: + + \mathbb{p}(m_f, m_i) &= \left< m_f | T_{10} | m_f \right> - + \left< m_i | T_{10} | m_i \right> \\ + &= m_f - m_i + + where :math:`T_{10}` is the irreducible 1st rank tensor operator in the + rotating tilted frame. + + :param float mf: The quantum number associated with the final energy state. + :param float mi: The quantum number associated with the initial energy + state. + :rtype: float + :return: The transition symmetry function p. + """ + return mf - mi + + +def d(mf, mi): + r""" + Transition symmetry function from irreducible 2nd rank tensor. + The function evaluates, + + .. math:: + + \mathbb{d}(m_f, m_i) &= \left< m_f | T_{20} | m_f \right> - + \left< m_i | T_{20} | m_i \right> \\ + &= \sqrt{\frac{3}{2}} \left(m_f^2 - m_i^2 \right) + + where :math:`T_{20}` is the irreducible 2nd rank tensor operator in the + rotating tilted frame. + + :param float mf: The quantum number associated with the final energy state. + :param float mi: The quantum number associated with the initial energy + state. + :rtype: float + :return: The transition symmetry function d. + """ + return 1.2247448714 * (mf * mf - mi * mi) + + +def f(mf, mi, spin): + r""" + Transition symmetry function from irreducible 3rd rank tensor. + The function evaluates, + + .. math:: + + \mathbb{f}(m_f, m_i) &= \left< m_f | T_{30} | m_f \right> - + \left< m_i | T_{30} | m_i \right> \\ + &= \frac{1}{\sqrt{10}} [5(m_f^3 - m_i^3) + (1 - 3I(I+1))(m_f-m_i)] + + where :math:`T_{30}` is the irreducible 3rd rank tensor operator in the + rotating tilted frame. + + :param float mf: The quantum number associated with the final energy state. + :param float mi: The quantum number associated with the initial energy + state. + :rtype: float + :return: The transition symmetry function f. + """ + f_ = 1.0 - 3.0 * spin * (spin + 1.0) + f_ *= mf - mi + f_ += 5.0 * (mf * mf * mf - mi * mi * mi) + f_ *= 0.316227766 + return f_ + + +def quad_ci(mf, mi, spin): + f_ = f(mf, mi, spin) + p_ = p(mf, mi) + temp = spin * (spin + 1.0) - 0.75 + c0 = 0.3577708764 * temp * p_ + 0.8485281374 * f_ + c2 = 0.1069044968 * temp * p_ + -1.0141851057 * f_ + c4 = -0.1434274331 * temp * p_ + -1.2850792082 * f_ + return c0, c2, c4 + + +def dIS(mIf, mIi, mSf, mSi): + return mIf * mSf - mIi * mSi diff --git a/src/mrsimulator/python/utils.py b/src/mrsimulator/python/utils.py new file mode 100644 index 000000000..2e0a9f960 --- /dev/null +++ b/src/mrsimulator/python/utils.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +import numpy as np + +__author__ = "Deepansh J. Srivastava" +__email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] + + +def pre_phase_components(number_of_sidebands, spin_frequency): + r""" + Calculates a 2D matrix `pre_phase` of shape (number_of_sidebands, 9) with + the following definition + + `pre_phase = $2i\pi \frac{(\exp(i m \omega_r t) - 1)}{i m \omega_r}$` + + where m goes from -4 to 4 and t is the time points of size + `number_of_points`. The time increment is given as + `tau = 1.0 / (number_of_sidebands * spin_frequency)`. + """ + n = int(number_of_sidebands) + m_wr = spin_frequency * 2.0 * np.pi * (np.arange(9) - 4.0) + time = np.arange(n) / (number_of_sidebands * spin_frequency) + time, m_wr = np.meshgrid(time, m_wr) + pht = 1j * time * m_wr + pre_phase = np.empty(pht.shape, np.complex128) + np.expm1(pht, out=pre_phase) + pre_phase[0:4] *= (2.0 * np.pi) / m_wr[0:4] + pre_phase[5:9] *= (2.0 * np.pi) / m_wr[5:9] + pre_phase[4] = 0.0 + return pre_phase diff --git a/src/mrsimulator/simulator.py b/src/mrsimulator/simulator.py old mode 100644 new mode 100755 index b392f64c8..7f9fc448b --- a/src/mrsimulator/simulator.py +++ b/src/mrsimulator/simulator.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- -from pydash import has, get +# from pydash import has, get from astropy import units as u from mrsimulator import Isotopomer, Spectrum from mrsimulator.spectrum import ISOTOPE_DATA from mrsimulator.methods import one_d_spectrum +from mrsimulator.importer import import_json + __author__ = "Deepansh J. Srivastava" __email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] @@ -14,7 +16,7 @@ class Simulator: The simulator class. """ - def __init__(self, isotopomers, spectrum): + def __init__(self, isotopomers=[], spectrum={}): self.isotopomers = isotopomers self.spectrum = spectrum @@ -24,11 +26,7 @@ def allowed_isotopes(): Returns a list of all valid isotopes for this simulator """ return list( - { - isotope - for isotope, data in ISOTOPE_DATA.items() - if data["spin"] == 1 - } + {isotope for isotope, data in ISOTOPE_DATA.items() if data["spin"] == 1} ) @property @@ -59,10 +57,24 @@ def valid_isotope_list(self): } ) + def load_isotopomers(self, filename): + """ + Load a JSON serialized isotopomers file. + + See an + `example `_ + of JSON serialized isotopomers file. For details, refer to the + :ref:`load_isotopomers` section. + """ + contents = import_json(filename) + json_data = contents["isotopomers"] + self.isotopomers = [Isotopomer.parse_json_with_units(obj) for obj in json_data] + def run(self, method, **kwargs): return self.one_d_spectrum(**kwargs) - def one_d_spectrum(self, verbose=False, **kwargs): + def one_d_spectrum(self, **kwargs): """ Simulate the spectrum using the specified method. The keyword argument are the arguments of the specified `method`. @@ -76,10 +88,6 @@ def one_d_spectrum(self, verbose=False, **kwargs): array, whereas, the frequency is a `Quantity `_ array. The default value is False. - - :returns: A `csdm` object if `data_object` is True, else a tuple of - frequency and amplitude. For details, refer to the - description of `data_object`. """ isotopomers = [ @@ -94,7 +102,7 @@ def one_d_spectrum(self, verbose=False, **kwargs): """The frequency is in the units of Hz.""" freq *= u.Unit("Hz") """The larmor_frequency is in the units of MHz.""" - larmor_frequency *= u.Unit("MHz") + # larmor_frequency *= u.Unit("MHz") - isotopo_ = [self.isotopomers[i] for i in list_index_isotopomer] + # isotopo_ = [self.isotopomers[i] for i in list_index_isotopomer] return freq, amp diff --git a/src/mrsimulator/site.py b/src/mrsimulator/site.py index 66dd6b8e6..cd167f76f 100644 --- a/src/mrsimulator/site.py +++ b/src/mrsimulator/site.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from typing import ClassVar, Optional from mrsimulator import Parseable, SymmetricTensor, AntisymmetricTensor @@ -17,9 +18,7 @@ class Site(Parseable): "isotropic_chemical_shift": ["dimensionless", "frequency"] } - property_default_units: ClassVar = { - "isotropic_chemical_shift": ["ppm", "Hz"] - } + property_default_units: ClassVar = {"isotropic_chemical_shift": ["ppm", "Hz"]} @classmethod def parse_json_with_units(cls, json_dict): @@ -38,20 +37,16 @@ def parse_json_with_units(cls, json_dict): def to_freq_dict(self, larmor_frequency): """ - Enforces units of Hz by multiplying any ppm values by the Larmor frequency in MHz - MHz*ppm -> Hz + Enforces units of Hz by multiplying any ppm values by the Larmor frequency in + MHz, MHz*ppm -> Hz """ temp_dict = self.dict() - for k in [ - "shielding_symmetric", - "shielding_antisymmetric", - "quadrupolar", - ]: - if getattr(self,k): - temp_dict[k] = getattr(self,k).to_freq_dict(larmor_frequency) + for k in ["shielding_symmetric", "shielding_antisymmetric", "quadrupolar"]: + if getattr(self, k): + temp_dict[k] = getattr(self, k).to_freq_dict(larmor_frequency) - if self.property_units["isotropic_chemical_shift"] is "ppm": + if self.property_units["isotropic_chemical_shift"] == "ppm": temp_dict["isotropic_chemical_shift"] *= larmor_frequency return temp_dict diff --git a/src/mrsimulator/spectrum.py b/src/mrsimulator/spectrum.py index 072933e93..7411484b7 100644 --- a/src/mrsimulator/spectrum.py +++ b/src/mrsimulator/spectrum.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os.path import re from monty.serialization import loadfn @@ -16,7 +17,7 @@ class Spectrum(Parseable): Base class for an NMR Spectrum Args: - number_of_points: Number of points + number_of_points: Number of points spectral_width: Width of spectrum region to consider in Hz reference_offset: Offset frequecy in Hz magnetic_flux_density: Magnetic flux density in T @@ -73,7 +74,7 @@ def parse_json_with_units(cls, json_dict): json_dict.update(json_dict["direct_dimension"]) if "isotope" in json_dict: - isotope_data = get_isotope_data(json_dict["nucleus"]) + isotope_data = get_isotope_data(json_dict["isotope"]) json_dict.update(isotope_data) return super().parse_json_with_units(json_dict) @@ -85,14 +86,14 @@ def get_isotope_data(isotope_string): data file """ result = re.match(r"(\d+)\s*(\w+)", isotope_string) - nucleus = result.group(2) + isotope = result.group(2) A = result.group(1) - formatted_isotope_string = f"{A}{nucleus}" + formatted_isotope_string = f"{A}{isotope}" if formatted_isotope_string in ISOTOPE_DATA: isotope_dict = dict(ISOTOPE_DATA[formatted_isotope_string]) - isotope_dict.update({"nucleus": formatted_isotope_string}) + isotope_dict.update({"isotope": formatted_isotope_string}) return isotope_dict else: raise Exception(f"Could not parse isotope string {formatted_isotope_string}") diff --git a/src/mrsimulator/tensors.py b/src/mrsimulator/tensors.py index ba861c384..d3132a4b9 100644 --- a/src/mrsimulator/tensors.py +++ b/src/mrsimulator/tensors.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from typing import ClassVar, Optional from mrsimulator import Parseable @@ -29,11 +30,11 @@ class SymmetricTensor(Parseable): def to_freq_dict(self, larmor_frequency): """ - Enforces units of Hz by multiplying any ppm values by the Larmor frequency in MHz - MHz*ppm -> Hz + Enforces units of Hz by multiplying any ppm values by the Larmor frequency in + MHz, MHz*ppm -> Hz """ temp_dict = self.dict() - if self.property_units["anisotropy"] is "ppm": + if self.property_units["anisotropy"] == "ppm": temp_dict["anisotropy"] *= larmor_frequency return temp_dict @@ -59,11 +60,11 @@ class AntisymmetricTensor(Parseable): def to_freq_dict(self, larmor_frequency): """ - Enforces units of Hz by multiplying any ppm values by the Larmor frequency in MHz - MHz*ppm -> Hz + Enforces units of Hz by multiplying any ppm values by the Larmor frequency in + MHz, MHz*ppm -> Hz """ temp_dict = self.dict() - if self.property_units["anisotropy"] is "ppm": + if self.property_units["anisotropy"] == "ppm": temp_dict["anisotropy"] *= larmor_frequency return temp_dict diff --git a/src/mrsimulator/tests/__init__.py b/src/mrsimulator/tests/__init__.py index 7b8b05ea2..575a9d2a1 100644 --- a/src/mrsimulator/tests/__init__.py +++ b/src/mrsimulator/tests/__init__.py @@ -1,11 +1,10 @@ +# -*- coding: utf-8 -*- import os.path import pytest from monty.serialization import loadfn MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -TEST_FOLDER = os.path.abspath( - os.path.join(MODULE_DIR, "..", "..", "..", "test_files") -) +TEST_FOLDER = os.path.abspath(os.path.join(MODULE_DIR, "..", "..", "..", "test_files")) @pytest.fixture diff --git a/src/mrsimulator/tests/test_core.py b/src/mrsimulator/tests/test_core.py index d0aa294ae..465d769d4 100644 --- a/src/mrsimulator/tests/test_core.py +++ b/src/mrsimulator/tests/test_core.py @@ -1,52 +1,107 @@ -# coding: utf-8 +# -*- coding: utf-8 -*- """ Tests for the base Parseable pattern """ -import os.path +# import os.path import pytest -from typing import ClassVar +import numpy as np + +# from typing import ClassVar from mrsimulator import Site, Isotopomer, Spectrum -from mrsimulator.tests import mas_data, static_data + +# from mrsimulator.examples import mas_data, static_data + # Test Site def test_direct_init_site(): - Site(nucleus="29Si", isotropic_chemical_shift=10) + the_site = Site(isotope="29Si", isotropic_chemical_shift=10) + assert the_site.isotope == "29Si" + assert the_site.isotropic_chemical_shift == 10 + # assert the_site.property_units["isotropic_chemical_shift"] == "Hz" + assert the_site.shielding_antisymmetric is None + assert the_site.quadrupolar is None + assert the_site.shielding_symmetric is None def test_parse_json_site(): good_json = { - "isotope_symbol": "1H", + "isotope": "1H", "isotropic_chemical_shift": "0 ppm", "shielding_symmetric": {"anisotropy": "13.89 ppm", "asymmetry": 0.25}, } - good_json_2 = {"isotope_symbol": "1H", "isotropic_chemical_shift": "0 ppm"} - - bad_json = {"isotope_symbol": "1H", "isotropic_chemical_shift": "0 rad"} + the_site = Site.parse_json_with_units(good_json) + assert the_site.isotope == "1H" + assert the_site.isotropic_chemical_shift == 0 + assert the_site.property_units["isotropic_chemical_shift"] == "ppm" + assert the_site.shielding_antisymmetric is None + assert the_site.quadrupolar is None + assert the_site.shielding_symmetric.anisotropy == 13.89 + assert the_site.shielding_symmetric.property_units["anisotropy"] == "ppm" + assert the_site.shielding_symmetric.asymmetry == 0.25 + assert the_site.shielding_symmetric.alpha is None + assert the_site.shielding_symmetric.beta is None + assert the_site.shielding_symmetric.gamma is None + + good_json_2 = {"isotope": "14N", "isotropic_chemical_shift": "-120 Hz"} + + the_site = Site.parse_json_with_units(good_json_2) + assert the_site.isotope == "14N" + assert the_site.isotropic_chemical_shift == -120 + assert the_site.property_units["isotropic_chemical_shift"] == "Hz" + assert the_site.shielding_antisymmetric is None + assert the_site.quadrupolar is None + assert the_site.shielding_symmetric is None + + result = { + "isotope": "14N", + "isotropic_chemical_shift": -120.0, + "property_units": {"isotropic_chemical_shift": "Hz"}, + "quadrupolar": None, + "shielding_symmetric": None, + "shielding_antisymmetric": None, + } + assert the_site.dict() == result - Site.parse_json_with_units(good_json) - Site.parse_json_with_units(good_json_2) + bad_json = {"isotope": "1H", "isotropic_chemical_shift": "0 rad"} with pytest.raises(Exception): Site.parse_json_with_units(bad_json) def test_direct_init_isotopomer(): - Isotopomer(sites=[], abundance=10) - test_site = Site(nucleus="29Si", isotropic_chemical_shift=10) + the_isotopomer = Isotopomer(sites=[], abundance=10) + + assert the_isotopomer.sites == [] + assert the_isotopomer.abundance == 10.0 - Isotopomer(sites=[test_site], abundance=10) - Isotopomer(sites=[test_site, test_site], abundance=10) + test_site = Site(isotope="29Si", isotropic_chemical_shift=10) + + assert test_site.isotope == "29Si" + assert test_site.isotropic_chemical_shift == 10.0 + # assert test_site.property_units["isotropic_chemical_shift"] == "Hz" + + the_isotopomer = Isotopomer(sites=[test_site], abundance=10) + assert isinstance(the_isotopomer.sites[0], Site) + assert the_isotopomer.abundance == 10.0 + + the_isotopomer = Isotopomer(sites=[test_site, test_site], abundance=10) + assert isinstance(the_isotopomer.sites[0], Site) + assert isinstance(the_isotopomer.sites[1], Site) + assert id(the_isotopomer.sites[0] != the_isotopomer.sites[1]) + assert the_isotopomer.abundance == 10.0 + + # This should raise an error, but it is not. + the_isotopomer.sites[0] = "Trash" + assert the_isotopomer.sites[0] == "Trash" def test_parse_json_isotopomer(): good_json = {"sites": [], "abundance": "10"} good_json2 = { - "sites": [ - {"isotope_symbol": "1H", "isotropic_chemical_shift": "0 ppm"} - ], + "sites": [{"isotope": "1H", "isotropic_chemical_shift": "0 ppm"}], "abundance": "10", } @@ -60,7 +115,11 @@ def test_parse_json_isotopomer(): def test_direct_init_spectrum(): - Spectrum() + the_spectrum = Spectrum() + assert the_spectrum.number_of_points == 1024 + assert the_spectrum.spectral_width == 100 + # assert the_spectrum.property_units["spectral_width"] == 'Hz' + Spectrum( number_of_points=1024, spectral_width=100, @@ -69,7 +128,7 @@ def test_direct_init_spectrum(): rotor_frequency=0, rotor_angle=0.9553, # 54.935 degrees in radians rotor_phase=0, - nucleus="1H", + isotope="1H", spin=1, natural_abundance=0.04683, gyromagnetic_ratio=-8.465, @@ -83,29 +142,31 @@ def test_parse_json_spectrum(): "reference_offset": "0 Hz", "magnetic_flux_density": "9.4 T", "rotor_frequency": "0 Hz", - "rotor_angle": "0.9553 rad", # 54.935 degrees in radians + "rotor_angle": "54.935 degree", # 54.935 degrees in radians "rotor_phase": "0 rad", - "nucleus": "1H", + "isotope": "1H", } spec = Spectrum.parse_json_with_units(good_json) assert "spin" in spec.dict() assert spec.spin == 1 + assert spec.isotope == "1H" + assert np.allclose(spec.rotor_angle, 0.95879662) -def test_parsing(mas_data, static_data): - mas = Spectrum.parse_json_with_units(mas_data["spectrum"]) - static = Spectrum.parse_json_with_units(static_data["spectrum"]) +# def test_parsing(mas_data, static_data): +# mas = Spectrum.parse_json_with_units(mas_data["spectrum"]) +# static = Spectrum.parse_json_with_units(static_data["spectrum"]) - [ - Isotopomer.parse_json_with_units(isotopomer) - for isotopomer in mas_data["isotopomers"] - ] +# [ +# Isotopomer.parse_json_with_units(isotopomer) +# for isotopomer in mas_data["isotopomers"] +# ] - [ - Isotopomer.parse_json_with_units(isotopomer) - for isotopomer in static_data["isotopomers"] - ] +# [ +# Isotopomer.parse_json_with_units(isotopomer) +# for isotopomer in static_data["isotopomers"] +# ] - assert static.rotor_frequency == 0 - assert mas.rotor_frequency == 1000 \ No newline at end of file +# assert static.rotor_frequency == 0 +# assert mas.rotor_frequency == 1000 diff --git a/src/mrsimulator/tests/test_parseable.py b/src/mrsimulator/tests/test_parseable.py index be975b7dc..43ef45e4e 100644 --- a/src/mrsimulator/tests/test_parseable.py +++ b/src/mrsimulator/tests/test_parseable.py @@ -1,8 +1,8 @@ -# coding: utf-8 +# -*- coding: utf-8 -*- """ Tests for the base Parseable pattern """ -import os +# import os import pytest from typing import ClassVar diff --git a/src/mrsimulator/tests/test_simulator.py b/src/mrsimulator/tests/test_simulator.py index dd9ea92dc..69d749143 100644 --- a/src/mrsimulator/tests/test_simulator.py +++ b/src/mrsimulator/tests/test_simulator.py @@ -1,8 +1,8 @@ -# coding: utf-8 +# -*- coding: utf-8 -*- """ Tests for the base Parseable pattern """ -import os.path +# import os.path import pytest from mrsimulator import Site, Isotopomer, Spectrum, Simulator @@ -28,8 +28,7 @@ def spectrum(): def isotopomers(): return [ Isotopomer( - sites=[Site(isotope="29Si", isotropic_chemical_shift=10)], - abundance=10, + sites=[Site(isotope="29Si", isotropic_chemical_shift=10)], abundance=10 ) ] diff --git a/src/mrsimulator/unit.py b/src/mrsimulator/unit.py index 46391aedf..ddc4ba59f 100644 --- a/src/mrsimulator/unit.py +++ b/src/mrsimulator/unit.py @@ -206,24 +206,3 @@ def unit_to_latex(unit): # string = string.replace('* ( *', '(') # string = string.replace('* ) *', ')') return string - - -if __name__ == "__main__": - # import numpy as np - from timeit import default_timer as timer - - start = timer() - s = "1 µHz/Hz" - # s = '5 cm^-1 µs °' - # print (s, type(s)) - # a = string_to_quantity('1102 µh/km') - # print(display_unit(a)) - # print(a.to('s/m')) - a = string_to_quantity(s) # dtype=np.float32) - # print(timer() - start) - print(a) - print(a.unit) - print(a.to(_ppm)) - # print(type(a.unit), a.unit.physical_type) - # print('val_object', value_object_format(a, unit=True)) - print(unit_to_latex(a.unit)) diff --git a/src/mrsimulator/widgets.py b/src/mrsimulator/widgets.py new file mode 100644 index 000000000..e48c436e7 --- /dev/null +++ b/src/mrsimulator/widgets.py @@ -0,0 +1,364 @@ +# -*- coding: utf-8 -*- +import dash_core_components as dcc +import dash_html_components as html +import dash_table + + +__author__ = "Deepansh J. Srivastava" +__email__ = ["srivastava.89@osu.edu", "deepansh2012@gmail.com"] + + +colors = {"background": "#e2e2e2", "text": "#585858"} + + +def top_bar(): + return ( + html.Div( + className="row", + children=[ + html.Div( + className="col s12 m7 l7", + children=[ + html.H5(id="filename_dataset"), + html.H6( + id="data_time", + style={"textAlign": "left", "color": colors["text"]}, + ), + ], + ), + html.Div( + className="col s12 m5 l5", + children=[ + dcc.Upload( + id="upload_data", + children=html.Div( + ["Drag and Drop or ", html.A("Select File")] + ), + style={ + "width": "100%", + "height": "50px", + "lineHeight": "50px", + "borderWidth": "1px", + "borderStyle": "dashed", + "borderRadius": "5px", + "textAlign": "center", + "margin": "1px", + }, + # Allow multiple files to be uploaded + multiple=False, + ), + html.Label( + id="error_message", + # 'color': 'red'} + style={"textAlign": "center"}, + ), + ], + ), + ], + ), + ) + + +def table_css(): + return { + "selector": ".dash-cell div.dash-cell-value", + "rule": { + "display": "inline", + "white-space": "inherit", + "overflow": "inherit", + "text-overflow": "inherit", + "overflowX": "scroll", + "border": "thin lightgrey solid", + }, + } + + +def display_isotopomers(isotope, isotopomer_list): + columns_ = ["index", "site", "isotropic chemical shift", "anisotropy", "asymmetry"] + + child = [] + for j, isotopomer in enumerate(isotopomer_list): + sites_ = [] + for i, site in enumerate(isotopomer["sites"]): + if site["isotope"] == isotope: + site_ = {} + site_["index"] = i + site_["isotope"] = site["isotope"] + site_["isotropic chemical shift"] = site["isotropic_chemical_shift"] + site_["anisotropy"] = site["shielding_symmetric"]["anisotropy"] + site_["asymmetry"] = site["shielding_symmetric"]["asymmetry"] + + # ravel_.append(site['isotope']) + # ravel_.append(site['isotropic_chemical_shift']) + # ravel_.append(site['shielding_symmetric']['anisotropy']) + # ravel_.append(site['shielding_symmetric']['asymmetry']) + sites_.append(site_) + + child.append( + html.Div( + className="row", + children=[ + html.Div( + className="card-panel hoverable col s12 m6 l6", + children=[ + html.H6( + children="".join(["Isotopomer ", str(j)]), + style={ + "textAlign": "left", + "color": colors["text"], + }, + ), + dash_table.DataTable( + style_data={"whiteSpace": "normal"}, + css=[table_css()], + style_as_list_view=True, + style_cell={ + "textAlign": "left", + "padding": "5px", + }, + style_header={ + "backgroundColor": "white", + "fontWeight": "bold", + "fontSize": 12, + "color": "#585858", + }, + style_cell_conditional=[ + { + "if": {"row_index": "odd"}, + "backgroundColor": "#f8f8f8", + } + ], + columns=[ + {"name": i, "id": i} for i in columns_ + ], + data=sites_, + ), + ], + ) + ], + ) + ) + + # abundance = isotopomer['abundance'] + return child + + +def plot_object_widget(): + return [ + html.Div( + className="card-panel hoverable", + children=[ + html.H4( + id="spectrum_id", + children="Spectrum", + style={"textAlign": "left", "color": colors["text"]}, + ), + # dcc.Dropdown( + # id="download", + # options=[ + # {"label": "csv", "value": "csv"}, + # {"label": "csdf", "value": "csdf"}, + # ], + # value="csdf", + # ), + html.A(id="download_csv", children="\u21E9 Download CSV"), + html.A(id="download_csdf", children="\u21E9 Download CSDM"), + dcc.Graph(id="nmr_spectrum", figure={"data": []}), + ], + # style={'width': '100%', 'margin': '0px'} + ) + ] + + +def spectrum_object_widget(object_=[]): + """ + Return the layout for the isotope, number of points, + spectral width and reference offset. + """ + return [ + html.Div( + className="card-panel hoverable", + children=[ + html.H4( + children="Spectrum parameters", + style={"textAlign": "left", "color": colors["text"]}, + ), + # dcc.Tabs( + # id="tabs", + # value='direct_dimension', + # children=[ + # dcc.Tab( + # label='Direct dimension', + # value='direct_dimension' + # ), + # # dcc.Tab(label='Tab Two', value='tab-2-example'), + # ] + # ), + html.Div(id="tabs_content", children=object_), + ], + # style={'width': '100%', 'margin': '0px'} + ) + ] + + +def direct_dimension_setup(): + return [ + html.H5( + children="Direct dimension", + style={"textAlign": "left", "color": colors["text"]}, + ), + # environment + html.Div( + [html.H6("Environment parameters")], + style={ + "margin-bottom": "5px", + "margin-top": "35px", + "color": colors["text"], + }, + ), + # isotope + html.Label("Isotope"), + html.Div( + id="isotope_widget_id", + children=[dcc.Dropdown(id="isotope_id")], + style={"margin-bottom": "10px", "margin-top": "0px"}, + ), + # Magnetic flux density + html.Label(id="Magnetic_flux_density_output_container"), + html.Div( + [ + dcc.Slider( + id="magnetic_flux_density", min=0, max=30, step=0.1, value=9.4 + ) + ], + style={"margin-bottom": "10px", "margin-top": "0px"}, + ), + # Magic angle spinning + html.Label(id="spinning_frequency_output_container"), + html.Div( + className="row", + children=[ + dcc.Slider( + className="col s6 m6 l6", + id="spinning_frequency_in_kHz_coarse", + min=0.0, + max=110, + step=5.0, + value=0, + marks={0: "0 Hz", 50: "50 kHz", 110: "110 kHz"}, + # style={'textAlign': 'right'} + ), + dcc.Slider( + className="col s6 m6 l6", + id="spinning_frequency_in_kHz_fine", + min=0, + max=5, + step=0.050, + value=2.5, + marks={2.5: "+2.5 kHz", 5: "+5 kHz"}, + ), + ], + style={"margin-bottom": "10px", "margin-top": "0px"}, + ), + # dimension + html.Div( + className="row", + children=[ + html.H6(className="col s12 m12 l12", children="Dimension parameters"), + # daq.BooleanSwitch( + # id='ppm_switch', + # className='col s6 m6 l6', + # label='Show ppm', + # labelPosition='bottom', + # # size=40, + # style={ + # 'margin-bottom': '0px', + # 'margin-top': '5px', + # 'color': colors['text'] + # } + # ) + ], + style={ + "margin-bottom": "0px", + "margin-top": "35px", + "color": colors["text"], + }, + ), + # Number of points + html.Label(id="number_of_points_output_container"), + html.Div( + [ + dcc.Slider( + id="number_of_points", + min=8, + max=16, + step=1, + value=10, + marks={ + 8: "", + 9: "", + 10: "", + 11: "", + 12: "", + 13: "", + 14: "", + 15: "", + 16: "", + }, + ) + ], + style={"margin-bottom": "10px", "margin-top": "0px"}, + ), + # Spectral width + html.Label(id="frequency_bandwidth_output_container"), + html.Div( + className="row", + children=[ + dcc.Slider( + className="col s6 m6 l6", + id="frequency_bandwidth_coarse", + min=0.0, + max=1000, + step=50, + value=100, + marks={0: "0 Hz", 500: "0.5 MHz", 1000: "1 MHz"}, + ), + dcc.Slider( + className="col s6 m6 l6", + id="frequency_bandwidth_fine", + min=0.0, + max=50, + step=0.050, + value=25, + marks={0: "", 25: "+25 kHz", 50: "+50 kHz"}, + ), + ], + # style={'margin-bottom': '10px', 'margin-top': '0px'} + ), + # Reference offset + html.Label(id="reference_offset_output_container"), + html.Div( + className="row", + children=[ + dcc.Slider( + className="col s6 m6 l6", + id="reference_offset_coarse", + min=0.0, + max=100, + step=10, + value=0, + marks={0: "0 Hz", 50: "50 kHz", 100: "100 kHz"}, + ), + dcc.Slider( + className="col s6 m6 l6", + id="reference_offset_fine", + min=0, + max=10, + step=0.050, + value=0, + marks={0: "", 5: "+5 kHz", 10: "+10 kHz"}, + ), + ], + # style={'margin-bottom': '10px', 'margin-top': '0px'} + ), + ] diff --git a/tests/automate_test.py b/tests/automate_test.py index a26908f77..fb4100a36 100644 --- a/tests/automate_test.py +++ b/tests/automate_test.py @@ -6,8 +6,9 @@ from numpy.fft import fft from numpy.fft import fftshift -from mrsimulator import Site, Isotopomer, Spectrum, Simulator -from mrsimulator.methods import one_d_spectrum +from mrsimulator import Isotopomer, Spectrum, Simulator + +TEST_C = True def _import_json(filename): @@ -39,8 +40,8 @@ def _get_header_and_footer(source_file): return skip_header, skip_footer -def read_and_compare_data(filename): - """Load a simpson output file""" +def get_data(filename): + """Load a simpson or DMfit output file""" # source data data_object = _import_json(filename) @@ -88,17 +89,14 @@ def read_and_compare_data(filename): data_source /= data_source.max() - # if test_data_object["source"] == "python": - # label_source = "python" - if test_data_object["source"] == "dmfit": data_source = data_source[::-1] data_source = np.roll(data_source, 1) - # label_source = "dmfit" - # if test_data_object["source"] == "simpson": - # label_source = "simpson" + return data_object, data_source + +def c_setup(data_object, data_source): # mrsimulator spectrum = Spectrum.parse_json_with_units(data_object["spectrum"]) isotopomer = [ @@ -107,80 +105,66 @@ def read_and_compare_data(filename): ] s1 = Simulator(isotopomer, spectrum) - freq, data_mrsimulator = s1.one_d_spectrum( - geodesic_polyhedron_frequency=120 - ) + freq, data_mrsimulator = s1.one_d_spectrum(geodesic_polyhedron_frequency=120) data_mrsimulator /= data_mrsimulator.max() return data_mrsimulator, data_source - - return satisfy - -# --------------------------------------------------------------------------- # -# The test pass criterion -# np.all((mrsimulator_vector - test_vector) < 0.0 -# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- # # Test against simpson calculations -def test00_sim(): - path_ = path.join("tests", "simpson") - file_ = path.join(path_, "test00", "test00.json") - - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) - - -def test01_sim(): - path_ = path.join("tests", "simpson") - file_ = path.join(path_, "test01", "test01.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) - - -def test02_sim(): - path_ = path.join("tests", "simpson") - file_ = path.join(path_, "test02", "test02.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) - +if TEST_C: -def test03_sim(): - path_ = path.join("tests", "simpson") - file_ = path.join(path_, "test03", "test03.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) + def test00_sim(): + path_ = path.join("tests", "simpson") + file_ = path.join(path_, "test00", "test00.json") + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) -def test04_sim(): - path_ = path.join("tests", "simpson") - file_ = path.join(path_, "test04", "test04.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) + def test01_sim(): + path_ = path.join("tests", "simpson") + file_ = path.join(path_, "test01", "test01.json") + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) + def test02_sim(): + path_ = path.join("tests", "simpson") + file_ = path.join(path_, "test02", "test02.json") + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) -def test05_sim(): - path_ = path.join("tests", "simpson") - file_ = path.join(path_, "test05", "test05.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) + def test03_sim(): + path_ = path.join("tests", "simpson") + file_ = path.join(path_, "test03", "test03.json") + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) + def test04_sim(): + path_ = path.join("tests", "simpson") + file_ = path.join(path_, "test04", "test04.json") + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) -def test06_sim(): - path_ = path.join("tests", "simpson") - file_ = path.join(path_, "test06", "test06.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) + def test05_sim(): + path_ = path.join("tests", "simpson") + file_ = path.join(path_, "test05", "test05.json") + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) + def test06_sim(): + path_ = path.join("tests", "simpson") + file_ = path.join(path_, "test06", "test06.json") + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) -def test07_sim(): - path_ = path.join("tests", "simpson") - file_ = path.join(path_, "test07", "test07.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) + def test07_sim(): + path_ = path.join("tests", "simpson") + file_ = path.join(path_, "test07", "test07.json") + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) # --------------------------------------------------------------------------- # @@ -191,33 +175,33 @@ def test07_sim(): def test00_python(): path_ = path.join("tests", "python") file_ = path.join(path_, "test00", "test00.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) def test01_python(): path_ = path.join("tests", "python") file_ = path.join(path_, "test01", "test01.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) def test02_python(): path_ = path.join("tests", "python") file_ = path.join(path_, "test02", "test02.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) def test03_python(): path_ = path.join("tests", "python") file_ = path.join(path_, "test03", "test03.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) def test04_python(): path_ = path.join("tests", "python") file_ = path.join(path_, "test04", "test04.json") - data_mrsimulator,data_source = read_and_compare_data(file_) - np.testing.assert_almost_equal(data_mrsimulator,data_source,decimal=2) + data_mrsimulator, data_source = c_setup(*get_data(file_)) + np.testing.assert_almost_equal(data_mrsimulator, data_source, decimal=2) diff --git a/tests/orientations_test.py b/tests/orientations_test.py new file mode 100644 index 000000000..293512f47 --- /dev/null +++ b/tests/orientations_test.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +import numpy as np +from mrsimulator.python.orientation import cosine_of_polar_angles_and_amplitudes +from mrsimulator.python.orientation import triangle_interpolation + +import mrsimulator.sandbox as clib + + +def test_polar_coordinates_and_amps(): + nt = 64 + cos_alpha_py, cos_beta_py, amp_py = cosine_of_polar_angles_and_amplitudes(nt) + cos_alpha_c, cos_beta_c, amp_c = clib.cosine_of_polar_angles_and_amplitudes(nt) + + assert np.allclose(cos_alpha_py, cos_alpha_c, atol=1e-15) + assert np.allclose(cos_beta_py, cos_beta_c, atol=1e-15) + assert np.allclose(amp_py, amp_c, atol=1e-15) + + +def test_triangle_interpolation(): + f = np.asarray([10, 30, 50]) + amp_py = np.zeros(100) + triangle_interpolation(f, amp_py) + + amp_c = np.zeros(100) + clib.triangle_interpolation(f, amp_c) + + assert np.allclose(amp_py, amp_c, atol=1e-15) diff --git a/tests/phase_test.py b/tests/phase_test.py new file mode 100644 index 000000000..f187afcc6 --- /dev/null +++ b/tests/phase_test.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +import mrsimulator.sandbox as clib +from mrsimulator.python.utils import pre_phase_components +import numpy as np + + +def test_phase_components(): + number_of_sidebands = 64 + spin_frequency = 10 + pre_phase_py = pre_phase_components(number_of_sidebands, spin_frequency) + pre_phase_c = clib.pre_phase_components(number_of_sidebands, spin_frequency) + + assert np.allclose(pre_phase_c, pre_phase_py) diff --git a/tests/python/generate_static_spectrum.py b/tests/python/generate_static_spectrum.py.scr similarity index 73% rename from tests/python/generate_static_spectrum.py rename to tests/python/generate_static_spectrum.py.scr index 307854ef7..687b769f3 100644 --- a/tests/python/generate_static_spectrum.py +++ b/tests/python/generate_static_spectrum.py.scr @@ -23,57 +23,41 @@ def get_CSA_frequencies(three_cos_m1, two_phi2_m1, iso, zeta, eta): def brute_force_bin(freq, npts, spectral_width): delta = spectral_width / npts - range_ = ( - np.asarray([-spectral_width / 2.0, spectral_width / 2.0]) - delta / 2 - ) + range_ = np.asarray([-spectral_width / 2.0, spectral_width / 2.0]) - delta / 2 x, y = np.histogram(freq, npts, range_) return x, y def test00(three_cos_m1, two_phi2_m1): - freq = get_CSA_frequencies( - three_cos_m1, two_phi2_m1, iso=0, zeta=8000, eta=0.5 - ) + freq = get_CSA_frequencies(three_cos_m1, two_phi2_m1, iso=0, zeta=8000, eta=0.5) x, y = brute_force_bin(freq, npts=2048, spectral_width=100000) np.savetxt("test00.csv", np.asarray([x]).T, fmt="%.4f", delimiter=",") def test01(three_cos_m1, two_phi2_m1): - freq = get_CSA_frequencies( - three_cos_m1, two_phi2_m1, iso=2000, zeta=6000, eta=0.75 - ) + freq = get_CSA_frequencies(three_cos_m1, two_phi2_m1, iso=2000, zeta=6000, eta=0.75) x, y = brute_force_bin(freq, npts=2048, spectral_width=100000) np.savetxt("test01.csv", np.asarray([x]).T, fmt="%.4f", delimiter=",") def test02(three_cos_m1, two_phi2_m1): - freq = get_CSA_frequencies( - three_cos_m1, two_phi2_m1, iso=-120, zeta=9340, eta=0.0 - ) + freq = get_CSA_frequencies(three_cos_m1, two_phi2_m1, iso=-120, zeta=9340, eta=0.0) x, y = brute_force_bin(freq, npts=2048, spectral_width=100000) np.savetxt("test02.csv", np.asarray([x]).T, fmt="%.4f", delimiter=",") def test03(three_cos_m1, two_phi2_m1): - freq = get_CSA_frequencies( - three_cos_m1, two_phi2_m1, iso=200, zeta=7310, eta=1.0 - ) + freq = get_CSA_frequencies(three_cos_m1, two_phi2_m1, iso=200, zeta=7310, eta=1.0) x, y = brute_force_bin(freq, npts=2048, spectral_width=100000) np.savetxt("test03.csv", np.asarray([x]).T, fmt="%.4f", delimiter=",") def test04(three_cos_m1, two_phi2_m1): - freq = get_CSA_frequencies( - three_cos_m1, two_phi2_m1, iso=1200, zeta=5310, eta=0.31 - ) + freq = get_CSA_frequencies(three_cos_m1, two_phi2_m1, iso=1200, zeta=5310, eta=0.31) x1, y1 = brute_force_bin(freq, npts=2048, spectral_width=100000) - freq = get_CSA_frequencies( - three_cos_m1, two_phi2_m1, iso=0, zeta=-3310, eta=0.86 - ) + freq = get_CSA_frequencies(three_cos_m1, two_phi2_m1, iso=0, zeta=-3310, eta=0.86) x2, y2 = brute_force_bin(freq, npts=2048, spectral_width=100000) - np.savetxt( - "test04.csv", np.asarray([x1 + x2]).T, fmt="%.4f", delimiter="," - ) + np.savetxt("test04.csv", np.asarray([x1 + x2]).T, fmt="%.4f", delimiter=",") if __name__ == "__main__": diff --git a/tests/python/test00/test00.json b/tests/python/test00/test00.json index 77bce3a63..d4a7043be 100644 --- a/tests/python/test00/test00.json +++ b/tests/python/test00/test00.json @@ -4,14 +4,14 @@ "rotor_frequency": "0 kHz", "number_of_points": 2048, "spectral_width": "100 kHz", - "nucleus": "1H" + "isotope": "1H" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "1H", + "isotope": "1H", "isotropic_chemical_shift": "0 Hz", "shielding_symmetric": { "anisotropy": "8 kHz", diff --git a/tests/python/test01/test01.json b/tests/python/test01/test01.json index 2a37c2a9f..193e73f65 100644 --- a/tests/python/test01/test01.json +++ b/tests/python/test01/test01.json @@ -4,14 +4,14 @@ "rotor_frequency": "0 kHz", "number_of_points": 2048, "spectral_width": "100 kHz", - "nucleus": "1H" + "isotope": "1H" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "1H", + "isotope": "1H", "isotropic_chemical_shift": "2 kHz", "shielding_symmetric": { "anisotropy": "6 kHz", diff --git a/tests/python/test02/test02.json b/tests/python/test02/test02.json index 72108ef67..a1704af53 100644 --- a/tests/python/test02/test02.json +++ b/tests/python/test02/test02.json @@ -4,14 +4,14 @@ "rotor_frequency": "0 kHz", "number_of_points": 2048, "spectral_width": "100 kHz", - "nucleus": "1H" + "isotope": "1H" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "1H", + "isotope": "1H", "isotropic_chemical_shift": "-120 Hz", "shielding_symmetric": { "anisotropy": "9.34 kHz", diff --git a/tests/python/test03/test03.json b/tests/python/test03/test03.json index 782ad3e9e..7106ec701 100644 --- a/tests/python/test03/test03.json +++ b/tests/python/test03/test03.json @@ -4,14 +4,14 @@ "rotor_frequency": "0 kHz", "number_of_points": 2048, "spectral_width": "100 kHz", - "nucleus": "1H" + "isotope": "1H" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "1H", + "isotope": "1H", "isotropic_chemical_shift": "200 Hz", "shielding_symmetric": { "anisotropy": "7.31 kHz", diff --git a/tests/python/test04/test04.json b/tests/python/test04/test04.json index 5b9cb5edb..ab3985aaa 100644 --- a/tests/python/test04/test04.json +++ b/tests/python/test04/test04.json @@ -4,14 +4,14 @@ "rotor_frequency": "0 kHz", "number_of_points": 2048, "spectral_width": "100 kHz", - "nucleus": "1H" + "isotope": "1H" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "1H", + "isotope": "1H", "isotropic_chemical_shift": "1200 Hz", "shielding_symmetric": { "anisotropy": "5310 Hz", @@ -23,7 +23,7 @@ { "sites": [ { - "isotope_symbol": "1H", + "isotope": "1H", "isotropic_chemical_shift": "0 Hz", "shielding_symmetric": { "anisotropy": "-3310 Hz", diff --git a/tests/simpson/test00/test00.json b/tests/simpson/test00/test00.json index 0221950e2..93c46049d 100644 --- a/tests/simpson/test00/test00.json +++ b/tests/simpson/test00/test00.json @@ -4,14 +4,14 @@ "rotor_frequency": "1 kHz", "number_of_points": 100, "spectral_width": "100 kHz", - "nucleus": "29Si" + "isotope": "29Si" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "29Si", + "isotope": "29Si", "isotropic_chemical_shift": "0 Hz", "shielding_symmetric": { "anisotropy": "12 kHz", diff --git a/tests/simpson/test01/test01.json b/tests/simpson/test01/test01.json index 80d0410eb..73e6787ae 100644 --- a/tests/simpson/test01/test01.json +++ b/tests/simpson/test01/test01.json @@ -4,14 +4,14 @@ "rotor_frequency": "5 kHz", "number_of_points": 100, "spectral_width": "100 kHz", - "nucleus": "29Si" + "isotope": "29Si" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "29Si", + "isotope": "29Si", "isotropic_chemical_shift": "0 Hz", "shielding_symmetric": { "anisotropy": "22 kHz", diff --git a/tests/simpson/test02/test02.json b/tests/simpson/test02/test02.json index 3b2ad860f..ade20d13f 100644 --- a/tests/simpson/test02/test02.json +++ b/tests/simpson/test02/test02.json @@ -4,14 +4,14 @@ "rotor_frequency": "50 Hz", "number_of_points": 200, "spectral_width": "10 kHz", - "nucleus": "29Si" + "isotope": "29Si" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "29Si", + "isotope": "29Si", "isotropic_chemical_shift": "0 Hz", "shielding_symmetric": { "anisotropy": "2 kHz", diff --git a/tests/simpson/test03/test03.json b/tests/simpson/test03/test03.json index 9ee0c99b0..170efd4af 100644 --- a/tests/simpson/test03/test03.json +++ b/tests/simpson/test03/test03.json @@ -4,14 +4,14 @@ "rotor_frequency": "1 kHz", "number_of_points": 512, "spectral_width": "64 kHz", - "nucleus": "29Si" + "isotope": "29Si" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "29Si", + "isotope": "29Si", "isotropic_chemical_shift": "0 Hz", "shielding_symmetric": { "anisotropy": "8 kHz", diff --git a/tests/simpson/test04/test04.json b/tests/simpson/test04/test04.json index db0598cb1..b018e301a 100644 --- a/tests/simpson/test04/test04.json +++ b/tests/simpson/test04/test04.json @@ -4,14 +4,14 @@ "rotor_frequency": "1 kHz", "number_of_points": 512, "spectral_width": "64 kHz", - "nucleus": "29Si" + "isotope": "29Si" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "29Si", + "isotope": "29Si", "isotropic_chemical_shift": "0 Hz", "shielding_symmetric": { "anisotropy": "12 kHz", diff --git a/tests/simpson/test05/test05.json b/tests/simpson/test05/test05.json index e4c8dab3a..a132b8b18 100644 --- a/tests/simpson/test05/test05.json +++ b/tests/simpson/test05/test05.json @@ -4,14 +4,14 @@ "rotor_frequency": "1 kHz", "number_of_points": 512, "spectral_width": "64 kHz", - "nucleus": "29Si" + "isotope": "29Si" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "29Si", + "isotope": "29Si", "isotropic_chemical_shift": "0 Hz", "shielding_symmetric": { "anisotropy": "5 kHz", diff --git a/tests/simpson/test06/test06.json b/tests/simpson/test06/test06.json index c64388112..1ac26be41 100644 --- a/tests/simpson/test06/test06.json +++ b/tests/simpson/test06/test06.json @@ -4,14 +4,14 @@ "rotor_frequency": "1 kHz", "number_of_points": 512, "spectral_width": "64 kHz", - "nucleus": "29Si" + "isotope": "29Si" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "29Si", + "isotope": "29Si", "isotropic_chemical_shift": "0 Hz", "shielding_symmetric": { "anisotropy": "3 kHz", diff --git a/tests/simpson/test07/test07.json b/tests/simpson/test07/test07.json index 32e2b3ad3..50acdeb48 100644 --- a/tests/simpson/test07/test07.json +++ b/tests/simpson/test07/test07.json @@ -4,14 +4,14 @@ "rotor_frequency": "1 kHz", "number_of_points": 512, "spectral_width": "64 kHz", - "nucleus": "29Si" + "isotope": "29Si" } }, "isotopomers": [ { "sites": [ { - "isotope_symbol": "29Si", + "isotope": "29Si", "isotropic_chemical_shift": "0 Hz", "shielding_symmetric": { "anisotropy": "8 kHz", diff --git a/tests/wigner/l=2_cx=0.5.npy b/tests/wigner/l=2_cx=0.5.npy new file mode 100644 index 000000000..71ae7bd59 Binary files /dev/null and b/tests/wigner/l=2_cx=0.5.npy differ diff --git a/tests/wigner/l=2_cx=[-0.5498, 0.230].npy b/tests/wigner/l=2_cx=[-0.5498, 0.230].npy new file mode 100644 index 000000000..f538c6d5d Binary files /dev/null and b/tests/wigner/l=2_cx=[-0.5498, 0.230].npy differ diff --git a/tests/wigner/l=4_cx=-0.8459.npy b/tests/wigner/l=4_cx=-0.8459.npy new file mode 100644 index 000000000..309458b7b Binary files /dev/null and b/tests/wigner/l=4_cx=-0.8459.npy differ diff --git a/tests/wigner/l=4_cx=[-0.934, 0.4958].npy b/tests/wigner/l=4_cx=[-0.934, 0.4958].npy new file mode 100644 index 000000000..490ff9da8 Binary files /dev/null and b/tests/wigner/l=4_cx=[-0.934, 0.4958].npy differ diff --git a/tests/wigner/wigner_matrix_test.py b/tests/wigner/wigner_matrix_test.py new file mode 100644 index 000000000..157eee91a --- /dev/null +++ b/tests/wigner/wigner_matrix_test.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +import mrsimulator.sandbox as clib +from mrsimulator.python.angular_momentum import ( + wigner_d_matrix_cosines, + wigner_dm0_vector, +) +import numpy as np +from sympy.physics.quantum.spin import Rotation + + +def wigner_dm0_vector_sympy(l, angle): + R_out = np.empty(2 * l + 1, dtype=np.float64) + for i in range(2 * l + 1): + R_out[i] = complex(Rotation.d(l, -l + i, 0, angle).doit()).real + return R_out + + +def wigner(l, cos_beta): + # python test + cos_beta = np.asarray([cos_beta]).ravel() + wigner_py = wigner_d_matrix_cosines(l, cos_beta) + + # c test + wigner_c = clib.wigner_d_matrix_cosines(l, cos_beta) + return wigner_py.ravel(), wigner_c.ravel() + + +# All wigner matrix are tested against the wigner matrix computed using +# Sympy Rotaion methods. See the main function in this file. + + +def test_wigner_2j_matrix_cosine_00(): + l = 2 + cos_beta = 0.5 + wigner_py, wigner_c = wigner(l, cos_beta) + array = np.load("tests/wigner/l=2_cx=0.5.npy") + assert np.allclose(array, wigner_py, atol=1e-15) + assert np.allclose(array, wigner_c, atol=1e-15) + + +def test_wigner_2j_matrix_cosine_01(): + l = 2 + cos_beta = [-0.5498, 0.230] + wigner_py, wigner_c = wigner(l, cos_beta) + array = np.load("tests/wigner/l=2_cx=[-0.5498, 0.230].npy") + assert np.allclose(array, wigner_py, atol=1e-15) + assert np.allclose(array, wigner_c, atol=1e-15) + + +def test_wigner_4j_matrix_cosine_02(): + l = 4 + cos_beta = [-0.8459] + wigner_py, wigner_c = wigner(l, cos_beta) + array = np.load("tests/wigner/l=4_cx=-0.8459.npy") + assert np.allclose(array, wigner_py, atol=1e-15) + assert np.allclose(array, wigner_c, atol=1e-15) + + +def test_wigner_4j_matrix_cosine_03(): + l = 4 + cos_beta = [-0.934, 0.4958] + wigner_py, wigner_c = wigner(l, cos_beta) + array = np.load("tests/wigner/l=4_cx=[-0.934, 0.4958].npy") + assert np.allclose(array, wigner_py, atol=1e-15) + assert np.allclose(array, wigner_c, atol=1e-15) + + +def test_wigner_2j_dm0_vector(): + R_py = wigner_dm0_vector(2, 0.235) + R_c = clib.wigner_dm0_vector(2, 0.235) + R_sympy = wigner_dm0_vector_sympy(2, 0.235) + assert np.allclose(R_py, R_sympy, atol=1e-15) + assert np.allclose(R_c, R_sympy, atol=1e-15) + + +def test_wigner_4j_dm0_vector(): + R_py = wigner_dm0_vector(4, 0.235) + R_c = clib.wigner_dm0_vector(4, 0.235) + R_sympy = wigner_dm0_vector_sympy(4, 0.235) + assert np.allclose(R_py, R_sympy, atol=1e-15) + assert np.allclose(R_c, R_sympy, atol=1e-15) + + +# def triangle(f, n_points): +# f = np.sort(f) +# h = 2.0 / (f[2] - f[0]) +# x = f +# y = [0, h, 0] + +# p = np.arange(int(x[1] - x[0])) +# spec = np.arange(n_points) +# y[int(x[0]):int(x[0]) + p] = y[0] + (y[1] - y[0]) / (x[1] - x[0]) * (p - x[0]) + + +if __name__ == "__main__": + value = [] + l = 2 + cos_beta = [-0.5498, 0.230] + for k, cos_b in enumerate(cos_beta): + for i in range(2 * l + 1): + for j in range(2 * l + 1): + value.append( + complex(Rotation.d(l, -l + j, -l + i, np.arccos(cos_b)).doit()).real + ) + value = np.asarray(value) + np.save("tests/wigner/l=2_cx=[-0.5498, 0.230]", value) + + value = [] + l = 2 + cos_beta = 0.5 + for i in range(2 * l + 1): + for j in range(2 * l + 1): + value.append( + complex(Rotation.d(l, -l + j, -l + i, np.arccos(cos_beta)).doit()).real + ) + value = np.asarray(value) + np.save("tests/wigner/l=2_cx=0.5", value) + + value = [] + l = 4 + cos_beta = -0.8459 + for i in range(2 * l + 1): + for j in range(2 * l + 1): + value.append( + complex(Rotation.d(l, -l + j, -l + i, np.arccos(cos_beta)).doit()).real + ) + value = np.asarray(value) + np.save("tests/wigner/l=4_cx=-0.8459", value) + + value = [] + l = 4 + cos_beta = [-0.934, 0.4958] + for k, cos_b in enumerate(cos_beta): + for i in range(2 * l + 1): + for j in range(2 * l + 1): + value.append( + complex(Rotation.d(l, -l + j, -l + i, np.arccos(cos_b)).doit()).real + ) + value = np.asarray(value) + np.save("tests/wigner/l=4_cx=[-0.934, 0.4958]", value) diff --git a/tests/wigner/wigner_rotation_test.py b/tests/wigner/wigner_rotation_test.py new file mode 100644 index 000000000..3daeaad53 --- /dev/null +++ b/tests/wigner/wigner_rotation_test.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +import mrsimulator.sandbox as clib +from mrsimulator.python.angular_momentum import wigner_rotation +import numpy as np + + +def test_wigner_2j_rotation_00(): + cos_beta = 0.5 + cos_alpha = 0.5 + cos_beta = np.asarray([cos_beta], dtype=np.float64).ravel() + cos_alpha = np.asarray([cos_alpha], dtype=np.float64).ravel() + + R_in = np.asarray([0 + 0.5j, 0, 0 + 0.1j, 0, 0 - 0.5j], dtype=np.complex128) + + R_out_c = clib.wigner_rotation(2, R_in, cos_alpha, cos_beta) + R_out_py = wigner_rotation(2, R_in, cos_alpha, cos_beta) + + assert np.allclose(R_out_py, R_out_c, atol=1e-15) + + +def test_wigner_2j_rotation_01(): + cos_beta = [0.15, 0.51, 0.223] + cos_alpha = [0.53, 0.95, 0.391] + cos_beta = np.asarray([cos_beta], dtype=np.float64).ravel() + cos_alpha = np.asarray([cos_alpha], dtype=np.float64).ravel() + + R_in = np.asarray([0 + 0.5j, 0, 0 + 0.1j, 0, 0 - 0.5j], dtype=np.complex128) + + R_out_c = clib.wigner_rotation(2, R_in, cos_alpha, cos_beta) + R_out_py = wigner_rotation(2, R_in, cos_alpha, cos_beta) + + assert np.allclose(R_out_py, R_out_c, atol=1e-15) + + +def test_wigner_2j_rotation_02(): + cos_beta = 2.0 * np.random.rand(1024) - 1.0 + cos_alpha = 2.0 * np.random.rand(1024) - 1.0 + cos_beta = np.asarray([cos_beta], dtype=np.float64).ravel() + cos_alpha = np.asarray([cos_alpha], dtype=np.float64).ravel() + + R_in = np.asarray([0 + 0.5j, 0, 0 + 0.1j, 0, 0 - 0.5j], dtype=np.complex128) + + R_out_c = clib.wigner_rotation(2, R_in, cos_alpha, cos_beta) + R_out_py = wigner_rotation(2, R_in, cos_alpha, cos_beta) + + assert np.allclose(R_out_py, R_out_c, atol=1e-15) + + +def test_wigner_4j_rotation_03(): + cos_beta = 0.35 + cos_alpha = 0.598 + cos_beta = np.asarray([cos_beta], dtype=np.float64).ravel() + cos_alpha = np.asarray([cos_alpha], dtype=np.float64).ravel() + + R_in = np.asarray( + [0 - 0.2j, 0, 0 + 0.5j, 0, 0 + 0.1j, 0, 0 - 0.5j, 0, 0 + 0.2j], + dtype=np.complex128, + ) + + R_out_c = clib.wigner_rotation(4, R_in, cos_alpha, cos_beta) + R_out_py = wigner_rotation(4, R_in, cos_alpha, cos_beta) + + assert np.allclose(R_out_py, R_out_c, atol=1e-15) + + +def test_wigner_4j_rotation_04(): + cos_beta = 2.0 * np.random.rand(1024) - 1.0 + cos_alpha = 2.0 * np.random.rand(1024) - 1.0 + cos_beta = np.asarray([cos_beta], dtype=np.float64).ravel() + cos_alpha = np.asarray([cos_alpha], dtype=np.float64).ravel() + + R_in = np.asarray( + [0 - 0.2j, 0, 0 + 0.5j, 0, 0 + 0.1j, 0, 0 - 0.5j, 0, 0 + 0.2j], + dtype=np.complex128, + ) + + R_out_c = clib.wigner_rotation(4, R_in, cos_alpha, cos_beta) + R_out_py = wigner_rotation(4, R_in, cos_alpha, cos_beta) + + assert np.allclose(R_out_py, R_out_c, atol=1e-15)