diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d89c5ca --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,151 @@ +name: Build and upload to PyPI + +on: + push: + release: + types: + - published + +jobs: + # Build the wheels for Linux, Windows and macOS for Python 3.9 and newer + build_wheels: + name: Build wheel for cp${{ matrix.python }}-${{ matrix.platform_id }} + runs-on: ${{ matrix.os }} + strategy: + # Ensure that a wheel builder finishes even if another fails + fail-fast: false + matrix: + include: + # Window 64 bit + - os: windows-latest + python: 39 + platform_id: win_amd64 + - os: windows-latest + python: 310 + platform_id: win_amd64 + - os: windows-latest + python: 311 + platform_id: win_amd64 + - os: windows-latest + python: 312 + platform_id: win_amd64 + - os: windows-latest + python: 313 + platform_id: win_amd64 + - os: windows-latest + python: 313t + platform_id: win_amd64 + free_threaded_support: True + + # Linux 64 bit manylinux2014 + - os: ubuntu-latest + python: 39 + platform_id: manylinux_x86_64 + - os: ubuntu-latest + python: 310 + platform_id: manylinux_x86_64 + - os: ubuntu-latest + python: 311 + platform_id: manylinux_x86_64 + - os: ubuntu-latest + python: 312 + platform_id: manylinux_x86_64 + - os: ubuntu-latest + python: 313 + platform_id: manylinux_x86_64 + - os: ubuntu-latest + python: 313t + platform_id: manylinux_x86_64 + free_threaded_support: True + + # MacOS x86_64 + - os: macos-13 + python: 39 + platform_id: macosx_x86_64 + - os: macos-13 + python: 310 + platform_id: macosx_x86_64 + - os: macos-13 + python: 311 + platform_id: macosx_x86_64 + - os: macos-13 + python: 312 + platform_id: macosx_x86_64 + - os: macos-13 + python: 313 + platform_id: macosx_x86_64 + - os: macos-13 + python: 313t + platform_id: macosx_x86_64 + free_threaded_support: True + + # MacOS arm64 + - os: macos-14 + python: 39 + platform_id: macosx_arm64 + - os: macos-14 + python: 310 + platform_id: macosx_arm64 + - os: macos-14 + python: 311 + platform_id: macosx_arm64 + - os: macos-14 + python: 312 + platform_id: macosx_arm64 + - os: macos-14 + python: 313 + platform_id: macosx_arm64 + - os: macos-14 + python: 313t + platform_id: macosx_arm64 + free_threaded_support: True + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + env: + CIBW_FREE_THREADED_SUPPORT: ${{ matrix.free_threaded_support }} + CIBW_BUILD: cp${{ matrix.python }}-${{ matrix.platform_id }} + CIBW_TEST_REQUIRES: pytest psutil + CIBW_TEST_COMMAND: pytest -v {project}/test + CIBW_ENVIRONMENT: USE_CYTHON=1 + uses: pypa/cibuildwheel@v2.22.0 + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + upload_pypi: + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/stochastic-arrow + permissions: + id-token: write + if: github.event_name == 'release' && github.event.action == 'published' + steps: + - uses: actions/download-artifact@v4 + with: + # unpacks all CIBW artifacts into dist/ + pattern: cibw-* + path: dist + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index 5352095..4fa54bf 100644 --- a/.gitignore +++ b/.gitignore @@ -10,9 +10,9 @@ profile /.idea/ /tmp/ -stochastic_arrow/arrowhead.c -stochastic_arrow/arrowhead.*.pyd -stochastic_arrow/*.html +src/stochastic_arrow/arrowhead.c +src/stochastic_arrow/arrowhead.*.pyd +src/stochastic_arrow/*.html arrow.egg-info/ stochastic_arrow.egg-info/ diff --git a/MANIFEST.in b/MANIFEST.in index 1f4aaf8..2c1e001 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ -include stochastic_arrow/obsidian.h -include stochastic_arrow/mersenne.h \ No newline at end of file +include src/stochastic_arrow/obsidian.h +include src/stochastic_arrow/mersenne.h \ No newline at end of file diff --git a/Makefile b/Makefile index 41f5961..67deef3 100644 --- a/Makefile +++ b/Makefile @@ -2,16 +2,19 @@ .DEFAULT_GOAL := compile clean: - ### Files for older versions of stochastic_arrow are in arrow folder + ### Files for older versions of stochastic_arrow are in arrow or stochastic_arrow folder rm -rf arrow/arrowhead*.so arrow/arrowhead.c arrow/arrowhead.html rm -rf stochastic_arrow/arrowhead*.so stochastic_arrow/arrowhead.c stochastic_arrow/arrowhead.html build/ dist/ MANIFEST .pytest_cache/ stochastic_arrow.egg-info/ + ### Newer versions of stochastic_arrow are in src/stochastic_arrow folder + rm -rf src/stochastic_arrow/arrowhead*.so src/stochastic_arrow/arrowhead.c src/stochastic_arrow/arrowhead.html src/stochastic_arrow.egg-info/ find . -name "*.pyc" -delete find . -name "__pycache__" -delete compile: - USE_CYTHON=1 python setup.py build_ext --inplace + USE_CYTHON=1 python -m pip install -e . + +test: clean compile + pytest test dist: - ### bdist_wheel is disabled on linux since the distribution machinery doesn't - ### yet have a way to specify compatible linux distros. - USE_CYTHON=1 python setup.py sdist # bdist_wheel + USE_CYTHON=1 python -m build --sdist diff --git a/README.md b/README.md index 7885bb9..1cdd3eb 100644 --- a/README.md +++ b/README.md @@ -91,37 +91,53 @@ from stochastic_arrow import reenact_events history = reenact_events(stoichiometric_matrix, result['events'], state) ``` -## Testing - -`stochastic_arrow` uses [pytest](https://docs.pytest.org/en/latest/). To test it: +## Building > make clean compile - > pytest + +This builds the extension package and installs it in editable mode. **NOTE:** `make compile` without an explicit `clean` might not fully build the extension. -There are more command line features in test_arrow: +## Testing + +`stochastic_arrow` uses [pytest](https://docs.pytest.org/en/latest/). +To run the main tests, in the source tree: - > python -m stochastic_arrow.test.test_arrow --complexation + > make test - > python -m stochastic_arrow.test.test_arrow --plot +or - > python -m stochastic_arrow.test.test_arrow --obsidian + > pytest - > python -m stochastic_arrow.test.test_arrow --memory +There are additional command line features in test_arrow: - > python -m stochastic_arrow.test.test_arrow --time + > python -m test.test_arrow --help + > python -m test.test_arrow --complexation + > python -m test.test_arrow --complexation --runs 3 + > python -m test.test_arrow --obsidian + > python -m test.test_arrow --memory + > python -m test.test_arrow --time + > python -m test.test_arrow --pickle + > python -m test.test_arrow --test-fail-flagella + > python -m test.test_arrow --test-fail-stdout + > python -m test.test_hang -More examples: +This test requires installing a version of matplotlib that's compatible with the installed numpy: - > python -m stochastic_arrow.test.test_hang + > python -m test.test_arrow --plot - > pytest -m stochastic_arrow/test/test_arrow.py +More examples: > pytest -k flagella ## Changelog +### Version 1.1.0 +* Update build toolchain and automatically build/publish wheels for all +major platforms and recent Python versions. +* Build wheels with Numpy 2+ support + ### Version 1.0.0 * Rename module to `stochastic_arrow` to avoid name conflict (Issue #51). **All users must update their import statements to use `stochastic_arrow` instead of `arrow`.** diff --git a/optional_requirements.txt b/optional_requirements.txt index ae9e316..6ccafc3 100644 --- a/optional_requirements.txt +++ b/optional_requirements.txt @@ -1 +1 @@ -matplotlib==2.2.3 +matplotlib diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c34e725 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel", "cython==3.1.0a1; python_version=='3.13'", "cython; python_version!='3.13'", "numpy"] +build-backend = "setuptools.build_meta" diff --git a/release.sh b/release.sh deleted file mode 100755 index b7d9cad..0000000 --- a/release.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -# Build a new release including a git tag and and publish it to PyPI. -# Do some basic checks to avoid mistakes. -# -# Usage: ./release.sh 0.1.0 - -set -eu - -version=$1 - -# Check the version number. -setup_py_version="$(python setup.py --version)" -if [ "$setup_py_version" != "$version" ]; then - echo "setup.py has version `$setup_py_version`, not `$version`." - echo "Aborting." - exit 1 -fi - -# Check that the working directory is clean. -if [ ! -z "$(git status --untracked-files=no --porcelain)" ]; then - echo "You have uncommitted git changes." - echo "Aborting." - exit 1 -fi - -# Check that we are on master. -branch="$(git rev-parse --abbrev-ref HEAD)" -if [ "$branch" != "master" ]; then - echo "You are on git branch `$branch` but should release from `master`." - echo "Aborting." - exit 1 -fi - -# Create and push a git tag. -git tag -m "Version v$version" "v$version" -git push --tags - -# Compile and test, allowing test failures. -make clean compile -set +e -pytest -set -e - -# Create and publish the package. -rm -rf dist build *.egg-info -python setup.py sdist -twine upload dist/* - -echo "Version v$version has been published on PyPI and has a git tag." -echo "Please make a GitHub Release from that tag." diff --git a/requirements.txt b/requirements.txt index 0c0af41..1c09869 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ pip setuptools +build Cython>=0.29.34 numpy diff --git a/setup.py b/setup.py index 3f06677..2706347 100644 --- a/setup.py +++ b/setup.py @@ -1,41 +1,34 @@ -# distutils is deprecated; to be removed for Python 3.12. -# See https://numpy.org/devdocs/reference/distutils_status_migration.html for -# migration advice. -# This setup.py file no longer uses numpy.distutils so it might be easy to -# move fully to setuptools. - import os -import setuptools # used indirectly for bdist_wheel cmd and long_description_content_type -from distutils.core import setup -from distutils.extension import Extension +from setuptools import Extension, find_packages, setup +import sys import numpy as np -_ = setuptools - - -with open("README.md", 'r', encoding="utf-8") as readme: - long_description = readme.read() - -current_dir = os.getcwd() -arrow_dir = os.path.join(current_dir, 'stochastic_arrow') +if sys.version_info[0] < 3: + with open("README.md", 'r') as readme: + long_description = readme.read().decode('utf-8') +else: + with open("README.md", 'r', encoding="utf-8") as readme: + long_description = readme.read() # Compile the Cython code to C for development builds: -# USE_CYTHON=1 python setup.py build_ext --inplace +# USE_CYTHON=1 python -m pip install -e . # and for building source distribution packages: -# USE_CYTHON=1 python setup.py sdist +# USE_CYTHON=1 python -m build --sdist # and *not* when installing a distribution package. # See http://docs.cython.org/en/latest/src/userguide/source_files_and_compilation.html#distributing-cython-modules USE_CYTHON = 'USE_CYTHON' in os.environ ext = '.pyx' if USE_CYTHON else '.c' +arrow_dir = os.path.join('src', 'stochastic_arrow') + cython_extensions = [ Extension('stochastic_arrow.arrowhead', sources=[ - 'stochastic_arrow/mersenne.c', - 'stochastic_arrow/obsidian.c', - 'stochastic_arrow/arrowhead'+ext,], - include_dirs=['stochastic_arrow', np.get_include()], + os.path.join(arrow_dir, 'mersenne.c'), + os.path.join(arrow_dir, 'obsidian.c'), + os.path.join(arrow_dir, 'arrowhead'+ext),], + include_dirs=[arrow_dir, np.get_include()], define_macros=[('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION')], )] @@ -43,23 +36,23 @@ from Cython.Build import cythonize cython_extensions = cythonize( cython_extensions, - include_path=['stochastic_arrow'], + include_path=[arrow_dir], annotate=True, # to get an HTML code listing ) setup( name='stochastic-arrow', - version='1.0.0', - packages=['stochastic_arrow'], + version='1.1.0', + packages=find_packages('src'), + package_dir={'': 'src'}, author='Ryan Spangler, John Mason, Jerry Morrison, Chris Skalnik, Travis Ahn-Horst, Sean Cheah', author_email='ryan.spangler@gmail.com', url='https://github.com/CovertLab/arrow', license='MIT', - include_dirs=[arrow_dir, np.get_include()], ext_modules=cython_extensions, long_description=long_description, long_description_content_type='text/markdown', - requires=['numpy (>=1.14)', 'six'], + install_requires=['numpy>=1.14', 'six'], classifiers=[ 'Development Status :: 3 - Alpha', 'License :: OSI Approved :: MIT License', diff --git a/stochastic_arrow/__init__.py b/src/stochastic_arrow/__init__.py similarity index 100% rename from stochastic_arrow/__init__.py rename to src/stochastic_arrow/__init__.py diff --git a/src/stochastic_arrow/arrow.pxd b/src/stochastic_arrow/arrow.pxd new file mode 100644 index 0000000..2d8bce5 --- /dev/null +++ b/src/stochastic_arrow/arrow.pxd @@ -0,0 +1,2 @@ +# This file works around a Cython 3.0.0a1 error on arrowhead.pyx: +# stochastic_arrow/arrowhead.pyx:14:0: 'arrow.pxd' not found diff --git a/stochastic_arrow/arrow.py b/src/stochastic_arrow/arrow.py similarity index 100% rename from stochastic_arrow/arrow.py rename to src/stochastic_arrow/arrow.py diff --git a/stochastic_arrow/arrowhead.pyx b/src/stochastic_arrow/arrowhead.pyx similarity index 99% rename from stochastic_arrow/arrowhead.pyx rename to src/stochastic_arrow/arrowhead.pyx index ddd0e94..902680c 100644 --- a/stochastic_arrow/arrowhead.pyx +++ b/src/stochastic_arrow/arrowhead.pyx @@ -1,4 +1,5 @@ # cython: language_level=3str +# cython: freethreading_compatible = True from __future__ import absolute_import, division, print_function diff --git a/stochastic_arrow/math.py b/src/stochastic_arrow/math.py similarity index 100% rename from stochastic_arrow/math.py rename to src/stochastic_arrow/math.py diff --git a/stochastic_arrow/mersenne.c b/src/stochastic_arrow/mersenne.c similarity index 100% rename from stochastic_arrow/mersenne.c rename to src/stochastic_arrow/mersenne.c diff --git a/stochastic_arrow/mersenne.h b/src/stochastic_arrow/mersenne.h similarity index 100% rename from stochastic_arrow/mersenne.h rename to src/stochastic_arrow/mersenne.h diff --git a/stochastic_arrow/mersenne.pxd b/src/stochastic_arrow/mersenne.pxd similarity index 93% rename from stochastic_arrow/mersenne.pxd rename to src/stochastic_arrow/mersenne.pxd index 5e70d0a..c9e24cb 100644 --- a/stochastic_arrow/mersenne.pxd +++ b/src/stochastic_arrow/mersenne.pxd @@ -1,5 +1,3 @@ -# cython: language_level=3str - from libc.stdint cimport uint32_t diff --git a/stochastic_arrow/obsidian.c b/src/stochastic_arrow/obsidian.c similarity index 100% rename from stochastic_arrow/obsidian.c rename to src/stochastic_arrow/obsidian.c diff --git a/stochastic_arrow/obsidian.h b/src/stochastic_arrow/obsidian.h similarity index 100% rename from stochastic_arrow/obsidian.h rename to src/stochastic_arrow/obsidian.h diff --git a/stochastic_arrow/obsidian.pxd b/src/stochastic_arrow/obsidian.pxd similarity index 97% rename from stochastic_arrow/obsidian.pxd rename to src/stochastic_arrow/obsidian.pxd index 16981df..612bfb4 100644 --- a/stochastic_arrow/obsidian.pxd +++ b/src/stochastic_arrow/obsidian.pxd @@ -1,5 +1,3 @@ -# cython: language_level=3str - from libc.stdint cimport int64_t, uint32_t from mersenne cimport MTState diff --git a/stochastic_arrow/reference.py b/src/stochastic_arrow/reference.py similarity index 100% rename from stochastic_arrow/reference.py rename to src/stochastic_arrow/reference.py diff --git a/stochastic_arrow/arrow.pxd b/stochastic_arrow/arrow.pxd deleted file mode 100644 index a5b4559..0000000 --- a/stochastic_arrow/arrow.pxd +++ /dev/null @@ -1,4 +0,0 @@ -# cython: language_level=3str - -# This file works around a Cython 3.0.0a1+ error on arrowhead.pyx: -# stochastic_arrow/arrowhead.pyx:14:0: 'arrow.pxd' not found diff --git a/stochastic_arrow/analysis/__init__.py b/test/__init__.py similarity index 100% rename from stochastic_arrow/analysis/__init__.py rename to test/__init__.py diff --git a/stochastic_arrow/test/__init__.py b/test/analysis/__init__.py similarity index 100% rename from stochastic_arrow/test/__init__.py rename to test/analysis/__init__.py diff --git a/stochastic_arrow/analysis/distributions.py b/test/analysis/distributions.py similarity index 97% rename from stochastic_arrow/analysis/distributions.py rename to test/analysis/distributions.py index c7eaf02..b430f6c 100644 --- a/stochastic_arrow/analysis/distributions.py +++ b/test/analysis/distributions.py @@ -96,7 +96,7 @@ def _last_where(bool_array): if __name__ == '__main__': import matplotlib.pyplot as plt - from stochastic_arrow.analysis.plotting import plot_full_history + from .plotting import plot_full_history (fig, axes) = plt.subplots(constrained_layout = True) diff --git a/stochastic_arrow/analysis/plotting.py b/test/analysis/plotting.py similarity index 100% rename from stochastic_arrow/analysis/plotting.py rename to test/analysis/plotting.py diff --git a/stochastic_arrow/test/complex-counts.npy b/test/complex-counts.npy similarity index 100% rename from stochastic_arrow/test/complex-counts.npy rename to test/complex-counts.npy diff --git a/data/complexation/beginning-final.json b/test/data/complexation/beginning-final.json similarity index 100% rename from data/complexation/beginning-final.json rename to test/data/complexation/beginning-final.json diff --git a/data/complexation/beginning-initial.json b/test/data/complexation/beginning-initial.json similarity index 100% rename from data/complexation/beginning-initial.json rename to test/data/complexation/beginning-initial.json diff --git a/data/complexation/large-initial.json b/test/data/complexation/large-initial.json similarity index 100% rename from data/complexation/large-initial.json rename to test/data/complexation/large-initial.json diff --git a/data/complexation/simple-final.json b/test/data/complexation/simple-final.json similarity index 100% rename from data/complexation/simple-final.json rename to test/data/complexation/simple-final.json diff --git a/data/complexation/simple-initial.json b/test/data/complexation/simple-initial.json similarity index 100% rename from data/complexation/simple-initial.json rename to test/data/complexation/simple-initial.json diff --git a/data/complexation/stoichiometry.json b/test/data/complexation/stoichiometry.json similarity index 100% rename from data/complexation/stoichiometry.json rename to test/data/complexation/stoichiometry.json diff --git a/stochastic_arrow/test/negative_counts.py b/test/negative_counts.py similarity index 73% rename from stochastic_arrow/test/negative_counts.py rename to test/negative_counts.py index 6f2209f..5a314ee 100644 --- a/stochastic_arrow/test/negative_counts.py +++ b/test/negative_counts.py @@ -1,4 +1,5 @@ import json +import os from stochastic_arrow import StochasticSystem import numpy as np @@ -6,7 +7,10 @@ duration = 2**31 -with open('data/complexation/large-initial.json') as f: +# Build the path to the data file relative to the script's directory +data_path = os.path.join(os.path.dirname(__file__), 'data', 'complexation', 'large-initial.json') + +with open(data_path) as f: data = json.load(f) stoich = np.array(data['stoich']) diff --git a/stochastic_arrow/test/rates.npy b/test/rates.npy similarity index 100% rename from stochastic_arrow/test/rates.npy rename to test/rates.npy diff --git a/stochastic_arrow/test/stoich.npy b/test/stoich.npy similarity index 100% rename from stochastic_arrow/test/stoich.npy rename to test/stoich.npy diff --git a/stochastic_arrow/test/test_arrow.py b/test/test_arrow.py similarity index 94% rename from stochastic_arrow/test/test_arrow.py rename to test/test_arrow.py index d97284b..ffc00f4 100644 --- a/stochastic_arrow/test/test_arrow.py +++ b/test/test_arrow.py @@ -15,7 +15,6 @@ from time import time as seconds_since_epoch import json import numpy as np -from pathlib import Path import platform import psutil import pytest @@ -85,7 +84,7 @@ def test_dimerization(): def load_complexation(prefix='simple'): - fixtures_root = os.path.join('data', 'complexation') + fixtures_root = os.path.join(os.path.dirname(__file__), 'data', 'complexation') def load_state(filename): with open(os.path.join(fixtures_root, filename)) as f: @@ -225,7 +224,7 @@ def test_memory(): print('obsidian C implementation elapsed seconds for {} runs: {}'.format( amplify, obsidian_end - obsidian_start)) - if platform.system() == 'Windows': + if platform.system() == 'Windows' or platform.system() == 'Darwin': assert memory_increases <= 10 else: assert memory_increases <= 1 @@ -296,12 +295,15 @@ def test_fail_flagella(): # All reaction propensities should be printed if simulation fails def test_fail_stdout(): - curr_file = Path(os.path.realpath(__file__)) - main_dir = curr_file.parents[2] + curr_file = os.path.realpath(__file__) + main_dir = os.path.dirname(os.path.dirname(os.path.dirname(curr_file))) # sys.executable more reliable than 'python' in Windows virtualenv - result = subprocess.run( + env = os.environ.copy() + env.update({'PYTHONPATH': str(main_dir)}) + result = subprocess.Popen( [sys.executable, curr_file, '--test-fail-flagella'], - capture_output=True, env={**os.environ, 'PYTHONPATH': str(main_dir)}) + stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + stdout, stderr = result.communicate() assert re.search(( 'failed simulation: total propensity is NaN.*' 'reaction 0 is -?0.000000.*' @@ -311,8 +313,8 @@ def test_fail_stdout(): 'reaction 4 is -?0.000000.*' 'reaction 5 is -?nan.*' 'largest reaction is 5 at -?nan.*'), - result.stdout.decode('utf-8'), flags=re.DOTALL) - assert result.stderr == b'' + stdout.decode('utf-8'), flags=re.DOTALL) + assert stderr == b'' def test_get_set_random_state(): stoich = np.array([[1, 1, -1, 0], [-2, 0, 0, 1], [-1, -1, 1, 0]], @@ -376,7 +378,7 @@ def check_gillespie_reference(): system() else: import matplotlib.pyplot as plt - from stochastic_arrow.analysis.plotting import plot_full_history + from .analysis.plotting import plot_full_history n_systems = len(systems) @@ -398,7 +400,7 @@ def check_gillespie_reference(): all_axes = np.asarray(all_axes) for (axes, system) in moves.zip(all_axes.flatten(), systems): - print(f'Running {system.__name__}') + print('Running {}'.format(system.__name__)) axes.set_title(system.__name__) time, counts, events = system() @@ -410,9 +412,10 @@ def check_gillespie_reference(): if __name__ == '__main__': parser = argparse.ArgumentParser( - description='Run one of these tests') + description='Run the specified test or some default tests') parser.add_argument('--complexation', action='store_true') - parser.add_argument('--runs', type=int, default=1) + parser.add_argument('--runs', type=int, default=1, + help='an option for complexation') parser.add_argument('--plot', action='store_true') parser.add_argument('--obsidian', action='store_true') parser.add_argument('--memory', action='store_true') diff --git a/stochastic_arrow/test/test_hang.py b/test/test_hang.py similarity index 100% rename from stochastic_arrow/test/test_hang.py rename to test/test_hang.py