gemla 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. gemla/__init__.py +9 -0
  2. gemla/__version.py +1 -0
  3. gemla/benchmarks/__init__.py +19 -0
  4. gemla/benchmarks/run_all_benchmarks.py +31 -0
  5. gemla/benchmarks/runner.py +148 -0
  6. gemla/cli/__init__.py +3 -0
  7. gemla/cli/main.py +469 -0
  8. gemla/controls/__init__.py +3 -0
  9. gemla/controls/control_suite.py +26 -0
  10. gemla/controls/phase_shuffle.py +0 -0
  11. gemla/controls/residue_scramble.py +0 -0
  12. gemla/controls/wrong_sign.py +0 -0
  13. gemla/core/action.py +0 -0
  14. gemla/core/eml.py +0 -0
  15. gemla/core/gamma_operator.py +0 -0
  16. gemla/core/surrogate.py +0 -0
  17. gemla/data/__init__.py +11 -0
  18. gemla/data/cyber.py +70 -0
  19. gemla/data/industrial.py +69 -0
  20. gemla/data/market.py +71 -0
  21. gemla/data/synthetic.py +34 -0
  22. gemla/gates/__init__.py +3 -0
  23. gemla/gates/anchor_gate.py +0 -0
  24. gemla/gates/reva_v2.py +0 -0
  25. gemla/gates/reva_v2_sf.py +80 -0
  26. gemla/gates/transport_acceptance.py +0 -0
  27. gemla/gates/winding_gate.py +0 -0
  28. gemla/integrations/__init__.py +1 -0
  29. gemla/integrations/latent/__init__.py +9 -0
  30. gemla/integrations/latent/adapter.py +84 -0
  31. gemla/integrations/vjepa/__init__.py +11 -0
  32. gemla/integrations/vjepa/adapter.py +87 -0
  33. gemla/lifted/__init__.py +13 -0
  34. gemla/lifted/anchors.py +42 -0
  35. gemla/lifted/lifted_phase.py +43 -0
  36. gemla/lifted/spectral_flatness.py +25 -0
  37. gemla/lifted/winding.py +27 -0
  38. gemla/pipelines/__init__.py +11 -0
  39. gemla/pipelines/gemla_pipeline.py +87 -0
  40. gemla/pipelines/latent_pipeline.py +107 -0
  41. gemla/reports/__init__.py +3 -0
  42. gemla/reports/markdown.py +86 -0
  43. gemla-1.0.0.dist-info/METADATA +333 -0
  44. gemla-1.0.0.dist-info/RECORD +48 -0
  45. gemla-1.0.0.dist-info/WHEEL +5 -0
  46. gemla-1.0.0.dist-info/entry_points.txt +2 -0
  47. gemla-1.0.0.dist-info/licenses/LICENSE +0 -0
  48. gemla-1.0.0.dist-info/top_level.txt +1 -0
gemla/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("gemla")
5
+ except PackageNotFoundError:
6
+ # Fallback when running directly from source before installation.
7
+ __version__ = "0.1.0"
8
+
9
+ __all__ = ["__version__"]
gemla/__version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,19 @@
1
+ from gemla.benchmarks.runner import (
2
+ BenchmarkCase,
3
+ BenchmarkRecord,
4
+ default_benchmark_cases,
5
+ run_benchmark_case,
6
+ run_benchmark_suite,
7
+ summarize_benchmark_records,
8
+ write_benchmark_results,
9
+ )
10
+
11
+ __all__ = [
12
+ "BenchmarkCase",
13
+ "BenchmarkRecord",
14
+ "default_benchmark_cases",
15
+ "run_benchmark_case",
16
+ "run_benchmark_suite",
17
+ "summarize_benchmark_records",
18
+ "write_benchmark_results",
19
+ ]
@@ -0,0 +1,31 @@
1
+ from gemla.benchmarks import (
2
+ run_benchmark_suite,
3
+ summarize_benchmark_records,
4
+ write_benchmark_results,
5
+ )
6
+
7
+
8
+ def main() -> None:
9
+ records = run_benchmark_suite()
10
+ summary = summarize_benchmark_records(records)
11
+ paths = write_benchmark_results(records)
12
+
13
+ print("GEMLA Benchmark Suite")
14
+ print("---------------------")
15
+ print(f"Total cases: {summary['total_cases']}")
16
+ print(f"Passed cases: {summary['passed_cases']}")
17
+ print(f"Failed cases: {summary['failed_cases']}")
18
+ print(f"Pass rate: {summary['pass_rate']:.2%}")
19
+ print(f"All wrong-sign rejected: {summary['all_wrong_sign_rejected']}")
20
+ print(f"All phase-shuffle rejected: {summary['all_phase_shuffle_rejected']}")
21
+ print(f"All residue-scramble rejected: {summary['all_residue_scramble_rejected']}")
22
+ print(f"Minimum anchor count: {summary['min_anchor_count']}")
23
+ print(f"Minimum winding jumps: {summary['min_winding_jumps']}")
24
+ print()
25
+ print(f"CSV written to: {paths['csv']}")
26
+ print(f"JSON written to: {paths['json']}")
27
+ print(f"Summary written to: {paths['summary']}")
28
+
29
+
30
+ if __name__ == "__main__":
31
+ main()
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ import csv
4
+ import json
5
+ from dataclasses import asdict, dataclass
6
+ from pathlib import Path
7
+
8
+ from gemla.data import make_synthetic_transport
9
+ from gemla.pipelines import GemlaPipeline
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class BenchmarkCase:
14
+ name: str
15
+ n: int = 1200
16
+ t_max: float = 80.0
17
+ noise: float = 0.01
18
+ seed: int = 7
19
+ pipeline_seed: int = 11
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class BenchmarkRecord:
24
+ name: str
25
+ verdict: bool
26
+ spectral_flatness_score: float
27
+ wrong_sign_rejected: bool
28
+ phase_shuffle_rejected: bool
29
+ residue_scramble_rejected: bool
30
+ anchor_count: int
31
+ anchor_spacing_cv: float
32
+ winding_jumps: int
33
+ winding_cells: int
34
+ n: int
35
+ t_max: float
36
+ noise: float
37
+ seed: int
38
+ pipeline_seed: int
39
+
40
+
41
+ def default_benchmark_cases() -> list[BenchmarkCase]:
42
+ """
43
+ Minimal v0.2 benchmark suite.
44
+
45
+ These cases test whether the vertical slice remains stable across
46
+ modest changes in seed, noise, and trajectory length.
47
+ """
48
+ return [
49
+ BenchmarkCase(name="baseline", n=1200, t_max=80.0, noise=0.01, seed=7),
50
+ BenchmarkCase(name="low_noise", n=1200, t_max=80.0, noise=0.005, seed=7),
51
+ BenchmarkCase(name="no_noise", n=1200, t_max=80.0, noise=0.0, seed=7),
52
+ BenchmarkCase(name="alt_seed_1", n=1200, t_max=80.0, noise=0.01, seed=13),
53
+ BenchmarkCase(name="alt_seed_2", n=1200, t_max=80.0, noise=0.01, seed=29),
54
+ BenchmarkCase(name="shorter_series", n=800, t_max=60.0, noise=0.01, seed=7),
55
+ BenchmarkCase(name="longer_series", n=1600, t_max=100.0, noise=0.01, seed=7),
56
+ ]
57
+
58
+
59
+ def run_benchmark_case(case: BenchmarkCase) -> BenchmarkRecord:
60
+ X = make_synthetic_transport(
61
+ n=case.n,
62
+ t_max=case.t_max,
63
+ noise=case.noise,
64
+ seed=case.seed,
65
+ )
66
+
67
+ result = GemlaPipeline(seed=case.pipeline_seed).fit_evaluate(X)
68
+
69
+ main = result.gate_result["main"]
70
+ controls = result.gate_result["controls"]
71
+
72
+ return BenchmarkRecord(
73
+ name=case.name,
74
+ verdict=result.verdict,
75
+ spectral_flatness_score=float(main["flatness"]["score"]),
76
+ wrong_sign_rejected=bool(controls["wrong_sign"]["rejected"]),
77
+ phase_shuffle_rejected=bool(controls["phase_shuffle"]["rejected"]),
78
+ residue_scramble_rejected=bool(controls["residue_scramble"]["rejected"]),
79
+ anchor_count=int(result.anchor_result["anchor_count"]),
80
+ anchor_spacing_cv=float(result.anchor_result["spacing_cv"]),
81
+ winding_jumps=int(result.winding_result["jump_count"]),
82
+ winding_cells=int(result.winding_result["total_winding_cells"]),
83
+ n=case.n,
84
+ t_max=case.t_max,
85
+ noise=case.noise,
86
+ seed=case.seed,
87
+ pipeline_seed=case.pipeline_seed,
88
+ )
89
+
90
+
91
+ def run_benchmark_suite(
92
+ cases: list[BenchmarkCase] | None = None,
93
+ ) -> list[BenchmarkRecord]:
94
+ if cases is None:
95
+ cases = default_benchmark_cases()
96
+
97
+ return [run_benchmark_case(case) for case in cases]
98
+
99
+
100
+ def summarize_benchmark_records(records: list[BenchmarkRecord]) -> dict:
101
+ total = len(records)
102
+ passed = sum(record.verdict for record in records)
103
+
104
+ return {
105
+ "total_cases": total,
106
+ "passed_cases": passed,
107
+ "failed_cases": total - passed,
108
+ "pass_rate": passed / total if total else 0.0,
109
+ "all_passed": passed == total,
110
+ "all_wrong_sign_rejected": all(r.wrong_sign_rejected for r in records),
111
+ "all_phase_shuffle_rejected": all(r.phase_shuffle_rejected for r in records),
112
+ "all_residue_scramble_rejected": all(r.residue_scramble_rejected for r in records),
113
+ "min_anchor_count": min((r.anchor_count for r in records), default=0),
114
+ "min_winding_jumps": min((r.winding_jumps for r in records), default=0),
115
+ }
116
+
117
+
118
+ def write_benchmark_results(
119
+ records: list[BenchmarkRecord],
120
+ output_dir: str | Path = "benchmarks/results",
121
+ ) -> dict[str, Path]:
122
+ output_dir = Path(output_dir)
123
+ output_dir.mkdir(parents=True, exist_ok=True)
124
+
125
+ csv_path = output_dir / "benchmark_results.csv"
126
+ json_path = output_dir / "benchmark_results.json"
127
+ summary_path = output_dir / "benchmark_summary.json"
128
+
129
+ rows = [asdict(record) for record in records]
130
+
131
+ if rows:
132
+ with csv_path.open("w", newline="", encoding="utf-8") as f:
133
+ writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
134
+ writer.writeheader()
135
+ writer.writerows(rows)
136
+ else:
137
+ csv_path.write_text("", encoding="utf-8")
138
+
139
+ json_path.write_text(json.dumps(rows, indent=2), encoding="utf-8")
140
+
141
+ summary = summarize_benchmark_records(records)
142
+ summary_path.write_text(json.dumps(summary, indent=2), encoding="utf-8")
143
+
144
+ return {
145
+ "csv": csv_path,
146
+ "json": json_path,
147
+ "summary": summary_path,
148
+ }
gemla/cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from gemla.cli.main import main
2
+
3
+ __all__ = ["main"]
gemla/cli/main.py ADDED
@@ -0,0 +1,469 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+ import numpy as np
6
+
7
+ from gemla.benchmarks import (
8
+ run_benchmark_suite,
9
+ summarize_benchmark_records,
10
+ write_benchmark_results,
11
+ )
12
+ from gemla.data import make_synthetic_transport
13
+ from gemla.pipelines import GemlaPipeline
14
+ from gemla.reports import export_markdown_report
15
+ from gemla.integrations.latent import make_synthetic_latents
16
+ from gemla.pipelines import GemlaLatentPipeline
17
+ from gemla.integrations.vjepa import (
18
+ load_vjepa_embeddings,
19
+ save_sample_vjepa_like_embeddings,
20
+ )
21
+ from gemla.data import (
22
+ make_market_microstructure,
23
+ make_industrial_telemetry,
24
+ make_cyber_event_transport,
25
+ make_synthetic_transport
26
+ )
27
+
28
+
29
+
30
+ def build_parser() -> argparse.ArgumentParser:
31
+ parser = argparse.ArgumentParser(
32
+ prog="gemla",
33
+ description="GEMLA: Γ–EML–α Transport Architecture CLI",
34
+ )
35
+
36
+ subparsers = parser.add_subparsers(dest="command", required=True)
37
+
38
+ evaluate = subparsers.add_parser(
39
+ "evaluate",
40
+ help="Run the minimal GEMLA transport evaluation pipeline.",
41
+ )
42
+
43
+ evaluate.add_argument(
44
+ "--output",
45
+ type=str,
46
+ default="reports/gemla_transport_report.md",
47
+ help="Path where the Markdown report will be written.",
48
+ )
49
+
50
+ evaluate.add_argument(
51
+ "--n",
52
+ type=int,
53
+ default=1200,
54
+ help="Number of synthetic samples.",
55
+ )
56
+
57
+ evaluate.add_argument(
58
+ "--t-max",
59
+ type=float,
60
+ default=80.0,
61
+ help="Maximum synthetic time value.",
62
+ )
63
+
64
+ evaluate.add_argument(
65
+ "--noise",
66
+ type=float,
67
+ default=0.01,
68
+ help="Synthetic noise level.",
69
+ )
70
+
71
+ evaluate.add_argument(
72
+ "--seed",
73
+ type=int,
74
+ default=7,
75
+ help="Synthetic data random seed.",
76
+ )
77
+
78
+ evaluate.add_argument(
79
+ "--pipeline-seed",
80
+ type=int,
81
+ default=11,
82
+ help="Pipeline/control random seed.",
83
+ )
84
+
85
+ benchmark = subparsers.add_parser(
86
+ "benchmark",
87
+ help="Run the GEMLA benchmark suite.",
88
+ )
89
+
90
+ benchmark.add_argument(
91
+ "--output-dir",
92
+ type=str,
93
+ default="benchmarks/results",
94
+ help="Directory where benchmark CSV/JSON outputs will be written.",
95
+ )
96
+
97
+ evaluate_latent = subparsers.add_parser(
98
+ "evaluate-latent",
99
+ help="Run GEMLA transport evaluation on latent embeddings.",
100
+ )
101
+
102
+ evaluate_latent.add_argument(
103
+ "--input",
104
+ type=str,
105
+ default=None,
106
+ help="Optional .npy file containing latent embeddings of shape (n_steps, latent_dim).",
107
+ )
108
+
109
+ evaluate_latent.add_argument(
110
+ "--n",
111
+ type=int,
112
+ default=1200,
113
+ help="Number of synthetic latent samples if --input is not provided.",
114
+ )
115
+
116
+ evaluate_latent.add_argument(
117
+ "--latent-dim",
118
+ type=int,
119
+ default=16,
120
+ help="Synthetic latent dimension if --input is not provided.",
121
+ )
122
+
123
+ evaluate_latent.add_argument(
124
+ "--t-max",
125
+ type=float,
126
+ default=80.0,
127
+ help="Maximum synthetic time value.",
128
+ )
129
+
130
+ evaluate_latent.add_argument(
131
+ "--noise",
132
+ type=float,
133
+ default=0.01,
134
+ help="Synthetic latent noise level.",
135
+ )
136
+
137
+ evaluate_latent.add_argument(
138
+ "--seed",
139
+ type=int,
140
+ default=17,
141
+ help="Synthetic latent random seed.",
142
+ )
143
+
144
+ evaluate_latent.add_argument(
145
+ "--component-a",
146
+ type=int,
147
+ default=0,
148
+ help="Latent component used as real part.",
149
+ )
150
+
151
+ evaluate_latent.add_argument(
152
+ "--component-b",
153
+ type=int,
154
+ default=1,
155
+ help="Latent component used as imaginary part.",
156
+ )
157
+
158
+ evaluate_vjepa = subparsers.add_parser(
159
+ "evaluate-vjepa",
160
+ help="Run GEMLA transport evaluation on V-JEPA-style embeddings.",
161
+ )
162
+
163
+ evaluate_vjepa.add_argument(
164
+ "--input",
165
+ type=str,
166
+ default=None,
167
+ help="Path to a .npy file containing embeddings of shape (n_steps, latent_dim).",
168
+ )
169
+
170
+ evaluate_vjepa.add_argument(
171
+ "--synthetic",
172
+ action="store_true",
173
+ help="Generate synthetic V-JEPA-like embeddings for a demo run.",
174
+ )
175
+
176
+ evaluate_vjepa.add_argument(
177
+ "--synthetic-output",
178
+ type=str,
179
+ default="examples/vjepa_latent_transport/sample_vjepa_like_embeddings.npy",
180
+ help="Where synthetic V-JEPA-like embeddings will be saved.",
181
+ )
182
+
183
+ evaluate_vjepa.add_argument(
184
+ "--n",
185
+ type=int,
186
+ default=1200,
187
+ help="Number of synthetic embedding samples.",
188
+ )
189
+
190
+ evaluate_vjepa.add_argument(
191
+ "--latent-dim",
192
+ type=int,
193
+ default=32,
194
+ help="Synthetic latent dimension.",
195
+ )
196
+
197
+ evaluate_vjepa.add_argument(
198
+ "--t-max",
199
+ type=float,
200
+ default=80.0,
201
+ help="Maximum synthetic time value.",
202
+ )
203
+
204
+ evaluate_vjepa.add_argument(
205
+ "--noise",
206
+ type=float,
207
+ default=0.01,
208
+ help="Synthetic embedding noise level.",
209
+ )
210
+
211
+ evaluate_vjepa.add_argument(
212
+ "--seed",
213
+ type=int,
214
+ default=23,
215
+ help="Synthetic embedding random seed.",
216
+ )
217
+
218
+ evaluate_vjepa.add_argument(
219
+ "--component-a",
220
+ type=int,
221
+ default=0,
222
+ help="Embedding component used as real part.",
223
+ )
224
+
225
+ evaluate_vjepa.add_argument(
226
+ "--component-b",
227
+ type=int,
228
+ default=1,
229
+ help="Embedding component used as imaginary part.",
230
+ )
231
+
232
+ demo_industrial = subparsers.add_parser(
233
+ "demo-industrial",
234
+ help="Run the GEMLA industrial telemetry demo.",
235
+ )
236
+ demo_industrial.add_argument(
237
+ "--output",
238
+ type=str,
239
+ default="reports/industrial_telemetry_report.md",
240
+ help="Path where the Markdown report will be written.",
241
+ )
242
+
243
+ demo_market = subparsers.add_parser(
244
+ "demo-market",
245
+ help="Run the GEMLA market microstructure demo.",
246
+ )
247
+ demo_market.add_argument(
248
+ "--output",
249
+ type=str,
250
+ default="reports/market_microstructure_report.md",
251
+ help="Path where the Markdown report will be written.",
252
+ )
253
+
254
+ demo_cyber = subparsers.add_parser(
255
+ "demo-cyber",
256
+ help="Run the GEMLA cyber event transport demo.",
257
+ )
258
+ demo_cyber.add_argument(
259
+ "--output",
260
+ type=str,
261
+ default="reports/cyber_event_transport_report.md",
262
+ help="Path where the Markdown report will be written.",
263
+ )
264
+
265
+ return parser
266
+
267
+
268
+ def run_evaluate(args: argparse.Namespace) -> int:
269
+ X = make_synthetic_transport(
270
+ n=args.n,
271
+ t_max=args.t_max,
272
+ noise=args.noise,
273
+ seed=args.seed,
274
+ )
275
+
276
+ pipe = GemlaPipeline(seed=args.pipeline_seed)
277
+ result = pipe.fit_evaluate(X)
278
+
279
+ print(result.summary())
280
+
281
+ report_path = export_markdown_report(
282
+ result,
283
+ output_path=Path(args.output),
284
+ )
285
+
286
+ print()
287
+ print(f"Report written to: {report_path}")
288
+
289
+ return 0 if result.verdict else 1
290
+
291
+
292
+ def run_benchmark(args: argparse.Namespace) -> int:
293
+ records = run_benchmark_suite()
294
+ summary = summarize_benchmark_records(records)
295
+ paths = write_benchmark_results(
296
+ records,
297
+ output_dir=Path(args.output_dir),
298
+ )
299
+
300
+ print("GEMLA Benchmark Suite")
301
+ print("---------------------")
302
+ print(f"Total cases: {summary['total_cases']}")
303
+ print(f"Passed cases: {summary['passed_cases']}")
304
+ print(f"Failed cases: {summary['failed_cases']}")
305
+ print(f"Pass rate: {summary['pass_rate']:.2%}")
306
+ print(f"All wrong-sign rejected: {summary['all_wrong_sign_rejected']}")
307
+ print(f"All phase-shuffle rejected: {summary['all_phase_shuffle_rejected']}")
308
+ print(f"All residue-scramble rejected: {summary['all_residue_scramble_rejected']}")
309
+ print(f"Minimum anchor count: {summary['min_anchor_count']}")
310
+ print(f"Minimum winding jumps: {summary['min_winding_jumps']}")
311
+ print()
312
+ print(f"CSV written to: {paths['csv']}")
313
+ print(f"JSON written to: {paths['json']}")
314
+ print(f"Summary written to: {paths['summary']}")
315
+
316
+ return 0 if summary["all_passed"] else 1
317
+
318
+
319
+ def main() -> int:
320
+ parser = build_parser()
321
+ args = parser.parse_args()
322
+
323
+ if args.command == "evaluate":
324
+ return run_evaluate(args)
325
+
326
+ if args.command == "benchmark":
327
+ return run_benchmark(args)
328
+
329
+ if args.command == "evaluate-latent":
330
+ return run_evaluate_latent(args)
331
+
332
+ if args.command == "evaluate-vjepa":
333
+ return run_evaluate_vjepa(args)
334
+
335
+ if args.command == "demo-industrial":
336
+ return run_demo_industrial(args)
337
+
338
+ if args.command == "demo-market":
339
+ return run_demo_market(args)
340
+
341
+ if args.command == "demo-cyber":
342
+ return run_demo_cyber(args)
343
+
344
+ parser.print_help()
345
+ return 2
346
+
347
+
348
+ if __name__ == "__main__":
349
+ raise SystemExit(main())
350
+
351
+ def run_evaluate_latent(args: argparse.Namespace) -> int:
352
+ if args.input is not None:
353
+ latents = np.load(args.input)
354
+ else:
355
+ latents = make_synthetic_latents(
356
+ n=args.n,
357
+ latent_dim=args.latent_dim,
358
+ t_max=args.t_max,
359
+ noise=args.noise,
360
+ seed=args.seed,
361
+ )
362
+
363
+ pipe = GemlaLatentPipeline(
364
+ component_a=args.component_a,
365
+ component_b=args.component_b,
366
+ )
367
+
368
+ result = pipe.fit_evaluate(latents)
369
+
370
+ print(result.summary())
371
+
372
+ return 0 if result.verdict else 1
373
+ def run_evaluate_vjepa(args: argparse.Namespace) -> int:
374
+ if args.synthetic:
375
+ embedding_path = save_sample_vjepa_like_embeddings(
376
+ args.synthetic_output,
377
+ n=args.n,
378
+ latent_dim=args.latent_dim,
379
+ t_max=args.t_max,
380
+ noise=args.noise,
381
+ seed=args.seed,
382
+ )
383
+ embeddings = load_vjepa_embeddings(embedding_path)
384
+ elif args.input is not None:
385
+ embedding_path = Path(args.input)
386
+ embeddings = load_vjepa_embeddings(embedding_path)
387
+ else:
388
+ print("Error: provide --input path/to/embeddings.npy or use --synthetic.")
389
+ return 2
390
+
391
+ pipe = GemlaLatentPipeline(
392
+ component_a=args.component_a,
393
+ component_b=args.component_b,
394
+ )
395
+
396
+ result = pipe.fit_evaluate(embeddings)
397
+
398
+ print("GEMLA V-JEPA-Style Latent Transport Evaluation")
399
+ print("----------------------------------------------")
400
+ print(f"Input embeddings: {embedding_path}")
401
+ print()
402
+ print(result.summary())
403
+
404
+ return 0 if result.verdict else 1
405
+
406
+ def run_demo_industrial(args: argparse.Namespace) -> int:
407
+ X = make_industrial_telemetry()
408
+
409
+ pipe = GemlaPipeline()
410
+ result = pipe.fit_evaluate(X)
411
+
412
+ print("GEMLA Industrial Telemetry Demo")
413
+ print("-------------------------------")
414
+ print(result.summary())
415
+
416
+ report_path = export_markdown_report(
417
+ result,
418
+ output_path=Path(args.output),
419
+ title="GEMLA Industrial Telemetry Report",
420
+ )
421
+
422
+ print()
423
+ print(f"Report written to: {report_path}")
424
+
425
+ return 0 if result.verdict else 1
426
+
427
+
428
+ def run_demo_market(args: argparse.Namespace) -> int:
429
+ X = make_market_microstructure()
430
+
431
+ pipe = GemlaPipeline()
432
+ result = pipe.fit_evaluate(X)
433
+
434
+ print("GEMLA Market Microstructure Demo")
435
+ print("--------------------------------")
436
+ print(result.summary())
437
+
438
+ report_path = export_markdown_report(
439
+ result,
440
+ output_path=Path(args.output),
441
+ title="GEMLA Market Microstructure Report",
442
+ )
443
+
444
+ print()
445
+ print(f"Report written to: {report_path}")
446
+
447
+ return 0 if result.verdict else 1
448
+
449
+
450
+ def run_demo_cyber(args: argparse.Namespace) -> int:
451
+ X = make_cyber_event_transport()
452
+
453
+ pipe = GemlaPipeline()
454
+ result = pipe.fit_evaluate(X)
455
+
456
+ print("GEMLA Cyber Event Transport Demo")
457
+ print("--------------------------------")
458
+ print(result.summary())
459
+
460
+ report_path = export_markdown_report(
461
+ result,
462
+ output_path=Path(args.output),
463
+ title="GEMLA Cyber Event Transport Report",
464
+ )
465
+
466
+ print()
467
+ print(f"Report written to: {report_path}")
468
+
469
+ return 0 if result.verdict else 1
@@ -0,0 +1,3 @@
1
+ from gemla.controls.control_suite import make_controls
2
+
3
+ __all__ = ["make_controls"]