Skip to content

Commit

Permalink
First python implementation (#2)
Browse files Browse the repository at this point in the history
Add python implementation.
Add python packaging.
Add github workflows.
TODO: Finalise the package naming ~ this is only going to test pypi at the moment, but is otherwise complete
  • Loading branch information
Skenvy authored Mar 23, 2022
1 parent 15f2706 commit 6d41c79
Show file tree
Hide file tree
Showing 18 changed files with 866 additions and 4 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/python-build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Python 🐍 Build, Release, and Publish 📦 to PyPI and TestPyPI
on:
push:
branches:
- 'main'
paths:
- 'python/src/collatz/__version__.py'
workflow_dispatch: # manual, rather than tag triggering
jobs:
build-n-publish:
name: Build, Release, and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
runs-on: ubuntu-latest
defaults:
run:
shell: bash
working-directory: python
steps:
- name: 🏁 Checkout
uses: actions/checkout@v3
- name: 🐍 Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.10'
- name: 🧱 Install build dependencies
run: |
make venv
- name: 🎡 Build wheel and source
run: |
make build
- name: 🦏 Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >-
export VER=$(cut -d \" -f 2 src/collatz/__version__.py) &&
gh release create
python-v$VER
"$(find dist | grep \\-none\\-any\\.whl)#Wheel"
"$(find dist | grep \\.tar\\.gz)#Tarball"
--generate-notes
-t "Python: Version $VER"
- name: 📦 Publish distribution to Test PyPI
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
- name: 📦 Publish distribution to PyPI
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.PYPI_API_TOKEN }}
36 changes: 36 additions & 0 deletions .github/workflows/python-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Python 🐍 Tox 🦂 Pytest
on:
push:
paths:
- 'python/**'
- '.github/workflows/python-*'
pull_request:
types: [opened, reopened]
branches:
- 'main'
paths:
- 'python/**'
- '.github/workflows/python-*'
jobs:
tox-pytest:
runs-on: '${{ matrix.os }}'
defaults:
run:
working-directory: python
strategy:
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- name: 🏁 Checkout
uses: actions/checkout@v3
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: 🍑 Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox tox-gh-actions
- name: 🦂 Test with tox
run: tox
12 changes: 12 additions & 0 deletions python/.pypirc-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[distutils]
index-servers =
pypi
testpypi

[pypi]
username = __token__
password = <pypi-token>

[testpypi]
username = __token__
password = <test-pypi-token>
3 changes: 3 additions & 0 deletions python/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
graft tests
global-exclude *.py[cod]
prune */__pycache__/*
44 changes: 44 additions & 0 deletions python/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Virtual Environment
VE=source .ve/bin/activate &&
.PHONY: venv clean clean_again test build upload_test upload
SHELL:=/bin/bash

# https://code.visualstudio.com/docs/python/environments
# https://docs.python.org/3/library/venv.html < for pwsh execution policy.
# Will need to `python -m venv .ve` from pwsh separately to create the
# ~/.ve/Scripts/python.exe if this Makefile is run from WSL, which doesn't
# populate the ~/.ve/Scripts folder, but is needed to run the venv as the
# interpreter by an IDE on windows. Make sure to create it at a different
# place to the .ve in ~/python/ as the venv is mutually exclusive between them.
# Despite being in a venv pip install --upgrade pip still needs admin on windows
venv:
python3 -m venv .ve
$(VE) pip install --upgrade pip
$(VE) pip install --upgrade -r requirements-venv.txt

clean clean_again:
rm -rf src/*.egg-info/
rm -rf dist/
rm -rf */__pycache__/
rm -rf */*/__pycache__/
rm -rf .pytest_cache/

test: clean
$(VE) pytest

# build will automatically include an adjacent "LICENSE*" ~ but if it's
# referenced as ../LICENSE in either manifest or [metadate]license_files then
# it will result in being added outside the built wheel? So just shuffle it
# around here and remove it after to always use the top level one.
build: test clean_again
cp ../LICENSE LICENSE
$(VE) python3 -m build
rm LICENSE
$(VE) pip install --force-reinstall dist/*-none-any.whl

# https://twine.readthedocs.io/en/stable/#twine-upload
upload_test: build
$(VE) twine upload --config-file ./.pypirc --repository testpypi dist/*

upload: upload_test
$(VE) twine upload --config-file ./.pypirc --repository pypi dist/*
97 changes: 93 additions & 4 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,93 @@
# Collatz: Python
Functions related to the Collatz/Syracuse/3N+1 problem, implemented in Python
## Package
The [default Python package index](https://pypi.org/) ~ [search results for collatz](https://pypi.org/project/collatz/) (which appears to already be in unique use on PyPi)
# [Collatz](https://github.com/Skenvy/Collatz): [Python](https://github.com/Skenvy/Collatz/tree/main/python)
Functions related to [the Collatz/Syracuse/3N+1 problem](https://en.wikipedia.org/wiki/Collatz_conjecture), implemented in Python.
## Getting Started
To [install the latest](https://test.pypi.org/project/collatz-skenvy/);
```
pip install -i https://test.pypi.org/simple/ collatz-skenvy
```
### Developing
#### The first time virtual env
```
git clone https://github.com/Skenvy/Collatz.git
cd Collatz/python
make venv
```
#### Iterative development
`make build` will test and build the wheel and force reinstall it into the local venv, to test the built distribution
## Usage
Provides the basic functionality to interact with the Collatz conjecture.
The parameterisation uses the same `(P,a,b)` notation as Conway's generalisations.
Besides the function and reverse function, there is also functionality to retrieve the hailstone sequence, the "stopping time"/"total stopping time", or tree-graph.
The only restriction placed on parameters is that both `P` and `a` can't be `0`.
### collatz.function(~)
`(n:int, P:int=2, a:int=3, b:int=1)`
```
>>> import collatz
>>> # The default "Collatz function"
>>> collatz.function(5)
16
>>> # Alternatively, you can parameterise the function.
>>> collatz.function(5, P=7, a=5, b=17)
42
```
### collatz.reverse_function(~)
`(n:int, P:int=2, a:int=3, b:int=1)`
```
>>> import collatz
>>> # Get the list of values that return the input.
>>> collatz.reverse_function(4)
[1, 8]
>>> # Alternatively, you can parameterise the reverse_function.
>>> collatz.reverse_function(5, P=5, a=2, b=3)
[1, 25]
```
### collatz.hailstone_sequence(~)
`(initial_value:int, P:int=2, a:int=3, b:int=1, max_total_stopping_time:int=1000, total_stopping_time:bool=True, verbose:bool=True)`
```
>>> import collatz
>>> # Get the sequence of values forming the hailstone from an initial value
>>> collatz.hailstone_sequence(10)
[10, 5, 16, 8, 4, 2, 1, ['TOTAL_STOPPING_TIME', 6]]
>>> # Determines if it's in a cycle
>>> collatz.hailstone_sequence(-56)
[-56, -28, 'CYCLE_INIT', [-14, -7, -20, -10, -5], ['CYCLE_LENGTH', 5]]
>>> # The verbose messages can be muted, although this might leave a sense of ambiguity for larger lists.
>>> collatz.hailstone_sequence(-200, verbose=False)
[-200, -100, -50, -25, -74, -37, -110, -55, -164, -82, -41, -122, -61, -182, -91, -272, -136, -68, -34, -17, -50]
>>> # Although hailstones typically go to the "total stop" of 1, they can be set to terminate on the regular stop
>>> collatz.hailstone_sequence(5, total_stopping_time=False)
[5, 16, 8, 4, ['STOPPING_TIME', 3]]
```
### collatz.stopping_time(~)
`(initial_value:int, P:int=2, a:int=3, b:int=1, max_stopping_time:int=1000, total_stopping_time:bool=False)`
```
>>> import collatz
>>> # Reports the stopping time, the amount of iterations of the function to reach a value lower than the initial value.
>>> collatz.stopping_time(5)
3
>>> # Can be used to find the "total stopping time" as well, the amount of iterations to reach "1"
>>> collatz.stopping_time(5, total_stopping_time=True)
5
>>> # Although most cylces have a stopping time, by targetting the total stopping time, you can see if a value leads into a cycle by the 'inf' return
>>> collatz.stopping_time(-17, total_stopping_time=True)
inf
>>> # Some cycles are small enough that starting on the lowest absolute value will still identify a cycle.
>>> collatz.stopping_time(-1)
inf
>>> # If it overruns maximum stopping time, returns nothing.
>>> collatz.stopping_time(5, max_stopping_time=-1)
>>> # <None>
```
### collatz.tree_graph(~)
`(initial_value:int, max_orbit_distance:int, P:int=2, a:int=3, b:int=1)`
```
>>> import collatz
>>> # See the tree graph built by a reverse function traversal, to the depth specified by max_orbit_distance.
>>> collatz.tree_graph(1, 3)
{1: {2: {4: {'CYCLE_INIT': 1, 8: {}}}}}
>>> collatz.tree_graph(1, 12)
{1: {2: {4: {'CYCLE_INIT': 1, 8: {16: {5: {10: {3: {6: {12: {24: {48: {96: {}}}}}}, 20: {40: {13: {26: {52: {17: {}, 104: {}}}}, 80: {160: {53: {106: {}}, 320: {640: {}}}}}}}}, 32: {64: {21: {42: {84: {168: {336: {672: {}}}}}}, 128: {256: {85: {170: {340: {113: {}, 680: {}}}}, 512: {1024: {341: {682: {}}, 2048: {4096: {}}}}}}}}}}}}}}
>>> # Can also be parameterised;
>>> collatz.tree_graph(1, 2, P=5, a=2, b=3)
{1: {-1: {-5: {}, -2: {}}, 5: {'CYCLE_INIT': 1, 25: {}}}}
```
5 changes: 5 additions & 0 deletions python/devlog.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
# Devlog
At the moment, a [python package already exists on pypi](https://pypi.org/project/collatz/), owned by [NGeorgescu](https://github.com/NGeorgescu/collatz), and there appear to be more recent uploads with more squashed names, such as {[simple-collatz](https://pypi.org/project/simple-collatz/)|[collatzconj](https://pypi.org/project/collatzconj/)|[lychrel](https://pypi.org/project/lychrel/)}.

We can follow the [pypi packaging doc](https://packaging.python.org/en/latest/tutorials/packaging-projects/) for the recommended steps to start setting up, although it looks like the '~/src' directory is a new recommendation, which seems to have a non inconsequential effect on imports, and the current tutorial on python.org appears to have no follow up to default testing recommendations besides "make a test folder for later..", although `from src import <thing>` works fine.

Because the overall project layout doesn't include the license in the python folder from which the build is run, might need to look into using [the manifest file](https://packaging.python.org/en/latest/guides/using-manifest-in/) to add it. Once we've [set up testing with pytest](https://docs.pytest.org/en/7.1.x/getting-started.html#get-started), will have a look at [the recommended github actions](https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/). Also worth double checking [the actual recommendation on aligning the readme with pypi's expectations for it](https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/).
8 changes: 8 additions & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"
[tool.pytest.ini_options]
addopts = "-r a -v"
15 changes: 15 additions & 0 deletions python/requirements-venv.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/

# The version of build should be major version locked but minor version
# permissive. The most recent verified working for `make build` is 0.7.0
build>=0.7.0,<1
# Twine in general, for uploading to pypi, with minimum version lock by the PyPi
# friendly README doc above, although the current tip used is 3.8.0
twine>=1.11.0
# pytest breaks heavily between minor versions, so only allow patch drift.
pytest>=7.1.0,<7.2
# Allow patch drift for tox as well.
tox>=3.24.5,<3.25
# Other version mins specd by the PyPi friendly README doc.
setuptools>=38.6.0
wheel>=0.31.0
Empty file added python/requirements.txt
Empty file.
23 changes: 23 additions & 0 deletions python/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[metadata]
name = collatz-skenvy
author = Skenvy
author_email = [email protected]
description = Enabling experimenting with functions related to or involved in the Collatz conjecture.
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/Skenvy/Collatz
project_urls =
Bug Tracker = https://github.com/Skenvy/Collatz/issues
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: Apache Software License
Operating System :: OS Independent

[options]
package_dir =
= src
packages = find:
python_requires = >=3.6

[options.packages.find]
where = src
11 changes: 11 additions & 0 deletions python/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import setuptools
# Because this is run via "build" and not "setup with args" it needs 'here'
# added to the path manually to be able to find 'src'
import sys, os
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.'))
from src.collatz import __version__


setuptools.setup(
version=__version__,
)
5 changes: 5 additions & 0 deletions python/src/collatz/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .__version__ import __version__
from .parameterised import *
from .parameterised import _ErrMsg
from .parameterised import _CC
from .parameterised import _KNOWN_CYCLES
1 change: 1 addition & 0 deletions python/src/collatz/__version__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.1.2"
Loading

0 comments on commit 6d41c79

Please sign in to comment.