From aa6bdec301c28addbe3cfde8c49da5c55bed506d Mon Sep 17 00:00:00 2001 From: Diego Prada Date: Tue, 28 Mar 2023 15:56:14 -0600 Subject: [PATCH] Function stack added --- .github/workflows/CI.yaml | 49 ++- .../build_and_upload_conda_packages.yaml | 39 +- .../workflows/sphinx_docs_to_gh_pages.yaml | 47 ++- README.md | 2 +- docs/contents/user/Lists_and_arrays.ipynb | 391 ++++++++++++++++++ docs/index.rst | 1 + pyunitwizard/__init__.py | 1 + pyunitwizard/main.py | 70 +++- pyunitwizard/tests/test_concatenate.py | 8 - pyunitwizard/tests/test_stack.py | 17 + 10 files changed, 569 insertions(+), 56 deletions(-) create mode 100644 docs/contents/user/Lists_and_arrays.ipynb create mode 100644 pyunitwizard/tests/test_stack.py diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 61bfb40..0ddc53f 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -14,7 +14,7 @@ on: # Nightly tests run on master by default: # Scheduled workflows run on the latest commit on the default or base branch. # (from https://help.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule) - - cron: "0 0 * * *" + - cron: "0 9 * * MON" workflow_dispatch: @@ -28,21 +28,18 @@ jobs: #os: ["macOS-latest", "ubuntu-latest"] # windows-latest (https://github.com/openmm/openmm/issues/3618#issuecomment-1166019901) #python-version: ["3.8", "3.9", "3.10"] cfg: - - { os: ubuntu-latest, python-version: "3.7"} - { os: ubuntu-latest, python-version: "3.8"} - { os: ubuntu-latest, python-version: "3.9"} #- { os: ubuntu-latest, python-version: "3.10"} - - { os: macos-latest, python-version: "3.7"} - { os: macos-latest, python-version: "3.8"} - { os: macos-latest, python-version: "3.9"} #- { os: macos-latest, python-version: "3.10"} - - { os: windows-latest, python-version: "3.7"} #- { os: windows-latest, python-version: "3.8"} #- { os: windows-latest, python-version: "3.9"} #- { os: windows-latest, python-version: "3.10"} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 @@ -53,18 +50,28 @@ jobs: # More info on options: https://github.com/conda-incubator/setup-miniconda - - uses: conda-incubator/setup-miniconda@v2 + #- uses: conda-incubator/setup-miniconda@v2 + # with: + # python-version: ${{ matrix.cfg.python-version }} + # environment-file: devtools/conda-envs/test_env.yaml + # #mamba-version: "*" + # #use-mamba: true + # channels: conda-forge,defaults + + # activate-environment: test + # auto-update-conda: false + # auto-activate-base: false + # show-channel-urls: true + + - name: Setup conda env + uses: mamba-org/provision-with-micromamba@main with: - python-version: ${{ matrix.cfg.python-version }} environment-file: devtools/conda-envs/test_env.yaml - #mamba-version: "*" - #use-mamba: true - channels: conda-forge,defaults + environment-name: test + channels: uibcdf, conda-forge, ambermd, defaults - activate-environment: test - auto-update-conda: false - auto-activate-base: false - show-channel-urls: true + extra-specs: | + python==${{ matrix.cfg.python-version }} - name: Install package @@ -72,13 +79,21 @@ jobs: shell: bash -l {0} run: | python setup.py develop - conda list - - name: Checking version + - name: Info conda + shell: bash -l {0} + run: | + micromamba info + micromamba list + - name: Test import module shell: bash -l {0} run: | - echo 'import pyunitwizard ; print(pyunitwizard.__version__)' | python + echo "::group::Importing module from home directory" + cd + pwd + echo 'import molsysmt; print("Version of the package: {}".format(molsysmt.__version__))' | python + echo "::endgroup::" - name: Run tests diff --git a/.github/workflows/build_and_upload_conda_packages.yaml b/.github/workflows/build_and_upload_conda_packages.yaml index 954748f..0c8fa8f 100644 --- a/.github/workflows/build_and_upload_conda_packages.yaml +++ b/.github/workflows/build_and_upload_conda_packages.yaml @@ -13,22 +13,39 @@ jobs: strategy: fail-fast: false # do not cancel all in-progress jobs if any job variation fails matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] # 3.10 with errors + python-version: ["3.8", "3.9"] # 3.10 with errors steps: - - uses: actions/checkout@v2 + + - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Conda environment creation and activation - uses: conda-incubator/setup-miniconda@v2 + #- name: Conda environment creation and activation + # uses: conda-incubator/setup-miniconda@v2 + # with: + # python-version: ${{ matrix.python-version }} + # environment-file: devtools/conda-envs/build_env.yaml + # #mamba-version: "*" + # #use-mamba: true + # auto-activate-base: false + # auto-update-conda: false + # show-channel-urls: true + + - name: Setup conda env + uses: mamba-org/provision-with-micromamba@main with: - python-version: ${{ matrix.python-version }} environment-file: devtools/conda-envs/build_env.yaml - #mamba-version: "*" - #use-mamba: true - auto-activate-base: false - auto-update-conda: false - show-channel-urls: true - + environment-name: build + channels: uibcdf, conda-forge, ambermd, defaults + + extra-specs: | + python==${{ matrix.python-version }} + + - name: Info conda + shell: bash -l {0} + run: | + micromamba info + micromamba list + - name: Build and upload the conda packages uses: uibcdf/action-build-and-upload-conda-packages@v1.2.0 with: diff --git a/.github/workflows/sphinx_docs_to_gh_pages.yaml b/.github/workflows/sphinx_docs_to_gh_pages.yaml index 185ef69..6f13b19 100644 --- a/.github/workflows/sphinx_docs_to_gh_pages.yaml +++ b/.github/workflows/sphinx_docs_to_gh_pages.yaml @@ -21,27 +21,52 @@ jobs: run: | uname -a - - name: Make conda environment - uses: conda-incubator/setup-miniconda@v2 # https://github.com/conda-incubator/setup-miniconda + - name: Setup conda env + uses: mamba-org/provision-with-micromamba@main with: - python-version: 3.9 environment-file: devtools/conda-envs/docs_env.yaml - #mamba-version: "*" - #use-mamba: true - auto-update-conda: false - auto-activate-base: false - show-channel-urls: true + environment-name: docs + channels: uibcdf, conda-forge, ambermd, defaults + + extra-specs: | + python=="3.9" + + #- name: Make conda environment + # uses: conda-incubator/setup-miniconda@v2 # https://github.com/conda-incubator/setup-miniconda + # with: + # python-version: 3.9 + # environment-file: devtools/conda-envs/docs_env.yaml + # #mamba-version: "*" + # #use-mamba: true + # auto-update-conda: false + # auto-activate-base: false + # show-channel-urls: true - name: Installing the library shell: bash -l {0} run: | python setup.py develop - conda list - - name: Checking version + - name: Info conda + shell: bash -l {0} + run: | + micromamba info + micromamba list + + #- name: Info conda + # shell: bash -l {0} + # run: | + # conda info + # conda list + + - name: Test import module shell: bash -l {0} run: | - echo 'import pyunitwizard; print(pyunitwizard.__version__)' | python + echo "::group::Importing module from home directory" + cd + pwd + echo 'import molsysmt; print("Version of the package: {}".format(molsysmt.__version__))' | python + echo "::endgroup::" - name: Running the Sphinx to gh-pages Action uses: uibcdf/action-sphinx-docs-to-gh-pages@v1.1.0-beta diff --git a/README.md b/README.md index cefc80a..1f11067 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![GitHub Actions Build Status](https://github.com/uibcdf/PyUnitWizard/workflows/CI/badge.svg)](https://github.com/uibcdf/PyUnitWizard/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/uibcdf/PyUnitWizard/branch/master/graph/badge.svg)](https://codecov.io/gh/uibcdf/PyUnitWizard/branch/master) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![](https://img.shields.io/badge/Python-3.7%20%7C%203.8%20%7C%203.9-blue.svg)](https://www.python.org/downloads/) +[![](https://img.shields.io/badge/Python-3.8%20%7C%203.9-blue.svg)](https://www.python.org/downloads/) [![Install with conda](https://img.shields.io/badge/Install%20with-conda-brightgreen.svg)](https://conda.anaconda.org/uibcdf/pyunitwizard) There are several Python libraries to work with physical quantities in the market, such as pint, unyt or openmm.unit. Imagine that your project or workflow requires the interaction with more than one of these tools, or that you are not sure if you will work with a different quantities library in the future. Wouldn't having a unique API to work with different forms of physical quantities be a relief? PyUnitWizard just does that. It is the wizard you need in your code to change the form of your quantities with no effort. diff --git a/docs/contents/user/Lists_and_arrays.ipynb b/docs/contents/user/Lists_and_arrays.ipynb new file mode 100644 index 0000000..1eaf375 --- /dev/null +++ b/docs/contents/user/Lists_and_arrays.ipynb @@ -0,0 +1,391 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Working with lists and arrays\n", + "*PyUnitWizard helps you to concatenate or stack lists, tuples or arrays of quantities*\n", + "\n", + "Sometimes we work with a list of quantites, but we need a quantity where the value is a list. Or sometimes we work with a list of array quantities and we need to stack them in a single quantity. Have a look to this documentation section to find out some useful tools to work with lists of quantities.\n", + "\n", + "Let's show how the methods `pyunitwizard.string_to_quantity()` and `pyunitwizard.string_to_unit()` work:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Concatenate\n", + "\n", + "Let's show how the method `pyunitwizard.concatenate()` works with a simple example. Let's suppose we have a list of quantities:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import pyunitwizard as puw\n", + "import numpy as np\n", + "puw.configure.load_library(['pint', 'openmm.unit'])\n", + "puw.configure.set_default_form('pint')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "list_quantities = [puw.quantity(ii, 'nm') for ii in range(10)]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0 ,\n", + " 1 ,\n", + " 2 ,\n", + " 3 ,\n", + " 4 ,\n", + " 5 ,\n", + " 6 ,\n", + " 7 ,\n", + " 8 ,\n", + " 9 ]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_quantities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But we need a unique quantity with all former values in a single list:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "quantity = puw.concatenate(list_quantities)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Magnitude
[[0 1 2]
[0 1 2]
[0 1 2]
[0 1 2]]
Unitsnanometer
" + ], + "text/latex": [ + "$\\begin{pmatrix}0 & 1 & 2\\\\ \n", + "0 & 1 & 2\\\\ \n", + "0 & 1 & 2\\\\ \n", + "0 & 1 & 2\\end{pmatrix}\\ \\mathrm{nanometer}$" + ], + "text/plain": [ + "array([[0, 1, 2],\n", + " [0, 1, 2],\n", + " [0, 1, 2],\n", + " [0, 1, 2]]) " + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "quantity" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function `pyunitwizard.concatenate()` can operate also with numpy array:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "list_quantities = [ puw.quantity(np.zeros([3,2]), 'nm') for ii in range(2)]\n", + "list_quantities += [ puw.quantity(np.ones([3,2]), 'angstroms') for ii in range(2)]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array([[0., 0.],\n", + " [0., 0.],\n", + " [0., 0.]]) ,\n", + " array([[0., 0.],\n", + " [0., 0.],\n", + " [0., 0.]]) ,\n", + " array([[1., 1.],\n", + " [1., 1.],\n", + " [1., 1.]]) ,\n", + " array([[1., 1.],\n", + " [1., 1.],\n", + " [1., 1.]]) ]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_quantities" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "quantity = puw.concatenate(list_quantities, type_value='numpy.ndarray', to_unit='nm', to_form='openmm.unit')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantity(value=array([[[0. , 0. ],\n", + " [0. , 0. ],\n", + " [0. , 0. ]],\n", + "\n", + " [[0. , 0. ],\n", + " [0. , 0. ],\n", + " [0. , 0. ]],\n", + "\n", + " [[0.1, 0.1],\n", + " [0.1, 0.1],\n", + " [0.1, 0.1]],\n", + "\n", + " [[0.1, 0.1],\n", + " [0.1, 0.1],\n", + " [0.1, 0.1]]]), unit=nanometer)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "quantity" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that the concatenate function does not only work with sequences of quantities, it can also process sequences of sequences of quantities:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "list_quantities = [ [puw.quantity(ii, 'nm') for ii in range(3)] for jj in range(4)]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[0 , 1 , 2 ],\n", + " [0 , 1 , 2 ],\n", + " [0 , 1 , 2 ],\n", + " [0 , 1 , 2 ]]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_quantities" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "quantity = puw.concatenate(list_quantities)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Magnitude
[0 1 2 0 1 2 0 1 2 0 1 2]
Unitsnanometer
" + ], + "text/latex": [ + "$\\begin{pmatrix}0 & 1 & 2 & 0 & 1 & 2 & 0 & 1 & 2 & 0 & 1 & 2\\end{pmatrix}\\ \\mathrm{nanometer}$" + ], + "text/plain": [ + "array([0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]) " + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "quantity" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Stack\n", + "\n", + "The function `pyunitwizard.stack()` joins a sequence of sequences of quantities along a new axis (same behaviour as numpy.stack). Let's have a look to the following example:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "list_list_quantities = [ [puw.quantity(ii, 'nm') for ii in range(3)] for jj in range(4)]" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[[0 , 1 , 2 ],\n", + " [0 , 1 , 2 ],\n", + " [0 , 1 , 2 ],\n", + " [0 , 1 , 2 ]]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_list_quantities" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "quantity = puw.stack(list_quantities, type_value='numpy.ndarray')" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Magnitude
[[0 1 2]
[0 1 2]
[0 1 2]
[0 1 2]]
Unitsnanometer
" + ], + "text/latex": [ + "$\\begin{pmatrix}0 & 1 & 2\\\\ \n", + "0 & 1 & 2\\\\ \n", + "0 & 1 & 2\\\\ \n", + "0 & 1 & 2\\end{pmatrix}\\ \\mathrm{nanometer}$" + ], + "text/plain": [ + "array([[0, 1, 2],\n", + " [0, 1, 2],\n", + " [0, 1, 2],\n", + " [0, 1, 2]]) " + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "quantity" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/index.rst b/docs/index.rst index db3e71f..4881028 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -40,6 +40,7 @@ Libraries supported contents/user/Dimensionality.ipynb contents/user/Convert.ipynb contents/user/Strings.ipynb + contents/user/Lists_and_arrays.ipynb contents/user/Standardize.ipynb contents/user/Check.ipynb contents/user/In_Your_Library.ipynb diff --git a/pyunitwizard/__init__.py b/pyunitwizard/__init__.py index a594488..f786384 100644 --- a/pyunitwizard/__init__.py +++ b/pyunitwizard/__init__.py @@ -15,6 +15,7 @@ from .main import get_value, get_unit, get_value_and_unit, change_value from .main import convert from .main import get_standard_units, standardize, get_dimensionality, compatibility, similarity, check +from .main import concatenate, stack from . import configure from . import kernel as _kernel diff --git a/pyunitwizard/main.py b/pyunitwizard/main.py index 4b91b7d..be861e2 100644 --- a/pyunitwizard/main.py +++ b/pyunitwizard/main.py @@ -799,24 +799,78 @@ def check(quantity_or_unit: Any, return True -def concatenate(quantities, to_unit=None, to_form=None, type_value='tuple', standardized=False): +def concatenate(sequence, to_unit=None, to_form=None, type_value='tuple', standardized=False): + + sequence_of_sequences = False + + if isinstance(sequence, (list, tuple, np.ndarray)): + if isinstance(sequence[0], (list, tuple, np.ndarray)): + sequence_of_sequences = True + + if not sequence_of_sequences: + + if to_unit is None: + output_unit = get_unit(sequence[0]) + else: + output_unit = to_unit + + output_value = [] + + for aux_quantity in sequence: + output_value.append(get_value(aux_quantity, to_unit=output_unit)) + + if type_value=='list': + return quantity(output_value, output_unit, form=to_form, standardized=standardized) + elif type_value=='tuple': + return quantity(tuple(output_value), output_unit, form=to_form, standardized=standardized) + elif type_value=='numpy.ndarray': + return quantity(np.array(output_value), output_unit, form=to_form, standardized=standardized) + else: + raise ValueError + + else: + + if to_unit is None: + output_unit = get_unit(sequence[0][0]) + else: + output_unit = to_unit + + output_value = [] + + for aux_seq in sequence: + for aux_quantity in aux_seq: + output_value.append(get_value(aux_quantity, to_unit=output_unit)) + + if type_value=='list': + return quantity(output_value, output_unit, form=to_form, standardized=standardized) + elif type_value=='tuple': + return quantity(tuple(output_value), output_unit, form=to_form, standardized=standardized) + elif type_value=='numpy.ndarray': + return quantity(np.array(output_value), output_unit, form=to_form, standardized=standardized) + else: + raise ValueError + +def stack(sequence, to_unit=None, to_form=None, type_value='tuple', standardized=False): if to_unit is None: - output_unit = get_unit(quantities[0]) + output_unit = get_unit(sequence[0][0]) else: output_unit = to_unit output_value = [] - for aux_quantity in quantities: - output_value.append(get_value(aux_quantity, to_unit=output_unit)) - + for aux_seq in sequence: + aux_list = [] + for aux_quantity in aux_seq: + aux_list.append(get_value(aux_quantity, to_unit=output_unit)) + output_value.append(aux_list) + if type_value=='list': - return quantity(output_value, output_unit, form=to_form, standardized=standardized) + return quantity(output_value.tolist(), output_unit, form=to_form, standardized=standardized) elif type_value=='tuple': - return quantity(tuple(output_value), output_unit, form=to_form, standardized=standardized) + return quantity(tuple(output_value.tolist()), output_unit, form=to_form, standardized=standardized) elif type_value=='numpy.ndarray': - return quantity(np.array(output_value), output_unit, form=to_form, standardized=standardized) + return quantity(output_value, output_unit, form=to_form, standardized=standardized) else: raise ValueError diff --git a/pyunitwizard/tests/test_concatenate.py b/pyunitwizard/tests/test_concatenate.py index 5e1b41c..cae73ec 100644 --- a/pyunitwizard/tests/test_concatenate.py +++ b/pyunitwizard/tests/test_concatenate.py @@ -15,11 +15,3 @@ def test_concatenate_1(): assert value.shape==(10, 6, 3) - - - - - - - - diff --git a/pyunitwizard/tests/test_stack.py b/pyunitwizard/tests/test_stack.py new file mode 100644 index 0000000..90d9088 --- /dev/null +++ b/pyunitwizard/tests/test_stack.py @@ -0,0 +1,17 @@ +from pyunitwizard._private.exceptions import LibraryWithoutParserError +import pyunitwizard as puw +import pytest +import openmm.unit as openmm_unit +import unyt +import numpy as np + +def test_stack_1(): + puw.configure.reset() + puw.configure.load_library(['pint', 'openmm.unit']) + + list_list_quantities = [ [puw.quantity(ii, 'nm') for ii in range(3)] for jj in range(4)] + quantity = puw.stack(list_list_quantities, type_value='numpy.ndarray') + value = puw.get_value(quantity) + assert value.shape==(4, 3) + +