pyrazobill 1.0.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.
- pyrazobill-1.0.0/Guide.md +487 -0
- pyrazobill-1.0.0/LICENSE +201 -0
- pyrazobill-1.0.0/MANIFEST.in +12 -0
- pyrazobill-1.0.0/NOTICE +44 -0
- pyrazobill-1.0.0/PKG-INFO +315 -0
- pyrazobill-1.0.0/PyRazobill.cpp +513 -0
- pyrazobill-1.0.0/README.md +280 -0
- pyrazobill-1.0.0/includeCore/librazobill/Backend.hpp +87 -0
- pyrazobill-1.0.0/includeCore/librazobill/Channel.hpp +80 -0
- pyrazobill-1.0.0/includeCore/librazobill/Dialect.hpp +60 -0
- pyrazobill-1.0.0/includeCore/librazobill/FormCatalog.hpp +73 -0
- pyrazobill-1.0.0/includeCore/librazobill/Hash.hpp +105 -0
- pyrazobill-1.0.0/includeCore/librazobill/MemoryArena.hpp +162 -0
- pyrazobill-1.0.0/includeCore/librazobill/Razobill.hpp +143 -0
- pyrazobill-1.0.0/includeCore/librazobill/RazobillC.h +142 -0
- pyrazobill-1.0.0/includeCore/librazobill/Scanner.hpp +186 -0
- pyrazobill-1.0.0/includeCore/librazobill/SimdScanner.hpp +257 -0
- pyrazobill-1.0.0/includeCore/librazobill/Slots.hpp +96 -0
- pyrazobill-1.0.0/includeCore/librazobill/Stats.hpp +222 -0
- pyrazobill-1.0.0/includeCore/librazobill/Status.hpp +45 -0
- pyrazobill-1.0.0/includeCore/librazobill/StringPool.hpp +115 -0
- pyrazobill-1.0.0/includeCore/librazobill/ThreadPool.hpp +118 -0
- pyrazobill-1.0.0/includeCore/librazobill/Version.hpp +54 -0
- pyrazobill-1.0.0/pyproject.toml +61 -0
- pyrazobill-1.0.0/pyrazobill/Core.cp313-win_amd64.pyd +0 -0
- pyrazobill-1.0.0/pyrazobill/Core.pyi +186 -0
- pyrazobill-1.0.0/pyrazobill/__init__.py +133 -0
- pyrazobill-1.0.0/pyrazobill/py.typed +1 -0
- pyrazobill-1.0.0/pyrazobill.egg-info/PKG-INFO +315 -0
- pyrazobill-1.0.0/pyrazobill.egg-info/SOURCES.txt +45 -0
- pyrazobill-1.0.0/pyrazobill.egg-info/dependency_links.txt +1 -0
- pyrazobill-1.0.0/pyrazobill.egg-info/requires.txt +5 -0
- pyrazobill-1.0.0/pyrazobill.egg-info/top_level.txt +1 -0
- pyrazobill-1.0.0/setup.cfg +4 -0
- pyrazobill-1.0.0/setup.py +88 -0
- pyrazobill-1.0.0/srcCore/BackendAdbc.cpp +283 -0
- pyrazobill-1.0.0/srcCore/Channel.cpp +290 -0
- pyrazobill-1.0.0/srcCore/Dialect.cpp +98 -0
- pyrazobill-1.0.0/srcCore/Razobill.cpp +608 -0
- pyrazobill-1.0.0/srcCore/RazobillC.cpp +247 -0
- pyrazobill-1.0.0/srcCore/Slots.cpp +159 -0
- pyrazobill-1.0.0/srcCore/Stats.cpp +451 -0
- pyrazobill-1.0.0/tests/TestPyRazobill.py +317 -0
- pyrazobill-1.0.0/thirdpartyCore/adbc/adbc.h +2413 -0
- pyrazobill-1.0.0/thirdpartyCore/doctest.h +9119 -0
- pyrazobill-1.0.0/thirdpartyCore/duckdbcapi/duckdb.h +4545 -0
- pyrazobill-1.0.0/thirdpartyCore/duckdbcapi/duckdb_extension.h +1027 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# pyrazobill Developer & Operations Guide
|
|
2
|
+
|
|
3
|
+
This guide provides deep technical instructions for developing, embedding, and operating **pyrazobill 1.0.0** within production Python applications, streaming telemetry backends, analytical lakehouses, and information-theoretic research pipelines.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Architectural Philosophy & Safety Invariants
|
|
8
|
+
|
|
9
|
+
Python string objects are managed dynamically by the CPython heap. When passing strings into high-performance native C++ libraries, passing raw pointers or string views without lifetime guarantees risks dangling memory references once CPython garbage collects or reallocates the object.
|
|
10
|
+
|
|
11
|
+
To prevent dangling pointers with zero cognitive burden on the Python developer, `pyrazobill` encapsulates parsed results in a memory-safe `PySlotRecord`:
|
|
12
|
+
|
|
13
|
+
```cpp
|
|
14
|
+
// PyRazobill.cpp
|
|
15
|
+
struct PySlotRecord {
|
|
16
|
+
std::shared_ptr<std::string> msgBuffer; // Retains ownership of the underlying buffer
|
|
17
|
+
core::SlotRecord slots; // String views point safely into msgBuffer
|
|
18
|
+
// ...
|
|
19
|
+
};
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Safety & Structural Invariants:
|
|
23
|
+
1. **Lifetime Safety**: A `SlotRecord` can be passed across Python threads, stored in queues, or serialized without memory corruption or dangling pointer risks.
|
|
24
|
+
2. **Deterministic Round-Trip (Lossless Invertibility)**: Every parsed record satisfies the invertibility law:
|
|
25
|
+
```python
|
|
26
|
+
reconstructed = record.reconstruct()
|
|
27
|
+
assert reconstructed == originalMsg
|
|
28
|
+
```
|
|
29
|
+
3. **Zero-Underscore Convention**: All methods, functions, and properties adhere strictly to camelCase naming (`formKey`, `formKeyHash`, `parseBatch`, `auditParallel`, `computeLedger`).
|
|
30
|
+
4. **Gil-Free Concurrency**: All heavy batch loops (`parseBatch`, `decomposeBatch`, `auditParallel`) release CPython's Global Interpreter Lock (GIL) via `py::gil_scoped_release`, allowing true multi-core parallel scaling across hardware threads.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 2. Ingestion Strategies & Threading Models
|
|
35
|
+
|
|
36
|
+
CPython's Global Interpreter Lock ordinarily constrains CPU-bound Python workloads to a single core. `pyrazobill` bypasses this barrier by delegating batch processing directly to its C++20 engine with the GIL released.
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
┌────────────────────────────────────────────────────────────────────────────────────────┐
|
|
40
|
+
│ GIL RELEASE & NATIVE THREAD POOL PIPELINE │
|
|
41
|
+
└────────────────────────────────────────────────────────────────────────────────────────┘
|
|
42
|
+
Python Main Process: `pyrazobill.parseBatch(msgs)` or `decomposeBatch(msgs)`
|
|
43
|
+
│
|
|
44
|
+
▼
|
|
45
|
+
┌────────────────────────────────────────────────────────────────────────────┐
|
|
46
|
+
│ py::gil_scoped_release (CPython GIL Dropped — Other Python Threads Free) │
|
|
47
|
+
└──────────────────────────────────────┬─────────────────────────────────────┘
|
|
48
|
+
│
|
|
49
|
+
┌───────────────────────┼───────────────────────┐
|
|
50
|
+
▼ ▼ ▼
|
|
51
|
+
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
|
52
|
+
│ Worker 0 (Chunk 0) │ │ Worker 1 (Chunk 1) │ │ Worker N (Chunk N) │
|
|
53
|
+
├─────────────────────┤ ├─────────────────────┤ ├─────────────────────┤
|
|
54
|
+
│ SIMD AVX2 Parse │ │ SIMD AVX2 Parse │ │ SIMD AVX2 Parse │
|
|
55
|
+
│ Local Bump Arena │ │ Local Bump Arena │ │ Local Bump Arena │
|
|
56
|
+
└──────────┬──────────┘ └──────────┬──────────┘ └──────────┬──────────┘
|
|
57
|
+
│ │ │
|
|
58
|
+
└───────────────────────┼───────────────────────┘
|
|
59
|
+
▼
|
|
60
|
+
┌────────────────────────────────────────────────────────────────────────────┐
|
|
61
|
+
│ Thread Pool Aggregate: Combined Slot Records / Hashes (Zero Mutex Jitter) │
|
|
62
|
+
└──────────────────────────────────────┬─────────────────────────────────────┘
|
|
63
|
+
│
|
|
64
|
+
▼
|
|
65
|
+
┌────────────────────────────────────────────────────────────────────────────┐
|
|
66
|
+
│ py::gil_scoped_acquire (GIL Reacquired — Memory Safe PySlotRecord Returned)│
|
|
67
|
+
└────────────────────────────────────────────────────────────────────────────┘
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```mermaid
|
|
71
|
+
flowchart TD
|
|
72
|
+
PythonCaller["Python Process (Main Thread)"] -->|"Lines (List of Strings)"| GilRelease["Pybind11: py::gil_scoped_release"]
|
|
73
|
+
|
|
74
|
+
subgraph NativeCore["librazobill Thread Pool"]
|
|
75
|
+
Worker0["Worker Thread 0: Chunk 0"]
|
|
76
|
+
Worker1["Worker Thread 1: Chunk 1"]
|
|
77
|
+
WorkerN["Worker Thread N: Chunk N"]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
GilRelease --> NativeCore
|
|
81
|
+
Worker0 -->|"Local SIMD Parse & Hash"| LocalSlots0["Local Scratch Arena"]
|
|
82
|
+
Worker1 -->|"Local SIMD Parse & Hash"| LocalSlots1["Local Scratch Arena"]
|
|
83
|
+
WorkerN -->|"Local SIMD Parse & Hash"| LocalSlotsN["Local Scratch Arena"]
|
|
84
|
+
|
|
85
|
+
LocalSlots0 --> Aggregate["Combine Global Metrics"]
|
|
86
|
+
LocalSlots1 --> Aggregate
|
|
87
|
+
LocalSlotsN --> Aggregate
|
|
88
|
+
|
|
89
|
+
Aggregate --> GilReacquire["Reacquire GIL"]
|
|
90
|
+
GilReacquire --> Return["Return Python AuditResult"]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Ingestion API Catalog:
|
|
94
|
+
|
|
95
|
+
| Use Case | Recommended API | GIL Released? | Scaling Characteristic |
|
|
96
|
+
| :--- | :--- | :---: | :--- |
|
|
97
|
+
| Single event processing (e.g., HTTP webhook) | `parse(msg, mode)` | No | Sub-microsecond ($0.1\,\mu\text{s}$) latency, returns `SlotRecord` |
|
|
98
|
+
| Micro-batch streaming (e.g., Kafka consumer) | `parseBatch(msgs, mode)` | **Yes** | Multi-threaded record vector creation |
|
|
99
|
+
| Direct tuple extraction (single event) | `decompose(msg, mode)` | No | Returns `(formKey, ints, strs)` without wrapper allocation |
|
|
100
|
+
| High-throughput columnar batch ingestion | `decomposeBatch(msgs, mode)` | **Yes** | Returns `list[tuple]`, releases GIL, 515k–737k+ logs/sec |
|
|
101
|
+
| Symmetrical tuple reconstruction | `reconstruct(formKey, ints, strs)` | No | Exact bit-for-bit raw log string recovery |
|
|
102
|
+
| Large-scale file or corpus audit | `auditParallel(lines, mode, numThreads)` | **Yes** | Hardware thread pool, zero allocations, up to 69.4M msgs/sec |
|
|
103
|
+
| Lightweight template key lookup | `formKey(msg, mode)` | No | Direct string return |
|
|
104
|
+
| Information Ledger calculation | `computeLedger(cells, totalEvents, alpha)` | No | Theorem 1 & 4 balance identities with finite-sample bounds |
|
|
105
|
+
| Consequence decomposition | `consequenceDecomposition(xy, map)` | No | Theorem 6 diagnostic sufficiency decomposition |
|
|
106
|
+
| Finite-sample bias correction | `millerMadow(plugin, categories, n)` | No | First-order asymptotic bias compensation |
|
|
107
|
+
| Proposition 10 certified bounds | `certifiedInterval(k, n, alpha)` | No | Finite-sample confidence interval |
|
|
108
|
+
| Steganographic capacity audit | `channelAnalyzeCapacity(sequence)` | No | Theorem 4c safe payload bits calculation |
|
|
109
|
+
| Dynamic log watermarking | `channelEmbed(seq, msg, bits, key)` | No | Arithmetic cumulative interval embedding |
|
|
110
|
+
| Watermark verification & extraction | `channelDecode(seq, forms, bits, key)` | No | Arithmetic decoding from observed form indices |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 3. Step-by-Step Code Walkthroughs
|
|
115
|
+
|
|
116
|
+
### Recipe A: Streaming Micro-Batch Ingestion
|
|
117
|
+
Ingesting events from a Kafka topic, Redis Stream, or AWS SQS consumer:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import pyrazobill
|
|
121
|
+
|
|
122
|
+
def processStreamBatch(eventBatch: list[str]) -> list[dict]:
|
|
123
|
+
# parseBatch releases GIL while parsing hundreds or thousands of messages
|
|
124
|
+
records = pyrazobill.parseBatch(eventBatch, pyrazobill.ScanMode.Semantic)
|
|
125
|
+
|
|
126
|
+
structuredPayloads = []
|
|
127
|
+
for rec in records:
|
|
128
|
+
structuredPayloads.append({
|
|
129
|
+
"formKey": rec.formKey,
|
|
130
|
+
"formHash": rec.formKeyHash,
|
|
131
|
+
"ints": rec.ints,
|
|
132
|
+
"strs": rec.strs,
|
|
133
|
+
})
|
|
134
|
+
return structuredPayloads
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### Recipe B: Multi-Core Ingestion and Lossless Verification
|
|
140
|
+
Processing a 1,000,000-line server log file and auditing exact lossless reconstruction across all hardware threads:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
import sys
|
|
144
|
+
import time
|
|
145
|
+
from pathlib import Path
|
|
146
|
+
import pyrazobill
|
|
147
|
+
|
|
148
|
+
def auditLogFile(filePath: Path):
|
|
149
|
+
print(f"Reading {filePath.name}...")
|
|
150
|
+
t0 = time.perf_counter()
|
|
151
|
+
with open(filePath, "r", encoding="utf-8", errors="replace") as f:
|
|
152
|
+
lines = [line.rstrip("\r\n") for line in f if line.strip()]
|
|
153
|
+
tRead = time.perf_counter() - t0
|
|
154
|
+
print(f"Loaded {len(lines):,} lines in {tRead:.2f}s")
|
|
155
|
+
|
|
156
|
+
# Audit across all hardware cores with zero GIL contention
|
|
157
|
+
res = pyrazobill.auditParallel(
|
|
158
|
+
lines,
|
|
159
|
+
mode=pyrazobill.ScanMode.Semantic,
|
|
160
|
+
numThreads=0 # auto-detect CPU count
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
print("\n--- Audit Results ---")
|
|
164
|
+
print(f"Lines Processed : {res.totalLines:,}")
|
|
165
|
+
print(f"Data Volume : {res.totalBytes / (1024**2):.2f} MB")
|
|
166
|
+
print(f"Processing Wall Time : {res.elapsedSec:.3f} s")
|
|
167
|
+
print(f"Throughput : {res.msgsPerSec:,.0f} msgs/s ({res.mbPerSec:.2f} MB/s)")
|
|
168
|
+
print(f"Unique Form Skeletons: {res.uniqueFormsCount:,}")
|
|
169
|
+
print(f"Bit Mismatches : {res.mismatches} (100% Exact)")
|
|
170
|
+
|
|
171
|
+
if res.mismatches == 0:
|
|
172
|
+
print("INVARIANT SATISFIED: Every message is 100% losslessly reconstructible.")
|
|
173
|
+
else:
|
|
174
|
+
raise RuntimeError(f"Reconstruction error: {res.mismatches} mismatches detected!")
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
auditLogFile(Path("server.log"))
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
### Recipe C: Parallel Stream Decomposition with ThreadPoolExecutor
|
|
183
|
+
When feeding high-throughput streaming events into columnar memory formats (DuckDB Appender, Apache Arrow, Polars, or Parquet), decomposing directly into tuples avoids Python object allocation. Releasing the GIL in `decomposeBatch()` allows Python worker threads to scale linearly:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
import concurrent.futures
|
|
187
|
+
import time
|
|
188
|
+
import pyrazobill
|
|
189
|
+
|
|
190
|
+
def streamWorkerParallel(batches: list[list[str]], numWorkers: int = 4) -> list[tuple]:
|
|
191
|
+
"""
|
|
192
|
+
Decomposes log batches concurrently across multiple worker threads.
|
|
193
|
+
Because pyrazobill.decomposeBatch releases the GIL, workers achieve true multi-core scaling.
|
|
194
|
+
"""
|
|
195
|
+
t0 = time.perf_counter()
|
|
196
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=numWorkers) as executor:
|
|
197
|
+
# Each worker processes a chunk of messages in C++
|
|
198
|
+
futures = list(executor.map(lambda b: pyrazobill.decomposeBatch(b, pyrazobill.ScanMode.Semantic), batches))
|
|
199
|
+
|
|
200
|
+
# Flatten results: list of (formKey, ints, strs) tuples
|
|
201
|
+
allDecomposed = [record for batch in futures for record in batch]
|
|
202
|
+
elapsed = time.perf_counter() - t0
|
|
203
|
+
|
|
204
|
+
totalCount = len(allDecomposed)
|
|
205
|
+
rate = totalCount / elapsed if elapsed > 0 else 0
|
|
206
|
+
print(f"Decomposed {totalCount:,} events in {elapsed:.3f}s ({rate:,.0f} logs/sec across {numWorkers} threads)")
|
|
207
|
+
return allDecomposed
|
|
208
|
+
|
|
209
|
+
# Example invocation
|
|
210
|
+
if __name__ == "__main__":
|
|
211
|
+
testEvents = [
|
|
212
|
+
f"Server node-{i%10} reported status {200 if i%5!=0 else 500} responseTime={i*3%150}ms client={10+i%5}.0.0.1"
|
|
213
|
+
for i in range(100_000)
|
|
214
|
+
]
|
|
215
|
+
chunkSize = 25_000
|
|
216
|
+
workChunks = [testEvents[i:i + chunkSize] for i in range(0, len(testEvents), chunkSize)]
|
|
217
|
+
|
|
218
|
+
decomposedTuples = streamWorkerParallel(workChunks, numWorkers=4)
|
|
219
|
+
# Validate lossless invertibility on sample
|
|
220
|
+
form, ints, strs = decomposedTuples[0]
|
|
221
|
+
assert pyrazobill.reconstruct(form, ints, strs) == testEvents[0]
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
### Recipe D: Lossless Storage Compaction with DuckDB Single-Pass Struct
|
|
227
|
+
Eliminating raw log strings to cut persistent storage requirements by ~45% while maintaining transparent querying.
|
|
228
|
+
|
|
229
|
+
> [!TIP]
|
|
230
|
+
> Use `rbDecomposeSemantic` or `rbDecompose` which returns a single composite `STRUCT(form VARCHAR, ints BIGINT[], strs VARCHAR[])`. This cuts parsing overhead by 66% compared to calling separate scalar functions.
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
import duckdb
|
|
234
|
+
import pyrazobill
|
|
235
|
+
|
|
236
|
+
# Initialize DuckDB connection
|
|
237
|
+
con = duckdb.connect("logs.duckdb", config={"allow_unsigned_extensions": "true"})
|
|
238
|
+
|
|
239
|
+
# Dynamically load the native librazobill extension
|
|
240
|
+
extPath = pyrazobill.extensionPath().replace("\\", "/")
|
|
241
|
+
con.execute(f"LOAD '{extPath}';")
|
|
242
|
+
|
|
243
|
+
# 1. Create compressed physical storage table
|
|
244
|
+
con.execute("""
|
|
245
|
+
CREATE TABLE IF NOT EXISTS logsPhysical (
|
|
246
|
+
timestamp TIMESTAMP,
|
|
247
|
+
level VARCHAR,
|
|
248
|
+
form VARCHAR,
|
|
249
|
+
ints BIGINT[],
|
|
250
|
+
strs VARCHAR[]
|
|
251
|
+
);
|
|
252
|
+
""")
|
|
253
|
+
|
|
254
|
+
# 2. Ingest raw log strings into decomposed columnar primitives via single-pass struct
|
|
255
|
+
rawEvents = [
|
|
256
|
+
("2026-10-07 12:00:01", "INFO", "User 1042 authenticated from 192.168.1.10"),
|
|
257
|
+
("2026-10-07 12:00:02", "WARN", "Connection timeout on port 5432 after 3 retries"),
|
|
258
|
+
("2026-10-07 12:00:03", "ERROR", "Failed to bind socket 8080: address in use"),
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
con.executemany("""
|
|
262
|
+
INSERT INTO logsPhysical
|
|
263
|
+
SELECT
|
|
264
|
+
CAST(? AS TIMESTAMP),
|
|
265
|
+
?,
|
|
266
|
+
d.form,
|
|
267
|
+
d.ints,
|
|
268
|
+
d.strs
|
|
269
|
+
FROM (SELECT rbDecomposeSemantic(?) AS d);
|
|
270
|
+
""", [(ts, lvl, msg) for ts, lvl, msg in rawEvents])
|
|
271
|
+
|
|
272
|
+
# 3. Create transparent zero-copy reconstruction view
|
|
273
|
+
con.execute("""
|
|
274
|
+
CREATE OR REPLACE VIEW logs AS
|
|
275
|
+
SELECT
|
|
276
|
+
timestamp,
|
|
277
|
+
level,
|
|
278
|
+
form,
|
|
279
|
+
rbReconstruct(form, ints, strs) AS msg
|
|
280
|
+
FROM logsPhysical;
|
|
281
|
+
""")
|
|
282
|
+
|
|
283
|
+
# 4. Analysts query 'logs' naturally as if raw strings were stored on disk
|
|
284
|
+
results = con.execute("SELECT timestamp, level, msg FROM logs WHERE level = 'WARN';").fetchall()
|
|
285
|
+
for row in results:
|
|
286
|
+
print(row)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
### Recipe E: Information-Theoretic Entropy Monitoring
|
|
292
|
+
Detecting operational anomalies and schema drift using compensated Shannon entropy:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
from collections import Counter
|
|
296
|
+
import pyrazobill
|
|
297
|
+
|
|
298
|
+
def computeClusterEntropy(records: list[pyrazobill.SlotRecord]) -> float:
|
|
299
|
+
# Tally frequency of each structural form key
|
|
300
|
+
counts = Counter(r.formKey for r in records)
|
|
301
|
+
frequencyVector = [float(c) for c in counts.values()]
|
|
302
|
+
|
|
303
|
+
# Calculate Shannon entropy with Neumaier floating-point compensation
|
|
304
|
+
bits = pyrazobill.entropy(frequencyVector)
|
|
305
|
+
return bits
|
|
306
|
+
|
|
307
|
+
# Baseline observation window
|
|
308
|
+
normalLogs = [
|
|
309
|
+
"GET /api/v1/health 200 OK",
|
|
310
|
+
"GET /api/v1/health 200 OK",
|
|
311
|
+
"GET /api/v1/health 200 OK",
|
|
312
|
+
"POST /api/v1/items 201 Created",
|
|
313
|
+
]
|
|
314
|
+
hNormal = computeClusterEntropy([pyrazobill.parse(m) for m in normalLogs])
|
|
315
|
+
print(f"Normal Operational Entropy: {hNormal:.4f} bits")
|
|
316
|
+
|
|
317
|
+
# Anomaly window (sudden emergence of novel error templates)
|
|
318
|
+
anomalyLogs = [
|
|
319
|
+
"GET /api/v1/health 200 OK",
|
|
320
|
+
"Internal server error: DB connection pool exhausted",
|
|
321
|
+
"Kernel OOM killer invoked for pid 9481",
|
|
322
|
+
"Segfault at 0x7fff0032 in libnative.so",
|
|
323
|
+
]
|
|
324
|
+
hAnomaly = computeClusterEntropy([pyrazobill.parse(m) for m in anomalyLogs])
|
|
325
|
+
print(f"Anomaly Operational Entropy: {hAnomaly:.4f} bits")
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### Recipe F: Information Ledger & Finite-Sample Correction
|
|
331
|
+
Auditing the information balance of a log cluster according to Theorems 1 and 4 from *Latitude and Doubt* (Atta, 2026):
|
|
332
|
+
|
|
333
|
+
$$\Lambda(Y \mid X) = H(Y \mid X) - \Delta(Y \mid X)$$
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
import pyrazobill
|
|
337
|
+
|
|
338
|
+
# Define joint observation counts between form templates and underlying semantic meanings
|
|
339
|
+
cells = [
|
|
340
|
+
pyrazobill.Cell(form=1, meaning=1, mass=500.0),
|
|
341
|
+
pyrazobill.Cell(form=2, meaning=1, mass=300.0), # Synonymous form
|
|
342
|
+
pyrazobill.Cell(form=3, meaning=2, mass=200.0),
|
|
343
|
+
]
|
|
344
|
+
totalEvents = 1000.0
|
|
345
|
+
|
|
346
|
+
# Compute exact information ledger with 95% confidence bounds
|
|
347
|
+
ledger = pyrazobill.computeLedger(cells, totalEvents, confidenceAlpha=0.05)
|
|
348
|
+
|
|
349
|
+
print(f"Coverage Ratio : {ledger.coverage:.4f}")
|
|
350
|
+
print(f"Content H(Y) : {ledger.content:.4f} bits")
|
|
351
|
+
print(f"Expression H(X) : {ledger.expression:.4f} bits")
|
|
352
|
+
print(f"Latitude Lambda(Y|X) : {ledger.latitude:.4f} bits")
|
|
353
|
+
print(f"Doubt Delta(Y|X) : {ledger.doubt:.4f} bits")
|
|
354
|
+
print(f"Conveyance I(X;Y) : {ledger.conveyance:.4f} bits")
|
|
355
|
+
print(f"Variation Distance : {ledger.distance:.4f} bits")
|
|
356
|
+
|
|
357
|
+
# Verify fundamental mathematical invariants
|
|
358
|
+
assert ledger.satisfiesLedgerIdentities()
|
|
359
|
+
assert ledger.satisfiesVocabularyLaw(nDistinctForms=3)
|
|
360
|
+
|
|
361
|
+
# Evaluate Theorem 6 diagnostic sufficiency
|
|
362
|
+
xy = [
|
|
363
|
+
pyrazobill.XY(form=1, y=1, n=500.0),
|
|
364
|
+
pyrazobill.XY(form=2, y=1, n=300.0),
|
|
365
|
+
pyrazobill.XY(form=3, y=2, n=200.0),
|
|
366
|
+
]
|
|
367
|
+
mapping = [
|
|
368
|
+
pyrazobill.FormMeaning(form=1, meaning=1, p=1.0),
|
|
369
|
+
pyrazobill.FormMeaning(form=2, meaning=1, p=1.0),
|
|
370
|
+
pyrazobill.FormMeaning(form=3, meaning=2, p=1.0),
|
|
371
|
+
]
|
|
372
|
+
decomp = pyrazobill.consequenceDecomposition(xy, mapping)
|
|
373
|
+
print(f"Consequence Decomposition: E={decomp.expression:.4f}, I={decomp.consequence:.4f}, H_M={decomp.hedge:.4f}")
|
|
374
|
+
assert decomp.satisfiesIdentity()
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
### Recipe G: Latitude Channel Capacity & Provenance Watermarking
|
|
380
|
+
Embedding tamper-evident provenance watermarks or tracing metadata into dynamic log streams using synonym selection without alerting downstream consumers (Theorem 4c):
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
import pyrazobill
|
|
384
|
+
|
|
385
|
+
# Define sequence of semantic events with available synonymous log forms
|
|
386
|
+
seq = [
|
|
387
|
+
pyrazobill.MeaningChoice(
|
|
388
|
+
meaning=101,
|
|
389
|
+
options=[
|
|
390
|
+
pyrazobill.SynonymousOption(formIndex=0, probability=0.5), # "User auth succeeded"
|
|
391
|
+
pyrazobill.SynonymousOption(formIndex=1, probability=0.5), # "Authentication OK for user"
|
|
392
|
+
]
|
|
393
|
+
)
|
|
394
|
+
for _ in range(32)
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
# 1. Analyze safe steganographic transmission capacity
|
|
398
|
+
capacity = pyrazobill.channelAnalyzeCapacity(seq)
|
|
399
|
+
print(f"Mean Capacity: {capacity.meanCapacityBits:.2f} bits")
|
|
400
|
+
print(f"Safe Payload : {capacity.safePayloadBits} bits")
|
|
401
|
+
|
|
402
|
+
# 2. Embed a 16-bit payload (e.g. cluster node ID = 42) into form selections
|
|
403
|
+
payload = 42
|
|
404
|
+
sharedKey = 0.81726354
|
|
405
|
+
res = pyrazobill.channelEmbed(seq, payload, payloadBits=16, sharedKey=sharedKey)
|
|
406
|
+
assert res.success
|
|
407
|
+
print(f"Chosen Form Indices: {res.chosenForms[:8]}...")
|
|
408
|
+
|
|
409
|
+
# 3. Decode payload from observed forms on downstream aggregator
|
|
410
|
+
dec = pyrazobill.channelDecode(seq, res.chosenForms, payloadBits=16, sharedKey=sharedKey)
|
|
411
|
+
assert dec.success
|
|
412
|
+
assert dec.message == payload
|
|
413
|
+
print(f"Successfully decoded watermark payload: {dec.message}")
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## 4. ClickHouse chDB Compatibility
|
|
419
|
+
|
|
420
|
+
`librazobill` uses xxHash64 (`hash64`) which is byte-for-byte identical to ClickHouse's native `xxHash64()` scalar function:
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
import chdb
|
|
424
|
+
import pyrazobill
|
|
425
|
+
|
|
426
|
+
msg = "Service started on port 8080"
|
|
427
|
+
pyHash = pyrazobill.hash64(msg)
|
|
428
|
+
|
|
429
|
+
chRes = chdb.query(f"SELECT xxHash64('{msg}') AS h;").to_dict()
|
|
430
|
+
chHash = chRes["h"][0]
|
|
431
|
+
|
|
432
|
+
assert pyHash == chHash
|
|
433
|
+
print(f"Parity Verified: pyrazobill ({pyHash}) == chDB ({chHash})")
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 5. Summary Cheat Sheet: API & Properties
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
# Scan Modes
|
|
442
|
+
pyrazobill.ScanMode.Exact
|
|
443
|
+
pyrazobill.ScanMode.DigitOnly
|
|
444
|
+
pyrazobill.ScanMode.Semantic
|
|
445
|
+
|
|
446
|
+
# Core Parsing Functions
|
|
447
|
+
record = pyrazobill.parse(msg, mode) # -> SlotRecord
|
|
448
|
+
records = pyrazobill.parseBatch(msgs, mode) # -> list[SlotRecord] (releases GIL)
|
|
449
|
+
form, ints, strs = pyrazobill.decompose(msg, mode) # -> tuple[str, list[int], list[str]]
|
|
450
|
+
tuples = pyrazobill.decomposeBatch(msgs, mode) # -> list[tuple] (releases GIL)
|
|
451
|
+
orig = pyrazobill.reconstruct(record) # -> str
|
|
452
|
+
orig = pyrazobill.reconstruct(form, ints, strs) # -> str
|
|
453
|
+
key = pyrazobill.formKey(msg, mode) # -> str
|
|
454
|
+
tmpl = pyrazobill.templateOf(form) # -> str (converts to <*>)
|
|
455
|
+
h64 = pyrazobill.hash64(str) # -> int (xxHash64)
|
|
456
|
+
ent = pyrazobill.entropy(counts) # -> float (Neumaier-compensated bits)
|
|
457
|
+
audit = pyrazobill.auditParallel(lines, mode, threads) # -> AuditResult (multi-threaded, releases GIL)
|
|
458
|
+
|
|
459
|
+
# Statistical & Information Theory Functions
|
|
460
|
+
ledger = pyrazobill.computeLedger(cells, totalEvents, alpha)
|
|
461
|
+
decomp = pyrazobill.consequenceDecomposition(xy, mapping)
|
|
462
|
+
hCorrected = pyrazobill.millerMadow(pluginEntropy, k, n)
|
|
463
|
+
eps, halfW = pyrazobill.certifiedInterval(k, n, alpha)
|
|
464
|
+
|
|
465
|
+
# Latitude Steganographic Channel Functions
|
|
466
|
+
cap = pyrazobill.channelAnalyzeCapacity(sequence)
|
|
467
|
+
emb = pyrazobill.channelEmbed(sequence, msg, bits, key, dither)
|
|
468
|
+
dec = pyrazobill.channelDecode(sequence, observedForms, bits, key)
|
|
469
|
+
|
|
470
|
+
# SlotRecord Properties
|
|
471
|
+
record.formKey # -> str (e.g. 'User {} logged in')
|
|
472
|
+
record.formKeyHash # -> int (64-bit uint)
|
|
473
|
+
record.ints # -> list[int]
|
|
474
|
+
record.strs # -> list[str]
|
|
475
|
+
record.skeleton # -> str
|
|
476
|
+
record.types # -> str (e.g. 'IS')
|
|
477
|
+
record.reconstruct() # -> str (rebuilds original string)
|
|
478
|
+
|
|
479
|
+
# AuditResult Properties
|
|
480
|
+
audit.totalLines # -> int
|
|
481
|
+
audit.totalBytes # -> int
|
|
482
|
+
audit.elapsedSec # -> float
|
|
483
|
+
audit.uniqueFormsCount # -> int
|
|
484
|
+
audit.msgsPerSec # -> float
|
|
485
|
+
audit.mbPerSec # -> float
|
|
486
|
+
audit.mismatches # -> int (always 0 in valid runs)
|
|
487
|
+
```
|