expressive


Nameexpressive JSON
Version 2.0.0 PyPI version JSON
download
home_pagehttps://gitlab.com/expressive-py/expressive
SummaryA library for quickly applying symbolic expressions to NumPy arrays
upload_time2025-01-29 22:58:00
maintainerRussell Fordyce
docs_urlNone
authorNone
requires_python>=3.7
licenseApache License 2.0
keywords sympy numba numpy
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # expressive

A library for quickly applying symbolic expressions to NumPy arrays

By enabling callers to front-load sample data, developers can move the runtime cost of Numba's JIT to the application's initial loading (or an earlier build) and also avoid `exec` during runtime, which is otherwise needed when lambdifying symbolic expressions

Inspired in part by this Stack Overflow Question [Using numba.autojit on a lambdify'd sympy expression](https://stackoverflow.com/questions/22793601/using-numba-autojit-on-a-lambdifyd-sympy-expression)

## installation

via pip https://pypi.org/project/expressive/

```shell
pip install expressive
```

## usage

refer to tests for examples for now

generally follow a workflow like
* create instance `expr = Expressive("a + log(b)")`
* build instance `expr.build(sample_data)`
* instance is now callable `expr(full_data)`

the `data` should be provided as dict of NumPy arrays

 ```python
data_sample = {  # simplified data to build and test expr
    "a": numpy.array([1,2,3,4], dtype="int64"),
    "b": numpy.array([4,3,2,1], dtype="int64"),
}
data = {  # real data user wants to process
    "a": numpy.array(range(1_000_000), dtype="int64"),
    "b": numpy.array(range(1_000_000), dtype="int64"),
}
E = Expressive("expr")  # string or SymPy expr
E.build(data_sample)  # data's types used to compile a fast version
E(data)  # very fast callable
```

simple demo

```python
import time
import contextlib
import numpy
import matplotlib.pyplot as plt
from expressive import Expressive

# simple projectile motion in a plane
E_position = Expressive("y = v0*t*sin(a0) + 1/2(g*t^2)")

# expr is built early in the process runtime by user
def build():
    # create some sample data and build with it
    # the types are used to compile a fast version for full data
    data_example = {
        "v0": 100,  # initial velocity m/s
        "g": -9.81, # earth gravity m/s/s
        "a0": .785,  # starting angle ~45° in radians
        "t": numpy.linspace(0, 15, dtype="float64"),  # 15 seconds is probably enough
    }
    assert len(data_example["t"]) == 50  # linspace default
    time_start = time.perf_counter()
    E_position.build(data_example)  # verify is implied with little data
    time_run = time.perf_counter() - time_start

    # provide some extra display details
    count = len(data_example["t"])
    print(f"built in {time_run*1000:.2f}ms on {count:,} points")
    print(f"  {E_position}")

def load_data(
    point_count=10**8,  # 100 million points (*count of angles), maybe 4GiB here
    initial_velocity=100,  # m/s
):
    # manufacture lots of data, which would be loaded in a real example
    time_array = numpy.linspace(0, 15, point_count, dtype="float64")
    # collect the results
    data_collections = []
    # process much more data than the build sample
    for angle in (.524, .785, 1.047):  # initial angles (30°, 45°, 60°)
        data = {  # data is just generated in this case
            "v0": initial_velocity,  # NOTE type must match example data
            "g": -9.81, # earth gravity m/s/s
            "a0": angle,  # radians
            "t": time_array,  # just keep re-using the times for this example
        }
        data_collections.append(data)

    # data collections are now loaded (created)
    return data_collections

# later during the process runtime
# user calls the object directly with new data
def runtime(data_collections):
    """ whatever the program is normally up to """

    # create equivalent function for numpy compare
    def numpy_cmp(v0, g, a0, t):
        return v0*t*numpy.sin(a0) + 1/2*(g*t**2)

    # TODO also compare numexpr demo

    # call already-built object directly on each data
    results = []
    for data in data_collections:
        # expressive run
        t_start_e = time.perf_counter()  # just to show time, prefer timeit for perf
        results.append(E_position(data))
        t_run_e = time.perf_counter() - t_start_e

        # simple numpy run
        t_start_n = time.perf_counter()
        result_numpy = numpy_cmp(**data)
        t_run_n = time.perf_counter() - t_start_n

        # provide some extra display details
        angle = data["a0"]
        count = len(data["t"])
        t_run_e = t_run_e * 1000  # convert to ms
        t_run_n = t_run_n * 1000
        print(f"initial angle {angle}rad ran in {t_run_e:.2f}ms on {count:,} points (numpy:{t_run_n:.2f}ms)")

    # decimate to avoid very long matplotlib processing
    def sketchy_downsample(ref, count=500):
        offset = len(ref) // count
        return ref[::offset]

    # display results to show it worked
    for result, data in zip(results, data_collections):
        x = sketchy_downsample(data["t"])
        y = sketchy_downsample(result)
        plt.scatter(x, y)
    plt.xlabel("time (s)")
    plt.ylabel("position (m)")
    plt.show()

def main():
    build()
    data_collections = load_data()
    runtime(data_collections)

main()
```

![](https://gitlab.com/expressive-py/docs/-/raw/d1e43411242fda9cc81ced55484f9e7575acb6c3/img/expressive_examples_2d_motion.png)

## compatibility matrix

generally this strives to only rely on high-level support from SymPy and Numba, though Numba has stricter requirements for NumPy and llvmlite

| Python | Numba | NumPy | SymPy | commit | coverage |
| --- | --- | --- | --- | --- | --- |
| 3.7.17 | 0.56.4 | 1.21.6 | 1.6 | a3986cb | {'expressive.py': '🟠 99% m 753', 'test.py': '🟢 100%'} |
| 3.8.20 | 0.58.1 | 1.24.4 | 1.7 | a3986cb | {'expressive.py': '🟠 99% m 753', 'test.py': '🟢 100%'} |
| 3.9.19 | 0.53.1 | 1.23.5 | 1.7 | a3986cb | {'expressive.py': '🟠 99% m 753', 'test.py': '🟢 100%'} |
| 3.9.19 | 0.60.0 | 2.0.1 | 1.13.2 | a3986cb | 🟢 100% |
| 3.10.16 | 0.61.0 | 2.1.3 | 1.13.3 | a3986cb | 🟢 100% |
| 3.11.11 | 0.61.0 | 2.1.3 | 1.13.3 | a3986cb | 🟢 100% |
| 3.12.8 | 0.61.0 | 2.1.3 | 1.13.3 | a3986cb | 🟢 100% |
| 3.13.1 | 0.61.0 | 2.1.3 | 1.13.3 | a3986cb | 🟢 100% |

#### further compatibility notes

these runs build the package themselves internally, while my publishing environment is currently Python 3.11.2

though my testing indicates that this works under a wide variety of quite old versions of Python/Numba/SymPy, upgrading to the highest dependency versions you can will generally be best
* Python 3 major version status https://devguide.python.org/versions/
* https://numba.readthedocs.io/en/stable/release-notes-overview.html

NumPy 1.x and 2.0 saw some major API changes, so older environments may need to adjust or discover working combinations themselves
* some versions of Numba rely on `numpy.MachAr`, which has been [deprecated since at least NumPy 1.22](https://numpy.org/doc/stable/release/1.22.0-notes.html#the-np-machar-class-has-been-deprecated) and may result in warnings

TBD publish multi-version test tool

## testing

Only `docker` is required in the host and used to generate and host testing

```shell
sudo apt install docker.io  # debian/ubuntu
sudo usermod -aG docker $USER
sudo su -l $USER  # login shell to self (reboot for all shells)
```

Run the test script from the root of the repository and it will build the docker test environment and run itself inside it automatically

```shell
./test/runtests.sh
```

## build + install locally

Follows the generic build and publish process
* https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives
* build (builder) https://pypi.org/project/build/

```shell
python3 -m build
python3 -m pip install ./dist/*.whl
```

## contributing

The development process is currently private (though most fruits are available here!), largely due to this being my first public project with the potential for other users than myself, and so the potential for more public gaffes is far greater

Please refer to [CONTRIBUTING.md](https://gitlab.com/expressive-py/expressive/-/blob/main/CONTRIBUTING.md) and [LICENSE.txt](https://gitlab.com/expressive-py/expressive/-/blob/main/LICENSE.txt) and feel free to provide feedback, bug reports, etc. via [Issues](https://gitlab.com/expressive-py/expressive/-/issues), subject to the former

#### additional future intentions for contributing
* improve internal development history as time, popularity, and practicality allows
* move to parallel/multi-version/grid CI over all-in-1, single-version dev+test container (partially done with 2.0.0!)
* ~~greatly relax dependency version requirements to improve compatibility~~
* publish majority of ticket ("Issue") history

## version history

##### v2.0.0
* enabled matrix/tensor support
* improved/reduced warnings from verify
* tested + greatly reduced dependency version requirements
* added a basic usage example (uses new docs repo https://gitlab.com/expressive-py/docs/ )

##### v1.9.0
* improved package layout
* build and install package during `runtests.sh` (earlier versions use relative importing)
* improve errors around invalid data/Symbol names

##### v1.8.1
* fixed a regex bug where multidigit offset indicies could become multiplied `x[i+10]` to `x[i+1*0]`
* improve complex result type guessing

##### v1.8.0
* support for passing a SymPy expr (`Expr`, `Equality`), not just strings

##### v1.7.0
* support for passing SymPy symbols to be used

##### v1.6.1 (unreleased)
* support indexed result array filling for complex dtypes

##### v1.6.0
* complex dtypes MVP (`complex64`, `complex128`)
* parse coefficients directly adjacent to parentheses `3(x+1)` -> `3*(x+1)`

##### v1.5.1 (unreleased)
* improved README wording of [testing](#testing) and added [building section](#building)
* better messages when testing and `docker` is absent or freshly installed

##### v1.5.0
* added `._repr_html_()` method for improved display in Jupyter/IPython notebooks

##### v1.4.2
* greatly improved verify
  * `numpy.allclose()` takes exactly 2 arrays to compare (further args are passed to `rtol`, `atol`)
  * SymPy namespace special values `oo`, `zoo`, `nan` are coerced to NumPy equivalents (`inf`, `-inf`, `nan`)
  * raise when result is `False`
  * groundwork to maintain an internal collection of results
* internal symbols collection maintains `IndexedBase` instances (`e.atoms(Symbol)` returns `Symbol` instances)
* improve Exceptions from data that can't be used
* new custom warning helper for testing as `assertWarnsRegex` annoyingly eats every warning it can

##### v1.4.1
* more sensibly fill the result array for non-floats when not provided (only float supports NaN)

##### v1.4.0
* add build-time verify step to help identify math and typing issues
* some improved logic flow and improved `warn()`

##### v1.3.2 (unreleased)
* improved publishing workflow
* improved README

##### v1.3.1
* fix bad math related to indexing range
* add an integration test

##### v1.3.0
* add support for parsing equality to result
* add support for (optionally) passing result array
* hugely improve docstrings

##### v1.2.1
* add more detail to [contributing block](#contributing)
* switch array dimensions checking from `.shape` to `.ndim`
* switch tests from `numpy.array(range())` to `numpy.arange()`

##### v1.2.0
* enable autobuilding (skip explicit `.build()` call)
* basic display support for `Expressive` instances

##### v1.1.1
* add version history block

##### v1.1.0
* fixed bug: signature ordering could be unaligned with symbols, resulting in bad types
* added support for non-vector data arguments

##### v1.0.0
* completely new code tree under Apache 2 license
* basic support for indexed offsets

##### v0.2.0 (unreleased)

##### v0.1.0
* very early version with support for python 3.5

            

Raw data

            {
    "_id": null,
    "home_page": "https://gitlab.com/expressive-py/expressive",
    "name": "expressive",
    "maintainer": "Russell Fordyce",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": null,
    "keywords": "sympy numba numpy",
    "author": null,
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/e6/46/57edc7857e29fcbd859053f03da8f5a5e8718c4c893975873a49190e51d3/expressive-2.0.0.tar.gz",
    "platform": null,
    "description": "# expressive\n\nA library for quickly applying symbolic expressions to NumPy arrays\n\nBy enabling callers to front-load sample data, developers can move the runtime cost of Numba's JIT to the application's initial loading (or an earlier build) and also avoid `exec` during runtime, which is otherwise needed when lambdifying symbolic expressions\n\nInspired in part by this Stack Overflow Question [Using numba.autojit on a lambdify'd sympy expression](https://stackoverflow.com/questions/22793601/using-numba-autojit-on-a-lambdifyd-sympy-expression)\n\n## installation\n\nvia pip https://pypi.org/project/expressive/\n\n```shell\npip install expressive\n```\n\n## usage\n\nrefer to tests for examples for now\n\ngenerally follow a workflow like\n* create instance `expr = Expressive(\"a + log(b)\")`\n* build instance `expr.build(sample_data)`\n* instance is now callable `expr(full_data)`\n\nthe `data` should be provided as dict of NumPy arrays\n\n ```python\ndata_sample = {  # simplified data to build and test expr\n    \"a\": numpy.array([1,2,3,4], dtype=\"int64\"),\n    \"b\": numpy.array([4,3,2,1], dtype=\"int64\"),\n}\ndata = {  # real data user wants to process\n    \"a\": numpy.array(range(1_000_000), dtype=\"int64\"),\n    \"b\": numpy.array(range(1_000_000), dtype=\"int64\"),\n}\nE = Expressive(\"expr\")  # string or SymPy expr\nE.build(data_sample)  # data's types used to compile a fast version\nE(data)  # very fast callable\n```\n\nsimple demo\n\n```python\nimport time\nimport contextlib\nimport numpy\nimport matplotlib.pyplot as plt\nfrom expressive import Expressive\n\n# simple projectile motion in a plane\nE_position = Expressive(\"y = v0*t*sin(a0) + 1/2(g*t^2)\")\n\n# expr is built early in the process runtime by user\ndef build():\n    # create some sample data and build with it\n    # the types are used to compile a fast version for full data\n    data_example = {\n        \"v0\": 100,  # initial velocity m/s\n        \"g\": -9.81, # earth gravity m/s/s\n        \"a0\": .785,  # starting angle ~45\u00b0 in radians\n        \"t\": numpy.linspace(0, 15, dtype=\"float64\"),  # 15 seconds is probably enough\n    }\n    assert len(data_example[\"t\"]) == 50  # linspace default\n    time_start = time.perf_counter()\n    E_position.build(data_example)  # verify is implied with little data\n    time_run = time.perf_counter() - time_start\n\n    # provide some extra display details\n    count = len(data_example[\"t\"])\n    print(f\"built in {time_run*1000:.2f}ms on {count:,} points\")\n    print(f\"  {E_position}\")\n\ndef load_data(\n    point_count=10**8,  # 100 million points (*count of angles), maybe 4GiB here\n    initial_velocity=100,  # m/s\n):\n    # manufacture lots of data, which would be loaded in a real example\n    time_array = numpy.linspace(0, 15, point_count, dtype=\"float64\")\n    # collect the results\n    data_collections = []\n    # process much more data than the build sample\n    for angle in (.524, .785, 1.047):  # initial angles (30\u00b0, 45\u00b0, 60\u00b0)\n        data = {  # data is just generated in this case\n            \"v0\": initial_velocity,  # NOTE type must match example data\n            \"g\": -9.81, # earth gravity m/s/s\n            \"a0\": angle,  # radians\n            \"t\": time_array,  # just keep re-using the times for this example\n        }\n        data_collections.append(data)\n\n    # data collections are now loaded (created)\n    return data_collections\n\n# later during the process runtime\n# user calls the object directly with new data\ndef runtime(data_collections):\n    \"\"\" whatever the program is normally up to \"\"\"\n\n    # create equivalent function for numpy compare\n    def numpy_cmp(v0, g, a0, t):\n        return v0*t*numpy.sin(a0) + 1/2*(g*t**2)\n\n    # TODO also compare numexpr demo\n\n    # call already-built object directly on each data\n    results = []\n    for data in data_collections:\n        # expressive run\n        t_start_e = time.perf_counter()  # just to show time, prefer timeit for perf\n        results.append(E_position(data))\n        t_run_e = time.perf_counter() - t_start_e\n\n        # simple numpy run\n        t_start_n = time.perf_counter()\n        result_numpy = numpy_cmp(**data)\n        t_run_n = time.perf_counter() - t_start_n\n\n        # provide some extra display details\n        angle = data[\"a0\"]\n        count = len(data[\"t\"])\n        t_run_e = t_run_e * 1000  # convert to ms\n        t_run_n = t_run_n * 1000\n        print(f\"initial angle {angle}rad ran in {t_run_e:.2f}ms on {count:,} points (numpy:{t_run_n:.2f}ms)\")\n\n    # decimate to avoid very long matplotlib processing\n    def sketchy_downsample(ref, count=500):\n        offset = len(ref) // count\n        return ref[::offset]\n\n    # display results to show it worked\n    for result, data in zip(results, data_collections):\n        x = sketchy_downsample(data[\"t\"])\n        y = sketchy_downsample(result)\n        plt.scatter(x, y)\n    plt.xlabel(\"time (s)\")\n    plt.ylabel(\"position (m)\")\n    plt.show()\n\ndef main():\n    build()\n    data_collections = load_data()\n    runtime(data_collections)\n\nmain()\n```\n\n![](https://gitlab.com/expressive-py/docs/-/raw/d1e43411242fda9cc81ced55484f9e7575acb6c3/img/expressive_examples_2d_motion.png)\n\n## compatibility matrix\n\ngenerally this strives to only rely on high-level support from SymPy and Numba, though Numba has stricter requirements for NumPy and llvmlite\n\n| Python | Numba | NumPy | SymPy | commit | coverage |\n| --- | --- | --- | --- | --- | --- |\n| 3.7.17 | 0.56.4 | 1.21.6 | 1.6 | a3986cb | {'expressive.py': '\ud83d\udfe0 99% m 753', 'test.py': '\ud83d\udfe2 100%'} |\n| 3.8.20 | 0.58.1 | 1.24.4 | 1.7 | a3986cb | {'expressive.py': '\ud83d\udfe0 99% m 753', 'test.py': '\ud83d\udfe2 100%'} |\n| 3.9.19 | 0.53.1 | 1.23.5 | 1.7 | a3986cb | {'expressive.py': '\ud83d\udfe0 99% m 753', 'test.py': '\ud83d\udfe2 100%'} |\n| 3.9.19 | 0.60.0 | 2.0.1 | 1.13.2 | a3986cb | \ud83d\udfe2 100% |\n| 3.10.16 | 0.61.0 | 2.1.3 | 1.13.3 | a3986cb | \ud83d\udfe2 100% |\n| 3.11.11 | 0.61.0 | 2.1.3 | 1.13.3 | a3986cb | \ud83d\udfe2 100% |\n| 3.12.8 | 0.61.0 | 2.1.3 | 1.13.3 | a3986cb | \ud83d\udfe2 100% |\n| 3.13.1 | 0.61.0 | 2.1.3 | 1.13.3 | a3986cb | \ud83d\udfe2 100% |\n\n#### further compatibility notes\n\nthese runs build the package themselves internally, while my publishing environment is currently Python 3.11.2\n\nthough my testing indicates that this works under a wide variety of quite old versions of Python/Numba/SymPy, upgrading to the highest dependency versions you can will generally be best\n* Python 3 major version status https://devguide.python.org/versions/\n* https://numba.readthedocs.io/en/stable/release-notes-overview.html\n\nNumPy 1.x and 2.0 saw some major API changes, so older environments may need to adjust or discover working combinations themselves\n* some versions of Numba rely on `numpy.MachAr`, which has been [deprecated since at least NumPy 1.22](https://numpy.org/doc/stable/release/1.22.0-notes.html#the-np-machar-class-has-been-deprecated) and may result in warnings\n\nTBD publish multi-version test tool\n\n## testing\n\nOnly `docker` is required in the host and used to generate and host testing\n\n```shell\nsudo apt install docker.io  # debian/ubuntu\nsudo usermod -aG docker $USER\nsudo su -l $USER  # login shell to self (reboot for all shells)\n```\n\nRun the test script from the root of the repository and it will build the docker test environment and run itself inside it automatically\n\n```shell\n./test/runtests.sh\n```\n\n## build + install locally\n\nFollows the generic build and publish process\n* https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives\n* build (builder) https://pypi.org/project/build/\n\n```shell\npython3 -m build\npython3 -m pip install ./dist/*.whl\n```\n\n## contributing\n\nThe development process is currently private (though most fruits are available here!), largely due to this being my first public project with the potential for other users than myself, and so the potential for more public gaffes is far greater\n\nPlease refer to [CONTRIBUTING.md](https://gitlab.com/expressive-py/expressive/-/blob/main/CONTRIBUTING.md) and [LICENSE.txt](https://gitlab.com/expressive-py/expressive/-/blob/main/LICENSE.txt) and feel free to provide feedback, bug reports, etc. via [Issues](https://gitlab.com/expressive-py/expressive/-/issues), subject to the former\n\n#### additional future intentions for contributing\n* improve internal development history as time, popularity, and practicality allows\n* move to parallel/multi-version/grid CI over all-in-1, single-version dev+test container (partially done with 2.0.0!)\n* ~~greatly relax dependency version requirements to improve compatibility~~\n* publish majority of ticket (\"Issue\") history\n\n## version history\n\n##### v2.0.0\n* enabled matrix/tensor support\n* improved/reduced warnings from verify\n* tested + greatly reduced dependency version requirements\n* added a basic usage example (uses new docs repo https://gitlab.com/expressive-py/docs/ )\n\n##### v1.9.0\n* improved package layout\n* build and install package during `runtests.sh` (earlier versions use relative importing)\n* improve errors around invalid data/Symbol names\n\n##### v1.8.1\n* fixed a regex bug where multidigit offset indicies could become multiplied `x[i+10]` to `x[i+1*0]`\n* improve complex result type guessing\n\n##### v1.8.0\n* support for passing a SymPy expr (`Expr`, `Equality`), not just strings\n\n##### v1.7.0\n* support for passing SymPy symbols to be used\n\n##### v1.6.1 (unreleased)\n* support indexed result array filling for complex dtypes\n\n##### v1.6.0\n* complex dtypes MVP (`complex64`, `complex128`)\n* parse coefficients directly adjacent to parentheses `3(x+1)` -> `3*(x+1)`\n\n##### v1.5.1 (unreleased)\n* improved README wording of [testing](#testing) and added [building section](#building)\n* better messages when testing and `docker` is absent or freshly installed\n\n##### v1.5.0\n* added `._repr_html_()` method for improved display in Jupyter/IPython notebooks\n\n##### v1.4.2\n* greatly improved verify\n  * `numpy.allclose()` takes exactly 2 arrays to compare (further args are passed to `rtol`, `atol`)\n  * SymPy namespace special values `oo`, `zoo`, `nan` are coerced to NumPy equivalents (`inf`, `-inf`, `nan`)\n  * raise when result is `False`\n  * groundwork to maintain an internal collection of results\n* internal symbols collection maintains `IndexedBase` instances (`e.atoms(Symbol)` returns `Symbol` instances)\n* improve Exceptions from data that can't be used\n* new custom warning helper for testing as `assertWarnsRegex` annoyingly eats every warning it can\n\n##### v1.4.1\n* more sensibly fill the result array for non-floats when not provided (only float supports NaN)\n\n##### v1.4.0\n* add build-time verify step to help identify math and typing issues\n* some improved logic flow and improved `warn()`\n\n##### v1.3.2 (unreleased)\n* improved publishing workflow\n* improved README\n\n##### v1.3.1\n* fix bad math related to indexing range\n* add an integration test\n\n##### v1.3.0\n* add support for parsing equality to result\n* add support for (optionally) passing result array\n* hugely improve docstrings\n\n##### v1.2.1\n* add more detail to [contributing block](#contributing)\n* switch array dimensions checking from `.shape` to `.ndim`\n* switch tests from `numpy.array(range())` to `numpy.arange()`\n\n##### v1.2.0\n* enable autobuilding (skip explicit `.build()` call)\n* basic display support for `Expressive` instances\n\n##### v1.1.1\n* add version history block\n\n##### v1.1.0\n* fixed bug: signature ordering could be unaligned with symbols, resulting in bad types\n* added support for non-vector data arguments\n\n##### v1.0.0\n* completely new code tree under Apache 2 license\n* basic support for indexed offsets\n\n##### v0.2.0 (unreleased)\n\n##### v0.1.0\n* very early version with support for python 3.5\n",
    "bugtrack_url": null,
    "license": "Apache License 2.0",
    "summary": "A library for quickly applying symbolic expressions to NumPy arrays",
    "version": "2.0.0",
    "project_urls": {
        "Homepage": "https://gitlab.com/expressive-py/expressive"
    },
    "split_keywords": [
        "sympy",
        "numba",
        "numpy"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8097534bc17d30372b906c21fcb4f15ac09ede9e24397a709b9cf4c0ab346cd2",
                "md5": "824156824aa4d297cde7772d14805d50",
                "sha256": "70a7432c390ef6d01e27f4654e3279a51173fc0449e086d936a62f56eae53ea8"
            },
            "downloads": -1,
            "filename": "expressive-2.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "824156824aa4d297cde7772d14805d50",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 26966,
            "upload_time": "2025-01-29T22:57:58",
            "upload_time_iso_8601": "2025-01-29T22:57:58.402739Z",
            "url": "https://files.pythonhosted.org/packages/80/97/534bc17d30372b906c21fcb4f15ac09ede9e24397a709b9cf4c0ab346cd2/expressive-2.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e64657edc7857e29fcbd859053f03da8f5a5e8718c4c893975873a49190e51d3",
                "md5": "65b110c96d11833fcbd6906b97a9b770",
                "sha256": "7c17bfebb6e5124d59e6703164ced6f9a59448ec66dd29e6aaae8bffabbc4c50"
            },
            "downloads": -1,
            "filename": "expressive-2.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "65b110c96d11833fcbd6906b97a9b770",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 43101,
            "upload_time": "2025-01-29T22:58:00",
            "upload_time_iso_8601": "2025-01-29T22:58:00.364134Z",
            "url": "https://files.pythonhosted.org/packages/e6/46/57edc7857e29fcbd859053f03da8f5a5e8718c4c893975873a49190e51d3/expressive-2.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-01-29 22:58:00",
    "github": false,
    "gitlab": true,
    "bitbucket": false,
    "codeberg": false,
    "gitlab_user": "expressive-py",
    "gitlab_project": "expressive",
    "lcname": "expressive"
}
        
Elapsed time: 0.97007s