transcrypto 1.3.0__py3-none-any.whl → 1.5.1__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.
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright 2025 Daniel Balparda (balparda@github.com) - Apache-2.0 license
4
+ #
5
+ """Balparda's TransCrypto Profiler command line interface.
6
+
7
+ See README.md for documentation on how to use.
8
+
9
+ Notes on the layout (quick mental model):
10
+
11
+ primes
12
+ dsa
13
+ doc md
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import logging
20
+ # import pdb
21
+ import sys
22
+ from typing import Callable
23
+
24
+ from . import base, modmath, dsa
25
+
26
+ __author__ = 'balparda@github.com'
27
+ __version__: str = base.__version__ # version comes from base!
28
+ __version_tuple__: tuple[int, ...] = base.__version_tuple__
29
+
30
+
31
+ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-statements,too-many-locals
32
+ """Construct the CLI argument parser (kept in sync with the docs)."""
33
+ # ========================= main parser ==========================================================
34
+ parser: argparse.ArgumentParser = argparse.ArgumentParser(
35
+ prog='poetry run profiler',
36
+ description=('profiler: CLI for TransCrypto Profiler, measure library performance.'),
37
+ epilog=(
38
+ 'Examples:\n\n'
39
+ ' # --- Primes ---\n'
40
+ ' poetry run profiler -p -n 10 primes\n'
41
+ ' poetry run profiler -n 20 dsa\n'
42
+ ),
43
+ formatter_class=argparse.RawTextHelpFormatter)
44
+ sub = parser.add_subparsers(dest='command')
45
+
46
+ # ========================= global flags =========================================================
47
+ # -v/-vv/-vvv/-vvvv for ERROR/WARN/INFO/DEBUG
48
+ parser.add_argument(
49
+ '-v', '--verbose', action='count', default=0,
50
+ help='Increase verbosity (use -v/-vv/-vvv/-vvvv for ERROR/WARN/INFO/DEBUG)')
51
+
52
+ thread_grp = parser.add_mutually_exclusive_group()
53
+ thread_grp.add_argument(
54
+ '-s', '--serial', action='store_true',
55
+ help='If test can be serial, do it like that with no parallelization (default)')
56
+ thread_grp.add_argument(
57
+ '-p', '--parallel', action='store_true',
58
+ help='If test can be parallelized into processes, do it like that')
59
+
60
+ parser.add_argument(
61
+ '-n', '--number', type=int, default=15,
62
+ help='Number of experiments (repeats) for every measurement')
63
+ parser.add_argument(
64
+ '-c', '--confidence', type=int, default=98,
65
+ help=('Confidence level to evaluate measurements at as int percentage points [50,99], '
66
+ 'inclusive, representing 50% to 99%'))
67
+
68
+ parser.add_argument(
69
+ '-b', '--bits', type=str, default='1000,9000,1000',
70
+ help=('Bit lengths to investigate as "int,int,int"; behaves like arguments for range(), '
71
+ 'i.e., "start,stop,step", eg. "1000,3000,500" will investigate 1000,1500,2000,2500'))
72
+
73
+ # ========================= Prime Generation =====================================================
74
+
75
+ # Regular prime generation
76
+ sub.add_parser(
77
+ 'primes',
78
+ help='Measure regular prime generation.',
79
+ epilog=('-n 30 -b 9000,11000,1000 primes\nStarting SERIAL regular primes test\n'
80
+ '9000 → 38.88 s ± 14.74 s [24.14 s … 53.63 s]98%CI@30\n'
81
+ '10000 → 41.26 s ± 22.82 s [18.44 s … 1.07 min]98%CI@30\nFinished in 40.07 min'))
82
+
83
+ # DSA primes generation
84
+ sub.add_parser(
85
+ 'dsa',
86
+ help='Measure DSA prime generation.',
87
+ epilog=('-p -n 2 -b 1000,1500,100 -c 80 dsa\nStarting PARALLEL DSA primes test\n'
88
+ '1000 → 236.344 ms ± 273.236 ms [*0.00 s … 509.580 ms]80%CI@2\n'
89
+ '1100 → 319.308 ms ± 639.775 ms [*0.00 s … 959.083 ms]80%CI@2\n'
90
+ '1200 → 523.885 ms ± 879.981 ms [*0.00 s … 1.40 s]80%CI@2\n'
91
+ '1300 → 506.285 ms ± 687.153 ms [*0.00 s … 1.19 s]80%CI@2\n'
92
+ '1400 → 552.840 ms ± 47.012 ms [505.828 ms … 599.852 ms]80%CI@2\nFinished in 4.12 s'))
93
+
94
+ # ========================= Markdown Generation ==================================================
95
+
96
+ # Documentation generation
97
+ doc: argparse.ArgumentParser = sub.add_parser(
98
+ 'doc', help='Documentation utilities. (Not for regular use: these are developer utils.)')
99
+ doc_sub = doc.add_subparsers(dest='doc_command')
100
+ doc_sub.add_parser(
101
+ 'md',
102
+ help='Emit Markdown docs for the CLI (see README.md section "Creating a New Version").',
103
+ epilog='doc md > profiler.md\n<<saves file>>')
104
+
105
+ return parser
106
+
107
+
108
+ def _PrimeProfiler(
109
+ prime_callable: Callable[[int], int],
110
+ repeats: int, n_bits_range: tuple[int, int, int], confidence: float, /) -> None:
111
+ primes: dict[int, list[float]] = {}
112
+ for n_bits in range(*n_bits_range):
113
+ # investigate for size n_bits
114
+ primes[n_bits] = []
115
+ for _ in range(repeats):
116
+ with base.Timer(emit_log=False) as tmr:
117
+ pr: int = prime_callable(n_bits)
118
+ assert pr and pr.bit_length() == n_bits
119
+ primes[n_bits].append(tmr.elapsed)
120
+ # finished collecting n_bits-sized primes
121
+ measurements: str = base.HumanizedMeasurements(
122
+ primes[n_bits], parser=base.HumanizedSeconds, confidence=confidence)
123
+ print(f'{n_bits} → {measurements}')
124
+
125
+
126
+ def main(argv: list[str] | None = None, /) -> int: # pylint: disable=invalid-name,too-many-locals,too-many-branches,too-many-statements
127
+ """Main entry point."""
128
+ # build the parser and parse args
129
+ parser: argparse.ArgumentParser = _BuildParser()
130
+ args: argparse.Namespace = parser.parse_args(argv)
131
+ # take care of global options
132
+ levels: list[int] = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
133
+ logging.basicConfig(
134
+ level=levels[min(args.verbose, len(levels) - 1)], # type: ignore
135
+ format=getattr(base, 'LOG_FORMAT', '%(levelname)s:%(message)s'))
136
+ logging.captureWarnings(True)
137
+
138
+ try:
139
+ # get the command, do basic checks and switch
140
+ command: str = args.command.lower().strip() if args.command else ''
141
+ repeats: int = 1 if args.number < 1 else args.number
142
+ confidence: int = 55 if args.confidence < 55 else args.confidence
143
+ confidence = 99 if confidence > 99 else confidence
144
+ args.serial = True if (not args.serial and not args.parallel) else args.serial # make default
145
+ bits: tuple[int, ...] = tuple(int(x, 10) for x in args.bits.strip().split(','))
146
+ if len(bits) != 3:
147
+ raise base.InputError('-b/--bits should be 3 ints, like: start,stop,step; eg.: 1000,3000,500')
148
+ with base.Timer(emit_log=False) as tmr:
149
+ match command:
150
+ # -------- Primes ----------
151
+ case 'primes':
152
+ print(f'Starting {"SERIAL" if args.serial else "PARALLEL"} regular primes test')
153
+ _PrimeProfiler(
154
+ lambda n: modmath.NBitRandomPrimes(n, serial=args.serial, n_primes=1).pop(),
155
+ repeats, bits, confidence / 100.0)
156
+
157
+ case 'dsa':
158
+ print(f'Starting {"SERIAL" if args.serial else "PARALLEL"} DSA primes test')
159
+ _PrimeProfiler(
160
+ lambda n: dsa.NBitRandomDSAPrimes(n, n // 2, serial=args.serial)[0],
161
+ repeats, bits, confidence / 100.0)
162
+
163
+ # -------- Documentation ----------
164
+ case 'doc':
165
+ doc_command: str = (
166
+ args.doc_command.lower().strip() if getattr(args, 'doc_command', '') else '')
167
+ match doc_command:
168
+ case 'md':
169
+ print(base.GenerateCLIMarkdown(
170
+ 'profiler', _BuildParser(), description=(
171
+ '`profiler` is a command-line utility that provides stats on TransCrypto '
172
+ 'performance.')))
173
+ case _:
174
+ raise NotImplementedError()
175
+
176
+ case _:
177
+ parser.print_help()
178
+
179
+ if command not in ('doc',):
180
+ print(f'Finished in {tmr}')
181
+
182
+ except NotImplementedError as err:
183
+ print(f'Invalid command: {err}')
184
+ except (base.Error, ValueError) as err:
185
+ print(str(err))
186
+
187
+ return 0
188
+
189
+
190
+ if __name__ == '__main__':
191
+ sys.exit(main())