phoneme-entropy 0.1.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.
@@ -0,0 +1,44 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install (with dev extras)
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ pip install -e ".[dev]"
28
+
29
+ - name: Run tests
30
+ run: pytest -q
31
+
32
+ build:
33
+ # Confirm the sdist/wheel build and pass metadata checks on every push.
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: actions/setup-python@v5
38
+ with:
39
+ python-version: "3.12"
40
+ - name: Build & check distribution
41
+ run: |
42
+ python -m pip install --upgrade pip build twine
43
+ python -m build
44
+ twine check dist/*
@@ -0,0 +1,86 @@
1
+ name: Release
2
+
3
+ # Two-stage publishing, both via PyPI Trusted Publishing (OIDC — no stored tokens):
4
+ #
5
+ # * push a tag v* -> build + publish to TestPyPI (release candidate)
6
+ # * publish a GitHub Release -> build + publish to PyPI (promote to real PyPI)
7
+ # * manual dispatch -> build + publish to TestPyPI
8
+ #
9
+ # Typical flow:
10
+ # git tag v0.1.0 && git push origin v0.1.0 # -> lands on TestPyPI, smoke-test it
11
+ # # then, on GitHub, cut a Release for tag v0.1.0 -> lands on PyPI
12
+ #
13
+ # One-time setup (per index) at https://<index>/manage/account/publishing/ :
14
+ # add a pending publisher -> owner: suchirsalhan, repo: phoneme,
15
+ # workflow: release.yml, environment: testpypi (on TestPyPI) / pypi (on PyPI).
16
+ # Also create matching GitHub environments (repo Settings -> Environments):
17
+ # `testpypi` and `pypi`.
18
+ #
19
+ # Prefer API tokens? Remove the environment/permissions blocks, add repo secrets
20
+ # TEST_PYPI_API_TOKEN / PYPI_API_TOKEN, and set `password:` on the publish steps.
21
+
22
+ on:
23
+ push:
24
+ tags: ["v*"]
25
+ release:
26
+ types: [published]
27
+ workflow_dispatch:
28
+
29
+ jobs:
30
+ build:
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ - uses: actions/setup-python@v5
35
+ with:
36
+ python-version: "3.12"
37
+ - name: Build sdist + wheel
38
+ run: |
39
+ python -m pip install --upgrade pip build twine
40
+ python -m build
41
+ twine check dist/*
42
+ - name: Upload dist artifact
43
+ uses: actions/upload-artifact@v4
44
+ with:
45
+ name: dist
46
+ path: dist/
47
+
48
+ publish-testpypi:
49
+ # Release candidates: tag pushes and manual runs go to TestPyPI.
50
+ needs: build
51
+ if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
52
+ runs-on: ubuntu-latest
53
+ environment: testpypi
54
+ permissions:
55
+ id-token: write # required for Trusted Publishing (OIDC)
56
+ steps:
57
+ - name: Download dist artifact
58
+ uses: actions/download-artifact@v4
59
+ with:
60
+ name: dist
61
+ path: dist/
62
+ - name: Publish to TestPyPI
63
+ uses: pypa/gh-action-pypi-publish@release/v1
64
+ with:
65
+ repository-url: https://test.pypi.org/legacy/
66
+ # password: ${{ secrets.TEST_PYPI_API_TOKEN }} # token auth alternative
67
+
68
+ publish-pypi:
69
+ # Promotion: publishing a GitHub Release ships to real PyPI.
70
+ needs: build
71
+ if: github.event_name == 'release'
72
+ runs-on: ubuntu-latest
73
+ environment: pypi
74
+ permissions:
75
+ id-token: write # required for Trusted Publishing (OIDC)
76
+ steps:
77
+ - name: Download dist artifact
78
+ uses: actions/download-artifact@v4
79
+ with:
80
+ name: dist
81
+ path: dist/
82
+ - name: Publish to PyPI
83
+ uses: pypa/gh-action-pypi-publish@release/v1
84
+ # repository-url defaults to https://upload.pypi.org/legacy/ (real PyPI)
85
+ # with:
86
+ # password: ${{ secrets.PYPI_API_TOKEN }} # token auth alternative
@@ -0,0 +1,45 @@
1
+ # --- Local, developer-only instructions (never commit) ---
2
+ LOCAL_INSTRUCTIONS.md
3
+
4
+ # --- Python ---
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ *.egg-info/
9
+ .eggs/
10
+ build/
11
+ dist/
12
+ wheels/
13
+ *.egg
14
+ .installed.cfg
15
+
16
+ # --- Virtual environments ---
17
+ .venv/
18
+ venv/
19
+ env/
20
+ ENV/
21
+ .python-version
22
+
23
+ # --- Testing / coverage ---
24
+ .pytest_cache/
25
+ .coverage
26
+ .coverage.*
27
+ htmlcov/
28
+ .tox/
29
+ .ruff_cache/
30
+ .mypy_cache/
31
+
32
+ # --- Jupyter ---
33
+ .ipynb_checkpoints/
34
+
35
+ # --- Hugging Face / data caches (do not commit private data) ---
36
+ .cache/
37
+ *.arrow
38
+ hf_cache/
39
+ data_cache/
40
+
41
+ # --- OS / editor ---
42
+ .DS_Store
43
+ .idea/
44
+ .vscode/
45
+ *.swp
@@ -0,0 +1,21 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below, along with the upstream entropy-estimators project."
3
+ title: "phoneme-entropy: phoneme-level entropy, informativity, and maximum-entropy tools"
4
+ authors:
5
+ - family-names: "Moscoso del Prado Martín"
6
+ given-names: Fermín
7
+ - family-names: Salhan
8
+ given-names: Suchir
9
+ type: software
10
+ license: MIT
11
+ repository-code: "https://github.com/suchirsalhan/phoneme"
12
+ version: "0.1.0"
13
+ references:
14
+ - type: software
15
+ title: "entropy-estimators"
16
+ authors:
17
+ - family-names: "Moscoso del Prado Martín"
18
+ given-names: "Fermín"
19
+ repository-code: "https://github.com/fermosc24/entropy-estimators"
20
+ license: MIT
21
+ year: 2025
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fermín Moscoso del Prado Martín & Suchir Salhan
4
+
5
+ Portions of this library extend and depend on the `entropy-estimators` project
6
+ (https://github.com/fermosc24/entropy-estimators), Copyright (c) 2025 Fermín
7
+ Moscoso del Prado Martín, also distributed under the MIT License.
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: phoneme-entropy
3
+ Version: 0.1.0
4
+ Summary: Phoneme-level entropy, informativity, and maximum-entropy tools, extending entropy-estimators.
5
+ Project-URL: Homepage, https://github.com/suchirsalhan/phoneme
6
+ Project-URL: Repository, https://github.com/suchirsalhan/phoneme
7
+ Project-URL: Upstream (entropy-estimators), https://github.com/fermosc24/entropy-estimators
8
+ Author: Fermín Moscoso del Prado Martín
9
+ Author-email: Suchir Salhan <suchirsalhan@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: dirichlet,entropy,information theory,maximum entropy,phonemes,phonology,psycholinguistics
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: mpmath>=1.2
20
+ Requires-Dist: numpy>=1.21
21
+ Requires-Dist: pandas>=1.3
22
+ Requires-Dist: scipy>=1.7
23
+ Provides-Extra: data
24
+ Requires-Dist: datasets>=2.14; extra == 'data'
25
+ Requires-Dist: huggingface-hub>=0.16; extra == 'data'
26
+ Provides-Extra: dev
27
+ Requires-Dist: jupyter; extra == 'dev'
28
+ Requires-Dist: matplotlib>=3.4; extra == 'dev'
29
+ Requires-Dist: pytest>=7.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.1; extra == 'dev'
31
+ Requires-Dist: seaborn>=0.11; extra == 'dev'
32
+ Provides-Extra: plot
33
+ Requires-Dist: matplotlib>=3.4; extra == 'plot'
34
+ Requires-Dist: seaborn>=0.11; extra == 'plot'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # phoneme-entropy
38
+
39
+ Phoneme-level entropy, informativity, and maximum-entropy tools. A standalone
40
+ library that bundles the entropy estimators from
41
+ [`entropy-estimators`](https://github.com/fermosc24/entropy-estimators) (MIT,
42
+ © 2025 fermosc24) and adds phonology-oriented analysis utilities.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install phoneme-entropy
48
+ # or, from source:
49
+ pip install .
50
+ ```
51
+
52
+ Optional extras: `pip install "phoneme-entropy[plot]"` (matplotlib/seaborn for
53
+ `plot_ranks`), `pip install "phoneme-entropy[data]"` (Hugging Face `datasets`).
54
+
55
+ ## Usage
56
+
57
+ ```python
58
+ from collections import defaultdict
59
+ import numpy as np, pandas as pd
60
+ from phoneme_entropy import (
61
+ Entropy, estimate_alpha_entropy,
62
+ segment_informativity, phoneme_prefix_entropy,
63
+ compute_maxent_from_matrix,
64
+ )
65
+ from phoneme_entropy.data import load_sample
66
+
67
+ sample = load_sample() # bundled CELEX sample (Word = space-separated phonemes)
68
+
69
+ # Per-phoneme next-phoneme surprisal and prefix entropy
70
+ surprisal = segment_informativity(sample["Word"])
71
+ prefix_H = phoneme_prefix_entropy(sample["Word"])
72
+
73
+ # Symmetric-Dirichlet concentration implied by an entropy estimate
74
+ phon = defaultdict(int)
75
+ for w in sample["Word"]:
76
+ for p in w.split(): phon[p] += 1
77
+ alpha, se = estimate_alpha_entropy(list(phon.values()), n_boot=0)
78
+ ```
79
+
80
+ See [`examples/quickstart.py`](examples/quickstart.py) and
81
+ [`examples/usage_example.ipynb`](examples/usage_example.ipynb) for a full
82
+ walkthrough.
83
+
84
+ ### Data
85
+
86
+ A small CELEX-derived sample ships with the package (`load_sample()`). The full
87
+ sample is mirrored as a **gated, permissions-only** Hugging Face dataset
88
+ [`suchirsalhan/celex-en-phoneme-sample`](https://huggingface.co/datasets/suchirsalhan/celex-en-phoneme-sample)
89
+ — access is granted to holders of a valid CELEX/LDC license
90
+ ([LDC96L14](https://catalog.ldc.upenn.edu/docs/LDC96L14/celex.readme.html)):
91
+
92
+ ```python
93
+ from phoneme_entropy.data import load_hf_dataset
94
+ ds = load_hf_dataset(split="train") # after your access request is approved
95
+ ```
96
+
97
+ ## API
98
+
99
+ | Import | Purpose |
100
+ | --- | --- |
101
+ | `Entropy`, `FreqShrink`, `JS_KullbackLeibler`, `JS_JensenShannon` | Entropy / divergence estimators (MLE, JSE, Chao-Shen, CWJ, NBRS, NSB), vendored. |
102
+ | `estimate_alpha_entropy`, `dirichlet_entropy`, `plot_ranks` | Symmetric-Dirichlet inference and rank/probability curves. |
103
+ | `segment_informativity`, `phoneme_prefix_entropy` | Segmental informativity over phoneme strings. |
104
+ | `compute_maxent_from_matrix` | Maximum-entropy fit to feature expectations. |
105
+ | `phoneme_entropy.data.load_sample`, `load_hf_dataset` | Data loaders. |
106
+
107
+ ## License
108
+
109
+ MIT — see [`LICENSE`](LICENSE). Bundles third-party code; see
110
+ [`THIRD_PARTY_LICENSES`](THIRD_PARTY_LICENSES). Please cite via
111
+ [`CITATION.cff`](CITATION.cff).
@@ -0,0 +1,75 @@
1
+ # phoneme-entropy
2
+
3
+ Phoneme-level entropy, informativity, and maximum-entropy tools. A standalone
4
+ library that bundles the entropy estimators from
5
+ [`entropy-estimators`](https://github.com/fermosc24/entropy-estimators) (MIT,
6
+ © 2025 fermosc24) and adds phonology-oriented analysis utilities.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install phoneme-entropy
12
+ # or, from source:
13
+ pip install .
14
+ ```
15
+
16
+ Optional extras: `pip install "phoneme-entropy[plot]"` (matplotlib/seaborn for
17
+ `plot_ranks`), `pip install "phoneme-entropy[data]"` (Hugging Face `datasets`).
18
+
19
+ ## Usage
20
+
21
+ ```python
22
+ from collections import defaultdict
23
+ import numpy as np, pandas as pd
24
+ from phoneme_entropy import (
25
+ Entropy, estimate_alpha_entropy,
26
+ segment_informativity, phoneme_prefix_entropy,
27
+ compute_maxent_from_matrix,
28
+ )
29
+ from phoneme_entropy.data import load_sample
30
+
31
+ sample = load_sample() # bundled CELEX sample (Word = space-separated phonemes)
32
+
33
+ # Per-phoneme next-phoneme surprisal and prefix entropy
34
+ surprisal = segment_informativity(sample["Word"])
35
+ prefix_H = phoneme_prefix_entropy(sample["Word"])
36
+
37
+ # Symmetric-Dirichlet concentration implied by an entropy estimate
38
+ phon = defaultdict(int)
39
+ for w in sample["Word"]:
40
+ for p in w.split(): phon[p] += 1
41
+ alpha, se = estimate_alpha_entropy(list(phon.values()), n_boot=0)
42
+ ```
43
+
44
+ See [`examples/quickstart.py`](examples/quickstart.py) and
45
+ [`examples/usage_example.ipynb`](examples/usage_example.ipynb) for a full
46
+ walkthrough.
47
+
48
+ ### Data
49
+
50
+ A small CELEX-derived sample ships with the package (`load_sample()`). The full
51
+ sample is mirrored as a **gated, permissions-only** Hugging Face dataset
52
+ [`suchirsalhan/celex-en-phoneme-sample`](https://huggingface.co/datasets/suchirsalhan/celex-en-phoneme-sample)
53
+ — access is granted to holders of a valid CELEX/LDC license
54
+ ([LDC96L14](https://catalog.ldc.upenn.edu/docs/LDC96L14/celex.readme.html)):
55
+
56
+ ```python
57
+ from phoneme_entropy.data import load_hf_dataset
58
+ ds = load_hf_dataset(split="train") # after your access request is approved
59
+ ```
60
+
61
+ ## API
62
+
63
+ | Import | Purpose |
64
+ | --- | --- |
65
+ | `Entropy`, `FreqShrink`, `JS_KullbackLeibler`, `JS_JensenShannon` | Entropy / divergence estimators (MLE, JSE, Chao-Shen, CWJ, NBRS, NSB), vendored. |
66
+ | `estimate_alpha_entropy`, `dirichlet_entropy`, `plot_ranks` | Symmetric-Dirichlet inference and rank/probability curves. |
67
+ | `segment_informativity`, `phoneme_prefix_entropy` | Segmental informativity over phoneme strings. |
68
+ | `compute_maxent_from_matrix` | Maximum-entropy fit to feature expectations. |
69
+ | `phoneme_entropy.data.load_sample`, `load_hf_dataset` | Data loaders. |
70
+
71
+ ## License
72
+
73
+ MIT — see [`LICENSE`](LICENSE). Bundles third-party code; see
74
+ [`THIRD_PARTY_LICENSES`](THIRD_PARTY_LICENSES). Please cite via
75
+ [`CITATION.cff`](CITATION.cff).
@@ -0,0 +1,37 @@
1
+ This distribution bundles third-party code. Their licenses are reproduced below.
2
+
3
+ ===============================================================================
4
+ entropy-estimators — https://github.com/fermosc24/entropy-estimators
5
+ Vendored into: src/phoneme_entropy/estimators.py
6
+ ===============================================================================
7
+
8
+ MIT License
9
+
10
+ Copyright (c) 2025 fermosc24
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+
30
+ -------------------------------------------------------------------------------
31
+ The NSB estimator portion of the vendored file is itself derived from:
32
+ * nsb_entropy.py by Sungho Hong (OIST, 2011), updated to Python 3 by
33
+ Charlie Strauss (LANL, 2019), based on Mathematica functions by
34
+ Christian Mendl. See http://nsb-entropy.sourceforge.net and
35
+ Nemenman, Shafee & Bialek, "Entropy and Inference, Revisited",
36
+ arXiv:physics/0108025.
37
+ -------------------------------------------------------------------------------
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env python3
2
+ """End-to-end quickstart for phoneme-entropy.
3
+
4
+ Reproduces the workflow from the original tutorial notebook using the packaged
5
+ API and the bundled CELEX sample. Run it after installing the package:
6
+
7
+ pip install -e ".[dev]"
8
+ python examples/quickstart.py
9
+
10
+ It prints intermediate results and (if matplotlib is available) draws the
11
+ predicted-vs-observed rank plot and the max-ent fit.
12
+ """
13
+
14
+ from collections import defaultdict
15
+
16
+ import numpy as np
17
+ import pandas as pd
18
+
19
+ from phoneme_entropy import (
20
+ compute_maxent_from_matrix,
21
+ estimate_alpha_entropy,
22
+ phoneme_prefix_entropy,
23
+ segment_informativity,
24
+ )
25
+ from phoneme_entropy.data import load_sample
26
+
27
+
28
+ def main() -> None:
29
+ # 1. Load the bundled CELEX English sample. "Word" holds space-separated
30
+ # phonemes; "Frequency" holds corpus frequencies.
31
+ sample = load_sample()
32
+ print(f"Loaded {len(sample)} words. Columns: {list(sample.columns)}")
33
+
34
+ # 2. Phoneme unigram frequencies.
35
+ phonfreqs = defaultdict(int)
36
+ for word in sample["Word"]:
37
+ for phoneme in word.split():
38
+ phonfreqs[phoneme] += 1
39
+ phonfreqs = pd.DataFrame(phonfreqs.items(), columns=["Phoneme", "Frequency"])
40
+ print(f"Inventory size (V): {len(phonfreqs)}")
41
+
42
+ # 3. Symmetric-Dirichlet concentration implied by the phoneme distribution.
43
+ # n_boot=0 -> deterministic point estimate (no bootstrap standard error).
44
+ alpha, se = estimate_alpha_entropy(phonfreqs["Frequency"], n_boot=0)
45
+ print(f"Estimated Dirichlet alpha: {alpha:.4f}")
46
+
47
+ # 4. Per-phoneme surprisal (next-phoneme informativity) and prefix entropy.
48
+ surprisal = segment_informativity(sample["Word"])
49
+ prefix_entropy = phoneme_prefix_entropy(sample["Word"])
50
+
51
+ # 5. Merge, form frequency-weighted target expectations, fit max-ent.
52
+ merged = phonfreqs.merge(surprisal, on="Phoneme").merge(prefix_entropy, on="Phoneme")
53
+ p = merged["Frequency"] / merged["Frequency"].sum()
54
+ targets = np.array(
55
+ [np.sum(p * merged["Surprisal"]), np.sum(p * merged["Entropy"])]
56
+ )
57
+ F = merged[["Surprisal", "Entropy"]].to_numpy()
58
+ probs, lambdas = compute_maxent_from_matrix(F, targets)
59
+ print(f"Max-ent Lagrange multipliers (lambda): {lambdas}")
60
+
61
+ # 6. Optional plots (only if matplotlib is installed).
62
+ try:
63
+ import matplotlib.pyplot as plt
64
+
65
+ from phoneme_entropy.dirichlet import plot_ranks
66
+
67
+ plot_ranks(phonfreqs["Frequency"], types="Reversed", loglog=False)
68
+ plt.figure()
69
+ plt.scatter(np.log(p), np.log(probs))
70
+ plt.xlabel("(log) observed probability")
71
+ plt.ylabel("(log) predicted probability")
72
+ plt.tight_layout()
73
+ plt.show()
74
+ except ImportError:
75
+ print("matplotlib not installed; skipping plots. Install with '.[plot]'.")
76
+
77
+
78
+ if __name__ == "__main__":
79
+ main()