pyextrusion 0.17.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyextrusion-0.17.0/.github/ISSUE_TEMPLATE/bug_report.yml +54 -0
- pyextrusion-0.17.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- pyextrusion-0.17.0/.github/ISSUE_TEMPLATE/feature_request.yml +28 -0
- pyextrusion-0.17.0/.github/workflows/ci.yml +56 -0
- pyextrusion-0.17.0/.github/workflows/docs.yml +26 -0
- pyextrusion-0.17.0/.github/workflows/pages.yml +44 -0
- pyextrusion-0.17.0/.github/workflows/publish-pypi.yml +86 -0
- pyextrusion-0.17.0/.github/workflows/publish-testpypi.yml +61 -0
- pyextrusion-0.17.0/AUTHORS.md +9 -0
- pyextrusion-0.17.0/CHANGELOG.md +248 -0
- pyextrusion-0.17.0/CITATION.cff +18 -0
- pyextrusion-0.17.0/INTEGRATION_GUIDE.md +222 -0
- pyextrusion-0.17.0/LICENSE +202 -0
- pyextrusion-0.17.0/MANIFEST.in +15 -0
- pyextrusion-0.17.0/MODEL_BOUNDARIES.md +80 -0
- pyextrusion-0.17.0/NOTICE +5 -0
- pyextrusion-0.17.0/PKG-INFO +234 -0
- pyextrusion-0.17.0/README.md +208 -0
- pyextrusion-0.17.0/docs/CNAME +1 -0
- pyextrusion-0.17.0/docs/about/changelog.md +248 -0
- pyextrusion-0.17.0/docs/about/citation.md +7 -0
- pyextrusion-0.17.0/docs/about/license.md +22 -0
- pyextrusion-0.17.0/docs/about/project.md +11 -0
- pyextrusion-0.17.0/docs/api-reference/cli.md +168 -0
- pyextrusion-0.17.0/docs/api-reference/errors.md +53 -0
- pyextrusion-0.17.0/docs/api-reference/index.md +5 -0
- pyextrusion-0.17.0/docs/api-reference/integration.md +222 -0
- pyextrusion-0.17.0/docs/api-reference/json.md +197 -0
- pyextrusion-0.17.0/docs/api-reference/python-api.md +310 -0
- pyextrusion-0.17.0/docs/api-reference/result-fields.md +104 -0
- pyextrusion-0.17.0/docs/examples/index.md +95 -0
- pyextrusion-0.17.0/docs/index.md +22 -0
- pyextrusion-0.17.0/docs/javascripts/mathjax.js +12 -0
- pyextrusion-0.17.0/docs/stylesheets/extra.css +23 -0
- pyextrusion-0.17.0/docs/technical-reference/boundaries-interpretation.md +53 -0
- pyextrusion-0.17.0/docs/technical-reference/formula-summary.md +228 -0
- pyextrusion-0.17.0/docs/technical-reference/geometry.md +227 -0
- pyextrusion-0.17.0/docs/technical-reference/index.md +7 -0
- pyextrusion-0.17.0/docs/technical-reference/inputs-validation.md +46 -0
- pyextrusion-0.17.0/docs/technical-reference/losses-productivity.md +249 -0
- pyextrusion-0.17.0/docs/technical-reference/planning-sequences-comparisons.md +85 -0
- pyextrusion-0.17.0/docs/technical-reference/references.md +67 -0
- pyextrusion-0.17.0/docs/technical-reference/scope-units.md +57 -0
- pyextrusion-0.17.0/docs/user-guide/basic-economics.md +178 -0
- pyextrusion-0.17.0/docs/user-guide/calculate-process.md +93 -0
- pyextrusion-0.17.0/docs/user-guide/compare-presses.md +84 -0
- pyextrusion-0.17.0/docs/user-guide/getting-started.md +127 -0
- pyextrusion-0.17.0/docs/user-guide/glossary.md +225 -0
- pyextrusion-0.17.0/docs/user-guide/index.md +62 -0
- pyextrusion-0.17.0/docs/user-guide/json-cli.md +114 -0
- pyextrusion-0.17.0/docs/user-guide/model-boundaries.md +80 -0
- pyextrusion-0.17.0/docs/user-guide/planning.md +120 -0
- pyextrusion-0.17.0/docs/user-guide/press.md +86 -0
- pyextrusion-0.17.0/docs/user-guide/production-sequences.md +136 -0
- pyextrusion-0.17.0/docs/user-guide/profile-process.md +127 -0
- pyextrusion-0.17.0/docs/user-guide/troubleshooting.md +96 -0
- pyextrusion-0.17.0/docs/user-guide/understand-results.md +133 -0
- pyextrusion-0.17.0/examples/annual_demand.py +10 -0
- pyextrusion-0.17.0/examples/annual_demand_20000kg.json +7 -0
- pyextrusion-0.17.0/examples/basic.py +30 -0
- pyextrusion-0.17.0/examples/case_basic.json +16 -0
- pyextrusion-0.17.0/examples/compare_presses.py +12 -0
- pyextrusion-0.17.0/examples/legacy_embedded_study_v0.3.json +57 -0
- pyextrusion-0.17.0/examples/manual_adjustments.py +21 -0
- pyextrusion-0.17.0/examples/multi_press_comparison.py +51 -0
- pyextrusion-0.17.0/examples/planning.py +42 -0
- pyextrusion-0.17.0/examples/planning_case_basic.json +14 -0
- pyextrusion-0.17.0/examples/press_example_8in.json +21 -0
- pyextrusion-0.17.0/examples/press_example_9in.json +21 -0
- pyextrusion-0.17.0/examples/production_sequence.json +107 -0
- pyextrusion-0.17.0/examples/production_sequence.py +34 -0
- pyextrusion-0.17.0/examples/select_results.py +18 -0
- pyextrusion-0.17.0/examples/special_configurations.py +29 -0
- pyextrusion-0.17.0/mkdocs.yml +103 -0
- pyextrusion-0.17.0/pyproject.toml +47 -0
- pyextrusion-0.17.0/requirements-docs.txt +3 -0
- pyextrusion-0.17.0/setup.cfg +4 -0
- pyextrusion-0.17.0/src/pyextrusion/__init__.py +124 -0
- pyextrusion-0.17.0/src/pyextrusion/_meta.py +8 -0
- pyextrusion-0.17.0/src/pyextrusion/_strict.py +93 -0
- pyextrusion-0.17.0/src/pyextrusion/api.py +78 -0
- pyextrusion-0.17.0/src/pyextrusion/cli.py +538 -0
- pyextrusion-0.17.0/src/pyextrusion/comparison.py +219 -0
- pyextrusion-0.17.0/src/pyextrusion/core.py +1143 -0
- pyextrusion-0.17.0/src/pyextrusion/demand.py +142 -0
- pyextrusion-0.17.0/src/pyextrusion/economics/__init__.py +20 -0
- pyextrusion-0.17.0/src/pyextrusion/economics/basic.py +402 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/__init__.py +201 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/extrusion_flow.py +133 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/materials.py +187 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/mechanics.py +119 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/metadata.py +163 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/power.py +204 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/press.py +274 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/pressure.py +335 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/rheology.py +122 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/saha_thermal.py +288 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/thermal.py +486 -0
- pyextrusion-0.17.0/src/pyextrusion/engineering/thermomechanical.py +344 -0
- pyextrusion-0.17.0/src/pyextrusion/errors.py +170 -0
- pyextrusion-0.17.0/src/pyextrusion/fields.py +146 -0
- pyextrusion-0.17.0/src/pyextrusion/io.py +374 -0
- pyextrusion-0.17.0/src/pyextrusion/models.py +1142 -0
- pyextrusion-0.17.0/src/pyextrusion/planning.py +494 -0
- pyextrusion-0.17.0/src/pyextrusion/py.typed +0 -0
- pyextrusion-0.17.0/src/pyextrusion/reporting.py +349 -0
- pyextrusion-0.17.0/src/pyextrusion/sequence.py +321 -0
- pyextrusion-0.17.0/src/pyextrusion/validation.py +245 -0
- pyextrusion-0.17.0/src/pyextrusion.egg-info/PKG-INFO +234 -0
- pyextrusion-0.17.0/src/pyextrusion.egg-info/SOURCES.txt +126 -0
- pyextrusion-0.17.0/src/pyextrusion.egg-info/dependency_links.txt +1 -0
- pyextrusion-0.17.0/src/pyextrusion.egg-info/entry_points.txt +2 -0
- pyextrusion-0.17.0/src/pyextrusion.egg-info/top_level.txt +1 -0
- pyextrusion-0.17.0/tests/test_comparison.py +132 -0
- pyextrusion-0.17.0/tests/test_core.py +862 -0
- pyextrusion-0.17.0/tests/test_economics.py +298 -0
- pyextrusion-0.17.0/tests/test_engineering.py +433 -0
- pyextrusion-0.17.0/tests/test_engineering_pressure.py +163 -0
- pyextrusion-0.17.0/tests/test_engineering_rheology.py +206 -0
- pyextrusion-0.17.0/tests/test_engineering_saha_thermal.py +161 -0
- pyextrusion-0.17.0/tests/test_engineering_thermal.py +229 -0
- pyextrusion-0.17.0/tests/test_engineering_thermomechanical.py +126 -0
- pyextrusion-0.17.0/tests/test_mass_balance_v28.py +200 -0
- pyextrusion-0.17.0/tests/test_model_v2.py +464 -0
- pyextrusion-0.17.0/tests/test_model_v21.py +286 -0
- pyextrusion-0.17.0/tests/test_model_v22.py +390 -0
- pyextrusion-0.17.0/tests/test_planning.py +271 -0
- pyextrusion-0.17.0/tests/test_sequence.py +281 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Report a reproducible problem in PyExtrusion
|
|
3
|
+
title: "[Bug]: "
|
|
4
|
+
labels: ["bug"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
Thanks for reporting a problem. Please remove any confidential plant, customer or production data before posting.
|
|
10
|
+
- type: input
|
|
11
|
+
id: version
|
|
12
|
+
attributes:
|
|
13
|
+
label: PyExtrusion version
|
|
14
|
+
description: For example, 0.16.0
|
|
15
|
+
validations:
|
|
16
|
+
required: true
|
|
17
|
+
- type: input
|
|
18
|
+
id: python
|
|
19
|
+
attributes:
|
|
20
|
+
label: Python version
|
|
21
|
+
description: For example, 3.12.5
|
|
22
|
+
validations:
|
|
23
|
+
required: true
|
|
24
|
+
- type: dropdown
|
|
25
|
+
id: interface
|
|
26
|
+
attributes:
|
|
27
|
+
label: Interface
|
|
28
|
+
options:
|
|
29
|
+
- Python API
|
|
30
|
+
- CLI
|
|
31
|
+
- JSON
|
|
32
|
+
- Documentation
|
|
33
|
+
- Other
|
|
34
|
+
validations:
|
|
35
|
+
required: true
|
|
36
|
+
- type: textarea
|
|
37
|
+
id: description
|
|
38
|
+
attributes:
|
|
39
|
+
label: Problem
|
|
40
|
+
description: What happened, and what did you expect to happen?
|
|
41
|
+
validations:
|
|
42
|
+
required: true
|
|
43
|
+
- type: textarea
|
|
44
|
+
id: reproduce
|
|
45
|
+
attributes:
|
|
46
|
+
label: Minimal reproducible example
|
|
47
|
+
description: Include the smallest code, command or JSON input that reproduces the issue. Remove confidential data.
|
|
48
|
+
render: shell
|
|
49
|
+
- type: textarea
|
|
50
|
+
id: info
|
|
51
|
+
attributes:
|
|
52
|
+
label: pyextrusion info
|
|
53
|
+
description: If relevant, paste the output of `pyextrusion info`.
|
|
54
|
+
render: text
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Suggest a new capability or improvement
|
|
3
|
+
title: "[Feature]: "
|
|
4
|
+
labels: ["enhancement"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
Please describe the engineering or integration need rather than only a proposed implementation. Do not include confidential plant or customer data.
|
|
10
|
+
- type: textarea
|
|
11
|
+
id: problem
|
|
12
|
+
attributes:
|
|
13
|
+
label: Problem or use case
|
|
14
|
+
description: What problem would this feature solve?
|
|
15
|
+
validations:
|
|
16
|
+
required: true
|
|
17
|
+
- type: textarea
|
|
18
|
+
id: proposal
|
|
19
|
+
attributes:
|
|
20
|
+
label: Proposed behavior
|
|
21
|
+
description: What should PyExtrusion do?
|
|
22
|
+
validations:
|
|
23
|
+
required: true
|
|
24
|
+
- type: textarea
|
|
25
|
+
id: context
|
|
26
|
+
attributes:
|
|
27
|
+
label: Additional context
|
|
28
|
+
description: Optional examples, constraints or references.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
name: Python ${{ matrix.python-version }}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v7
|
|
23
|
+
- uses: actions/setup-python@v7
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
cache: pip
|
|
27
|
+
- name: Install package and test tools
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
python -m pip install -e . pytest
|
|
31
|
+
- name: Run tests
|
|
32
|
+
run: python -m pytest -q
|
|
33
|
+
|
|
34
|
+
build:
|
|
35
|
+
name: Build distribution
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
needs: test
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v7
|
|
41
|
+
- uses: actions/setup-python@v7
|
|
42
|
+
with:
|
|
43
|
+
python-version: "3.12"
|
|
44
|
+
cache: pip
|
|
45
|
+
- name: Install build tools
|
|
46
|
+
run: |
|
|
47
|
+
python -m pip install --upgrade pip
|
|
48
|
+
python -m pip install build twine
|
|
49
|
+
- name: Build wheel and sdist
|
|
50
|
+
run: python -m build
|
|
51
|
+
- name: Validate distributions
|
|
52
|
+
run: python -m twine check dist/*
|
|
53
|
+
- uses: actions/upload-artifact@v4
|
|
54
|
+
with:
|
|
55
|
+
name: pyextrusion-dist
|
|
56
|
+
path: dist/*
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v7
|
|
17
|
+
- uses: actions/setup-python@v7
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
cache: pip
|
|
21
|
+
- name: Install documentation dependencies
|
|
22
|
+
run: |
|
|
23
|
+
python -m pip install --upgrade pip
|
|
24
|
+
python -m pip install -r requirements-docs.txt
|
|
25
|
+
- name: Build documentation strictly
|
|
26
|
+
run: mkdocs build --strict
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Deploy documentation to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
pages: write
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: pages
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
|
+
- uses: actions/setup-python@v7
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
cache: pip
|
|
24
|
+
- name: Install documentation dependencies
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
python -m pip install -r requirements-docs.txt
|
|
28
|
+
- name: Build documentation strictly
|
|
29
|
+
run: mkdocs build --strict
|
|
30
|
+
- uses: actions/configure-pages@v5
|
|
31
|
+
- uses: actions/upload-pages-artifact@v3
|
|
32
|
+
with:
|
|
33
|
+
path: site
|
|
34
|
+
|
|
35
|
+
deploy:
|
|
36
|
+
environment:
|
|
37
|
+
name: github-pages
|
|
38
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
needs: build
|
|
41
|
+
steps:
|
|
42
|
+
- name: Deploy to GitHub Pages
|
|
43
|
+
id: deployment
|
|
44
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
name: Build release distribution
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v7
|
|
17
|
+
with:
|
|
18
|
+
ref: ${{ github.event.release.tag_name }}
|
|
19
|
+
persist-credentials: false
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-python@v7
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
cache: pip
|
|
25
|
+
|
|
26
|
+
- name: Verify release tag matches package version
|
|
27
|
+
env:
|
|
28
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
29
|
+
run: |
|
|
30
|
+
python - <<'PY'
|
|
31
|
+
import os
|
|
32
|
+
import pathlib
|
|
33
|
+
import tomllib
|
|
34
|
+
|
|
35
|
+
tag = os.environ["RELEASE_TAG"]
|
|
36
|
+
if not tag.startswith("v"):
|
|
37
|
+
raise SystemExit(f"Release tag must start with 'v': {tag!r}")
|
|
38
|
+
|
|
39
|
+
version = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))["project"]["version"]
|
|
40
|
+
expected_tag = f"v{version}"
|
|
41
|
+
if tag != expected_tag:
|
|
42
|
+
raise SystemExit(
|
|
43
|
+
f"Release tag {tag!r} does not match pyproject version {version!r}; "
|
|
44
|
+
f"expected {expected_tag!r}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
print(f"Validated release {tag} for pyextrusion {version}")
|
|
48
|
+
PY
|
|
49
|
+
|
|
50
|
+
- name: Install build tools
|
|
51
|
+
run: |
|
|
52
|
+
python -m pip install --upgrade pip
|
|
53
|
+
python -m pip install build twine
|
|
54
|
+
|
|
55
|
+
- name: Build wheel and sdist
|
|
56
|
+
run: python -m build
|
|
57
|
+
|
|
58
|
+
- name: Validate distributions
|
|
59
|
+
run: python -m twine check dist/*
|
|
60
|
+
|
|
61
|
+
- name: Store distribution packages
|
|
62
|
+
uses: actions/upload-artifact@v5
|
|
63
|
+
with:
|
|
64
|
+
name: python-package-distributions
|
|
65
|
+
path: dist/
|
|
66
|
+
if-no-files-found: error
|
|
67
|
+
|
|
68
|
+
publish:
|
|
69
|
+
name: Publish distribution to PyPI
|
|
70
|
+
needs: build
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
environment:
|
|
73
|
+
name: pypi
|
|
74
|
+
url: https://pypi.org/p/pyextrusion
|
|
75
|
+
permissions:
|
|
76
|
+
id-token: write
|
|
77
|
+
|
|
78
|
+
steps:
|
|
79
|
+
- name: Download distribution packages
|
|
80
|
+
uses: actions/download-artifact@v6
|
|
81
|
+
with:
|
|
82
|
+
name: python-package-distributions
|
|
83
|
+
path: dist/
|
|
84
|
+
|
|
85
|
+
- name: Publish distribution to PyPI
|
|
86
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Publish to TestPyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
name: Build distribution
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
|
+
with:
|
|
17
|
+
persist-credentials: false
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v7
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
cache: pip
|
|
23
|
+
|
|
24
|
+
- name: Install build tools
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
python -m pip install build twine
|
|
28
|
+
|
|
29
|
+
- name: Build wheel and sdist
|
|
30
|
+
run: python -m build
|
|
31
|
+
|
|
32
|
+
- name: Validate distributions
|
|
33
|
+
run: python -m twine check dist/*
|
|
34
|
+
|
|
35
|
+
- name: Store distribution packages
|
|
36
|
+
uses: actions/upload-artifact@v5
|
|
37
|
+
with:
|
|
38
|
+
name: python-package-distributions
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
publish:
|
|
42
|
+
name: Publish distribution to TestPyPI
|
|
43
|
+
needs: build
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
environment:
|
|
46
|
+
name: testpypi
|
|
47
|
+
url: https://test.pypi.org/p/pyextrusion
|
|
48
|
+
permissions:
|
|
49
|
+
id-token: write
|
|
50
|
+
|
|
51
|
+
steps:
|
|
52
|
+
- name: Download distribution packages
|
|
53
|
+
uses: actions/download-artifact@v6
|
|
54
|
+
with:
|
|
55
|
+
name: python-package-distributions
|
|
56
|
+
path: dist/
|
|
57
|
+
|
|
58
|
+
- name: Publish distribution to TestPyPI
|
|
59
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
60
|
+
with:
|
|
61
|
+
repository-url: https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
- Adds the public `pyextrusion.engineering` foundation with strict provenance, metadata, force-limit, hydraulic, power, energy and specific-energy helpers.
|
|
6
|
+
- Adds basic deterministic production economics with recurring production cost, material/scrap economics, sales-margin calculations, one-time tooling/development cost, die-trial surcharges and optional customer tooling revenue.
|
|
7
|
+
- Corrects the physical boundary for downstream saw kerfs: puller and final-saw kerfs now reserve extruded material in billet sizing, runout-table occupancy and technical extrusion time, while billet-saw kerf remains an upstream stock loss.
|
|
8
|
+
- Reframes startup and complexity scrap as planning allowances rather than physical extruded-length/time contributions.
|
|
9
|
+
- Adds source-traced AA6063 hot-working constants from Sheppard and AA6060 hot-working constants attributed to Verlinden et al.; the two alloy models remain explicitly separate.
|
|
10
|
+
- Adds modified Feltham mean strain rate, Zener-Hollomon and Sheppard-Wright steady-state flow-stress calculations with strict validation.
|
|
11
|
+
- Adds Sheppard axisymmetric pressure correlations: deformation pressure, billet-container friction, breakthrough increment, peak pressure and equivalent-axisymmetric required-force baseline.
|
|
12
|
+
- Keeps the pressure/force model explicitly scoped to equivalent-axisymmetric direct extrusion; shaped-section, bridge-die and porthole-die corrections are not inferred or approximated with hidden multipliers.
|
|
13
|
+
- Adds an AA6063 single-operation thermomechanical composition that keeps mechanical and thermal model boundaries visible.
|
|
14
|
+
- Adds the Sheppard/Stuwe three-component surface-temperature estimate as a limited analytical baseline, not as a production-grade exit-temperature predictor.
|
|
15
|
+
- Adds source-traced Saha local thermal source terms for deformation, billet-container friction, dead-metal-zone friction and die-bearing friction.
|
|
16
|
+
- Keeps Saha's documented 45 degree semi-dead-metal-zone assumption isolated from the Sheppard/Feltham geometry model.
|
|
17
|
+
- Adds complete-sticking die-bearing shear/heat-flux helpers without claiming a transient temperature field or exit-temperature solution.
|
|
18
|
+
- Adds Sheppard Eq. 2.25 billet/tooling interface-temperature calculation with source-stated aluminium and Cr-V tooling-steel thermal properties and thermal-effusivity reporting.
|
|
19
|
+
- Explicitly avoids interpreting Eq. 2.25 as a source-exact friction-heat partition model.
|
|
20
|
+
- Keeps Saha's omitted boundary conditions, Integral Profile reconstruction, transient tooling heat storage and production-grade exit-temperature prediction outside the implemented scope.
|
|
21
|
+
- Adds regression coverage across Python 3.10-3.13 for the new engineering and thermomechanical calculations.
|
|
22
|
+
- Keeps the internal PyExtrusion calculation manual private and outside the package, public documentation and website.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## 0.16.0
|
|
26
|
+
|
|
27
|
+
- Aligns the multi-press comparison layer with the current PyExtrusion calculation engine.
|
|
28
|
+
- Adds `compare_processes()` for quantity-free process comparison across two or more presses.
|
|
29
|
+
- Adds `compare_planning()` for one explicit `PlanningRequest` across two or more presses.
|
|
30
|
+
- Adds `compare_production_sequences()` for the exact same ordered production list across two or more presses.
|
|
31
|
+
- Adds `ProcessComparisonResult`, `PlanningComparisonResult` and `ProductionSequenceComparisonResult`.
|
|
32
|
+
- Preserves press order and sequence order exactly.
|
|
33
|
+
- Performs no automatic ranking, winner selection, order allocation, load balancing or sequence optimisation.
|
|
34
|
+
- Adds strict JSON output and human-readable formatters for all three new comparison layers.
|
|
35
|
+
- Adds CLI `compare-process`, `compare-planning` and `compare-sequence`.
|
|
36
|
+
- Adds `PX1013 InvalidComparison`.
|
|
37
|
+
- Keeps JSON input schema `1.1`; existing press, planning-case and sequence input formats are reused.
|
|
38
|
+
- Does not change the v0.15.0 physical, billet-first, scrap, productivity, planning or sequence mathematics.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## 0.15.0
|
|
43
|
+
|
|
44
|
+
- Adds the public production-sequence layer on top of the existing planning API.
|
|
45
|
+
- Adds `ProductionOrder`, `ProductionSequenceEntry`, `ProductionSequenceResult` and `calculate_production_sequence()`.
|
|
46
|
+
- Preserves the exact user-supplied order; no automatic sequence optimisation or sorting is performed.
|
|
47
|
+
- Chains only existing planning time (`extrusion + technical dead time`) and inserts no setup, die-change, waiting, die-preparation, correction, maintenance or calendar assumptions.
|
|
48
|
+
- Supports sequence-order quantities in bars, kg, metres and exact billets.
|
|
49
|
+
- Rejects minutes/hours as sequence-order quantities; they remain planning capacity-window requests.
|
|
50
|
+
- Adds optional theoretical timestamps from a user-supplied `datetime`; without `start_at`, only relative cumulative minutes are returned.
|
|
51
|
+
- Keeps unresolved orders in place with diagnostics and zero calculable duration so the caller can decide what to do.
|
|
52
|
+
- Adds aggregate sequence totals for billets, pulls, bars, metres, good kg, scrap and technical press time.
|
|
53
|
+
- Adds strict JSON serialization for calculated sequence results and a human-readable sequence formatter.
|
|
54
|
+
- Adds `PX1012 InvalidProductionSequence`.
|
|
55
|
+
- Does not change v0.14.0 physical, billet-first, scrap, productivity or single-order planning mathematics.
|
|
56
|
+
|
|
57
|
+
## 0.14.0
|
|
58
|
+
|
|
59
|
+
- Consolidates the quantity-free planning API.
|
|
60
|
+
- Adds public `calculate_process(press, PlanningCase)` returning `ProcessResult` without any order quantity.
|
|
61
|
+
- `calculate_planning()` no longer creates a fictitious one-bar study to resolve billet, cuts, configuration or table occupancy.
|
|
62
|
+
- Adds `PlanningResult.process` as the canonical quantity-free process reference.
|
|
63
|
+
- Keeps `StudyCase` planning support only as a compatibility bridge and emits a deprecation warning.
|
|
64
|
+
- Adds direct planning outputs for optimized billet length, table occupancy and fixed/total scrap.
|
|
65
|
+
- Time-window planning creates a full order `CalculationResult` only when at least one complete billet is actually planned.
|
|
66
|
+
- Keeps JSON schema `1.1`; no persistence-schema change is required.
|
|
67
|
+
- Does not change the existing physical/productivity mathematics for equivalent valid inputs.
|
|
68
|
+
|
|
69
|
+
## 0.13.1
|
|
70
|
+
|
|
71
|
+
- Hardens input validation and public interfaces.
|
|
72
|
+
- Restricts nominal press size to strict whole-inch values from 6 to 16.
|
|
73
|
+
- Enforces configurable billet lengths of 100-3000 mm and runout tables of 10-100 m.
|
|
74
|
+
- Enforces finished cut lengths of 1000-15000 mm and rejects cuts longer than the table.
|
|
75
|
+
- Enforces exit/profile speed of 1-100 m/min and adds `ram_speed_mm_s` as an alternative process input.
|
|
76
|
+
- Rejects simultaneous exit-speed and ram-speed inputs as ambiguous; ram-input calculations derive and validate exit speed through volume constancy.
|
|
77
|
+
- Enforces technical dead time of 5-30 s and saw kerfs of 0 or 3-10 mm.
|
|
78
|
+
- Makes exits, manual cuts, requested bars, requested billets and nominal press size strict integers; rejects booleans, fractional counts and numeric strings.
|
|
79
|
+
- Rejects NaN, Infinity and non-finite intermediate values and writes strict JSON with schema `1.1`.
|
|
80
|
+
- Keeps schemas `0.2` through `1.0` readable, including migration of historical whole-valued float nominal press sizes.
|
|
81
|
+
- Hardens file/JSON errors into documented PX errors instead of predictable raw tracebacks.
|
|
82
|
+
- Replaces automatic cut enumeration with direct bounded billet-first integer limits to prevent resource-exhaustion inputs.
|
|
83
|
+
- Adds `geometry.exit_speed_m_min` and `process.speed_input_source` to the public result glossary (98 fields).
|
|
84
|
+
- Does not change the v0.13.0 calculation mathematics for inputs that remain valid under the new validation rules.
|
|
85
|
+
|
|
86
|
+
## 0.13.0
|
|
87
|
+
|
|
88
|
+
- Introduces dynamic multi-billet behaviour and the quantity-free planning architecture.
|
|
89
|
+
- Replaces the historical fixed `2_billets_1_profile` / `3_billets_1_profile` model with dynamic `k_billets_1_profile`.
|
|
90
|
+
- Implements billet-first optimisation: the longest valid billet is selected before `billets_per_pull` is derived from table length.
|
|
91
|
+
- Prevents the optimiser from shortening a billet merely to fit more billets on the runout table.
|
|
92
|
+
- Allows 4, 5 or more billets per continuous pull when geometry permits.
|
|
93
|
+
- Keeps `1_billet_2_profiles` and explicitly limits the supported model to a maximum of two sequential profiles per billet.
|
|
94
|
+
- Adds controlled `supported=False` diagnostics when a scenario would require 3+ profiles/billet.
|
|
95
|
+
- Counts multi-billet puller and final-saw losses per pull, including partial final pulls.
|
|
96
|
+
- Corrects multi-billet `cuts_ratio` to use cuts represented by the complete table-occupying pull.
|
|
97
|
+
- Adds `ProcessSpec` / `Process` and `PlanningCase` so process conditions can be defined independently from order quantity.
|
|
98
|
+
- Keeps `StudyCase` accepted by planning for backwards compatibility.
|
|
99
|
+
- Adds PlanningCase JSON load/save/validation and CLI planning support for quantity-free process files.
|
|
100
|
+
- Bumps current JSON write schema to `1.0`; schemas `0.2` through `0.9` remain readable.
|
|
101
|
+
- Expands the documented result glossary to 96 structured fields, including support status and pull-grouping fields.
|
|
102
|
+
- Adds regression coverage for dynamic k, billet-first behaviour, partial pulls, pull-level saw accounting, unsupported 3+ profiles/billet and PlanningCase workflows.
|
|
103
|
+
|
|
104
|
+
## 0.12.0
|
|
105
|
+
|
|
106
|
+
- Adds the first operational production-planning layer for daily and short/medium batch calculations.
|
|
107
|
+
- Adds `PlanningRequest`, `PlanningResult`, `calculate_planning()` and `format_planning_result()`.
|
|
108
|
+
- Supports explicit planning targets in bars, kg, metres and exact billet count.
|
|
109
|
+
- Supports available press-time windows in minutes or hours and returns the maximum number of complete billets that fit.
|
|
110
|
+
- Reports planned billets, bars, good metres, good kg, used time, remaining time and extra time required for the next billet.
|
|
111
|
+
- Planning uses complete billets only; fractional billets are never created.
|
|
112
|
+
- Planning does not apply the annual `supplement_10_pct` implicitly; an explicit operational target remains exact.
|
|
113
|
+
- Adds CLI `pyextrusion plan` for bars, kg, metres, billets, minutes and hours.
|
|
114
|
+
- Adds documented `PX1011 InvalidPlanningRequest`.
|
|
115
|
+
- Adds planning documentation and a runnable public planning example.
|
|
116
|
+
- Keeps the existing core formulas, JSON schema 0.9, provenance rules and all 0.11.2 calculation behavior unchanged.
|
|
117
|
+
|
|
118
|
+
## 0.11.2
|
|
119
|
+
|
|
120
|
+
- Fixes `1_billet_2_profiles` validation so an individual sequential pull cannot exceed `table_length_m`; sequential pulls do not occupy the table simultaneously, but each complete pull must fit.
|
|
121
|
+
- Adds a regression test covering manual cuts that exceed the runout-table length.
|
|
122
|
+
- Corrects the integration guides to state that JSON schema `0.9` is the current write format.
|
|
123
|
+
- Clarifies README and documentation availability for wheel-only users.
|
|
124
|
+
- No other calculation formulas, defaults, JSON schema, provenance rules, or productivity-index behaviour changed from v0.11.1.
|
|
125
|
+
|
|
126
|
+
## 0.11.1
|
|
127
|
+
|
|
128
|
+
- Fixes press JSON provenance round-tripping: inferred press values remain identified as inferred after save/load instead of being relabelled as `user_value`.
|
|
129
|
+
- Adds `_provenance` metadata to serialized press objects while keeping `Press.to_dict()` configuration-only for API compatibility.
|
|
130
|
+
- Preserves provenance in standalone press JSON and embedded study JSON.
|
|
131
|
+
- Bumps JSON output schema to `0.9`; schemas `0.2` through `0.8` remain readable.
|
|
132
|
+
- No calculation formulas, industrial defaults, productivity-index behaviour or complexity behaviour changed from v0.11.0.
|
|
133
|
+
|
|
134
|
+
## 0.11.0
|
|
135
|
+
|
|
136
|
+
- Refactors the calculation engine into the generic direct-extrusion model used by current PyExtrusion.
|
|
137
|
+
- Reframes the engine as a generic model for **direct aluminium extrusion presses**.
|
|
138
|
+
- Adds `nominal_size_in` press definition and documented inference of billet diameter, container diameter, billet-length limits and reference productivity.
|
|
139
|
+
- Makes `table_length_m` mandatory and keeps `press_force_t` optional.
|
|
140
|
+
- Changes extrusion ratio and ram-speed calculations to use **container-bore area**.
|
|
141
|
+
- Keeps actual billet area for billet mass, kg/mm and billet-length calculations.
|
|
142
|
+
- Retires the historical billet-area override from active v2.0 calculations; actual billet diameter now always defines billet area, while `billet_kg_per_mm_override` remains available for measured mass coefficients.
|
|
143
|
+
- Changes default butt discard to 15 mm for solid/plate and 20 mm for hollow/tubular.
|
|
144
|
+
- Changes default saw kerfs to 5/5/5 mm; `0` kerf explicitly means no material loss for that saw operation.
|
|
145
|
+
- Keeps 15 s as default dead time.
|
|
146
|
+
- Keeps complexity levels `normal`/`medium`/`high` at 3/5/7%, with `normal` default.
|
|
147
|
+
- Keeps the documented startup-scrap rule.
|
|
148
|
+
- Adds a reference-productivity heuristic based on nominal press size, with user value taking priority.
|
|
149
|
+
- Adds percentage-based productivity deficit and penalty.
|
|
150
|
+
- Adds an orientative 0-100 productivity index and rating.
|
|
151
|
+
- Adds mathematical and upward-rounded recommended billet lengths.
|
|
152
|
+
- Adds container/billet area separation to structured results.
|
|
153
|
+
- Suppresses downstream geometry/productivity warnings when a case is completely non-viable with `cuts=0`.
|
|
154
|
+
- Removes the old recommended minimum linear-weight concept from the public configuration.
|
|
155
|
+
- New JSON output schema is `0.8`; schemas `0.2` through `0.7` remain readable.
|
|
156
|
+
- New serialised press names use `press_force_t`, `billet_min_length_mm`, `billet_max_length_mm`, `density_kg_m3`, and `billet_kg_per_mm_override` while historical aliases remain accepted.
|
|
157
|
+
- Public structured field glossary expands to 82 fields.
|
|
158
|
+
- Direct calculation validation now raises the documented PX1001/PX1002/PX1003 error classes for press/profile/production input failures.
|
|
159
|
+
|
|
160
|
+
## 0.10.0
|
|
161
|
+
|
|
162
|
+
- Added RE status thresholds: solid/plate low <25, OK 25-85, high >85; hollow/tubular low <15, OK 15-75, high >75.
|
|
163
|
+
- Added the explicit optional `supplement_10_pct` demand flag, disabled by default.
|
|
164
|
+
- Applied the 10% supplement before annual-demand normalization.
|
|
165
|
+
- Removed the old automatic warning based on `extra_pct < 10%`.
|
|
166
|
+
- Redefined `extra_pct` as complete-billet rounding excess relative to the effective bar target.
|
|
167
|
+
- Added base/effective demand trace fields and CLI supplement overrides.
|
|
168
|
+
- Bumped JSON schema output to 0.7 while retaining backward compatibility with 0.2-0.6.
|
|
169
|
+
|
|
170
|
+
## 0.9.0
|
|
171
|
+
|
|
172
|
+
- Corrected bars per billet using `cuts × exits × profiles_per_billet`.
|
|
173
|
+
- Corrected billet count and manufactured quantity for the double-profile configuration.
|
|
174
|
+
- Corrected front-scrap, puller-saw and final-saw losses to account for two complete pulls per billet.
|
|
175
|
+
- Kept butt-discard and billet-saw losses once per billet.
|
|
176
|
+
- Kept the extra dead-time event per billet for `1_billet_2_profiles`.
|
|
177
|
+
- Added `production.profiles_per_billet` to the public result contract.
|
|
178
|
+
- Made `profile_type` mandatory for new profiles/cases.
|
|
179
|
+
- Added public aliases: `solid`, `plate`, `hollow`, `tubular`; aliases normalize to `solid`/`hollow` internally.
|
|
180
|
+
- Missing or invalid profile types raise `PX1002` with the valid values listed.
|
|
181
|
+
- Bumped current JSON schema version to `0.6`.
|
|
182
|
+
|
|
183
|
+
## 0.8.0
|
|
184
|
+
|
|
185
|
+
- Added the official result-field glossary with exact path, unit, data type and description.
|
|
186
|
+
- Added `FieldInfo`, `FIELD_GLOSSARY`, `list_fields()` and `describe_field()`.
|
|
187
|
+
- Added CLI `pyextrusion fields` and `pyextrusion field <path>`.
|
|
188
|
+
- Added immutable StudyCase derivation helpers: `with_profile()`, `with_production()` and flat `replace()`.
|
|
189
|
+
- Added stable documented errors `PX1001` through `PX1010` and public `PyExtrusionError` subclasses.
|
|
190
|
+
- Added `ERROR_CATALOG`, `get_error_info()` and `list_error_info()`.
|
|
191
|
+
- Added CLI `pyextrusion errors` and `pyextrusion error <code>`.
|
|
192
|
+
- Validation messages now include relevant PX codes for errors.
|
|
193
|
+
- CLI scalar output keeps Python numeric precision by design.
|
|
194
|
+
|
|
195
|
+
## 0.7.0
|
|
196
|
+
|
|
197
|
+
- Added structured result blocks for geometry, billet, production, scrap, productivity, timing and process trace.
|
|
198
|
+
- Exposed the complete scrap breakdown.
|
|
199
|
+
- Added `CalculationResult.value()`, `select()`, `section()`, `field_paths()` and dotted-path `__getitem__`.
|
|
200
|
+
- Added CLI `--field`, `--fields` and `--section` result selectors.
|
|
201
|
+
- Added butt-discard and front-scrap provenance.
|
|
202
|
+
- Added documented warnings for low cuts ratio, fixed scrap above 12%, and <=3 bars/billet.
|
|
203
|
+
- Preserved legacy flat result attributes for backwards compatibility.
|
|
204
|
+
|
|
205
|
+
## 0.6.0
|
|
206
|
+
|
|
207
|
+
- Adopted the Apache License 2.0 as the official project license.
|
|
208
|
+
- Added `LICENSE`, `NOTICE`, `AUTHORS.md` and `CITATION.cff`.
|
|
209
|
+
- Established **Enrique Calvo Ordonez** as project creator and lead author.
|
|
210
|
+
- Established **https://pyextrusion.com** as the official project website.
|
|
211
|
+
- Added `pyextrusion info` for version, description, author, license and website.
|
|
212
|
+
|
|
213
|
+
## 0.5.0
|
|
214
|
+
|
|
215
|
+
- Added annual demand normalization for kg/year, metres/year and bars/year.
|
|
216
|
+
- Added `AnnualDemandSpec`, `NormalizedAnnualDemand` and `AnnualCalculationResult`.
|
|
217
|
+
- Added `calculate_annual_demand()` and `normalize_annual_demand()`.
|
|
218
|
+
- Added annual demand JSON load/save helpers.
|
|
219
|
+
- Added `pyextrusion annual` CLI command.
|
|
220
|
+
- Added MkDocs + Material documentation source.
|
|
221
|
+
|
|
222
|
+
## 0.4.0
|
|
223
|
+
|
|
224
|
+
- Added `ProductionSpec` / `Production` and reusable `StudyCase`.
|
|
225
|
+
- Added `calculate_case()` to run one case on any press.
|
|
226
|
+
- Added `compare_presses()` and `ComparisonResult`.
|
|
227
|
+
- Added standalone press and case JSON persistence.
|
|
228
|
+
- Added human-readable press, result and comparison reports.
|
|
229
|
+
- CLI gained external press files and multi-press comparison.
|
|
230
|
+
|
|
231
|
+
## 0.3.0
|
|
232
|
+
|
|
233
|
+
- Added all four documented billet/profile configurations.
|
|
234
|
+
- Added multi-billet front-scrap recalculation.
|
|
235
|
+
- Added special dead-time rule for `1_billet_2_profiles`.
|
|
236
|
+
- Separated total extruded/configuration length from runout-table occupancy.
|
|
237
|
+
|
|
238
|
+
## 0.2.0
|
|
239
|
+
|
|
240
|
+
- Added `Press` and `Profile` friendly aliases.
|
|
241
|
+
- Added `calculate_simple()`.
|
|
242
|
+
- Added JSON schema support and validation.
|
|
243
|
+
- Added structured validation messages.
|
|
244
|
+
- Profile section inference uses press-configured aluminium density.
|
|
245
|
+
|
|
246
|
+
## 0.1.0
|
|
247
|
+
|
|
248
|
+
- Initial calculation-core development preview.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use PyExtrusion in research, technical publications, or documented engineering work, please cite the software using the metadata below."
|
|
3
|
+
title: "PyExtrusion"
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: "Calvo Ordonez"
|
|
7
|
+
given-names: "Enrique"
|
|
8
|
+
version: 0.17.0
|
|
9
|
+
date-released: 2026-09-26
|
|
10
|
+
url: "https://pyextrusion.com"
|
|
11
|
+
license: Apache-2.0
|
|
12
|
+
abstract: "PyExtrusion is a configurable Python toolkit for deterministic aluminium extrusion engineering calculations."
|
|
13
|
+
keywords:
|
|
14
|
+
- aluminium extrusion
|
|
15
|
+
- aluminum extrusion
|
|
16
|
+
- manufacturing
|
|
17
|
+
- engineering calculations
|
|
18
|
+
- Python
|