cpp-pd-code-simplify-interface 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,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: cpp-pd-code-simplify-interface
3
+ Version: 0.1.0
4
+ Summary: Python interface for cpp-pd-code-simplify with runtime C++ compilation.
5
+ License-Expression: MIT
6
+ Author: GGN_2015
7
+ Author-email: neko@jlulug.org
8
+ Requires-Python: >=3.10
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Dist: cpp-simple-interface (>=0.1.2)
16
+ Project-URL: Documentation, https://github.com/GGN-2015/cpp-pd-code-simplify/blob/main/docs/python-interface.md
17
+ Project-URL: Homepage, https://github.com/GGN-2015/cpp-pd-code-simplify
18
+ Project-URL: Repository, https://github.com/GGN-2015/cpp-pd-code-simplify
19
+ Description-Content-Type: text/markdown
20
+
21
+ # cpp-pd-code-simplify-interface
22
+
23
+ `cpp-pd-code-simplify-interface` is a Python package for calling the
24
+ `cpp-pd-code-simplify` C++ implementation from Python.
25
+
26
+ The package is designed for PyPI distribution:
27
+
28
+ ```sh
29
+ pip install cpp-pd-code-simplify-interface
30
+ ```
31
+
32
+ It ships the C++ source code inside the wheel and source distribution. On first
33
+ use, the package compiles a cached local dynamic library through
34
+ `cpp-simple-interface`; later calls reuse that library through `ctypes`. A C++14
35
+ compiler compatible with `g++` must be available at runtime.
36
+
37
+ Calls use the C++ library's default preprocessing pipeline: R1-move removal
38
+ followed by nugatory-crossing removal.
39
+
40
+ ## Example
41
+
42
+ ```python
43
+ import cpp_pd_code_simplify_interface as simplify
44
+
45
+ pd_code = "PD[X[1,5,2,4],X[3,1,4,6],X[5,3,6,2]]"
46
+ result = simplify.simplify(pd_code)
47
+ print(result["simplification_found"])
48
+ ```
49
+
50
+ Batch use:
51
+
52
+ ```python
53
+ results = simplify.simplify_many([pd_code, "PD[]"])
54
+ ```
55
+
56
+ To select a compiler:
57
+
58
+ ```sh
59
+ CXX=clang++ python your_script.py
60
+ ```
61
+
62
+ Windows PowerShell:
63
+
64
+ ```powershell
65
+ $env:CXX = "C:\path\to\g++.exe"
66
+ python your_script.py
67
+ ```
68
+
69
+ On Windows, use a compiler whose target architecture matches Python. A 64-bit
70
+ Python process needs a 64-bit compiler target.
71
+
72
+ Command-line use also supports multi-line PD-code files:
73
+
74
+ ```sh
75
+ python -m cpp_pd_code_simplify_interface --pd-file inputs.pd --max-paths 100
76
+ ```
77
+
78
+ ## Build And Publish
79
+
80
+ From this directory:
81
+
82
+ ```sh
83
+ poetry build
84
+ poetry publish
85
+ ```
86
+
87
+ Use `poetry publish --build` to build and upload in one command.
88
+
89
+ For local testing:
90
+
91
+ ```sh
92
+ poetry run python -m cpp_pd_code_simplify_interface "PD[]"
93
+ ```
94
+
@@ -0,0 +1,73 @@
1
+ # cpp-pd-code-simplify-interface
2
+
3
+ `cpp-pd-code-simplify-interface` is a Python package for calling the
4
+ `cpp-pd-code-simplify` C++ implementation from Python.
5
+
6
+ The package is designed for PyPI distribution:
7
+
8
+ ```sh
9
+ pip install cpp-pd-code-simplify-interface
10
+ ```
11
+
12
+ It ships the C++ source code inside the wheel and source distribution. On first
13
+ use, the package compiles a cached local dynamic library through
14
+ `cpp-simple-interface`; later calls reuse that library through `ctypes`. A C++14
15
+ compiler compatible with `g++` must be available at runtime.
16
+
17
+ Calls use the C++ library's default preprocessing pipeline: R1-move removal
18
+ followed by nugatory-crossing removal.
19
+
20
+ ## Example
21
+
22
+ ```python
23
+ import cpp_pd_code_simplify_interface as simplify
24
+
25
+ pd_code = "PD[X[1,5,2,4],X[3,1,4,6],X[5,3,6,2]]"
26
+ result = simplify.simplify(pd_code)
27
+ print(result["simplification_found"])
28
+ ```
29
+
30
+ Batch use:
31
+
32
+ ```python
33
+ results = simplify.simplify_many([pd_code, "PD[]"])
34
+ ```
35
+
36
+ To select a compiler:
37
+
38
+ ```sh
39
+ CXX=clang++ python your_script.py
40
+ ```
41
+
42
+ Windows PowerShell:
43
+
44
+ ```powershell
45
+ $env:CXX = "C:\path\to\g++.exe"
46
+ python your_script.py
47
+ ```
48
+
49
+ On Windows, use a compiler whose target architecture matches Python. A 64-bit
50
+ Python process needs a 64-bit compiler target.
51
+
52
+ Command-line use also supports multi-line PD-code files:
53
+
54
+ ```sh
55
+ python -m cpp_pd_code_simplify_interface --pd-file inputs.pd --max-paths 100
56
+ ```
57
+
58
+ ## Build And Publish
59
+
60
+ From this directory:
61
+
62
+ ```sh
63
+ poetry build
64
+ poetry publish
65
+ ```
66
+
67
+ Use `poetry publish --build` to build and upload in one command.
68
+
69
+ For local testing:
70
+
71
+ ```sh
72
+ poetry run python -m cpp_pd_code_simplify_interface "PD[]"
73
+ ```
@@ -0,0 +1,228 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import hashlib
5
+ import io
6
+ import shutil
7
+ import tarfile
8
+ import tempfile
9
+ import zipfile
10
+ from pathlib import Path
11
+
12
+ from poetry.core.masonry import api as poetry_api
13
+
14
+
15
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
16
+ REPO_ROOT = PROJECT_ROOT.parents[1]
17
+
18
+ SOURCE_FILES = [
19
+ (
20
+ REPO_ROOT / "src" / "pdcode_simplify.cpp",
21
+ PROJECT_ROOT / "cpp_pd_code_simplify_interface" / "data" / "src" / "pdcode_simplify.cpp",
22
+ "cpp_pd_code_simplify_interface/data/src/pdcode_simplify.cpp",
23
+ ),
24
+ (
25
+ PROJECT_ROOT / "cpp_pd_code_simplify_interface" / "data" / "src" / "native_interface.cpp",
26
+ PROJECT_ROOT / "cpp_pd_code_simplify_interface" / "data" / "src" / "native_interface.cpp",
27
+ "cpp_pd_code_simplify_interface/data/src/native_interface.cpp",
28
+ ),
29
+ (
30
+ REPO_ROOT / "include" / "pdcode_simplify" / "pdcode_simplify.hpp",
31
+ PROJECT_ROOT
32
+ / "cpp_pd_code_simplify_interface"
33
+ / "data"
34
+ / "include"
35
+ / "pdcode_simplify"
36
+ / "pdcode_simplify.hpp",
37
+ "cpp_pd_code_simplify_interface/data/include/pdcode_simplify/pdcode_simplify.hpp",
38
+ ),
39
+ ]
40
+
41
+
42
+ def _sync_cpp_sources() -> None:
43
+ missing = []
44
+ for source, target, _ in SOURCE_FILES:
45
+ if source.exists():
46
+ if source.resolve() != target.resolve():
47
+ target.parent.mkdir(parents=True, exist_ok=True)
48
+ shutil.copyfile(source, target)
49
+ elif not target.exists():
50
+ missing.append((source, target))
51
+ if missing:
52
+ details = ", ".join(f"{source} or {target}" for source, target in missing)
53
+ raise FileNotFoundError(f"cpp-pd-code-simplify source files were not found: {details}")
54
+
55
+
56
+ def _clean_cpp_sources() -> None:
57
+ for source, target, _ in SOURCE_FILES:
58
+ if source.exists() and target.exists() and source.resolve() != target.resolve():
59
+ target.unlink()
60
+
61
+
62
+ def _source_payloads() -> dict[str, bytes]:
63
+ payloads = {}
64
+ for source, target, wheel_name in SOURCE_FILES:
65
+ if target.exists():
66
+ payloads[wheel_name] = target.read_bytes()
67
+ elif source.exists():
68
+ payloads[wheel_name] = source.read_bytes()
69
+ else:
70
+ raise FileNotFoundError(f"source file was not available for packaging: {wheel_name}")
71
+ return payloads
72
+
73
+
74
+ def _wheel_hash_and_size(data: bytes) -> tuple[str, str]:
75
+ digest = hashlib.sha256(data).digest()
76
+ encoded = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
77
+ return f"sha256={encoded}", str(len(data))
78
+
79
+
80
+ def _make_universal_wheel_metadata(data: bytes) -> bytes:
81
+ text = data.decode("utf-8")
82
+ lines = []
83
+ tag_written = False
84
+ root_written = False
85
+ for line in text.splitlines():
86
+ if line.startswith("Root-Is-Purelib:"):
87
+ lines.append("Root-Is-Purelib: true")
88
+ root_written = True
89
+ elif line.startswith("Tag:"):
90
+ if not tag_written:
91
+ lines.append("Tag: py3-none-any")
92
+ tag_written = True
93
+ else:
94
+ lines.append(line)
95
+ if not root_written:
96
+ lines.append("Root-Is-Purelib: true")
97
+ if not tag_written:
98
+ lines.append("Tag: py3-none-any")
99
+ return ("\n".join(lines) + "\n").encode("utf-8")
100
+
101
+
102
+ def _universal_wheel_path(wheel_path: Path, record_name: str) -> Path:
103
+ dist_info = record_name.split("/", 1)[0]
104
+ base = dist_info.removesuffix(".dist-info")
105
+ return wheel_path.with_name(f"{base}-py3-none-any.whl")
106
+
107
+
108
+ def _rewrite_wheel_with_sources(wheel_path: Path) -> Path:
109
+ payloads = _source_payloads()
110
+ payload_names = set(payloads)
111
+ rows: list[tuple[str, str, str]] = []
112
+
113
+ with zipfile.ZipFile(wheel_path, "r") as zin:
114
+ record_name = next(
115
+ (name for name in zin.namelist() if name.endswith(".dist-info/RECORD")),
116
+ None,
117
+ )
118
+ if record_name is None:
119
+ raise RuntimeError(f"wheel RECORD file not found in {wheel_path}")
120
+ output_path = _universal_wheel_path(wheel_path, record_name)
121
+ temp_path = output_path.with_name(output_path.name + ".tmp")
122
+
123
+ with zipfile.ZipFile(temp_path, "w", compression=zipfile.ZIP_DEFLATED) as zout:
124
+ wheel_metadata_name = record_name.removesuffix("RECORD") + "WHEEL"
125
+
126
+ for item in zin.infolist():
127
+ if item.filename in payload_names or item.filename == record_name:
128
+ continue
129
+ data = zin.read(item.filename)
130
+ if item.filename == wheel_metadata_name:
131
+ data = _make_universal_wheel_metadata(data)
132
+ zout.writestr(item, data)
133
+ if not item.is_dir():
134
+ digest, size = _wheel_hash_and_size(data)
135
+ rows.append((item.filename, digest, size))
136
+
137
+ for name, data in sorted(payloads.items()):
138
+ source_info = zipfile.ZipInfo(name, date_time=(2016, 1, 1, 0, 0, 0))
139
+ source_info.compress_type = zipfile.ZIP_DEFLATED
140
+ zout.writestr(source_info, data)
141
+ digest, size = _wheel_hash_and_size(data)
142
+ rows.append((name, digest, size))
143
+
144
+ rows.append((record_name, "", ""))
145
+ record_text = "".join(f"{path},{digest},{size}\n" for path, digest, size in rows)
146
+ record_info = zipfile.ZipInfo(record_name, date_time=(2016, 1, 1, 0, 0, 0))
147
+ record_info.compress_type = zipfile.ZIP_DEFLATED
148
+ zout.writestr(record_info, record_text.encode("utf-8"))
149
+
150
+ temp_path.replace(output_path)
151
+ if output_path != wheel_path and wheel_path.exists():
152
+ wheel_path.unlink()
153
+ return output_path
154
+
155
+
156
+ def _rewrite_sdist_with_sources(sdist_path: Path) -> None:
157
+ payloads = _source_payloads()
158
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".tar.gz")
159
+ temp_path = Path(temp_file.name)
160
+ temp_file.close()
161
+
162
+ try:
163
+ with tarfile.open(sdist_path, "r:gz") as tin, tarfile.open(temp_path, "w:gz") as tout:
164
+ names = tin.getnames()
165
+ if not names:
166
+ raise RuntimeError(f"sdist is empty: {sdist_path}")
167
+ root = names[0].split("/", 1)[0]
168
+ payload_names = {f"{root}/{name}" for name in payloads}
169
+
170
+ for member in tin.getmembers():
171
+ if member.name in payload_names:
172
+ continue
173
+ if member.isfile():
174
+ extracted = tin.extractfile(member)
175
+ if extracted is None:
176
+ raise RuntimeError(f"could not read {member.name} from {sdist_path}")
177
+ with extracted:
178
+ tout.addfile(member, extracted)
179
+ else:
180
+ tout.addfile(member)
181
+
182
+ for name, data in sorted(payloads.items()):
183
+ source_info = tarfile.TarInfo(f"{root}/{name}")
184
+ source_info.size = len(data)
185
+ source_info.mtime = 0
186
+ source_info.mode = 0o644
187
+ tout.addfile(source_info, io.BytesIO(data))
188
+
189
+ temp_path.replace(sdist_path)
190
+ finally:
191
+ if temp_path.exists():
192
+ temp_path.unlink()
193
+
194
+
195
+ def get_requires_for_build_wheel(config_settings=None):
196
+ return poetry_api.get_requires_for_build_wheel(config_settings)
197
+
198
+
199
+ def get_requires_for_build_sdist(config_settings=None):
200
+ return poetry_api.get_requires_for_build_sdist(config_settings)
201
+
202
+
203
+ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
204
+ _sync_cpp_sources()
205
+ try:
206
+ return poetry_api.prepare_metadata_for_build_wheel(metadata_directory, config_settings)
207
+ finally:
208
+ _clean_cpp_sources()
209
+
210
+
211
+ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
212
+ _sync_cpp_sources()
213
+ try:
214
+ wheel_name = poetry_api.build_wheel(wheel_directory, config_settings, metadata_directory)
215
+ final_path = _rewrite_wheel_with_sources(Path(wheel_directory) / wheel_name)
216
+ return final_path.name
217
+ finally:
218
+ _clean_cpp_sources()
219
+
220
+
221
+ def build_sdist(sdist_directory, config_settings=None):
222
+ _sync_cpp_sources()
223
+ try:
224
+ sdist_name = poetry_api.build_sdist(sdist_directory, config_settings)
225
+ _rewrite_sdist_with_sources(Path(sdist_directory) / sdist_name)
226
+ return sdist_name
227
+ finally:
228
+ _clean_cpp_sources()
@@ -0,0 +1,21 @@
1
+ from .main import (
2
+ PdCodeSimplifyInterfaceError,
3
+ compile_simplifier,
4
+ get_simplifier_executable,
5
+ get_simplifier_library,
6
+ normalize_pd_code,
7
+ normalize_pd_codes,
8
+ simplify,
9
+ simplify_many,
10
+ )
11
+
12
+ __all__ = [
13
+ "PdCodeSimplifyInterfaceError",
14
+ "compile_simplifier",
15
+ "get_simplifier_executable",
16
+ "get_simplifier_library",
17
+ "normalize_pd_code",
18
+ "normalize_pd_codes",
19
+ "simplify",
20
+ "simplify_many",
21
+ ]
@@ -0,0 +1,4 @@
1
+ from .main import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,145 @@
1
+ #pragma once
2
+
3
+ #include <array>
4
+ #include <cstddef>
5
+ #include <iosfwd>
6
+ #include <string>
7
+ #include <vector>
8
+
9
+ #if defined(_WIN32) && (defined(PDCODE_SIMPLIFY_SHARED) || defined(PDCODE_SIMPLIFY_BUILD_SHARED))
10
+ #if defined(PDCODE_SIMPLIFY_BUILD_SHARED)
11
+ #define PDCODE_SIMPLIFY_API __declspec(dllexport)
12
+ #else
13
+ #define PDCODE_SIMPLIFY_API __declspec(dllimport)
14
+ #endif
15
+ #elif defined(PDCODE_SIMPLIFY_BUILD_SHARED) && defined(__GNUC__)
16
+ #define PDCODE_SIMPLIFY_API __attribute__((visibility("default")))
17
+ #else
18
+ #define PDCODE_SIMPLIFY_API
19
+ #endif
20
+
21
+ namespace pdcode_simplify {
22
+
23
+ struct Endpoint {
24
+ int crossing = -1;
25
+ int strand = -1;
26
+
27
+ friend bool operator==(const Endpoint& lhs, const Endpoint& rhs) {
28
+ return lhs.crossing == rhs.crossing && lhs.strand == rhs.strand;
29
+ }
30
+
31
+ friend bool operator!=(const Endpoint& lhs, const Endpoint& rhs) {
32
+ return !(lhs == rhs);
33
+ }
34
+ };
35
+
36
+ using Crossing = std::array<int, 4>;
37
+ using PDCode = std::vector<Crossing>;
38
+
39
+ enum class Direction {
40
+ Left,
41
+ Right
42
+ };
43
+
44
+ struct GreenCrossing {
45
+ int from_face = -1;
46
+ int to_face = -1;
47
+ std::string strand_level;
48
+ };
49
+
50
+ struct SimplifierOptions {
51
+ int max_paths = 100;
52
+ };
53
+
54
+ struct LinkComponentSummary {
55
+ std::vector<int> crossing_indices;
56
+ };
57
+
58
+ struct ComponentAnalysis {
59
+ std::vector<LinkComponentSummary> components;
60
+ std::size_t crossingless_components = 0;
61
+
62
+ std::size_t components_with_crossings() const {
63
+ return components.size();
64
+ }
65
+
66
+ std::size_t total_components() const {
67
+ return components.size() + crossingless_components;
68
+ }
69
+ };
70
+
71
+ struct RandomInflationOptions {
72
+ int moves = 16;
73
+ unsigned int seed = 1;
74
+ int type_ii_percentage = 50;
75
+ };
76
+
77
+ struct RandomInflationResult {
78
+ PDCode code;
79
+ unsigned int seed = 1;
80
+ int type_i_moves = 0;
81
+ int type_ii_moves = 0;
82
+ };
83
+
84
+ struct ReidemeisterSimplificationResult {
85
+ PDCode code;
86
+ std::size_t crossingless_components = 0;
87
+ int type_i_moves = 0;
88
+ int type_ii_moves = 0;
89
+ };
90
+
91
+ struct PDSimplificationResult {
92
+ PDCode code;
93
+ std::size_t crossingless_components = 0;
94
+ int reidemeister_i_moves = 0;
95
+ int nugatory_crossing_moves = 0;
96
+ };
97
+
98
+ struct SimplificationResult {
99
+ bool found = false;
100
+ Direction direction = Direction::Left;
101
+ std::vector<Endpoint> red_path;
102
+ std::vector<int> green_path;
103
+ std::vector<GreenCrossing> green_crossings;
104
+ std::size_t tested_red_paths = 0;
105
+ std::size_t tested_green_paths = 0;
106
+ };
107
+
108
+ PDCODE_SIMPLIFY_API PDCode parse_pd_code(const std::string& text);
109
+ PDCODE_SIMPLIFY_API std::string format_pd_code(const PDCode& code);
110
+ PDCODE_SIMPLIFY_API std::string format_endpoint(const Endpoint& endpoint);
111
+ PDCODE_SIMPLIFY_API std::string format_direction(Direction direction);
112
+
113
+ PDCODE_SIMPLIFY_API ComponentAnalysis analyze_components(
114
+ const PDCode& code,
115
+ std::size_t known_crossingless_components = 0);
116
+
117
+ PDCODE_SIMPLIFY_API ComponentAnalysis analyze_components_after_removing_crossings(
118
+ const PDCode& code,
119
+ const std::vector<int>& removed_crossings,
120
+ std::size_t known_crossingless_components = 0);
121
+
122
+ PDCODE_SIMPLIFY_API std::size_t count_crossingless_components_after_removing_crossings(
123
+ const PDCode& code,
124
+ const std::vector<int>& removed_crossings,
125
+ std::size_t known_crossingless_components = 0);
126
+
127
+ PDCODE_SIMPLIFY_API RandomInflationResult randomly_increase_crossings(
128
+ const PDCode& code,
129
+ const RandomInflationOptions& options = RandomInflationOptions{});
130
+
131
+ PDCODE_SIMPLIFY_API ReidemeisterSimplificationResult simplify_reidemeister_i_ii(
132
+ const PDCode& code,
133
+ std::size_t known_crossingless_components = 0);
134
+
135
+ PDCODE_SIMPLIFY_API PDSimplificationResult simplify_pd_code(
136
+ const PDCode& code,
137
+ std::size_t known_crossingless_components = 0);
138
+
139
+ PDCODE_SIMPLIFY_API SimplificationResult find_simplification(
140
+ const PDCode& code,
141
+ const SimplifierOptions& options = SimplifierOptions{});
142
+
143
+ PDCODE_SIMPLIFY_API std::ostream& operator<<(std::ostream& out, const Endpoint& endpoint);
144
+
145
+ } // namespace pdcode_simplify