epgx 0.1.dev0__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.
epgx/examples.py ADDED
@@ -0,0 +1,146 @@
1
+ """Utilities for the example scripts (examples/)
2
+
3
+ quick(): reduced sizes for fast runs (EPGX_QUICK=1 or `--quick`)
4
+ show(name): save the open figures if requested (EPGX_SAVEFIG=<folder> or
5
+ `--savefig [folder]`), then `plt.show()`
6
+ device_name(): readable name of the computing device (e.g. for timings)
7
+
8
+ Example headers (module docstring):
9
+
10
+ Title
11
+
12
+ Description (free text)
13
+
14
+ Shows: what the example demonstrates
15
+ Reference: Author et al. Journal Year;vol:pages. doi:... (optional)
16
+ Reproduces: figure(s) of the reference (optional)
17
+ Differences: w.r.t. the reference (optional)
18
+
19
+ Field values may continue on indented lines. The index of the examples
20
+ (examples/README.md) is generated from the headers (tools/make_examples_index.py).
21
+ """
22
+
23
+ import ast
24
+ import os
25
+ import pathlib
26
+ import platform
27
+ import re
28
+ import subprocess
29
+ import sys
30
+
31
+ QUICK_VAR = "EPGX_QUICK"
32
+ SAVEFIG_VAR = "EPGX_SAVEFIG"
33
+ DEFAULT_FOLDER = "figures"
34
+ HEADER_FIELDS = ("Shows", "Reference", "Reproduces", "Differences")
35
+
36
+
37
+ def parse_header(docstring):
38
+ """parse an example header (cf. module docstring)
39
+
40
+ Returns:
41
+ dict with "title", "description" and the header fields found
42
+ """
43
+ lines = (docstring or "").strip().splitlines()
44
+ if not lines:
45
+ raise ValueError("Empty example header")
46
+ header = {"title": lines[0].strip()}
47
+ pattern = re.compile(rf"^({'|'.join(HEADER_FIELDS)}):\s*(.*)$")
48
+ description, field = [], None
49
+ for line in lines[1:]:
50
+ match = pattern.match(line)
51
+ if match:
52
+ field = match.group(1)
53
+ header[field] = match.group(2).strip()
54
+ elif field and line.startswith((" ", "\t")) and line.strip():
55
+ header[field] += " " + line.strip()
56
+ else:
57
+ field = None
58
+ description.append(line)
59
+ header["description"] = "\n".join(description).strip()
60
+ return header
61
+
62
+
63
+ def read_header(path):
64
+ """parse the header of an example file"""
65
+ tree = ast.parse(pathlib.Path(path).read_text(encoding="utf-8"))
66
+ return parse_header(ast.get_docstring(tree))
67
+
68
+
69
+ def quick():
70
+ """True in quick mode (EPGX_QUICK=1 or `--quick` on the command line)"""
71
+ value = os.environ.get(QUICK_VAR, "").strip().lower()
72
+ return value in ("1", "true", "yes") or "--quick" in sys.argv
73
+
74
+
75
+ def savefig_folder():
76
+ """folder where to save figures (EPGX_SAVEFIG or `--savefig [folder]`), or None"""
77
+ if "--savefig" in sys.argv:
78
+ index = sys.argv.index("--savefig")
79
+ value = sys.argv[index + 1] if index + 1 < len(sys.argv) else ""
80
+ return pathlib.Path(
81
+ value if value and not value.startswith("-") else DEFAULT_FOLDER
82
+ )
83
+ value = os.environ.get(SAVEFIG_VAR)
84
+ return pathlib.Path(value) if value else None
85
+
86
+
87
+ def show(name):
88
+ """save the open figures if requested, then show them
89
+
90
+ Args:
91
+ name: name of the example (e.g. `__file__`), used for the file names:
92
+ <folder>/<example>_<figure label or number>.png
93
+ """
94
+ from matplotlib import pyplot as plt
95
+
96
+ folder = savefig_folder()
97
+ if folder is not None:
98
+ folder.mkdir(parents=True, exist_ok=True)
99
+ stem = pathlib.Path(name).stem
100
+ for number in plt.get_fignums():
101
+ figure = plt.figure(number)
102
+ label = figure.get_label() or str(number)
103
+ figure.savefig(folder / f"{stem}_{label}.png", dpi=100)
104
+ plt.show()
105
+
106
+
107
+ def device_name():
108
+ """readable name of the computing device and precision, e.g. for timings
109
+
110
+ GPU: model reported by JAX; CPU: processor name (registry on Windows,
111
+ /proc/cpuinfo on Linux, sysctl on macOS) and number of threads.
112
+ """
113
+ import jax
114
+
115
+ device = jax.devices()[0]
116
+ precision = "x64" if jax.config.jax_enable_x64 else "float32"
117
+ if device.platform != "cpu":
118
+ return f"{device.device_kind} ({device.platform}), {precision}"
119
+ return f"{_cpu_name()} ({os.cpu_count()} threads), {precision}"
120
+
121
+
122
+ def _cpu_name():
123
+ """processor name, or the generic platform description"""
124
+ try:
125
+ system = platform.system()
126
+ if system == "Windows":
127
+ import winreg
128
+
129
+ key = r"HARDWARE\DESCRIPTION\System\CentralProcessor\0"
130
+ with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key) as handle:
131
+ name = winreg.QueryValueEx(handle, "ProcessorNameString")[0]
132
+ elif system == "Linux":
133
+ text = pathlib.Path("/proc/cpuinfo").read_text()
134
+ name = re.search(r"model name\s*:\s*(.*)", text).group(1)
135
+ elif system == "Darwin":
136
+ name = subprocess.run(
137
+ ["sysctl", "-n", "machdep.cpu.brand_string"],
138
+ capture_output=True,
139
+ text=True,
140
+ check=True,
141
+ ).stdout
142
+ else:
143
+ name = ""
144
+ except Exception:
145
+ name = ""
146
+ return " ".join(name.split()) or platform.processor() or "CPU"