diff --git a/Cargo.lock b/Cargo.lock index 8cda8ba..51dd4c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,6 +94,13 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "parity" +version = "0.1.0" +dependencies = [ + "textkit", +] + [[package]] name = "proc-macro2" version = "1.0.107" diff --git a/Cargo.toml b/Cargo.toml index a5c05e3..10c55d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["crates/*"] +members = ["crates/*", "parity"] [workspace.package] version = "0.1.0" diff --git a/parity/Cargo.toml b/parity/Cargo.toml new file mode 100644 index 0000000..40aa71c --- /dev/null +++ b/parity/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "parity" +description = "Differential tests against the Python implementations these tools were ported from." +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +publish = false + +[features] +# Re-runs the Python oracle during the test instead of trusting the golden +# files. Off by default so the suite passes on a machine with no Python. +python = [] + +[dependencies] +textkit.workspace = true diff --git a/parity/KNOWN.md b/parity/KNOWN.md new file mode 100644 index 0000000..aea0ade --- /dev/null +++ b/parity/KNOWN.md @@ -0,0 +1,21 @@ +# Known divergences + +Places where the Rust and the Python deliberately do not agree, each with the +reason it is acceptable. A divergence that is not written down here is a bug, +and loosening a test instead of adding an entry here is how a port rots. + +Format: + +``` +## — + +**Python:** what it does. +**Rust:** what it does instead. +**Why this is acceptable:** the argument, including what input class is +affected and how often it occurs in practice. +**Measured:** how many records out of how many differ. +``` + +--- + +None recorded yet. diff --git a/parity/README.md b/parity/README.md new file mode 100644 index 0000000..ef9ebc2 --- /dev/null +++ b/parity/README.md @@ -0,0 +1,53 @@ +# parity + +Differential tests against the Python implementations these tools were ported +from. + +A reimplementation that is 98% right is worse than no reimplementation, +because the missing 2% is silent: a chunk boundary moves, a verdict flips on +one class of file, and nothing fails — the index is just quietly worse. These +tests exist to make that loud. + +## How it works + +Each ported function has a **golden file** under `golden/`, holding what the +Python produced for a fixture. The default test run compares Rust against +those files and needs nothing but Rust: + +```sh +cargo test -p parity +``` + +With an interpreter that has the Python dependencies installed, the oracles +can be re-run live, which catches a golden file drifting away from a Python +implementation that has moved on: + +```sh +CATENA_PARITY_PYTHON=/path/to/venv/bin/python cargo test -p parity --features python +``` + +## Regenerating + +Golden files are never regenerated by a test — a golden file that rewrites +itself when it fails is a test that cannot fail. Regenerate deliberately, +read the diff, and commit it with the reason: + +```sh +CATENA_PARITY_PYTHON=/path/to/venv/bin/python cargo run -p parity --bin regen +``` + +## Fixtures + +Generated, synthetic, or public domain — nothing else. `src/sample.rs` +generates its fixture deterministically from a fixed seed, so both sides +demonstrably read the same bytes and the clone stays small. + +Where a heuristic was tuned against copyrighted text, commit the **derived +signal** and not the text: the page character counts, the ASCII scores, the +word-shape tuples, the cue timings. A parity test over a text-layer classifier +needs the score inputs, not the book. + +## Divergences + +Where Rust and Python legitimately differ, record it in `KNOWN.md` with the +reason. Do not loosen a test to accommodate one. diff --git a/parity/golden/tokens.counts b/parity/golden/tokens.counts new file mode 100644 index 0000000..185bfbf --- /dev/null +++ b/parity/golden/tokens.counts @@ -0,0 +1,10002 @@ +# cl100k_base token counts, one per line of parity::sample::standard() +# seed=0x636174656e610001 lines=10000 +7 +10 +24 +0 +14 +5 +98 +10 +94 +6 +8 +7 +6 +0 +6 +61 +5 +14 +14 +7 +1 +107 +14 +21 +8 +12 +14 +2 +10 +10 +19 +13 +18 +44 +0 +15 +6 +7 +20 +8 +29 +9 +11 +8 +8 +16 +14 +11 +13 +7 +8 +9 +16 +0 +5 +0 +68 +19 +7 +22 +17 +9 +11 +21 +16 +55 +0 +1 +6 +20 +5 +5 +15 +17 +8 +9 +19 +111 +6 +9 +9 +6 +0 +5 +29 +12 +17 +11 +21 +18 +13 +93 +0 +20 +9 +13 +0 +0 +9 +15 +19 +26 +22 +13 +56 +11 +2 +14 +9 +8 +24 +0 +7 +6 +6 +3 +14 +16 +12 +19 +4 +4 +9 +51 +30 +3 +91 +11 +13 +14 +8 +7 +9 +4 +14 +11 +7 +27 +11 +95 +4 +14 +17 +16 +2 +10 +8 +11 +5 +11 +15 +14 +6 +67 +6 +5 +7 +8 +7 +5 +9 +28 +11 +5 +6 +8 +4 +7 +14 +7 +4 +8 +6 +10 +20 +20 +0 +17 +97 +14 +12 +0 +10 +21 +89 +6 +13 +7 +66 +9 +8 +9 +5 +20 +6 +6 +14 +0 +0 +8 +11 +14 +7 +27 +10 +11 +7 +8 +5 +14 +24 +0 +13 +6 +100 +11 +19 +3 +9 +9 +56 +5 +12 +5 +6 +6 +11 +6 +10 +10 +69 +21 +14 +18 +14 +11 +8 +7 +5 +12 +58 +11 +11 +15 +89 +9 +10 +0 +0 +23 +4 +30 +0 +7 +29 +7 +103 +0 +6 +4 +9 +14 +10 +14 +0 +11 +70 +9 +5 +0 +11 +0 +5 +16 +0 +10 +28 +15 +3 +20 +7 +21 +20 +4 +109 +0 +0 +17 +13 +6 +25 +0 +11 +0 +11 +14 +0 +93 +13 +16 +11 +14 +3 +17 +4 +6 +13 +4 +4 +11 +9 +5 +52 +8 +100 +10 +24 +2 +4 +3 +4 +5 +21 +10 +2 +6 +20 +3 +19 +29 +7 +16 +8 +6 +10 +11 +0 +7 +0 +65 +14 +15 +8 +16 +20 +11 +11 +35 +5 +12 +6 +5 +5 +7 +12 +6 +6 +27 +9 +53 +21 +63 +7 +5 +8 +10 +21 +10 +12 +0 +7 +0 +6 +0 +0 +6 +16 +13 +7 +7 +10 +93 +12 +8 +29 +85 +8 +12 +21 +0 +6 +5 +11 +16 +0 +8 +84 +6 +9 +91 +21 +13 +8 +23 +6 +19 +6 +8 +69 +0 +6 +0 +23 +10 +7 +10 +0 +0 +6 +13 +17 +11 +26 +4 +11 +104 +75 +10 +10 +56 +11 +1 +5 +41 +117 +21 +10 +9 +6 +10 +7 +10 +13 +0 +10 +9 +3 +5 +14 +4 +12 +10 +5 +8 +10 +24 +13 +104 +9 +8 +18 +88 +0 +6 +61 +5 +17 +8 +0 +4 +10 +10 +3 +14 +102 +6 +4 +11 +15 +13 +14 +13 +6 +13 +21 +20 +12 +2 +6 +10 +10 +8 +5 +10 +6 +9 +8 +14 +8 +20 +5 +45 +6 +18 +2 +7 +10 +82 +13 +8 +0 +7 +6 +13 +81 +9 +46 +5 +12 +53 +18 +7 +7 +7 +6 +0 +2 +7 +7 +6 +21 +9 +10 +0 +22 +10 +0 +5 +11 +7 +7 +5 +22 +0 +11 +74 +75 +12 +0 +13 +12 +10 +12 +1 +97 +11 +12 +18 +113 +4 +58 +14 +75 +14 +27 +13 +9 +99 +21 +3 +24 +5 +11 +5 +6 +14 +0 +8 +9 +14 +15 +95 +28 +25 +14 +7 +24 +12 +12 +97 +6 +5 +99 +10 +10 +16 +4 +11 +11 +15 +29 +8 +4 +49 +0 +22 +6 +16 +0 +0 +6 +5 +11 +8 +12 +8 +15 +9 +10 +30 +5 +6 +7 +17 +8 +14 +14 +24 +7 +6 +102 +5 +5 +96 +7 +0 +2 +14 +11 +18 +114 +64 +14 +7 +10 +20 +5 +14 +14 +9 +13 +0 +14 +26 +15 +0 +14 +10 +4 +6 +6 +0 +6 +0 +6 +10 +3 +0 +25 +10 +7 +12 +13 +25 +11 +18 +7 +10 +17 +5 +11 +6 +21 +24 +6 +10 +6 +7 +13 +6 +55 +0 +9 +5 +15 +0 +12 +7 +0 +0 +25 +15 +12 +10 +11 +9 +17 +5 +41 +19 +14 +7 +101 +9 +0 +0 +8 +20 +0 +6 +0 +17 +5 +6 +14 +2 +12 +6 +24 +11 +56 +16 +50 +15 +10 +6 +0 +26 +10 +17 +0 +0 +7 +14 +10 +10 +12 +6 +7 +14 +8 +16 +18 +11 +8 +8 +4 +25 +7 +25 +19 +3 +4 +119 +14 +23 +16 +20 +7 +10 +18 +9 +18 +8 +8 +17 +19 +101 +60 +77 +15 +10 +16 +0 +10 +10 +23 +24 +0 +12 +24 +0 +33 +102 +76 +11 +6 +7 +44 +98 +9 +0 +1 +5 +5 +6 +14 +50 +11 +13 +5 +10 +8 +76 +5 +20 +21 +0 +10 +18 +31 +75 +8 +17 +51 +11 +11 +15 +79 +13 +87 +11 +12 +7 +13 +6 +57 +63 +5 +13 +14 +23 +96 +0 +10 +115 +0 +6 +11 +7 +7 +3 +13 +10 +12 +17 +2 +14 +22 +11 +18 +8 +6 +15 +12 +12 +7 +0 +20 +6 +9 +12 +30 +0 +10 +14 +11 +8 +14 +12 +12 +16 +10 +10 +12 +5 +13 +8 +4 +4 +23 +0 +0 +14 +10 +9 +20 +5 +16 +9 +7 +17 +20 +16 +4 +6 +7 +19 +5 +9 +11 +76 +52 +0 +26 +11 +2 +8 +22 +11 +13 +5 +7 +5 +8 +7 +6 +10 +0 +13 +3 +9 +102 +14 +6 +15 +13 +9 +14 +9 +8 +15 +7 +6 +9 +22 +0 +0 +12 +3 +23 +0 +87 +11 +82 +7 +10 +10 +6 +68 +24 +0 +0 +20 +14 +8 +12 +7 +9 +8 +8 +5 +6 +102 +18 +0 +4 +14 +10 +43 +3 +11 +9 +11 +2 +11 +59 +7 +7 +13 +62 +10 +6 +0 +27 +10 +4 +8 +0 +5 +6 +0 +16 +6 +24 +3 +6 +6 +12 +7 +7 +0 +11 +0 +10 +0 +4 +15 +6 +0 +9 +7 +48 +12 +8 +75 +15 +10 +14 +18 +71 +19 +92 +6 +19 +29 +9 +21 +6 +11 +9 +6 +7 +31 +14 +24 +25 +17 +83 +103 +0 +6 +12 +0 +7 +7 +15 +7 +11 +5 +9 +11 +7 +28 +6 +21 +8 +17 +7 +6 +4 +16 +13 +7 +6 +2 +16 +11 +90 +6 +8 +11 +6 +14 +0 +9 +8 +0 +10 +4 +0 +13 +11 +3 +7 +6 +91 +10 +8 +13 +5 +9 +6 +11 +3 +7 +82 +23 +35 +10 +0 +8 +80 +5 +10 +5 +0 +6 +30 +6 +7 +27 +6 +11 +20 +80 +104 +11 +0 +0 +12 +23 +0 +9 +6 +18 +23 +0 +7 +11 +75 +3 +27 +11 +58 +0 +79 +9 +14 +17 +42 +0 +6 +16 +8 +11 +17 +5 +29 +16 +13 +13 +31 +9 +10 +5 +6 +25 +21 +14 +11 +12 +10 +13 +28 +11 +6 +10 +92 +25 +10 +2 +11 +7 +6 +10 +14 +12 +23 +7 +7 +14 +19 +0 +10 +25 +11 +16 +14 +14 +16 +4 +1 +78 +10 +12 +10 +7 +0 +7 +8 +80 +16 +13 +4 +26 +9 +10 +7 +0 +18 +6 +15 +10 +11 +81 +10 +12 +0 +46 +17 +97 +7 +114 +7 +11 +11 +28 +7 +14 +10 +10 +8 +26 +6 +85 +4 +9 +104 +6 +0 +5 +8 +0 +23 +0 +10 +27 +6 +8 +4 +11 +8 +12 +9 +58 +14 +23 +11 +6 +5 +11 +34 +85 +7 +6 +7 +2 +69 +4 +6 +6 +6 +0 +14 +11 +6 +6 +73 +11 +17 +11 +79 +18 +10 +0 +11 +14 +5 +10 +3 +11 +9 +10 +35 +9 +13 +8 +16 +0 +0 +17 +17 +8 +6 +20 +13 +0 +5 +22 +6 +100 +20 +16 +4 +7 +4 +8 +28 +17 +81 +38 +14 +24 +8 +8 +14 +70 +23 +15 +10 +11 +56 +0 +6 +17 +6 +9 +11 +0 +6 +9 +6 +9 +14 +15 +0 +6 +11 +11 +21 +7 +51 +10 +5 +3 +12 +7 +88 +10 +9 +5 +0 +0 +11 +13 +9 +0 +3 +2 +18 +5 +5 +8 +8 +100 +7 +0 +7 +13 +6 +0 +12 +10 +0 +25 +6 +10 +5 +7 +18 +10 +7 +103 +6 +101 +6 +0 +70 +6 +27 +8 +7 +14 +13 +12 +6 +24 +0 +6 +5 +16 +0 +11 +89 +0 +14 +20 +9 +10 +28 +8 +1 +7 +22 +19 +5 +26 +21 +7 +12 +6 +0 +8 +6 +27 +93 +10 +7 +4 +0 +0 +25 +7 +6 +9 +0 +13 +6 +6 +11 +10 +10 +6 +98 +62 +22 +8 +76 +19 +12 +13 +16 +19 +18 +15 +6 +11 +5 +9 +8 +5 +8 +16 +0 +6 +13 +9 +12 +12 +9 +24 +19 +9 +0 +8 +6 +14 +7 +5 +19 +94 +7 +5 +8 +14 +0 +14 +17 +0 +7 +4 +0 +0 +10 +6 +13 +12 +119 +10 +0 +11 +14 +6 +0 +7 +48 +0 +6 +0 +92 +5 +11 +6 +5 +0 +7 +8 +0 +6 +8 +7 +0 +4 +4 +11 +6 +4 +9 +22 +96 +13 +4 +6 +14 +8 +10 +0 +22 +4 +6 +13 +5 +6 +16 +23 +67 +7 +0 +17 +12 +6 +11 +8 +21 +11 +14 +13 +6 +14 +39 +0 +8 +21 +7 +4 +13 +6 +0 +10 +27 +4 +27 +11 +11 +5 +12 +9 +6 +12 +6 +14 +9 +10 +13 +21 +7 +1 +5 +22 +0 +10 +11 +0 +25 +10 +28 +12 +0 +53 +7 +31 +5 +8 +39 +43 +19 +17 +8 +9 +72 +17 +4 +64 +5 +10 +0 +5 +8 +5 +10 +14 +14 +4 +2 +9 +7 +107 +2 +7 +5 +8 +7 +5 +69 +1 +23 +14 +25 +11 +4 +5 +5 +6 +4 +6 +13 +105 +15 +7 +7 +11 +111 +0 +17 +55 +0 +12 +10 +14 +6 +0 +2 +11 +0 +0 +24 +7 +5 +11 +6 +0 +9 +3 +6 +2 +14 +5 +14 +10 +11 +49 +3 +0 +8 +7 +12 +22 +10 +6 +16 +17 +4 +11 +78 +14 +6 +6 +6 +11 +19 +8 +0 +95 +22 +17 +13 +20 +0 +15 +28 +11 +26 +6 +7 +21 +8 +9 +2 +15 +8 +6 +20 +9 +4 +5 +6 +0 +12 +0 +0 +0 +4 +7 +6 +5 +61 +18 +6 +9 +25 +5 +13 +14 +6 +10 +9 +102 +6 +4 +26 +14 +8 +9 +5 +0 +22 +10 +17 +16 +6 +5 +7 +5 +87 +12 +11 +5 +0 +4 +4 +13 +13 +6 +10 +3 +5 +11 +70 +16 +10 +10 +5 +19 +0 +7 +6 +14 +98 +21 +6 +11 +14 +1 +16 +6 +5 +6 +14 +50 +12 +13 +10 +8 +25 +11 +0 +7 +5 +9 +11 +76 +0 +11 +11 +14 +15 +19 +23 +0 +4 +7 +0 +0 +84 +14 +14 +21 +3 +59 +9 +5 +8 +18 +0 +3 +5 +0 +14 +29 +0 +9 +52 +9 +82 +23 +0 +4 +9 +12 +8 +2 +17 +13 +121 +6 +7 +6 +95 +13 +4 +10 +4 +10 +13 +0 +5 +17 +5 +7 +8 +17 +10 +81 +4 +108 +12 +80 +7 +18 +131 +86 +15 +10 +7 +12 +0 +14 +7 +14 +8 +10 +20 +69 +110 +10 +11 +6 +9 +12 +5 +12 +13 +23 +21 +26 +4 +7 +6 +5 +15 +15 +77 +10 +0 +7 +25 +11 +3 +10 +24 +6 +15 +17 +7 +21 +21 +11 +9 +7 +11 +11 +9 +9 +104 +0 +6 +13 +14 +17 +8 +14 +0 +13 +21 +0 +1 +11 +9 +10 +8 +15 +36 +17 +17 +74 +24 +6 +9 +0 +11 +12 +20 +5 +11 +1 +29 +21 +12 +10 +14 +0 +10 +13 +86 +2 +2 +18 +49 +9 +15 +22 +6 +6 +10 +68 +7 +11 +83 +15 +87 +3 +0 +7 +26 +14 +14 +0 +3 +8 +11 +17 +3 +84 +7 +6 +12 +103 +4 +51 +7 +17 +121 +8 +0 +17 +12 +11 +44 +6 +10 +7 +27 +10 +10 +29 +7 +5 +11 +18 +17 +54 +8 +6 +42 +6 +12 +19 +108 +10 +14 +7 +0 +5 +44 +1 +13 +19 +14 +3 +9 +0 +11 +14 +3 +9 +9 +5 +9 +25 +56 +25 +18 +14 +6 +27 +4 +8 +10 +8 +7 +7 +11 +11 +2 +4 +22 +15 +16 +0 +11 +11 +10 +14 +6 +9 +26 +2 +7 +9 +0 +6 +16 +55 +9 +3 +7 +15 +24 +5 +14 +19 +23 +5 +11 +22 +41 +11 +8 +4 +15 +11 +0 +3 +0 +55 +13 +37 +3 +50 +39 +13 +7 +14 +14 +104 +8 +5 +8 +10 +26 +6 +23 +0 +24 +3 +15 +9 +22 +26 +12 +91 +49 +49 +17 +22 +6 +13 +0 +2 +18 +0 +72 +24 +15 +8 +4 +97 +5 +0 +10 +3 +24 +43 +9 +5 +5 +11 +3 +2 +1 +69 +51 +8 +3 +0 +9 +2 +6 +5 +7 +0 +11 +10 +8 +7 +13 +18 +14 +0 +101 +6 +0 +7 +5 +7 +4 +15 +14 +14 +7 +7 +10 +0 +14 +0 +10 +6 +19 +4 +22 +6 +23 +10 +15 +14 +12 +8 +13 +0 +14 +55 +100 +14 +2 +7 +4 +17 +0 +13 +16 +14 +21 +9 +53 +12 +8 +10 +18 +21 +28 +4 +10 +11 +14 +21 +19 +19 +68 +10 +10 +5 +70 +17 +90 +6 +20 +11 +13 +31 +12 +12 +10 +24 +20 +0 +30 +12 +22 +3 +6 +7 +71 +132 +13 +10 +6 +103 +0 +14 +8 +9 +19 +15 +11 +25 +5 +0 +10 +15 +5 +91 +111 +14 +8 +6 +36 +27 +11 +93 +0 +5 +6 +13 +66 +98 +19 +8 +97 +98 +10 +9 +13 +7 +86 +103 +0 +4 +12 +21 +6 +4 +6 +7 +0 +92 +10 +4 +6 +10 +14 +16 +19 +14 +6 +29 +3 +107 +3 +3 +24 +4 +7 +13 +3 +10 +14 +46 +0 +105 +0 +3 +10 +5 +5 +26 +8 +14 +0 +61 +14 +46 +73 +11 +9 +6 +14 +17 +6 +10 +11 +11 +60 +7 +8 +8 +8 +7 +80 +8 +0 +5 +0 +13 +11 +17 +0 +23 +30 +0 +63 +14 +5 +12 +0 +89 +13 +6 +10 +16 +6 +10 +13 +9 +13 +0 +0 +4 +10 +0 +6 +0 +7 +0 +0 +7 +6 +33 +10 +23 +106 +18 +10 +10 +0 +75 +0 +41 +59 +53 +73 +12 +4 +10 +7 +6 +17 +26 +29 +5 +24 +15 +5 +65 +0 +0 +16 +13 +10 +0 +14 +10 +18 +6 +28 +4 +11 +3 +0 +11 +11 +5 +11 +11 +7 +8 +0 +6 +26 +67 +0 +8 +12 +0 +3 +3 +5 +14 +76 +20 +0 +14 +87 +11 +90 +6 +5 +10 +11 +3 +7 +0 +54 +2 +102 +12 +15 +7 +17 +8 +124 +17 +5 +35 +16 +23 +0 +14 +10 +0 +0 +4 +27 +22 +8 +10 +12 +8 +0 +109 +11 +12 +6 +26 +10 +16 +13 +12 +11 +3 +16 +10 +1 +26 +120 +6 +0 +5 +2 +10 +6 +10 +28 +13 +13 +12 +1 +11 +14 +9 +12 +15 +20 +8 +3 +6 +9 +6 +9 +10 +5 +10 +7 +7 +7 +10 +14 +6 +6 +5 +13 +99 +19 +24 +13 +8 +23 +11 +14 +26 +4 +5 +7 +11 +7 +6 +6 +20 +8 +41 +9 +58 +13 +9 +10 +14 +10 +10 +90 +6 +0 +10 +14 +10 +6 +33 +6 +11 +0 +16 +6 +99 +10 +11 +2 +0 +9 +9 +6 +2 +11 +2 +5 +17 +7 +14 +8 +9 +21 +27 +8 +7 +39 +0 +12 +0 +27 +5 +14 +6 +22 +118 +16 +0 +12 +6 +21 +7 +13 +0 +9 +12 +20 +79 +14 +6 +8 +6 +69 +14 +10 +12 +0 +14 +0 +12 +9 +18 +14 +15 +6 +4 +14 +12 +4 +24 +81 +48 +12 +13 +8 +6 +17 +10 +18 +5 +18 +24 +14 +7 +11 +64 +14 +11 +25 +7 +84 +13 +14 +0 +0 +0 +82 +10 +3 +7 +7 +0 +7 +9 +12 +21 +5 +0 +5 +11 +14 +0 +22 +7 +14 +10 +0 +73 +9 +4 +25 +6 +7 +22 +8 +6 +6 +0 +0 +3 +7 +25 +16 +10 +12 +60 +11 +0 +20 +21 +13 +17 +10 +7 +8 +59 +12 +72 +23 +12 +6 +12 +11 +99 +14 +22 +8 +0 +11 +28 +10 +17 +28 +26 +0 +33 +18 +8 +0 +21 +71 +11 +9 +8 +5 +18 +11 +11 +11 +4 +9 +7 +51 +8 +0 +23 +9 +16 +97 +9 +8 +17 +2 +0 +22 +15 +15 +12 +7 +18 +0 +74 +7 +11 +6 +19 +107 +7 +7 +21 +1 +13 +15 +5 +11 +8 +18 +26 +7 +20 +15 +9 +9 +18 +3 +15 +7 +22 +0 +25 +10 +3 +72 +8 +7 +9 +6 +45 +7 +10 +1 +15 +6 +25 +0 +62 +24 +18 +10 +21 +3 +4 +15 +17 +14 +20 +9 +7 +12 +6 +75 +6 +6 +0 +15 +11 +5 +20 +7 +26 +11 +8 +6 +6 +6 +10 +2 +4 +6 +7 +15 +11 +0 +5 +13 +12 +13 +4 +9 +6 +5 +24 +0 +1 +7 +3 +6 +41 +5 +11 +19 +7 +24 +14 +13 +9 +14 +11 +7 +0 +2 +10 +9 +0 +6 +14 +6 +73 +12 +7 +93 +11 +20 +10 +10 +0 +6 +62 +17 +0 +0 +22 +16 +25 +9 +7 +0 +7 +4 +8 +0 +8 +24 +25 +8 +9 +0 +106 +24 +15 +3 +0 +1 +12 +68 +0 +8 +10 +11 +9 +11 +8 +22 +5 +5 +80 +3 +10 +6 +7 +11 +3 +21 +0 +29 +10 +7 +100 +79 +8 +6 +16 +6 +5 +10 +93 +13 +10 +7 +13 +8 +7 +9 +7 +17 +15 +7 +11 +14 +8 +81 +5 +0 +2 +16 +7 +6 +10 +6 +74 +17 +90 +49 +7 +6 +7 +1 +9 +63 +10 +57 +7 +18 +9 +11 +11 +8 +8 +0 +10 +13 +8 +7 +12 +10 +7 +0 +106 +17 +9 +23 +12 +6 +9 +0 +9 +0 +6 +15 +15 +77 +18 +36 +11 +7 +12 +0 +4 +10 +8 +20 +85 +13 +6 +0 +12 +4 +6 +22 +30 +20 +73 +13 +5 +52 +6 +4 +12 +0 +8 +4 +8 +7 +10 +3 +2 +3 +0 +7 +7 +14 +12 +4 +11 +36 +33 +36 +5 +10 +18 +0 +0 +14 +0 +1 +4 +119 +9 +5 +1 +7 +10 +10 +11 +5 +17 +10 +0 +11 +8 +7 +0 +23 +10 +22 +18 +5 +5 +6 +12 +0 +9 +17 +17 +8 +9 +13 +11 +9 +24 +13 +6 +7 +4 +8 +26 +17 +102 +47 +0 +66 +6 +73 +10 +3 +6 +16 +0 +0 +33 +8 +0 +8 +14 +16 +11 +6 +111 +5 +0 +93 +3 +6 +17 +7 +12 +9 +1 +4 +2 +6 +14 +22 +10 +10 +64 +16 +13 +106 +0 +20 +24 +10 +8 +26 +6 +16 +13 +73 +9 +13 +110 +97 +2 +6 +28 +5 +9 +6 +19 +9 +9 +12 +8 +9 +0 +10 +2 +9 +11 +11 +24 +30 +11 +8 +4 +6 +2 +6 +57 +0 +9 +12 +10 +112 +8 +6 +2 +7 +11 +0 +15 +4 +3 +11 +34 +5 +2 +5 +20 +24 +23 +18 +0 +17 +11 +13 +16 +16 +12 +11 +3 +4 +17 +11 +7 +27 +19 +18 +0 +9 +20 +13 +15 +7 +9 +5 +13 +35 +64 +10 +14 +5 +9 +11 +8 +14 +7 +0 +14 +0 +56 +12 +14 +4 +4 +21 +6 +12 +4 +19 +2 +5 +14 +4 +0 +7 +3 +19 +23 +3 +17 +44 +1 +8 +14 +9 +5 +6 +18 +10 +7 +6 +10 +16 +18 +117 +0 +12 +10 +27 +6 +67 +37 +101 +5 +11 +11 +7 +13 +89 +8 +18 +11 +14 +10 +12 +0 +10 +0 +7 +26 +6 +18 +9 +4 +4 +7 +26 +5 +17 +10 +81 +22 +14 +76 +9 +5 +7 +0 +7 +3 +3 +11 +14 +7 +0 +61 +11 +12 +16 +4 +0 +60 +5 +6 +4 +0 +21 +14 +11 +37 +0 +8 +21 +13 +5 +15 +3 +0 +11 +0 +14 +11 +15 +17 +1 +27 +5 +7 +9 +36 +20 +9 +9 +0 +105 +10 +6 +58 +24 +8 +14 +10 +3 +12 +7 +10 +2 +12 +19 +11 +0 +6 +14 +8 +6 +10 +0 +25 +6 +25 +12 +15 +5 +5 +7 +1 +13 +58 +2 +8 +11 +10 +7 +14 +18 +12 +13 +77 +5 +12 +47 +5 +6 +14 +14 +0 +10 +0 +5 +0 +40 +17 +53 +104 +26 +17 +92 +51 +17 +53 +4 +9 +117 +0 +0 +11 +1 +10 +4 +5 +0 +21 +9 +13 +2 +56 +8 +29 +75 +11 +9 +2 +11 +16 +16 +0 +11 +17 +27 +21 +13 +13 +0 +19 +15 +13 +20 +79 +53 +7 +9 +6 +8 +17 +13 +8 +14 +0 +14 +10 +6 +7 +82 +9 +17 +0 +20 +4 +13 +8 +6 +16 +0 +8 +34 +71 +9 +22 +22 +76 +2 +13 +20 +7 +9 +9 +12 +8 +8 +15 +53 +14 +80 +18 +29 +13 +6 +10 +17 +5 +14 +0 +26 +19 +114 +6 +0 +12 +3 +9 +3 +7 +13 +112 +8 +1 +4 +15 +5 +6 +0 +5 +19 +2 +14 +14 +11 +6 +30 +8 +10 +90 +6 +17 +6 +0 +12 +12 +9 +14 +0 +9 +17 +29 +14 +13 +10 +0 +7 +22 +81 +11 +12 +4 +0 +6 +10 +14 +5 +9 +1 +0 +6 +6 +11 +7 +3 +24 +16 +22 +5 +16 +91 +16 +5 +8 +5 +9 +55 +16 +7 +0 +8 +11 +22 +14 +26 +55 +11 +103 +7 +16 +16 +0 +10 +86 +91 +8 +55 +4 +0 +19 +4 +9 +0 +7 +0 +8 +0 +25 +13 +7 +15 +0 +0 +12 +9 +6 +14 +15 +11 +14 +6 +28 +12 +88 +4 +8 +0 +8 +9 +7 +0 +2 +10 +5 +7 +7 +10 +12 +13 +10 +7 +26 +7 +13 +19 +30 +10 +19 +7 +14 +3 +13 +11 +10 +30 +0 +3 +15 +7 +6 +0 +7 +16 +6 +6 +8 +11 +69 +9 +13 +0 +10 +51 +10 +14 +0 +86 +9 +22 +16 +18 +6 +54 +6 +24 +49 +7 +68 +7 +9 +8 +5 +0 +20 +5 +9 +24 +5 +5 +11 +7 +0 +6 +17 +20 +4 +8 +6 +7 +6 +7 +0 +0 +110 +14 +14 +7 +16 +12 +22 +9 +0 +9 +7 +6 +1 +2 +7 +11 +5 +8 +3 +16 +22 +14 +12 +0 +6 +8 +7 +60 +14 +21 +7 +6 +13 +8 +10 +0 +1 +25 +16 +12 +11 +5 +11 +10 +0 +22 +14 +0 +14 +10 +20 +69 +3 +51 +104 +46 +5 +6 +0 +5 +10 +20 +7 +8 +28 +7 +10 +15 +6 +0 +22 +11 +6 +7 +48 +6 +0 +48 +13 +19 +12 +0 +18 +0 +0 +0 +7 +41 +11 +8 +8 +23 +0 +0 +14 +0 +20 +7 +8 +8 +12 +10 +1 +7 +8 +7 +13 +76 +7 +14 +5 +9 +23 +11 +10 +14 +7 +25 +0 +5 +11 +9 +16 +5 +4 +0 +5 +11 +9 +4 +10 +8 +109 +6 +0 +27 +8 +11 +12 +8 +9 +8 +82 +22 +16 +10 +10 +0 +42 +94 +6 +15 +17 +12 +8 +16 +0 +12 +9 +4 +9 +8 +16 +77 +14 +5 +7 +14 +12 +6 +12 +14 +5 +6 +25 +9 +6 +0 +12 +7 +8 +26 +41 +12 +14 +19 +8 +7 +6 +9 +18 +15 +13 +6 +63 +0 +11 +7 +11 +24 +14 +11 +22 +14 +7 +8 +6 +24 +29 +11 +56 +16 +8 +6 +6 +6 +0 +7 +90 +100 +0 +11 +15 +6 +10 +12 +14 +11 +10 +11 +13 +22 +8 +5 +13 +7 +11 +15 +4 +16 +10 +6 +12 +6 +3 +76 +106 +7 +9 +14 +8 +7 +7 +6 +4 +6 +10 +110 +66 +7 +5 +12 +5 +53 +0 +96 +95 +10 +8 +14 +10 +5 +0 +17 +10 +9 +9 +2 +0 +6 +6 +0 +22 +17 +15 +82 +8 +10 +8 +9 +40 +12 +8 +6 +8 +13 +4 +62 +12 +11 +10 +21 +26 +28 +10 +90 +0 +14 +11 +22 +11 +9 +0 +13 +26 +6 +0 +3 +10 +0 +15 +5 +9 +14 +18 +7 +11 +8 +8 +0 +0 +3 +11 +18 +11 +8 +16 +9 +9 +82 +8 +6 +2 +4 +8 +5 +6 +0 +15 +0 +58 +13 +55 +27 +4 +11 +22 +0 +0 +19 +22 +15 +97 +14 +28 +0 +6 +9 +3 +98 +11 +6 +11 +63 +14 +6 +28 +4 +12 +0 +0 +4 +0 +3 +47 +26 +93 +0 +14 +22 +8 +21 +11 +20 +16 +6 +6 +20 +11 +7 +8 +8 +0 +11 +44 +7 +1 +0 +0 +40 +0 +10 +6 +7 +89 +7 +5 +30 +0 +5 +54 +0 +0 +28 +7 +5 +10 +8 +10 +6 +10 +8 +8 +2 +11 +14 +6 +6 +12 +9 +8 +10 +20 +69 +10 +8 +15 +11 +21 +0 +18 +15 +112 +0 +6 +6 +0 +20 +11 +22 +24 +25 +14 +9 +0 +16 +81 +0 +17 +59 +11 +9 +0 +8 +0 +7 +4 +7 +16 +6 +109 +0 +18 +21 +14 +0 +75 +7 +16 +13 +11 +24 +9 +13 +6 +10 +8 +7 +8 +7 +10 +13 +0 +10 +1 +87 +9 +9 +12 +7 +4 +0 +24 +21 +4 +1 +4 +16 +87 +17 +14 +10 +16 +16 +14 +0 +65 +11 +19 +15 +2 +11 +11 +88 +7 +28 +9 +9 +25 +76 +8 +113 +4 +16 +21 +15 +11 +6 +8 +0 +14 +24 +82 +5 +5 +7 +18 +111 +10 +14 +14 +2 +11 +6 +21 +11 +12 +15 +3 +6 +1 +61 +15 +10 +59 +5 +21 +0 +0 +10 +6 +18 +11 +9 +8 +6 +37 +7 +0 +84 +11 +8 +85 +8 +14 +14 +17 +8 +10 +8 +12 +4 +11 +19 +9 +6 +11 +3 +97 +15 +12 +61 +102 +13 +5 +12 +108 +5 +7 +14 +10 +15 +1 +19 +86 +7 +7 +8 +20 +0 +15 +6 +6 +6 +20 +6 +0 +14 +5 +5 +14 +9 +10 +11 +6 +10 +5 +14 +10 +12 +47 +0 +12 +9 +10 +27 +11 +7 +14 +14 +26 +17 +14 +4 +12 +9 +16 +62 +16 +76 +13 +28 +111 +7 +14 +24 +0 +7 +14 +6 +0 +11 +9 +18 +10 +22 +74 +14 +31 +6 +21 +19 +86 +0 +7 +28 +13 +1 +18 +4 +2 +11 +7 +4 +6 +0 +65 +10 +14 +19 +11 +25 +8 +120 +22 +6 +5 +25 +11 +28 +4 +5 +8 +14 +9 +10 +5 +14 +8 +10 +17 +19 +0 +10 +0 +10 +22 +10 +14 +7 +5 +11 +111 +9 +10 +25 +15 +8 +10 +14 +10 +30 +98 +6 +6 +8 +0 +16 +4 +66 +4 +14 +4 +4 +3 +7 +8 +17 +14 +13 +14 +17 +26 +0 +20 +7 +0 +9 +93 +5 +6 +13 +6 +10 +25 +10 +13 +75 +5 +6 +15 +32 +86 +69 +2 +14 +24 +27 +6 +19 +38 +16 +7 +15 +9 +10 +20 +24 +5 +0 +11 +21 +15 +8 +18 +20 +81 +0 +4 +0 +12 +5 +13 +46 +24 +7 +6 +0 +17 +8 +10 +8 +10 +8 +45 +5 +9 +11 +0 +6 +50 +8 +9 +6 +7 +74 +0 +7 +14 +19 +7 +15 +16 +5 +16 +9 +114 +5 +4 +27 +17 +10 +5 +7 +0 +15 +14 +5 +0 +61 +10 +10 +14 +17 +6 +18 +2 +3 +93 +19 +9 +3 +7 +23 +7 +83 +19 +16 +14 +3 +6 +19 +5 +7 +71 +53 +8 +24 +5 +13 +7 +81 +10 +6 +8 +7 +9 +26 +3 +17 +7 +11 +11 +4 +14 +9 +10 +118 +0 +108 +9 +8 +0 +4 +7 +6 +7 +14 +5 +20 +8 +59 +9 +16 +11 +0 +6 +15 +16 +3 +6 +7 +6 +13 +9 +5 +72 +28 +7 +6 +14 +58 +6 +9 +4 +52 +9 +42 +65 +12 +0 +3 +71 +23 +7 +6 +13 +11 +0 +67 +6 +67 +19 +2 +22 +27 +21 +96 +9 +10 +0 +10 +16 +0 +14 +78 +20 +7 +5 +5 +5 +70 +14 +12 +15 +11 +11 +15 +11 +6 +6 +23 +17 +10 +71 +82 +8 +6 +14 +0 +7 +8 +4 +9 +4 +10 +14 +9 +5 +10 +3 +14 +0 +0 +23 +0 +5 +6 +8 +5 +0 +44 +13 +92 +22 +8 +0 +29 +0 +6 +9 +18 +10 +4 +17 +6 +6 +18 +0 +11 +8 +15 +7 +5 +9 +0 +20 +8 +2 +0 +9 +5 +83 +10 +7 +95 +21 +32 +12 +9 +4 +0 +89 +32 +0 +6 +10 +12 +8 +7 +23 +102 +5 +9 +4 +15 +11 +17 +9 +7 +0 +41 +8 +12 +6 +10 +22 +13 +17 +9 +7 +11 +104 +0 +10 +0 +17 +13 +4 +40 +0 +0 +5 +10 +24 +10 +14 +4 +19 +23 +5 +0 +19 +51 +19 +6 +26 +0 +10 +14 +1 +91 +14 +10 +6 +14 +5 +10 +5 +20 +12 +6 +15 +2 +8 +25 +11 +0 +4 +108 +5 +10 +9 +0 +8 +11 +10 +14 +4 +17 +14 +14 +22 +11 +17 +23 +15 +11 +8 +78 +10 +74 +10 +0 +15 +9 +11 +4 +7 +0 +41 +7 +84 +77 +10 +15 +14 +15 +6 +13 +0 +0 +10 +13 +11 +4 +21 +7 +10 +12 +6 +13 +16 +11 +5 +0 +24 +12 +8 +0 +20 +0 +22 +12 +11 +4 +0 +8 +15 +0 +22 +11 +0 +0 +10 +12 +12 +21 +16 +6 +20 +5 +8 +6 +16 +7 +15 +0 +29 +12 +17 +18 +10 +26 +36 +19 +13 +0 +7 +8 +1 +14 +0 +14 +0 +5 +6 +102 +71 +15 +0 +8 +11 +14 +7 +10 +20 +11 +10 +113 +7 +6 +20 +10 +14 +9 +8 +8 +54 +78 +9 +7 +32 +6 +8 +3 +111 +9 +14 +16 +6 +0 +16 +21 +6 +83 +6 +11 +7 +5 +6 +12 +14 +51 +10 +20 +9 +18 +22 +0 +11 +8 +0 +8 +8 +14 +17 +11 +12 +20 +0 +1 +26 +8 +12 +0 +9 +10 +8 +13 +7 +0 +95 +0 +10 +4 +99 +15 +10 +12 +0 +44 +13 +11 +5 +0 +6 +0 +2 +4 +18 +3 +11 +14 +10 +51 +27 +7 +22 +2 +5 +2 +50 +7 +11 +59 +25 +14 +7 +9 +17 +104 +8 +0 +28 +0 +24 +7 +16 +5 +23 +0 +13 +44 +15 +17 +8 +46 +75 +13 +8 +13 +10 +20 +0 +11 +5 +6 +0 +15 +7 +14 +8 +13 +17 +3 +15 +14 +14 +17 +9 +14 +7 +10 +5 +6 +103 +7 +0 +13 +14 +5 +11 +29 +7 +86 +11 +5 +11 +16 +0 +13 +50 +6 +10 +7 +9 +108 +13 +6 +19 +0 +95 +9 +10 +5 +5 +30 +12 +14 +8 +6 +11 +10 +0 +89 +8 +6 +4 +6 +61 +10 +0 +8 +10 +26 +8 +5 +9 +13 +10 +9 +26 +6 +24 +15 +3 +16 +14 +2 +7 +23 +15 +0 +9 +0 +11 +3 +26 +21 +5 +9 +6 +13 +8 +13 +0 +30 +6 +19 +58 +9 +0 +12 +22 +10 +81 +8 +28 +5 +6 +16 +5 +0 +23 +5 +17 +28 +6 +9 +0 +5 +2 +16 +12 +10 +4 +14 +66 +11 +3 +73 +0 +19 +0 +86 +16 +5 +99 +6 +15 +8 +26 +6 +16 +0 +5 +8 +6 +29 +11 +55 +10 +9 +4 +11 +5 +60 +19 +49 +5 +16 +7 +8 +10 +18 +14 +10 +0 +12 +10 +4 +11 +13 +8 +7 +0 +6 +6 +0 +7 +14 +8 +20 +6 +0 +13 +110 +12 +13 +0 +62 +14 +8 +4 +6 +94 +11 +7 +4 +17 +7 +11 +6 +70 +0 +23 +28 +97 +12 +0 +108 +21 +11 +10 +43 +0 +0 +11 +15 +0 +36 +7 +0 +10 +0 +22 +7 +4 +12 +13 +5 +15 +6 +20 +4 +14 +72 +14 +26 +17 +57 +5 +11 +0 +9 +0 +6 +0 +13 +0 +10 +20 +9 +5 +10 +34 +30 +24 +0 +8 +5 +102 +8 +38 +13 +93 +14 +55 +10 +118 +14 +13 +7 +5 +97 +18 +11 +0 +6 +11 +0 +4 +20 +32 +9 +7 +8 +18 +16 +10 +73 +7 +13 +0 +5 +17 +8 +4 +10 +90 +87 +15 +11 +0 +10 +7 +0 +14 +10 +0 +6 +28 +0 +16 +7 +9 +18 +20 +34 +6 +10 +0 +5 +6 +76 +5 +7 +6 +12 +88 +4 +49 +6 +0 +4 +79 +56 +25 +5 +11 +9 +9 +101 +8 +32 +38 +9 +12 +68 +13 +10 +0 +0 +14 +68 +44 +13 +10 +27 +11 +21 +6 +105 +13 +6 +103 +9 +0 +103 +19 +10 +7 +2 +93 +2 +6 +5 +12 +36 +12 +85 +6 +20 +20 +9 +4 +68 +55 +11 +20 +13 +13 +14 +33 +10 +8 +10 +8 +5 +17 +11 +11 +8 +79 +1 +11 +0 +5 +0 +0 +10 +9 +11 +105 +80 +6 +10 +16 +3 +58 +11 +5 +4 +30 +2 +4 +5 +10 +21 +5 +19 +0 +3 +24 +7 +3 +3 +4 +5 +6 +12 +23 +3 +7 +24 +13 +0 +20 +7 +10 +5 +7 +26 +6 +22 +8 +4 +87 +11 +5 +15 +0 +8 +10 +0 +10 +7 +6 +113 +5 +6 +9 +17 +4 +11 +6 +10 +71 +9 +6 +9 +99 +48 +9 +19 +0 +21 +9 +14 +11 +10 +0 +5 +7 +14 +5 +21 +21 +11 +7 +9 +6 +84 +11 +20 +8 +11 +15 +16 +9 +4 +15 +4 +9 +7 +36 +12 +14 +8 +10 +7 +7 +6 +16 +13 +14 +21 +7 +7 +6 +0 +9 +7 +13 +0 +22 +0 +11 +0 +14 +8 +0 +5 +22 +15 +25 +24 +6 +11 +14 +80 +6 +9 +98 +6 +7 +3 +12 +17 +59 +0 +23 +24 +5 +5 +10 +10 +6 +23 +6 +99 +21 +2 +84 +11 +5 +18 +5 +0 +12 +7 +6 +76 +6 +18 +11 +0 +6 +23 +9 +9 +8 +14 +14 +14 +10 +0 +0 +6 +6 +10 +4 +92 +8 +6 +9 +12 +5 +13 +16 +19 +7 +3 +25 +5 +0 +94 +12 +0 +12 +4 +67 +7 +4 +9 +9 +9 +7 +1 +10 +19 +39 +74 +15 +19 +10 +12 +3 +14 +5 +0 +6 +11 +10 +0 +30 +7 +6 +77 +91 +11 +15 +2 +9 +14 +7 +8 +0 +6 +8 +19 +7 +10 +6 +27 +0 +10 +6 +8 +9 +0 +11 +85 +50 +14 +9 +23 +0 +9 +9 +4 +8 +16 +10 +25 +0 +11 +8 +19 +14 +6 +1 +12 +10 +6 +4 +0 +12 +20 +14 +25 +8 +11 +0 +7 +15 +25 +23 +9 +108 +66 +11 +21 +8 +4 +58 +91 +4 +0 +6 +7 +13 +26 +14 +7 +3 +78 +0 +3 +100 +4 +15 +58 +30 +6 +20 +2 +0 +9 +0 +4 +0 +9 +26 +14 +6 +8 +8 +81 +11 +13 +14 +11 +8 +5 +11 +31 +8 +9 +5 +3 +23 +5 +5 +13 +4 +11 +7 +6 +22 +11 +6 +36 +7 +15 +0 +6 +10 +21 +4 +12 +4 +12 +11 +5 +0 +5 +40 +0 +5 +12 +7 +5 +10 +6 +12 +18 +13 +4 +6 +0 +7 +3 +10 +6 +14 +10 +15 +22 +20 +10 +14 +7 +8 +5 +26 +0 +9 +6 +5 +7 +23 +11 +13 +11 +6 +19 +6 +11 +7 +17 +10 +22 +0 +5 +4 +11 +10 +26 +9 +7 +21 +5 +35 +6 +16 +31 +9 +0 +65 +7 +23 +11 +25 +20 +28 +24 +15 +10 +0 +13 +7 +13 +9 +24 +9 +15 +48 +25 +2 +10 +14 +7 +8 +3 +25 +28 +11 +6 +11 +6 +0 +5 +11 +0 +0 +5 +12 +8 +4 +70 +38 +0 +14 +14 +0 +109 +6 +14 +13 +15 +6 +21 +12 +6 +6 +20 +6 +10 +17 +20 +114 +5 +6 +7 +11 +25 +7 +5 +24 +5 +0 +4 +22 +7 +11 +0 +0 +0 +10 +5 +10 +5 +60 +3 +16 +8 +20 +16 +8 +7 +6 +17 +6 +6 +0 +14 +19 +10 +7 +102 +6 +8 +8 +12 +7 +19 +11 +14 +7 +12 +16 +11 +7 +23 +0 +10 +3 +0 +0 +9 +14 +6 +0 +0 +10 +7 +10 +11 +7 +0 +6 +10 +8 +23 +1 +5 +3 +13 +8 +13 +6 +8 +11 +6 +6 +6 +2 +23 +11 +11 +25 +9 +9 +30 +38 +21 +9 +15 +8 +9 +22 +12 +80 +0 +75 +0 +11 +11 +13 +9 +6 +6 +22 +77 +8 +5 +11 +14 +10 +12 +0 +10 +0 +11 +0 +6 +4 +4 +4 +12 +0 +111 +11 +9 +13 +12 +61 +19 +3 +2 +66 +5 +8 +11 +6 +87 +9 +15 +18 +5 +13 +10 +6 +16 +11 +6 +6 +11 +6 +21 +13 +6 +24 +118 +3 +14 +106 +105 +11 +20 +7 +11 +7 +6 +6 +8 +0 +0 +1 +11 +6 +14 +14 +19 +80 +6 +12 +27 +6 +17 +23 +6 +7 +10 +12 +73 +0 +8 +24 +14 +9 +6 +11 +4 +7 +6 +118 +8 +19 +11 +6 +6 +14 +5 +79 +11 +27 +1 +13 +5 +10 +112 +13 +6 +2 +6 +8 +26 +0 +0 +17 +38 +28 +15 +6 +0 +16 +57 +94 +1 +9 +4 +14 +12 +7 +0 +6 +19 +31 +0 +15 +0 +13 +9 +13 +13 +40 +17 +6 +18 +0 +9 +13 +3 +10 +0 +99 +9 +0 +8 +6 +17 +100 +17 +15 +17 +9 +0 +5 +9 +2 +7 +13 +11 +10 +57 +65 +9 +14 +12 +0 +12 +18 +7 +73 +6 +4 +0 +7 +94 +7 +14 +0 +11 +10 +113 +3 +8 +11 +9 +11 +8 +6 +86 +0 +6 +27 +8 +8 +11 +97 +10 +88 +9 +6 +13 +23 +14 +77 +0 +25 +0 +6 +0 +4 +0 +12 +4 +11 +7 +8 +11 +6 +5 +8 +0 +0 +3 +0 +7 +9 +21 +6 +17 +10 +6 +23 +56 +7 +6 +6 +6 +17 +3 +0 +0 +10 +10 +97 +6 +9 +46 +9 +0 +7 +17 +65 +0 +20 +7 +11 +59 +0 +89 +16 +11 +0 +0 +0 +4 +4 +7 +19 +49 +62 +0 +18 +0 +0 +11 +7 +0 +97 +11 +58 +14 +0 +1 +28 +0 +0 +17 +8 +9 +15 +0 +16 +8 +0 +15 +14 +6 +14 +1 +0 +11 +59 +0 +8 +5 +10 +0 +12 +11 +14 +6 +16 +23 +21 +9 +11 +0 +23 +5 +0 +23 +16 +8 +0 +0 +6 +106 +12 +7 +11 +10 +16 +14 +11 +30 +21 +7 +11 +1 +20 +10 +1 +11 +3 +65 +4 +8 +0 +9 +8 +8 +22 +76 +8 +4 +26 +15 +11 +51 +8 +17 +6 +77 +24 +9 +11 +8 +52 +8 +7 +3 +17 +10 +15 +0 +24 +71 +0 +10 +10 +9 +14 +13 +14 +8 +0 +13 +11 +21 +13 +5 +22 +6 +14 +47 +14 +19 +8 +6 +12 +8 +4 +2 +10 +22 +19 +8 +16 +9 +10 +19 +105 +7 +13 +23 +110 +12 +14 +12 +11 +15 +6 +11 +0 +98 +11 +9 +20 +10 +26 +7 +11 +13 +2 +13 +7 +9 +12 +14 +5 +5 +45 +10 +8 +4 +7 +10 +15 +12 +9 +11 +75 +6 +19 +5 +5 +110 +0 +98 +20 +10 +11 +5 +17 +29 +12 +6 +44 +15 +6 +5 +3 +5 +0 +6 +7 +87 +0 +4 +111 +4 +23 +22 +20 +12 +5 +14 +7 +0 +30 +10 +10 +7 +12 +69 +15 +16 +8 +2 +2 +6 +6 +10 +11 +9 +5 +10 +8 +5 +15 +13 +5 +13 +17 +10 +9 +5 +14 +10 +3 +85 +22 +11 +22 +7 +7 +11 +9 +72 +9 +8 +9 +14 +16 +14 +1 +6 +9 +19 +22 +6 +10 +9 +12 +4 +8 +26 +0 +6 +5 +6 +6 +6 +7 +75 +10 +11 +6 +8 +5 +0 +6 +47 +10 +6 +20 +11 +30 +22 +15 +12 +78 +11 +10 +7 +99 +14 +23 +6 +73 +8 +17 +7 +10 +16 +11 +0 +26 +10 +19 +11 +21 +5 +9 +12 +7 +13 +5 +6 +13 +7 +8 +11 +6 +24 +7 +23 +7 +0 +3 +16 +14 +7 +1 +78 +15 +2 +14 +2 +53 +15 +7 +12 +8 +5 +6 +9 +21 +6 +0 +7 +3 +6 +20 +0 +7 +8 +2 +10 +9 +5 +9 +0 +3 +69 +6 +8 +11 +6 +6 +10 +6 +18 +11 +12 +7 +0 +14 +5 +9 +11 +5 +101 +4 +16 +25 +70 +11 +4 +23 +6 +8 +0 +9 +7 +7 +9 +7 +0 +0 +5 +17 +0 +11 +5 +52 +5 +24 +5 +22 +8 +15 +24 +10 +108 +11 +43 +6 +9 +10 +6 +17 +10 +19 +11 +2 +14 +11 +1 +8 +8 +76 +24 +18 +7 +8 +6 +55 +6 +22 +23 +0 +12 +7 +25 +5 +6 +14 +10 +9 +0 +11 +11 +54 +19 +17 +11 +6 +10 +0 +16 +7 +6 +10 +6 +110 +9 +24 +24 +4 +7 +13 +70 +5 +8 +16 +21 +0 +75 +9 +8 +4 +4 +26 +8 +13 +25 +84 +86 +6 +24 +9 +11 +15 +8 +7 +12 +7 +79 +23 +9 +12 +6 +13 +0 +5 +0 +44 +12 +10 +6 +4 +6 +0 +7 +20 +15 +6 +1 +7 +106 +22 +6 +20 +12 +7 +11 +4 +8 +85 +19 +4 +0 +58 +23 +24 +13 +6 +9 +6 +14 +58 +24 +11 +10 +12 +8 +0 +26 +11 +10 +0 +8 +19 +7 +6 +6 +9 +0 +5 +15 +96 +10 +11 +7 +116 +0 +20 +60 +10 +8 +5 +6 +60 +9 +7 +93 +12 +4 +0 +5 +6 +9 +53 +19 +8 +14 +9 +10 +0 +3 +11 +5 +6 +15 +14 +19 +8 +5 +10 +8 +16 +18 +9 +9 +0 +5 +5 +25 +6 +6 +8 +6 +5 +18 +10 +8 +0 +8 +7 +111 +7 +5 +17 +4 +18 +14 +0 +9 +112 +11 +12 +0 +46 +10 +68 +12 +6 +10 +8 +14 +5 +13 +20 +6 +6 +13 +5 +31 +15 +13 +0 +10 +15 +14 +7 +16 +11 +12 +7 +10 +21 +0 +8 +0 +8 +17 +4 +14 +24 +23 +5 +11 +9 +6 +18 +16 +10 +6 +11 +9 +4 +5 +12 +9 +47 +15 +11 +11 +6 +28 +8 +4 +7 +4 +7 +24 +7 +17 +7 +18 +6 +18 +25 +12 +7 +8 +11 +0 +84 +8 +11 +13 +20 +0 +0 +0 +18 +5 +82 +10 +14 +6 +26 +7 +14 +10 +27 +100 +92 +7 +20 +8 +9 +66 +7 +22 +0 +4 +10 +4 +66 +74 +57 +11 +8 +71 +6 +11 +6 +13 +0 +0 +7 +0 +37 +114 +17 +7 +21 +23 +10 +22 +7 +9 +0 +16 +0 +52 +9 +0 +5 +5 +11 +0 +0 +4 +28 +95 +63 +4 +17 +9 +14 +107 +0 +21 +5 +11 +4 +28 +5 +3 +5 +5 +27 +5 +14 +0 +11 +0 +42 +7 +0 +10 +11 +10 +28 +16 +58 +11 +0 +5 +4 +8 +0 +0 +8 +7 +0 +14 +6 +13 +13 +7 +2 +6 +11 +6 +0 +5 +0 +0 +4 +9 +4 +107 +17 +99 +7 +14 +6 +6 +11 +16 +10 +26 +21 +5 +5 +9 +4 +75 +11 +10 +14 +14 +6 +7 +31 +13 +6 +8 +9 +74 +11 +2 +7 +8 +8 +4 +11 +11 +14 +0 +10 +6 +23 +92 +5 +4 +14 +9 +25 +5 +0 +11 +10 +24 +13 +7 +78 +0 +16 +1 +18 +10 +13 +5 +9 +5 +6 +20 +7 +14 +0 +10 +15 +7 +9 +23 +22 +19 +8 +21 +8 +5 +7 +14 +14 +10 +11 +68 +6 +0 +10 +22 +2 +16 +13 +1 +6 +10 +14 +9 +11 +25 +10 +9 +14 +91 +14 +24 +11 +9 +23 +20 +25 +15 +7 +15 +71 +63 +6 +5 +16 +2 +4 +6 +28 +9 +14 +8 +26 +5 +2 +10 +20 +8 +108 +14 +0 +17 +24 +7 +43 +10 +15 +8 +6 +6 +0 +10 +100 +23 +2 +27 +10 +9 +0 +27 +0 +10 +9 +5 +6 +19 +6 +0 +21 +6 +0 +8 +15 +7 +7 +5 +6 +24 +6 +12 +11 +11 +6 +8 +11 +15 +110 +79 +10 +16 +11 +9 +14 +15 +23 +6 +4 +10 +9 +27 +14 +6 +4 +87 +7 +12 +7 +6 +20 +29 +7 +4 +8 +6 +11 +0 +0 +0 +0 +6 +2 +64 +8 +24 +99 +8 +14 +9 +9 +10 +25 +5 +7 +14 +7 +19 +5 +13 +10 +0 +4 +15 +115 +18 +1 +10 +11 +17 +23 +8 +4 +8 +2 +4 +2 +12 +24 +12 +5 +9 +11 +8 +6 +11 +0 +6 +20 +11 +4 +32 +7 +14 +10 +6 +10 +7 +18 +7 +6 +5 +0 +0 +0 +7 +0 +7 +16 +27 +36 +23 +6 +11 +3 +8 +14 +11 +6 +14 +0 +13 +9 +7 +9 +10 +105 +0 +11 +11 +8 +6 +4 +5 +8 +114 +4 +20 +1 +5 +9 +18 +15 +4 +43 +11 +10 +21 +23 +11 +16 +1 +43 +24 +12 +11 +20 +5 +21 +100 +7 +0 +10 +12 +13 +9 +8 +15 +7 +6 +14 +15 +24 +13 +127 +5 +9 +11 +8 +8 +2 +8 +6 +7 +6 +0 +0 +14 +11 +12 +8 +7 +23 +16 +11 +7 +9 +19 +22 +12 +80 +7 +9 +34 +7 +83 +9 +10 +3 +10 +10 +110 +2 +16 +9 +21 +7 +1 +11 +9 +0 +6 +7 +6 +9 +17 +2 +9 +13 +6 +24 +0 +8 +0 +20 +14 +8 +8 +12 +59 +11 +27 +5 +5 +7 +14 +9 +6 +6 +9 +13 +11 +11 +20 +9 +15 +9 +95 +0 +0 +0 +11 +5 +11 +0 +4 +26 +0 +12 +6 +12 +79 +4 +96 +21 +1 +3 +0 +10 +99 +10 +11 +47 +11 +16 +5 +16 +9 +0 +14 +7 +9 +0 +27 +12 +13 +71 +11 +7 +11 +6 +22 +8 +4 +87 +14 +96 +9 +68 +12 +14 +38 +7 +7 +25 +44 +14 +0 +6 +18 +10 +19 +8 +41 +62 +7 +6 +12 +10 +10 +0 +97 +12 +8 +0 +8 +7 +10 +11 +14 +19 +0 +48 +7 +27 +5 +11 +13 +0 +15 +8 +88 +3 +0 +10 +13 +22 +74 +15 +3 +12 +6 +12 +14 +11 +10 +25 +61 +4 +5 +10 +13 +10 +4 +81 +9 +9 +17 +21 +5 +20 +7 +6 +8 +8 +0 +9 +7 +10 +11 +12 +11 +15 +22 +11 +87 +12 +9 +115 +12 +6 +21 +0 +100 +10 +8 +27 +0 +21 +106 +14 +4 +27 +9 +4 +7 +19 +6 +6 +9 +16 +8 +6 +7 +3 +10 +10 +31 +12 +11 +20 +6 +18 +11 +6 +9 +2 +0 +14 +10 +4 +18 +9 +10 +0 +16 +2 +14 +0 +7 +0 +4 +7 +17 +0 +6 +16 +44 +4 +7 +10 +19 +20 +3 +8 +18 +0 +11 +14 +8 +23 +8 +19 +2 +8 +6 +76 +5 +20 +20 +4 +45 +10 +0 +9 +19 +4 +6 +11 +16 +0 +0 +87 +0 +12 +3 +10 +13 +11 +6 +13 +4 +6 +28 +19 +38 +42 +6 +33 +9 +18 +6 +6 +6 +6 +19 +23 +10 +10 +11 +0 +3 +7 +11 +44 +0 +106 +3 +22 +11 +14 +20 +14 +14 +8 +23 +4 +4 +14 +16 +10 +29 +10 +23 +0 +8 +17 +7 +5 +12 +0 +9 +11 +8 +11 +18 +12 +16 +11 +6 +1 +6 +16 +6 +7 +4 +0 +8 +6 +5 +10 +14 +6 +6 +3 +4 +25 +13 +10 +0 +8 +14 +96 +1 +0 +9 +14 +18 +72 +73 +9 +11 +72 +9 +5 +19 +14 +17 +16 +0 +9 +27 +104 +13 +0 +9 +7 +8 +2 +88 +23 +14 +10 +12 +7 +19 +20 +7 +102 +8 +21 +7 +17 +0 +10 +3 +7 +0 +12 +8 +15 +3 +6 +27 +48 +29 +6 +10 +0 +1 +4 +20 +0 +0 +14 +11 +7 +6 +22 +10 +11 +21 +4 +5 +7 +85 +11 +8 +13 +106 +7 +11 +4 +22 +10 +21 +9 +0 +8 +8 +13 +24 +8 +45 +26 +6 +8 +0 +16 +17 +82 +11 +10 +15 +14 +7 +19 +57 +8 +24 +38 +5 +22 +10 +96 +58 +0 +10 +10 +1 +22 +109 +15 +18 +4 +11 +4 +59 +11 +17 +11 +14 +26 +6 +16 +6 +26 +7 +13 +13 +8 +69 +13 +10 +5 +7 +6 +3 +1 +3 +5 +6 +23 +106 +3 +18 +10 +21 +15 +0 +10 +5 +89 +27 +18 +43 +10 +8 +4 +14 +4 +12 +0 +14 +7 +11 +20 +10 +0 +0 +9 +7 +8 +7 +10 +6 +10 +93 +51 +15 +7 +0 +95 +22 +10 +29 +6 +14 +6 +9 +45 +16 +8 +11 +14 +13 +10 +8 +0 +9 +4 +15 +5 +23 +6 +6 +10 +6 +17 +16 +4 +7 +95 +10 +7 +10 +7 +63 +10 +0 +8 +10 +0 +12 +9 +4 +12 +4 +0 +0 +21 +3 +11 +7 +6 +14 +5 +27 +6 +7 +0 +6 +4 +45 +18 +13 +7 +8 +25 +7 +96 +10 +10 +3 +23 +0 +10 +10 +9 +3 +6 +5 +72 +48 +22 +85 +8 +8 +6 +80 +9 +10 +5 +7 +69 +9 +14 +22 +13 +26 +8 +23 +13 +3 +29 +0 +0 +5 +99 +11 +11 +10 +20 +6 +13 +12 +20 +3 +11 +14 +7 +7 +9 +11 +133 +11 +10 +4 +6 +5 +29 +0 +13 +15 +93 +59 +13 +41 +0 +13 +0 +0 +68 +0 +82 +4 +10 +82 +9 +9 +0 +22 +14 +5 +13 +27 +0 +16 +19 +7 +6 +6 +9 +13 +14 +25 +9 +8 +66 +77 +8 +0 +14 +19 +9 +9 +11 +10 +8 +42 +9 +0 +12 +18 +18 +8 +0 +13 +14 +13 +45 +5 +9 +5 +22 +79 +17 +6 +14 +7 +19 +0 +8 +18 +13 +5 +12 +11 +18 +0 +11 +8 +6 +10 +12 +14 +0 +90 +0 +47 +4 +65 +8 +6 +12 +67 +0 +6 +67 +1 +0 +17 +6 +7 +10 +0 +19 +10 +9 +11 +6 +10 +14 +28 +19 +9 +15 +8 +15 +19 +22 +7 +18 +77 +11 +4 +9 +8 +4 +10 +14 +6 +11 +13 +0 +0 +25 +121 +21 +2 +6 +5 +12 +5 +7 +7 +9 +10 +3 +3 +15 +4 +21 +10 +11 +8 +8 +5 +6 +13 +0 +6 +20 +7 +7 +12 +11 +25 +21 +64 +0 +11 +42 +22 +7 +4 +18 +54 +7 +9 +6 +8 +0 +2 +11 +19 +10 +8 +28 +10 +10 +57 +22 +14 +11 +110 +9 +7 +22 +2 +0 +10 +77 +24 +6 +10 +0 +2 +22 +14 +14 +10 +8 +6 +18 +2 +10 +11 +16 +4 +12 +20 +69 +8 +6 +11 +6 +8 +5 +114 +0 +14 +6 +12 +102 +6 +0 +11 +13 +5 +11 +11 +0 +0 +11 +12 +6 +8 +0 +13 +48 +6 +6 +78 +13 +24 +14 +79 +19 +30 +0 +21 +7 +50 +21 +0 +11 +11 +5 +8 +9 +4 +12 +12 +21 +46 +7 +3 +10 +7 +26 +10 +11 +10 +11 +0 +95 +0 +14 +64 +13 +100 +3 +17 +6 +9 +4 +11 +0 +14 +0 +20 +7 +5 +5 +0 +23 +105 +0 +9 +20 +15 +14 +11 +10 +87 +6 +12 +90 +25 +10 +7 +0 +7 +5 +6 +41 +17 +38 +6 +5 +8 +17 +10 +7 +5 +6 +8 +18 +9 +0 +10 +7 +5 +0 +14 +17 +6 +4 +0 +10 +9 +49 +57 +26 +9 +10 +14 +12 +3 +29 +2 +7 +13 +0 +14 +112 +7 +62 +6 +20 +7 +65 +22 +6 +20 +0 +18 +4 +21 +14 +0 +23 +0 +5 +99 +12 +5 +4 +30 +0 +2 +67 +3 +10 +7 +6 +6 +6 +60 +26 +44 +16 +28 +3 +6 +68 +0 +90 +10 +82 +14 +10 +6 +12 +24 +6 +11 +5 +10 +10 +16 +7 +7 +4 +14 +53 +6 +14 +24 +22 +18 +23 +19 +0 +6 +7 +14 +11 +15 +53 +0 +10 +14 +14 +8 +10 +17 +11 +23 +8 +6 +17 +6 +15 +10 +10 +13 +7 +8 +14 +24 +7 +7 +6 +14 +9 +8 +4 +6 +15 +10 +19 +6 +17 +11 +11 +62 +0 +8 +8 +3 +2 +0 +10 +11 +18 +10 +78 +6 +9 +30 +11 +16 +99 +105 +6 +8 +6 +25 +47 +11 +8 +23 +71 +9 +9 +12 +24 +13 +0 +0 +5 +9 +8 +0 +10 +47 +12 +6 +8 +16 +8 +18 +85 +0 +6 +0 +6 +20 +13 +7 +24 +18 +3 +5 +11 +9 +5 +11 +0 +13 +9 +5 +10 +13 +3 +4 +3 +79 +8 +14 +14 +14 +18 +17 +19 +21 +4 +25 +77 +6 +7 +9 +6 +92 +11 +7 +26 +63 +12 +19 +64 +22 +0 +13 +0 +20 +6 +11 +20 +10 +10 +10 +10 +21 +22 +90 +6 +7 +10 +0 +13 +17 +10 +7 +23 +15 +2 +9 +8 +29 +10 +2 +0 +9 +58 +7 +12 +55 +6 +1 +6 +0 +10 +6 +5 +15 +21 +3 +11 +14 +12 +10 +6 +14 +18 +100 +6 +11 +10 +6 +90 +0 +33 +22 +18 +51 +88 +6 +15 +21 +9 +0 +13 +11 +8 +5 +5 +6 +79 +34 +107 +6 +8 +8 +6 +6 +12 +8 +11 +10 +9 +10 +12 +19 +12 +21 +0 +0 +26 +22 +0 +7 +22 +1 +6 +0 +11 +10 +62 +16 +10 +7 +6 +6 +6 +32 +8 +6 +17 +26 +11 +13 +0 +6 +10 +0 +6 +9 +8 +11 +4 +8 +0 +9 +6 +11 +4 +8 +0 +41 +6 +0 +117 +12 +8 +10 +38 +10 +8 +9 +13 +0 +0 +7 +12 +0 +0 +19 +57 +0 +25 +10 +3 +5 +21 +12 +100 +4 +9 +17 +5 +21 +8 +13 +49 +51 +0 +16 +11 +12 +14 +20 +19 +12 +14 +26 +0 +0 +7 +16 +5 +19 +6 +11 +12 +23 +17 +84 +6 +16 +24 +13 +29 +7 +11 +20 +28 +8 +10 +100 +9 +116 +17 +0 +27 +0 +10 +22 +14 +9 +17 +15 +42 +8 +0 +0 +7 +13 +8 +7 +73 +12 +0 +10 +12 +14 +56 +9 +54 +4 +24 +7 +23 +26 +10 +19 +9 +8 +14 +97 +11 +21 +9 +102 +64 +42 +93 +10 +6 +13 +18 +9 +14 +29 +8 +12 +6 +6 +12 +6 +47 +12 +14 +89 +11 +3 +12 +6 +14 +87 +6 +13 +24 +11 +0 +6 +6 +11 +27 +8 +14 +0 +78 +10 +6 +2 +7 +21 +23 +19 +7 +7 +0 +8 +52 +9 +14 +6 +5 +2 +17 +5 +9 +8 +0 +6 +4 +7 +9 +5 +11 +6 +6 +0 +23 +3 +8 +0 +109 +1 +28 +9 +5 +23 +20 +14 +8 +45 +17 +59 +7 +18 +10 +9 +14 +6 +12 +23 +61 +28 +16 +9 +8 +67 +7 +37 +7 +7 +27 +11 +5 +43 +6 +6 +11 +10 +20 +23 +18 +4 +10 +64 +8 +10 +14 +0 +5 +0 +9 +85 +6 +28 +10 +4 +5 +7 +14 +6 +6 +2 +9 +6 +6 +14 +10 +1 +8 +11 +31 +4 +70 +2 +18 +21 +8 +5 +13 +12 +28 +99 +0 +0 +23 +14 +0 +19 +9 +7 +8 +15 +10 +7 +6 +8 +22 +12 +7 +3 +11 +13 +19 +103 +0 +13 +6 +119 +16 +7 +12 +11 +15 +9 +9 +27 +9 +13 +6 +24 +10 +10 +6 +7 +0 +108 +0 +10 +8 +19 +94 +6 +5 +18 +70 +30 +13 +12 +0 +6 +27 +8 +22 +0 +54 +7 +6 +102 +14 +14 +20 +14 +11 +19 +8 +110 +105 +6 +5 +7 +9 +10 +5 +14 +9 +56 +10 +19 +7 +13 +21 +8 +26 +0 +9 +11 +8 +12 +6 +7 +23 +45 +11 +23 +11 +9 +0 +21 +6 +8 +0 +11 +7 +18 +5 +15 +10 +6 +3 +6 +83 +17 +88 +7 +26 +58 +8 +14 +15 +5 +8 +1 +3 +14 +20 +10 +18 +22 +5 +23 +14 +8 +13 +25 +12 +19 +5 +5 +10 +8 +40 +0 +83 +10 +13 +11 +6 +6 +13 +4 +14 +4 +8 +9 +4 +18 +8 +8 +0 +99 +51 +0 +6 +10 +6 +14 +23 +9 +4 +20 +11 +8 +11 +12 +6 +7 +23 +60 +24 +9 +6 +0 +10 +69 +6 +19 +43 +7 +8 +16 +7 +11 +13 +28 +2 +6 +16 +0 +91 +88 +10 +0 +12 +0 +15 +0 +21 +11 +14 +16 +10 +0 +8 +12 +22 +11 +12 +14 +7 +0 +19 +61 +14 +87 +5 +11 +17 +91 +13 +8 +5 +5 +0 +23 +5 +14 +2 +42 +13 +0 +14 +14 +0 +24 +14 +22 +25 +55 +6 +6 +6 +8 +5 +8 +7 +8 +9 +6 +7 +20 +9 +0 +12 +9 +10 +17 +9 +11 +10 +2 +14 +14 +1 +7 +10 +4 +9 +72 +15 +55 +20 +8 +14 +14 +12 +0 +7 +11 +8 +20 +7 +17 +6 +18 +5 +15 +6 +25 +0 +8 +10 +4 +11 +9 +73 +0 +3 +14 +8 +1 +9 +31 +22 +4 +22 +27 +112 +0 +22 +2 +0 +7 +5 +4 +3 +4 +10 +5 +6 +11 +0 +18 +6 +6 +9 +0 +20 +69 +17 +5 +12 +5 +4 +0 +19 +11 +0 +2 +11 +24 +4 +4 +11 +5 +17 +11 +10 +6 +13 +21 +14 +11 +8 +7 +17 +0 +6 +50 +11 +14 +0 +0 +19 +6 +13 +23 +8 +15 +11 +3 +6 +10 +25 +6 +0 +8 +14 +10 +6 +14 +6 +10 +13 +9 +0 +13 +10 +5 +4 +0 +0 +5 +9 +11 +0 +0 +2 +0 +19 +14 +8 +13 +23 +6 +12 +6 +74 +22 +10 +4 +27 +5 +0 +0 +7 +4 +7 +10 +7 +51 +68 +9 +10 +6 +0 +20 +6 +106 +17 +0 +8 +8 +7 +90 +10 +19 +78 +12 +5 +10 +87 +10 +13 +7 +4 +25 +10 +43 +20 +8 +19 +1 +11 +14 +9 +19 +11 +12 +10 +12 +5 +7 +6 +11 +7 +7 +42 +3 +11 +0 +4 +14 +10 +10 +68 +9 +4 +4 +42 +5 +10 +4 +0 +8 +6 +6 +0 +4 +11 +67 +7 +8 +73 +14 +7 +8 +26 +8 +2 +71 +0 +13 +0 +10 +22 +0 +4 +6 +2 +51 +9 +6 +6 +18 +20 +9 +6 +16 +21 +20 +44 +13 +5 +0 +6 +57 +15 +18 +21 +25 +8 +7 +10 +22 +9 +6 +9 +6 +28 +24 +9 +0 +19 +4 +2 +13 +16 +1 +10 +67 +0 +6 +0 +17 +0 +44 +0 +19 +6 +23 +12 diff --git a/parity/oracle/tokens.py b/parity/oracle/tokens.py new file mode 100644 index 0000000..61e7cba --- /dev/null +++ b/parity/oracle/tokens.py @@ -0,0 +1,26 @@ +"""Token counts from the Python tokeniser, one per line of the sample. + +Reads the sample on stdin as UTF-8 bytes and splits it on newlines itself, +rather than iterating the stream, so that universal-newline translation +cannot silently change how many lines there are. + + python3 oracle/tokens.py < sample.txt > golden/tokens.counts +""" + +import sys + +import tiktoken + +ENC = tiktoken.get_encoding("cl100k_base") + + +def main() -> int: + text = sys.stdin.buffer.read().decode("utf-8") + out = sys.stdout + for line in text.split("\n"): + out.write(f"{len(ENC.encode(line, disallowed_special=()))}\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/parity/src/bin/regen.rs b/parity/src/bin/regen.rs new file mode 100644 index 0000000..f6b50e3 --- /dev/null +++ b/parity/src/bin/regen.rs @@ -0,0 +1,35 @@ +//! Regenerates the golden files by running the Python oracles. +//! +//! Never run as part of a test: a golden file that regenerates itself when +//! it fails is a test that cannot fail. Run it deliberately, read the diff, +//! and commit the change with the reason. +//! +//! cargo run -p parity --bin regen + +use std::process::ExitCode; + +fn main() -> ExitCode { + match regen_tokens() { + Ok(path) => { + eprintln!("wrote {path}"); + ExitCode::SUCCESS + } + Err(e) => { + eprintln!("regen: {e}"); + ExitCode::FAILURE + } + } +} + +fn regen_tokens() -> Result { + let header = format!( + "# cl100k_base token counts, one per line of parity::sample::standard()\n\ + # seed={:#018x} lines={}\n", + parity::sample::SEED, + parity::sample::LINES, + ); + let counts = parity::run_oracle("tokens.py", &parity::sample::standard())?; + let path = parity::golden("tokens.counts"); + std::fs::write(&path, format!("{header}{counts}")).map_err(|e| e.to_string())?; + Ok(path.display().to_string()) +} diff --git a/parity/src/lib.rs b/parity/src/lib.rs new file mode 100644 index 0000000..0149a64 --- /dev/null +++ b/parity/src/lib.rs @@ -0,0 +1,88 @@ +//! Differential tests against the Python implementations these tools were +//! ported from. +//! +//! The risk in this port is not that Rust is hard. It is that the heuristics +//! were tuned against a real corpus over a long time, and a reimplementation +//! that is 98% right is worse than no port at all, because the 2% is silent. +//! +//! So every ported function is pinned to what the Python produced, by golden +//! files under `parity/golden/`. The default test run compares Rust against +//! those files and needs nothing but Rust. With `--features python` the +//! oracle is re-run live, which catches the golden files drifting away from +//! a Python implementation that has moved on. +//! +//! Fixtures are generated, synthetic, or public domain. No corpus text is +//! committed here, and for copyrighted sources only the derived signal — the +//! scores, the counts, the timings — is ever checked in. + +pub mod sample; + +use std::path::{Path, PathBuf}; + +/// The `parity/` directory, wherever this crate was checked out to. +pub fn dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf() +} + +/// Path to a golden file. +pub fn golden(name: &str) -> PathBuf { + dir().join("golden").join(name) +} + +/// Path to an oracle script. +pub fn oracle(name: &str) -> PathBuf { + dir().join("oracle").join(name) +} + +/// The Python interpreter to run an oracle with. +/// +/// Taken from `CATENA_PARITY_PYTHON`, because the interpreter that has the +/// pipeline's dependencies installed is a fact about one machine and must not +/// be compiled in. +pub fn python() -> String { + std::env::var("CATENA_PARITY_PYTHON").unwrap_or_else(|_| "python3".to_string()) +} + +/// Run an oracle script over `input` and return its stdout. +/// +/// The interpreter comes from the environment and the script from this +/// directory, so nothing about where a Python pipeline lives on a particular +/// machine is compiled in. +pub fn run_oracle(script: &str, input: &str) -> Result { + use std::io::Write as _; + use std::process::{Command, Stdio}; + + let script = oracle(script); + let mut child = Command::new(python()) + .arg(&script) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .map_err(|e| format!("running {} with {}: {e}", script.display(), python()))?; + child + .stdin + .take() + .expect("stdin was piped") + .write_all(input.as_bytes()) + .map_err(|e| format!("writing to {}: {e}", script.display()))?; + let out = child + .wait_with_output() + .map_err(|e| format!("waiting for {}: {e}", script.display()))?; + if !out.status.success() { + return Err(format!("{} exited with {}", script.display(), out.status)); + } + String::from_utf8(out.stdout) + .map_err(|e| format!("{} wrote invalid UTF-8: {e}", script.display())) +} + +/// Read a golden file, dropping its `#` header lines. +pub fn read_golden(name: &str) -> Vec { + let path = golden(name); + let text = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("reading {}: {e}", path.display())); + text.lines() + .filter(|line| !line.starts_with('#')) + .map(str::to_string) + .collect() +} diff --git a/parity/src/sample.rs b/parity/src/sample.rs new file mode 100644 index 0000000..3f200b9 --- /dev/null +++ b/parity/src/sample.rs @@ -0,0 +1,212 @@ +//! A deterministic sample of awkward text. +//! +//! Generated rather than committed, so the fixture costs the clone nothing, +//! and generated the same way on every machine, so Rust and Python are +//! demonstrably reading the same bytes. Change anything in here and every +//! golden file derived from it has to be regenerated. +//! +//! The lines are chosen to be hostile to a tokeniser rather than +//! representative of prose: scripts that are not Latin, combining marks, +//! zero-width characters, the literal spelling of a control token, numbers, +//! URLs, markdown, and runs of punctuation. Ordinary English is the easy case +//! and is well covered by the corpus itself. + +/// Lines in the standard sample. +pub const LINES: usize = 10_000; + +/// The seed the standard sample is generated from. +pub const SEED: u64 = 0x636174656e610001; + +/// The standard sample: [`LINES`] lines, newline-separated, no trailing +/// newline, no carriage returns. +/// +/// Carriage returns are excluded deliberately: Python's universal-newline +/// translation would turn a lone `\r` into a line break and the two sides +/// would then disagree about how many lines there even are. +pub fn standard() -> String { + generate(SEED, LINES) +} + +/// Generate `lines` lines from `seed`. +pub fn generate(seed: u64, lines: usize) -> String { + let mut rng = Rng::new(seed); + let mut out = String::new(); + for i in 0..lines { + if i > 0 { + out.push('\n'); + } + out.push_str(&line(&mut rng)); + } + out +} + +fn line(rng: &mut Rng) -> String { + match rng.below(10) { + 0 => words(rng, 3, 14), + 1 => format!("{} {}", pick(rng, SCRIPTS), words(rng, 1, 6)), + 2 => format!("# {}", words(rng, 2, 7)), + 3 => format!( + "- [{}]({}) {}", + words(rng, 1, 4), + pick(rng, URLS), + words(rng, 0, 5) + ), + 4 => format!("{} {} {}", numbers(rng), pick(rng, PUNCT), numbers(rng)), + 5 => format!("{}{}", pick(rng, AWKWARD), words(rng, 0, 6)), + 6 => format!(" {}", pick(rng, CODE)), + 7 => format!( + "{}{}", + words(rng, 1, 5), + pick(rng, PUNCT).repeat(rng.below(4) + 1) + ), + 8 => words(rng, 20, 60), + _ => String::new(), + } +} + +fn words(rng: &mut Rng, least: usize, most: usize) -> String { + let count = least + rng.below(most.saturating_sub(least) + 1); + let mut out = String::new(); + for i in 0..count { + if i > 0 { + out.push(' '); + } + out.push_str(pick(rng, WORDS)); + } + out +} + +fn numbers(rng: &mut Rng) -> String { + match rng.below(4) { + 0 => format!("{}", rng.below(10)), + 1 => format!("{}", rng.below(100_000)), + 2 => format!("{}.{}", rng.below(1000), rng.below(1000)), + _ => format!("{}-{}", rng.below(2100), rng.below(13)), + } +} + +fn pick<'a>(rng: &mut Rng, from: &[&'a str]) -> &'a str { + from[rng.below(from.len())] +} + +const WORDS: &[&str] = &[ + "the", + "second", + "estate", + "manuscript", + "marginalia", + "colophon", + "quire", + "recto", + "verso", + "palimpsest", + "incunabulum", + "folio", + "gloss", + "catena", + "scholion", + "lacuna", + "obelus", + "supralinear", + "hapax", + "stemma", + "apparatus", + "witness", + "conjecture", + "lemma", +]; + +const SCRIPTS: &[&str] = &[ + "café", + "naïve", + "Москва", + "東京", + "مرحبا", + "Ἀθῆναι", + "עברית", + "ॐ", + "Ægypt", + "first", + "Straße", +]; + +const URLS: &[&str] = &[ + "https://example.org/a/b?c=d&e=f", + "http://localhost:8080/x", + "#anchor-name", + "mailto:someone@example.invalid", +]; + +const PUNCT: &[&str] = &[".", "!", "?", "…", "—", ";", ":", ",", ")", "\"", "'"]; + +const CODE: &[&str] = &[ + "fn main() { println!(\"{}\", 1 + 1); }", + "SELECT * FROM t WHERE id = $1;", + "{\"v\":1,\"idx\":0}", + "", +]; + +/// Characters that have broken something before: a zero-width joiner, a +/// non-breaking space, a combining acute, the literal spelling of a control +/// token, and a tab. +const AWKWARD: &[&str] = &[ + "\u{200d}", + "\u{00a0}", + "e\u{0301}", + "<|endoftext|>", + "\t", + "\u{feff}", + "🙂", + "\u{1c}", +]; + +/// xorshift64*, so the sample is the same on every machine and every run. +struct Rng(u64); + +impl Rng { + fn new(seed: u64) -> Self { + Self(if seed == 0 { 1 } else { seed }) + } + + fn next(&mut self) -> u64 { + let mut x = self.0; + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + self.0 = x; + x.wrapping_mul(0x2545_f491_4f6c_dd1d) + } + + fn below(&mut self, n: usize) -> usize { + if n == 0 { + 0 + } else { + (self.next() % n as u64) as usize + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_sample_is_the_same_every_time() { + assert_eq!(generate(SEED, 50), generate(SEED, 50)); + } + + #[test] + fn a_shorter_sample_is_a_prefix_of_a_longer_one() { + let long = generate(SEED, 50); + let short = generate(SEED, 10); + assert!(long.starts_with(&short)); + } + + #[test] + fn the_standard_sample_has_the_advertised_shape() { + let text = standard(); + assert_eq!(text.split('\n').count(), LINES); + assert!(!text.contains('\r')); + assert!(!text.ends_with('\n')); + } +} diff --git a/parity/tests/tokens.rs b/parity/tests/tokens.rs new file mode 100644 index 0000000..abb6d09 --- /dev/null +++ b/parity/tests/tokens.rs @@ -0,0 +1,62 @@ +//! Token counts must agree with the Python tokeniser exactly. +//! +//! A count that is one off moves a chunk boundary, and a chunk boundary that +//! moves means every passage after it in the document is a different passage +//! than the one already embedded in an index. + +#[test] +fn every_line_counts_the_same_as_python() { + let golden = parity::read_golden("tokens.counts"); + let sample = parity::sample::standard(); + let lines: Vec<&str> = sample.split('\n').collect(); + + assert_eq!( + golden.len(), + lines.len(), + "the golden file has {} counts for {} lines — regenerate it", + golden.len(), + lines.len() + ); + + let mut wrong = Vec::new(); + for (at, (line, expected)) in lines.iter().zip(&golden).enumerate() { + let expected: usize = expected + .parse() + .unwrap_or_else(|e| panic!("golden line {}: {e}", at + 1)); + let got = textkit::count(line); + if got != expected { + wrong.push(format!( + "line {}: python {expected}, rust {got}, text {line:?}", + at + 1 + )); + } + } + + assert!( + wrong.is_empty(), + "{} of {} lines disagree:\n{}", + wrong.len(), + lines.len(), + wrong + .iter() + .take(20) + .cloned() + .collect::>() + .join("\n") + ); +} + +/// Catches the golden file drifting away from a Python that has moved on. +/// Needs an interpreter with `tiktoken`, named by `CATENA_PARITY_PYTHON`. +#[test] +#[cfg(feature = "python")] +fn the_golden_file_still_matches_live_python() { + let live = parity::run_oracle("tokens.py", &parity::sample::standard()) + .expect("the oracle should run"); + let live: Vec<&str> = live.lines().collect(); + let golden = parity::read_golden("tokens.counts"); + assert_eq!(live.len(), golden.len(), "line count moved"); + for (at, (live, golden)) in live.iter().zip(&golden).enumerate() { + assert_eq!(live, golden, "golden file is stale at line {}", at + 1); + } +}