cpp-pd-code-simplify-interface 0.1.0__py3-none-any.whl
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.
- cpp_pd_code_simplify_interface/__init__.py +21 -0
- cpp_pd_code_simplify_interface/__main__.py +4 -0
- cpp_pd_code_simplify_interface/data/include/pdcode_simplify/pdcode_simplify.hpp +145 -0
- cpp_pd_code_simplify_interface/data/src/native_interface.cpp +195 -0
- cpp_pd_code_simplify_interface/data/src/pdcode_simplify.cpp +1690 -0
- cpp_pd_code_simplify_interface/main.py +474 -0
- cpp_pd_code_simplify_interface/py.typed +1 -0
- cpp_pd_code_simplify_interface-0.1.0.dist-info/METADATA +94 -0
- cpp_pd_code_simplify_interface-0.1.0.dist-info/RECORD +11 -0
- cpp_pd_code_simplify_interface-0.1.0.dist-info/WHEEL +4 -0
- cpp_pd_code_simplify_interface-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import ast
|
|
5
|
+
import contextlib
|
|
6
|
+
import ctypes
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import pathlib
|
|
11
|
+
import platform
|
|
12
|
+
import re
|
|
13
|
+
import shlex
|
|
14
|
+
import struct
|
|
15
|
+
import sys
|
|
16
|
+
from importlib import resources
|
|
17
|
+
from typing import Any, Optional, Sequence, Union
|
|
18
|
+
|
|
19
|
+
import cpp_simple_interface
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
PdInput = Union[str, Sequence[Sequence[int]]]
|
|
23
|
+
PdManyInput = Union[str, Sequence[PdInput]]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PdCodeSimplifyInterfaceError(RuntimeError):
|
|
27
|
+
"""Raised when the C++ dynamic library cannot be built or called."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _format_pd(crossings: Sequence[Sequence[int]]) -> str:
|
|
31
|
+
parts = []
|
|
32
|
+
for crossing in crossings:
|
|
33
|
+
values = list(crossing)
|
|
34
|
+
if len(values) != 4:
|
|
35
|
+
raise ValueError(f"PD crossing must have four entries: {crossing!r}")
|
|
36
|
+
parts.append("X[{},{},{},{}]".format(*(int(value) for value in values)))
|
|
37
|
+
return "PD[" + ",".join(parts) + "]"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _parse_x_crossings(text: str) -> Optional[list[list[int]]]:
|
|
41
|
+
pattern = r"X\s*\[\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*\]"
|
|
42
|
+
crossings = []
|
|
43
|
+
for match in re.finditer(pattern, text):
|
|
44
|
+
crossings.append([int(match.group(i)) for i in range(1, 5)])
|
|
45
|
+
return crossings if crossings else None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _as_crossings(pd_code: PdInput) -> list[list[int]]:
|
|
49
|
+
if isinstance(pd_code, str):
|
|
50
|
+
body = pd_code.strip()
|
|
51
|
+
if ":" in body:
|
|
52
|
+
body = body.split(":", 1)[1].strip()
|
|
53
|
+
if body.replace(" ", "") in ("PD[]", "[]"):
|
|
54
|
+
return []
|
|
55
|
+
|
|
56
|
+
parsed = _parse_x_crossings(body)
|
|
57
|
+
if parsed is not None:
|
|
58
|
+
return parsed
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
value = ast.literal_eval(body)
|
|
62
|
+
except (SyntaxError, ValueError) as exc:
|
|
63
|
+
raise ValueError(f"unsupported PD-code string format: {pd_code!r}") from exc
|
|
64
|
+
else:
|
|
65
|
+
value = pd_code
|
|
66
|
+
|
|
67
|
+
crossings = []
|
|
68
|
+
for crossing in value:
|
|
69
|
+
values = list(crossing)
|
|
70
|
+
if len(values) != 4:
|
|
71
|
+
raise ValueError(f"PD crossing must have four entries: {crossing!r}")
|
|
72
|
+
crossings.append([int(item) for item in values])
|
|
73
|
+
return crossings
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def normalize_pd_code(pd_code: PdInput) -> str:
|
|
77
|
+
"""Normalize a supported PD-code value into standard ``PD[X[...],...]`` text."""
|
|
78
|
+
|
|
79
|
+
return _format_pd(_as_crossings(pd_code))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def normalize_pd_codes(pd_codes: PdManyInput) -> list[str]:
|
|
83
|
+
"""Normalize one or more PD codes into standard ``PD[X[...],...]`` strings."""
|
|
84
|
+
|
|
85
|
+
if isinstance(pd_codes, str):
|
|
86
|
+
return [line.strip() for line in pd_codes.splitlines() if line.strip()]
|
|
87
|
+
return [normalize_pd_code(pd_code) for pd_code in pd_codes]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@contextlib.contextmanager
|
|
91
|
+
def _resource_paths():
|
|
92
|
+
package = "cpp_pd_code_simplify_interface"
|
|
93
|
+
resource_names = [
|
|
94
|
+
resources.files(package) / "data" / "src" / "pdcode_simplify.cpp",
|
|
95
|
+
resources.files(package) / "data" / "src" / "native_interface.cpp",
|
|
96
|
+
resources.files(package) / "data" / "include" / "pdcode_simplify" / "pdcode_simplify.hpp",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
contexts = []
|
|
100
|
+
paths: list[pathlib.Path] = []
|
|
101
|
+
try:
|
|
102
|
+
for resource in resource_names:
|
|
103
|
+
context = resources.as_file(resource)
|
|
104
|
+
contexts.append(context)
|
|
105
|
+
path = pathlib.Path(context.__enter__())
|
|
106
|
+
if not path.exists():
|
|
107
|
+
break
|
|
108
|
+
paths.append(path)
|
|
109
|
+
if len(paths) == len(resource_names):
|
|
110
|
+
yield paths
|
|
111
|
+
return
|
|
112
|
+
except FileNotFoundError:
|
|
113
|
+
pass
|
|
114
|
+
finally:
|
|
115
|
+
while contexts:
|
|
116
|
+
contexts.pop().__exit__(None, None, None)
|
|
117
|
+
|
|
118
|
+
current = pathlib.Path(__file__).resolve()
|
|
119
|
+
for parent in current.parents:
|
|
120
|
+
candidate_cpp = parent / "src" / "pdcode_simplify.cpp"
|
|
121
|
+
candidate_wrapper = (
|
|
122
|
+
parent
|
|
123
|
+
/ "python_project"
|
|
124
|
+
/ "cpp-pd-code-simplify-interface"
|
|
125
|
+
/ "cpp_pd_code_simplify_interface"
|
|
126
|
+
/ "data"
|
|
127
|
+
/ "src"
|
|
128
|
+
/ "native_interface.cpp"
|
|
129
|
+
)
|
|
130
|
+
candidate_header = parent / "include" / "pdcode_simplify" / "pdcode_simplify.hpp"
|
|
131
|
+
if candidate_cpp.exists() and candidate_wrapper.exists() and candidate_header.exists():
|
|
132
|
+
yield [candidate_cpp, candidate_wrapper, candidate_header]
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
raise PdCodeSimplifyInterfaceError(
|
|
136
|
+
"cpp-pd-code-simplify C++ sources were not found. Installed wheels "
|
|
137
|
+
"include them under cpp_pd_code_simplify_interface/data; editable "
|
|
138
|
+
"checkouts use the repository root src/ and include/ directories."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _cache_dir() -> pathlib.Path:
|
|
143
|
+
env_value = os.environ.get("CPP_PD_CODE_SIMPLIFY_INTERFACE_CACHE_DIR")
|
|
144
|
+
if env_value:
|
|
145
|
+
root = pathlib.Path(env_value)
|
|
146
|
+
elif sys.platform == "win32":
|
|
147
|
+
root = pathlib.Path(os.environ.get("LOCALAPPDATA", pathlib.Path.home())) / "cpp-pd-code-simplify-interface"
|
|
148
|
+
elif sys.platform == "darwin":
|
|
149
|
+
root = pathlib.Path.home() / "Library" / "Caches" / "cpp-pd-code-simplify-interface"
|
|
150
|
+
else:
|
|
151
|
+
root = pathlib.Path(os.environ.get("XDG_CACHE_HOME", pathlib.Path.home() / ".cache")) / "cpp-pd-code-simplify-interface"
|
|
152
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
153
|
+
return root
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _library_suffix() -> str:
|
|
157
|
+
if platform.system() == "Windows":
|
|
158
|
+
return ".dll"
|
|
159
|
+
if platform.system() == "Darwin":
|
|
160
|
+
return ".dylib"
|
|
161
|
+
return ".so"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _default_compile_flags(include_dir: pathlib.Path, library_path: pathlib.Path) -> list[str]:
|
|
165
|
+
flags = ["-std=c++14", "-O3", "-DNDEBUG", "-I" + str(include_dir)]
|
|
166
|
+
if platform.system() != "Windows":
|
|
167
|
+
flags.append("-fPIC")
|
|
168
|
+
if platform.system() == "Darwin":
|
|
169
|
+
flags.extend(["-dynamiclib", "-install_name", "@rpath/" + library_path.name])
|
|
170
|
+
else:
|
|
171
|
+
flags.append("-shared")
|
|
172
|
+
native = os.environ.get("CPP_PD_CODE_SIMPLIFY_INTERFACE_NATIVE", "1").strip().lower()
|
|
173
|
+
if native not in ("0", "false", "no", "off"):
|
|
174
|
+
flags.append("-march=native")
|
|
175
|
+
extra = os.environ.get("CPP_PD_CODE_SIMPLIFY_INTERFACE_CXXFLAGS", "").strip()
|
|
176
|
+
if extra:
|
|
177
|
+
flags.extend(shlex.split(extra))
|
|
178
|
+
return flags
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _cache_key(source_bytes: bytes, flags: Sequence[str]) -> str:
|
|
182
|
+
digest = hashlib.sha256()
|
|
183
|
+
digest.update(source_bytes)
|
|
184
|
+
digest.update("\0".join(flags).encode("utf-8"))
|
|
185
|
+
digest.update(cpp_simple_interface.get_gpp_filepath().encode("utf-8"))
|
|
186
|
+
digest.update(platform.platform().encode("utf-8"))
|
|
187
|
+
return digest.hexdigest()[:20]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _compiler_runtime_path_entries() -> list[pathlib.Path]:
|
|
191
|
+
compiler = cpp_simple_interface.get_gpp_filepath().strip()
|
|
192
|
+
if not compiler:
|
|
193
|
+
return []
|
|
194
|
+
|
|
195
|
+
candidates = []
|
|
196
|
+
unquoted = compiler
|
|
197
|
+
if len(unquoted) >= 2 and unquoted[0] == unquoted[-1] and unquoted[0] in ("'", '"'):
|
|
198
|
+
unquoted = unquoted[1:-1]
|
|
199
|
+
candidates.append(unquoted)
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
candidates.extend(shlex.split(compiler, posix=True))
|
|
203
|
+
except ValueError:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
paths: list[pathlib.Path] = []
|
|
207
|
+
for candidate in candidates:
|
|
208
|
+
path = pathlib.Path(candidate)
|
|
209
|
+
if path.exists() and path.is_file() and path.parent not in paths:
|
|
210
|
+
paths.append(path.parent)
|
|
211
|
+
return paths
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _pe_machine_bits(path: pathlib.Path) -> Optional[int]:
|
|
215
|
+
if platform.system() != "Windows":
|
|
216
|
+
return None
|
|
217
|
+
data = path.read_bytes()
|
|
218
|
+
if len(data) < 0x40 or data[:2] != b"MZ":
|
|
219
|
+
return None
|
|
220
|
+
pe_offset = struct.unpack_from("<I", data, 0x3C)[0]
|
|
221
|
+
if len(data) < pe_offset + 6 or data[pe_offset : pe_offset + 4] != b"PE\0\0":
|
|
222
|
+
return None
|
|
223
|
+
machine = struct.unpack_from("<H", data, pe_offset + 4)[0]
|
|
224
|
+
if machine == 0x014C:
|
|
225
|
+
return 32
|
|
226
|
+
if machine in (0x8664, 0xAA64):
|
|
227
|
+
return 64
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _validate_library_architecture(path: pathlib.Path) -> None:
|
|
232
|
+
bits = _pe_machine_bits(path)
|
|
233
|
+
if bits is None:
|
|
234
|
+
return
|
|
235
|
+
python_bits = struct.calcsize("P") * 8
|
|
236
|
+
if bits != python_bits:
|
|
237
|
+
raise PdCodeSimplifyInterfaceError(
|
|
238
|
+
f"cached library is {bits}-bit but Python is {python_bits}-bit. "
|
|
239
|
+
"Set CXX to a compiler whose target architecture matches Python, "
|
|
240
|
+
"then delete the interface cache or call compile_simplifier(force=True)."
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def compile_simplifier(
|
|
245
|
+
*,
|
|
246
|
+
force: bool = False,
|
|
247
|
+
cxx: Optional[str] = None,
|
|
248
|
+
extra_flags: Optional[Sequence[str]] = None,
|
|
249
|
+
) -> pathlib.Path:
|
|
250
|
+
"""Compile the packaged C++ source as a cached dynamic library."""
|
|
251
|
+
|
|
252
|
+
if cxx:
|
|
253
|
+
cpp_simple_interface.set_gpp_filepath(cxx)
|
|
254
|
+
|
|
255
|
+
with _resource_paths() as paths:
|
|
256
|
+
pd_source, wrapper_source, header = paths
|
|
257
|
+
include_dir = header.parents[1]
|
|
258
|
+
source_bytes = pd_source.read_bytes() + b"\0" + wrapper_source.read_bytes() + b"\0" + header.read_bytes()
|
|
259
|
+
|
|
260
|
+
cache = _cache_dir()
|
|
261
|
+
placeholder = cache / ("pdcode-simplify-placeholder" + _library_suffix())
|
|
262
|
+
flags = _default_compile_flags(include_dir, placeholder)
|
|
263
|
+
if extra_flags:
|
|
264
|
+
flags.extend(str(flag) for flag in extra_flags)
|
|
265
|
+
library = cache / f"pdcode-simplify-{_cache_key(source_bytes, flags)}{_library_suffix()}"
|
|
266
|
+
flags = _default_compile_flags(include_dir, library)
|
|
267
|
+
if extra_flags:
|
|
268
|
+
flags.extend(str(flag) for flag in extra_flags)
|
|
269
|
+
|
|
270
|
+
if library.exists() and not force:
|
|
271
|
+
return library
|
|
272
|
+
|
|
273
|
+
tmp_library = cache / f"{library.name}.tmp-{os.getpid()}{_library_suffix()}"
|
|
274
|
+
if tmp_library.exists():
|
|
275
|
+
tmp_library.unlink()
|
|
276
|
+
|
|
277
|
+
success, message = cpp_simple_interface.compile_cpp_files(
|
|
278
|
+
[str(pd_source), str(wrapper_source)],
|
|
279
|
+
str(tmp_library),
|
|
280
|
+
other_flags=flags,
|
|
281
|
+
)
|
|
282
|
+
if not success and "-march=native" in flags:
|
|
283
|
+
fallback_flags = [flag for flag in flags if flag != "-march=native"]
|
|
284
|
+
success, message = cpp_simple_interface.compile_cpp_files(
|
|
285
|
+
[str(pd_source), str(wrapper_source)],
|
|
286
|
+
str(tmp_library),
|
|
287
|
+
other_flags=fallback_flags,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if not success:
|
|
291
|
+
raise PdCodeSimplifyInterfaceError(message)
|
|
292
|
+
if not tmp_library.exists():
|
|
293
|
+
raise PdCodeSimplifyInterfaceError(f"compiled dynamic library was not created: {tmp_library}")
|
|
294
|
+
os.replace(tmp_library, library)
|
|
295
|
+
return library
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def get_simplifier_library() -> pathlib.Path:
|
|
299
|
+
"""Return the cached dynamic library path, compiling it first when necessary."""
|
|
300
|
+
|
|
301
|
+
return compile_simplifier()
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def get_simplifier_executable() -> pathlib.Path:
|
|
305
|
+
"""Backward-compatible alias returning the cached dynamic library path."""
|
|
306
|
+
|
|
307
|
+
return get_simplifier_library()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
_LOADED_LIBRARY_PATH: Optional[pathlib.Path] = None
|
|
311
|
+
_LOADED_LIBRARY: Optional[ctypes.CDLL] = None
|
|
312
|
+
_DLL_DIRECTORY_HANDLES: list[Any] = []
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _prepare_dll_search_path(path: pathlib.Path) -> None:
|
|
316
|
+
if platform.system() != "Windows" or not hasattr(os, "add_dll_directory"):
|
|
317
|
+
return
|
|
318
|
+
for directory in [path.parent, *_compiler_runtime_path_entries()]:
|
|
319
|
+
try:
|
|
320
|
+
handle = os.add_dll_directory(str(directory))
|
|
321
|
+
except OSError:
|
|
322
|
+
continue
|
|
323
|
+
_DLL_DIRECTORY_HANDLES.append(handle)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _load_library() -> ctypes.CDLL:
|
|
327
|
+
global _LOADED_LIBRARY_PATH, _LOADED_LIBRARY
|
|
328
|
+
path = compile_simplifier()
|
|
329
|
+
if _LOADED_LIBRARY is not None and _LOADED_LIBRARY_PATH == path:
|
|
330
|
+
return _LOADED_LIBRARY
|
|
331
|
+
|
|
332
|
+
_validate_library_architecture(path)
|
|
333
|
+
_prepare_dll_search_path(path)
|
|
334
|
+
library = ctypes.CDLL(str(path))
|
|
335
|
+
library.pdcode_simplify_run_json.argtypes = [
|
|
336
|
+
ctypes.c_char_p,
|
|
337
|
+
ctypes.c_int,
|
|
338
|
+
ctypes.c_ulonglong,
|
|
339
|
+
ctypes.POINTER(ctypes.c_int),
|
|
340
|
+
ctypes.c_ulonglong,
|
|
341
|
+
]
|
|
342
|
+
library.pdcode_simplify_run_json.restype = ctypes.c_void_p
|
|
343
|
+
library.pdcode_simplify_free_string.argtypes = [ctypes.c_void_p]
|
|
344
|
+
library.pdcode_simplify_free_string.restype = None
|
|
345
|
+
_LOADED_LIBRARY_PATH = path
|
|
346
|
+
_LOADED_LIBRARY = library
|
|
347
|
+
return library
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _run_one(
|
|
351
|
+
pd_text: str,
|
|
352
|
+
*,
|
|
353
|
+
max_paths: int = 100,
|
|
354
|
+
known_crossingless_components: int = 0,
|
|
355
|
+
remove_crossings: Optional[Sequence[int]] = None,
|
|
356
|
+
) -> dict[str, Any]:
|
|
357
|
+
library = _load_library()
|
|
358
|
+
removed_count = 0 if remove_crossings is None else len(remove_crossings)
|
|
359
|
+
removed_array = None
|
|
360
|
+
if removed_count:
|
|
361
|
+
removed_array = (ctypes.c_int * removed_count)(*(int(value) for value in remove_crossings or []))
|
|
362
|
+
|
|
363
|
+
pointer = library.pdcode_simplify_run_json(
|
|
364
|
+
pd_text.encode("utf-8"),
|
|
365
|
+
int(max_paths),
|
|
366
|
+
int(known_crossingless_components),
|
|
367
|
+
removed_array,
|
|
368
|
+
int(removed_count),
|
|
369
|
+
)
|
|
370
|
+
if not pointer:
|
|
371
|
+
raise PdCodeSimplifyInterfaceError("C++ interface returned a null JSON pointer")
|
|
372
|
+
try:
|
|
373
|
+
text = ctypes.string_at(pointer).decode("utf-8")
|
|
374
|
+
finally:
|
|
375
|
+
library.pdcode_simplify_free_string(pointer)
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
result = json.loads(text)
|
|
379
|
+
except json.JSONDecodeError as exc:
|
|
380
|
+
raise PdCodeSimplifyInterfaceError(f"invalid simplifier JSON output: {text!r}") from exc
|
|
381
|
+
if isinstance(result, dict) and "error" in result:
|
|
382
|
+
raise PdCodeSimplifyInterfaceError(str(result["error"]))
|
|
383
|
+
return result
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def simplify(
|
|
387
|
+
pd_code: PdInput,
|
|
388
|
+
*,
|
|
389
|
+
max_paths: int = 100,
|
|
390
|
+
known_crossingless_components: int = 0,
|
|
391
|
+
remove_crossings: Optional[Sequence[int]] = None,
|
|
392
|
+
) -> dict[str, Any]:
|
|
393
|
+
"""Run the C++ simplifier for one PD code and return its JSON result."""
|
|
394
|
+
|
|
395
|
+
return _run_one(
|
|
396
|
+
normalize_pd_code(pd_code),
|
|
397
|
+
max_paths=max_paths,
|
|
398
|
+
known_crossingless_components=known_crossingless_components,
|
|
399
|
+
remove_crossings=remove_crossings,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def simplify_many(
|
|
404
|
+
pd_codes: PdManyInput,
|
|
405
|
+
*,
|
|
406
|
+
max_paths: int = 100,
|
|
407
|
+
known_crossingless_components: int = 0,
|
|
408
|
+
remove_crossings: Optional[Sequence[int]] = None,
|
|
409
|
+
) -> list[dict[str, Any]]:
|
|
410
|
+
"""Run the C++ simplifier for one or more PD codes and return JSON results."""
|
|
411
|
+
|
|
412
|
+
return [
|
|
413
|
+
_run_one(
|
|
414
|
+
pd_text,
|
|
415
|
+
max_paths=max_paths,
|
|
416
|
+
known_crossingless_components=known_crossingless_components,
|
|
417
|
+
remove_crossings=remove_crossings,
|
|
418
|
+
)
|
|
419
|
+
for pd_text in normalize_pd_codes(pd_codes)
|
|
420
|
+
]
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
424
|
+
parser = argparse.ArgumentParser(description="Run cpp-pd-code-simplify through the Python interface.")
|
|
425
|
+
parser.add_argument("pd_code", nargs="?", help="PD code as PD[...] text or a Python-style list of crossings.")
|
|
426
|
+
parser.add_argument("--pd-file", "-f", help="read one file containing one or more labelled PD-code lines")
|
|
427
|
+
parser.add_argument("--max-paths", type=int, default=100)
|
|
428
|
+
parser.add_argument("--known-crossingless-components", type=int, default=0)
|
|
429
|
+
parser.add_argument("--remove-crossings", help="comma-separated zero-based crossing indices")
|
|
430
|
+
args = parser.parse_args(argv)
|
|
431
|
+
if args.pd_file and args.pd_code:
|
|
432
|
+
parser.error("pass either a positional PD code or --pd-file, not both")
|
|
433
|
+
if not args.pd_file and not args.pd_code:
|
|
434
|
+
parser.error("a positional PD code or --pd-file is required")
|
|
435
|
+
remove_crossings = None
|
|
436
|
+
if args.remove_crossings:
|
|
437
|
+
remove_crossings = [int(token) for token in re.findall(r"-?\d+", args.remove_crossings)]
|
|
438
|
+
|
|
439
|
+
if args.pd_file:
|
|
440
|
+
lines = []
|
|
441
|
+
for line in pathlib.Path(args.pd_file).read_text(encoding="utf-8").splitlines():
|
|
442
|
+
cleaned = line.strip()
|
|
443
|
+
if not cleaned or cleaned.startswith("#"):
|
|
444
|
+
continue
|
|
445
|
+
payload = cleaned.split(":", 1)[1].strip() if ":" in cleaned else cleaned
|
|
446
|
+
lines.append(payload)
|
|
447
|
+
batch_payload = []
|
|
448
|
+
for line in lines:
|
|
449
|
+
try:
|
|
450
|
+
batch_payload.append(
|
|
451
|
+
simplify(
|
|
452
|
+
line,
|
|
453
|
+
max_paths=args.max_paths,
|
|
454
|
+
known_crossingless_components=args.known_crossingless_components,
|
|
455
|
+
remove_crossings=remove_crossings,
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
except Exception as exc:
|
|
459
|
+
batch_payload.append({"error": str(exc)})
|
|
460
|
+
payload: Any = batch_payload
|
|
461
|
+
else:
|
|
462
|
+
payload = simplify(
|
|
463
|
+
args.pd_code or "",
|
|
464
|
+
max_paths=args.max_paths,
|
|
465
|
+
known_crossingless_components=args.known_crossingless_components,
|
|
466
|
+
remove_crossings=remove_crossings,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
print(json.dumps(payload, indent=2))
|
|
470
|
+
return 0
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
if __name__ == "__main__":
|
|
474
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -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,11 @@
|
|
|
1
|
+
cpp_pd_code_simplify_interface/__init__.py,sha256=aaNwD--zgQX13xiU1hpuvgXy8vVTFGRk-r5zLm8zIRY,447
|
|
2
|
+
cpp_pd_code_simplify_interface/__main__.py,sha256=fdA1QWBe5LNX0fbtaJXa1-dwEhrg_4sS_5UnjaajcMo,80
|
|
3
|
+
cpp_pd_code_simplify_interface/main.py,sha256=WUCkkUpJo1vm8Y7Wmfv3Ab6dDMU4kEvyMeTWiDHA4zk,16471
|
|
4
|
+
cpp_pd_code_simplify_interface/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
5
|
+
cpp_pd_code_simplify_interface-0.1.0.dist-info/entry_points.txt,sha256=ThorC4Enxp317TKITsXm9pE0gUp7pt-pP6Dl5mEPUR8,91
|
|
6
|
+
cpp_pd_code_simplify_interface-0.1.0.dist-info/METADATA,sha256=x2DUbWcTMurcjF8zJUQI1_6gHB2scsCA47ABE8KT0zk,2516
|
|
7
|
+
cpp_pd_code_simplify_interface-0.1.0.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
|
|
8
|
+
cpp_pd_code_simplify_interface/data/include/pdcode_simplify/pdcode_simplify.hpp,sha256=Ei2uKlmPxHrK7-uBiJfJB1zJE_lffmzm4s-qecVdI6k,4015
|
|
9
|
+
cpp_pd_code_simplify_interface/data/src/native_interface.cpp,sha256=_YIi9ZN5-tsKhxCsCwAR3xQ7NL6tCsOnf4oJuQF2qmo,6709
|
|
10
|
+
cpp_pd_code_simplify_interface/data/src/pdcode_simplify.cpp,sha256=jNH-678Jg4i3DA-CPcdiShks8EEcXMjtva0NN9L9E3g,58276
|
|
11
|
+
cpp_pd_code_simplify_interface-0.1.0.dist-info/RECORD,,
|