sutra-dev 0.9.2__tar.gz → 0.9.3__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.
- {sutra_dev-0.9.2/sutra_dev.egg-info → sutra_dev-0.9.3}/PKG-INFO +1 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/pyproject.toml +1 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/__init__.py +1 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/__main__.py +35 -9
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/codegen.py +1 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/codegen_base.py +243 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/codegen_pytorch.py +107 -9
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/embedding.py +10 -2
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/fv.py +24 -0
- sutra_dev-0.9.3/sutra_compiler/fv_loop_convergence.py +177 -0
- sutra_dev-0.9.3/sutra_compiler/fv_sampler_convergence.py +199 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/parser.py +31 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/repl.py +87 -5
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/strings.su +6 -0
- sutra_dev-0.9.3/sutra_compiler/symbol_table.py +550 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/validator.py +250 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.3/sutra_dev.egg-info}/PKG-INFO +1 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_dev.egg-info/SOURCES.txt +21 -1
- sutra_dev-0.9.3/tests/test_arity_diagnostic.py +82 -0
- sutra_dev-0.9.3/tests/test_cast_codegen.py +183 -0
- sutra_dev-0.9.3/tests/test_fv_loop_convergence.py +164 -0
- sutra_dev-0.9.3/tests/test_fv_sampler_convergence.py +121 -0
- sutra_dev-0.9.3/tests/test_int_to_string.py +84 -0
- sutra_dev-0.9.3/tests/test_interp_string_codegen.py +86 -0
- sutra_dev-0.9.3/tests/test_make_string_idempotent.py +62 -0
- sutra_dev-0.9.3/tests/test_ntm_ram.py +492 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_parser.py +25 -0
- sutra_dev-0.9.3/tests/test_python_builtin_escape.py +66 -0
- sutra_dev-0.9.3/tests/test_repl.py +116 -0
- sutra_dev-0.9.3/tests/test_run_error_diagnostics.py +53 -0
- sutra_dev-0.9.3/tests/test_string_plus_concat.py +84 -0
- sutra_dev-0.9.3/tests/test_symbol_table.py +250 -0
- sutra_dev-0.9.3/tests/test_type_inference.py +105 -0
- sutra_dev-0.9.3/tests/test_unknown_function_diagnostic.py +100 -0
- sutra_dev-0.9.3/tests/test_unknown_type_diagnostic.py +89 -0
- sutra_dev-0.9.3/tests/test_unknown_variable_diagnostic.py +114 -0
- sutra_dev-0.9.3/tests/test_unsafe_override_codegen.py +42 -0
- sutra_dev-0.9.3/tests/test_wrong_arg_type_diagnostic.py +81 -0
- sutra_dev-0.9.2/tests/test_ntm_ram.py +0 -263
- sutra_dev-0.9.2/tests/test_repl.py +0 -73
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/LICENSE +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/README.md +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/setup.cfg +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/ast_nodes.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/axon_keys.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/cached_compile.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/codegen_thrml.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/diagnostics.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/fv_key_soundness.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/fv_obligation_checker.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/fv_poly_bound.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/inliner.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/lexer.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/loop_capture.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/loop_desugar.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/multi_process.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/preeval.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/promise_desugar.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/review.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/simplify.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/simplify_egglog.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/axons.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/bigint.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/bitwise.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/embed.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/javascript_object.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/javascript_primitives.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/logic.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/math.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/memory.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/modulus.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/numbers.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/promises.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/rotation.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/similarity.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/tensor.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib/vectors.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/stdlib_loader.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/sutradb_embedded.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/tabulate.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/trace.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_compiler/workspace.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_dev.egg-info/dependency_links.txt +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_dev.egg-info/entry_points.txt +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_dev.egg-info/requires.txt +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/sutra_dev.egg-info/top_level.txt +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_arrow_functions.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_await_midfunction.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_await_substrate_pure.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_axon_build.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_axon_device_coherence.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_axon_item_call_result.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_axon_keys.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_axon_project.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_branchless_loop.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_buffer_transcendentals.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_cached_compile.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_class_fields_runtime.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_codegen.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_codegen_pytorch.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_codegen_thrml.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_corpus.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_dimension_audit.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_dimension_audit_warning.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_embedding_provider.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_equality_autograd.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_fused_nn.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_fv_general_checker.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_fv_key_soundness.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_fv_kleene_grid_exactness.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_fv_poly_obligation_checker.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_fv_public_api.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_fv_termination.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_fv_worked_example.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_higher_order_functions.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_implicit_loop_desugar.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_inliner.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_int_dict.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_jvm_core.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_lexer.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_list_ops.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_load_matrix.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_loop_capture.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_loop_function_decl.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_matrix_literal.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_measurement_claim_sweep.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_mini_wasm_machine.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_multi_process_runtime.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_native_recursion.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_no_host_readout.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_optional_llm_model.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_preeval.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_process_pool_runtime.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_ram_direct_tensor.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_rotation_prewarm.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_simplify.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_simplify_egglog.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_simplify_egglog_cse.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_snap_diagnostic.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_stdlib_loader.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_string_equality.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_substrate_leak_sweep.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_sutradb_embedded.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_tabulate.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_terminal_string_decode.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_torch_compile_wrap.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_trainable_matrix.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_transcendentals.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_type_test_gap.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_vector_literal.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_wasm_core.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.3}/tests/test_workspace.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sutra-dev"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.3"
|
|
8
8
|
description = "Compiler for the Sutra programming language — tensor ops on frozen-LLM embedding vectors."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -28,7 +28,7 @@ The compiler is intentionally liberal where the spec is still open
|
|
|
28
28
|
forms and flags the clearly-forbidden ones.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
-
__version__ = "0.9.
|
|
31
|
+
__version__ = "0.9.3"
|
|
32
32
|
|
|
33
33
|
from .diagnostics import Diagnostic, DiagnosticLevel, DiagnosticBag
|
|
34
34
|
from .lexer import Lexer, Token, TokenKind
|
|
@@ -325,11 +325,24 @@ def _run_execute(path: str, *, runtime_dim: int, runtime_seed: int,
|
|
|
325
325
|
return 1
|
|
326
326
|
mod = types.ModuleType("_sutra_run")
|
|
327
327
|
mod.__file__ = f"<generated from {path}>"
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
328
|
+
try:
|
|
329
|
+
exec(compile(py_src, mod.__file__, "exec"), mod.__dict__)
|
|
330
|
+
if hasattr(mod, "main") and callable(mod.main):
|
|
331
|
+
result = mod.main()
|
|
332
|
+
if result is not None:
|
|
333
|
+
print(_decode_terminal_result(mod, result))
|
|
334
|
+
else:
|
|
335
|
+
# The substrate program is the body of main(); a file without one
|
|
336
|
+
# has nothing to run. Say so rather than exiting silently with no
|
|
337
|
+
# output (a newcomer otherwise can't tell why nothing happened).
|
|
338
|
+
print(f"{path}: no main() found - nothing to run", file=sys.stderr)
|
|
339
|
+
except Exception as exc:
|
|
340
|
+
# A runtime error in the generated module — e.g. a type mismatch the v0.1
|
|
341
|
+
# validator can't catch yet — should read like a Sutra diagnostic, not an
|
|
342
|
+
# uncaught Python traceback. KeyboardInterrupt / SystemExit are not caught
|
|
343
|
+
# (they subclass BaseException, not Exception), so Ctrl-C still works.
|
|
344
|
+
print(f"{path}: runtime error: {type(exc).__name__}: {exc}", file=sys.stderr)
|
|
345
|
+
return 1
|
|
333
346
|
return 0
|
|
334
347
|
|
|
335
348
|
|
|
@@ -387,7 +400,7 @@ def _run_viz(path: str, *, runtime_dim: int, runtime_seed: int,
|
|
|
387
400
|
# Inject tracing shim: after the _VSA = _TorchVSA(...) line,
|
|
388
401
|
# wrap every method with a tracing version.
|
|
389
402
|
shim = '''
|
|
390
|
-
#
|
|
403
|
+
# -- Tracing shim (injected by --run-viz) --
|
|
391
404
|
_orig_embed = _VSA.embed
|
|
392
405
|
_orig_bind = _VSA.bind
|
|
393
406
|
_orig_unbind = _VSA.unbind
|
|
@@ -417,7 +430,7 @@ _VSA.embed = _traced_embed
|
|
|
417
430
|
_VSA.bind = _traced_bind
|
|
418
431
|
_VSA.unbind = _traced_unbind
|
|
419
432
|
_VSA.bundle = _traced_bundle
|
|
420
|
-
#
|
|
433
|
+
# -- End tracing shim --
|
|
421
434
|
'''
|
|
422
435
|
# Find the _VSA = _TorchVSA(...) line and inject after it
|
|
423
436
|
lines = py_src.split('\n')
|
|
@@ -514,6 +527,19 @@ def main(argv: List[str] | None = None) -> int:
|
|
|
514
527
|
parser = argparse.ArgumentParser(
|
|
515
528
|
prog="sutrac",
|
|
516
529
|
description="Validate Sutra (.su) source files.",
|
|
530
|
+
epilog=(
|
|
531
|
+
"interactive REPL:\n"
|
|
532
|
+
" sutrac repl Launch the interactive evaluator (takes no\n"
|
|
533
|
+
" file). Type an expression to see its value;\n"
|
|
534
|
+
" declarations accumulate as session state.\n"
|
|
535
|
+
"\n"
|
|
536
|
+
"examples:\n"
|
|
537
|
+
" sutrac file.su Validate a source file.\n"
|
|
538
|
+
" sutrac --run file.su Compile and execute (needs torch).\n"
|
|
539
|
+
" sutrac --emit file.su Print the generated PyTorch module.\n"
|
|
540
|
+
" sutrac repl Explore interactively."
|
|
541
|
+
),
|
|
542
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
517
543
|
)
|
|
518
544
|
parser.add_argument(
|
|
519
545
|
"paths",
|
|
@@ -541,7 +567,7 @@ def main(argv: List[str] | None = None) -> int:
|
|
|
541
567
|
help=(
|
|
542
568
|
"Compile the first input file to self-contained torch Python and "
|
|
543
569
|
"print it to stdout. Picks CUDA at module init if available; "
|
|
544
|
-
"falls back to CPU otherwise. This is the one main codegen target
|
|
570
|
+
"falls back to CPU otherwise. This is the one main codegen target - "
|
|
545
571
|
"PyTorch is the runtime and the tensor-op library Sutra compiles "
|
|
546
572
|
"against."
|
|
547
573
|
),
|
|
@@ -564,7 +590,7 @@ def main(argv: List[str] | None = None) -> int:
|
|
|
564
590
|
"stdout. The default PyTorch path (--emit/--run) is untouched; this is "
|
|
565
591
|
"the Extropic thermodynamic-sampling compile target (queue.md approach "
|
|
566
592
|
"G). Requires jax + the thrml submodule. Lowering coverage is the "
|
|
567
|
-
"validated subset only
|
|
593
|
+
"validated subset only - see codegen_thrml.py."
|
|
568
594
|
),
|
|
569
595
|
)
|
|
570
596
|
parser.add_argument(
|
|
@@ -1700,7 +1700,7 @@ class Codegen(BaseCodegen):
|
|
|
1700
1700
|
self._emit("distinguish `'a'` (97 with flag) from `97` (97 without).")
|
|
1701
1701
|
self._emit("Arithmetic on chars works the same as on ints — both")
|
|
1702
1702
|
self._emit("live on the number axis. Downstream code that cares")
|
|
1703
|
-
self._emit("about the distinction can read the flag via `
|
|
1703
|
+
self._emit("about the distinction can read the flag via `is_string`.")
|
|
1704
1704
|
self._emit('"""')
|
|
1705
1705
|
self._emit("v = _np.zeros(self.dim, dtype=_np.float64)")
|
|
1706
1706
|
self._emit("v[self.semantic_dim + self.AXIS_REAL] = float(codepoint)")
|
|
@@ -3021,6 +3021,30 @@ class BaseCodegen:
|
|
|
3021
3021
|
"char", "Character", "string", "String"}
|
|
3022
3022
|
)
|
|
3023
3023
|
|
|
3024
|
+
#: Types whose values are substrate Strings (text family).
|
|
3025
|
+
_TEXT_TYPES = frozenset({"string", "String", "char", "Character"})
|
|
3026
|
+
|
|
3027
|
+
def _is_text_expr(self, expr: ast.Expr) -> bool:
|
|
3028
|
+
"""True iff expr is provably a text (String) value at compile
|
|
3029
|
+
time. Conservative — unknown falls through to the numeric /
|
|
3030
|
+
element-wise `+` paths, same posture as _is_number_expr."""
|
|
3031
|
+
if isinstance(expr, (ast.StringLiteral, ast.CharLiteral,
|
|
3032
|
+
ast.InterpolatedString)):
|
|
3033
|
+
return True
|
|
3034
|
+
if isinstance(expr, ast.Identifier):
|
|
3035
|
+
return self._var_type.get(expr.name) in self._TEXT_TYPES
|
|
3036
|
+
if isinstance(expr, ast.Parenthesized):
|
|
3037
|
+
return self._is_text_expr(expr.inner)
|
|
3038
|
+
if isinstance(expr, (ast.CastExpr, ast.UnsafeCastExpr)):
|
|
3039
|
+
return (expr.target_type is not None
|
|
3040
|
+
and expr.target_type.name in self._TEXT_TYPES)
|
|
3041
|
+
if isinstance(expr, ast.BinaryOp) and expr.op == "+":
|
|
3042
|
+
return (self._is_text_expr(expr.left)
|
|
3043
|
+
and self._is_text_expr(expr.right))
|
|
3044
|
+
if isinstance(expr, ast.Call):
|
|
3045
|
+
return self._resolved_return_type(expr.callee) in self._TEXT_TYPES
|
|
3046
|
+
return False
|
|
3047
|
+
|
|
3024
3048
|
def _is_synthetic_axis_expr(self, expr: ast.Expr) -> bool:
|
|
3025
3049
|
"""True iff expr is provably a synthetic-axis-encoded value at
|
|
3026
3050
|
compile time — int, float, complex, scalar, char, or string.
|
|
@@ -3380,6 +3404,21 @@ class BaseCodegen:
|
|
|
3380
3404
|
)
|
|
3381
3405
|
if user_op_name is not None:
|
|
3382
3406
|
return f"{user_op_name}({left}, {right})"
|
|
3407
|
+
# Text concatenation: `+` with BOTH operands provably text
|
|
3408
|
+
# dispatches to the substrate string_concat (round-17 audit
|
|
3409
|
+
# fix). Without this, lowercase-`string`-typed operands fell
|
|
3410
|
+
# through to ELEMENT-WISE VECTOR ADD — 'ab' + 'cd' silently
|
|
3411
|
+
# summed codepoint axes into garbage — and two string
|
|
3412
|
+
# literals crashed at runtime (host strs reached
|
|
3413
|
+
# string_concat). The String CLASS `operator +` still wins
|
|
3414
|
+
# above for class-typed operands. Literals re-translate with
|
|
3415
|
+
# dest_type="string" so they cross as substrate Strings.
|
|
3416
|
+
if (expr.op == "+" and self.supports_string_runtime
|
|
3417
|
+
and self._is_text_expr(expr.left)
|
|
3418
|
+
and self._is_text_expr(expr.right)):
|
|
3419
|
+
lt = self._translate_expr(expr.left, dest_type="string")
|
|
3420
|
+
rt = self._translate_expr(expr.right, dest_type="string")
|
|
3421
|
+
return f"_VSA.string_concat({lt}, {rt})"
|
|
3383
3422
|
# Logical operators dispatch through the substrate so they
|
|
3384
3423
|
# work uniformly on bool / fuzzy / trit / truth-axis-vector
|
|
3385
3424
|
# inputs. Zadeh fuzzy logic — min for AND, max for OR — on
|
|
@@ -3589,10 +3628,214 @@ class BaseCodegen:
|
|
|
3589
3628
|
return self._embed_expr_src(expr)
|
|
3590
3629
|
if isinstance(expr, ast.DefuzzyExpr):
|
|
3591
3630
|
return self._defuzzy_expr_src(expr)
|
|
3631
|
+
if isinstance(expr, ast.InterpolatedString):
|
|
3632
|
+
# `$"hello {s}!"` desugars to a make_string / string_concat
|
|
3633
|
+
# chain — every piece a substrate String, every join a
|
|
3634
|
+
# substrate op (strings.md). Only STRING-TYPED interpolants
|
|
3635
|
+
# are lowerable today: a number/fuzzy interpolant needs the
|
|
3636
|
+
# substrate number→string formatter, which is not built, so
|
|
3637
|
+
# those reject with a steer (same wall as the text casts).
|
|
3638
|
+
if not self.supports_string_runtime:
|
|
3639
|
+
raise CodegenNotSupported(
|
|
3640
|
+
expr,
|
|
3641
|
+
"interpolated strings need the substrate String "
|
|
3642
|
+
"runtime; use the PyTorch backend (codegen_pytorch)."
|
|
3643
|
+
)
|
|
3644
|
+
pieces: list[str] = []
|
|
3645
|
+
for part in expr.parts:
|
|
3646
|
+
if isinstance(part, str):
|
|
3647
|
+
if part:
|
|
3648
|
+
pieces.append(f"_VSA.make_string({part!r})")
|
|
3649
|
+
continue
|
|
3650
|
+
part_t = self._infer_cast_operand_type(part)
|
|
3651
|
+
if self._CAST_FAMILY.get(part_t) == "text":
|
|
3652
|
+
pieces.append(
|
|
3653
|
+
self._translate_expr(part, dest_type="string"))
|
|
3654
|
+
continue
|
|
3655
|
+
if part_t == "int":
|
|
3656
|
+
# strings.md § Integer formatting: the substrate
|
|
3657
|
+
# int→string formatter. int ONLY — a fractional
|
|
3658
|
+
# `number` rendering as its rounded integer would
|
|
3659
|
+
# silently lie, so it keeps rejecting below.
|
|
3660
|
+
pieces.append(
|
|
3661
|
+
f"_VSA.int_to_string("
|
|
3662
|
+
f"{self._translate_expr(part)})")
|
|
3663
|
+
continue
|
|
3664
|
+
if part_t is None:
|
|
3665
|
+
raise CodegenNotSupported(
|
|
3666
|
+
part,
|
|
3667
|
+
"interpolated expression must be string- or "
|
|
3668
|
+
"int-typed and statically inferable — assign it "
|
|
3669
|
+
"to a `string` (or `int`) variable first, or "
|
|
3670
|
+
"build the text with make_string / string_concat."
|
|
3671
|
+
)
|
|
3672
|
+
raise CodegenNotSupported(
|
|
3673
|
+
part,
|
|
3674
|
+
f"cannot interpolate a `{part_t}` value: only string- "
|
|
3675
|
+
"and int-typed interpolants lower (int_to_string is "
|
|
3676
|
+
"integer-shaped; fractional rendering is not built). "
|
|
3677
|
+
"For an integer value, declare it `int`; otherwise "
|
|
3678
|
+
"build the text with make_string / string_concat."
|
|
3679
|
+
)
|
|
3680
|
+
if not pieces:
|
|
3681
|
+
return "_VSA.make_string('')"
|
|
3682
|
+
src = pieces[0]
|
|
3683
|
+
for nxt in pieces[1:]:
|
|
3684
|
+
src = f"_VSA.string_concat({src}, {nxt})"
|
|
3685
|
+
return src
|
|
3686
|
+
if isinstance(expr, ast.UnsafeCastExpr):
|
|
3687
|
+
# types.md § "Casting — relabeling, not transformation":
|
|
3688
|
+
# unsafeCast<T>(x) is ALWAYS the pure relabel — the value
|
|
3689
|
+
# crosses unchanged and only the static type changes. It
|
|
3690
|
+
# never axis-moves; `(fuzzy) n` converts, `unsafeCast<fuzzy>(n)`
|
|
3691
|
+
# reinterprets (truth reading of a real-axis value is 0).
|
|
3692
|
+
return self._translate_expr(expr.expr)
|
|
3693
|
+
if isinstance(expr, ast.UnsafeOverrideExpr):
|
|
3694
|
+
# `unsafeOverride(v)` suppresses a type-check at the call
|
|
3695
|
+
# site and does not change the value — the runtime lowering
|
|
3696
|
+
# is the pure passthrough (same class as unsafeCast, minus
|
|
3697
|
+
# the type argument). Round-19 audit: was the last special
|
|
3698
|
+
# call form with no codegen.
|
|
3699
|
+
return self._translate_expr(expr.expr)
|
|
3700
|
+
if isinstance(expr, ast.CastExpr):
|
|
3701
|
+
return self._translate_cast(expr)
|
|
3592
3702
|
raise CodegenNotSupported(
|
|
3593
3703
|
expr, f"unsupported expression: {type(expr).__name__}"
|
|
3594
3704
|
)
|
|
3595
3705
|
|
|
3706
|
+
# ---- cast lowering (types.md § "Casting — relabeling, not
|
|
3707
|
+
# transformation") -------------------------------------------------
|
|
3708
|
+
#
|
|
3709
|
+
# The default cast is a NO-OP relabel: every Sutra value is already a
|
|
3710
|
+
# d-dim substrate vector (or a 0-d number at the entry boundary), and
|
|
3711
|
+
# downstream ops project the axes they need, so relabeling is free.
|
|
3712
|
+
# The ONE genuine axis-move pair is numeric↔truth: a number carries
|
|
3713
|
+
# its value on AXIS_REAL, a fuzzy/bool/trit on AXIS_TRUTH — a pure
|
|
3714
|
+
# relabel would strand the value on the wrong axis and every read
|
|
3715
|
+
# would see 0 (neutral). Text is walled off: number→string needs the
|
|
3716
|
+
# unbuilt substrate formatter; string→vector is the spec's "embedding
|
|
3717
|
+
# cast" whose real implementation is `embed()` at the entry boundary.
|
|
3718
|
+
|
|
3719
|
+
#: Sutra type name → cast family. Types not in the map (user classes,
|
|
3720
|
+
#: generics) relabel as "geometric" — the spec's free no-op default.
|
|
3721
|
+
_CAST_FAMILY = {
|
|
3722
|
+
"int": "number", "number": "number",
|
|
3723
|
+
"fuzzy": "truth", "bool": "truth", "trit": "truth",
|
|
3724
|
+
"complex": "geometric", "vector": "geometric", "role": "geometric",
|
|
3725
|
+
"matrix": "geometric",
|
|
3726
|
+
"string": "text", "String": "text",
|
|
3727
|
+
"char": "text", "Character": "text",
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
#: Whether this backend's runtime carries the numeric↔truth cast
|
|
3731
|
+
#: helpers (cast_number_to_truth / cast_truth_to_number / _cnum).
|
|
3732
|
+
#: The deprecated numpy backend does not; it keeps rejecting casts.
|
|
3733
|
+
supports_cast_lowering = False
|
|
3734
|
+
|
|
3735
|
+
#: Whether this backend's runtime carries the substrate String ops
|
|
3736
|
+
#: (make_string / string_concat). Gates InterpolatedString lowering;
|
|
3737
|
+
#: the deprecated numpy backend has no String runtime.
|
|
3738
|
+
supports_string_runtime = False
|
|
3739
|
+
|
|
3740
|
+
def _infer_cast_operand_type(self, expr: ast.Expr) -> str | None:
|
|
3741
|
+
"""Conservative static type of a cast operand: literals, typed
|
|
3742
|
+
locals/params (via _var_type), nested casts, parenthesized
|
|
3743
|
+
exprs. None when unknown — the caller decides whether the
|
|
3744
|
+
target makes the relabel/axis-move distinction load-bearing."""
|
|
3745
|
+
from .symbol_table import _LITERAL_TYPES
|
|
3746
|
+
kind = type(expr).__name__
|
|
3747
|
+
if kind in _LITERAL_TYPES:
|
|
3748
|
+
return _LITERAL_TYPES[kind]
|
|
3749
|
+
if isinstance(expr, ast.Identifier):
|
|
3750
|
+
return self._var_type.get(expr.name)
|
|
3751
|
+
if isinstance(expr, (ast.CastExpr, ast.UnsafeCastExpr)):
|
|
3752
|
+
return expr.target_type.name if expr.target_type else None
|
|
3753
|
+
if type(expr).__name__ == "Parenthesized" and hasattr(expr, "inner"):
|
|
3754
|
+
return self._infer_cast_operand_type(expr.inner)
|
|
3755
|
+
if isinstance(expr, ast.BinaryOp):
|
|
3756
|
+
if expr.op in ("==", "!=", "<", ">", "<=", ">=", "&&", "||"):
|
|
3757
|
+
return "bool"
|
|
3758
|
+
if expr.op in ("+", "-", "*", "/", "%"):
|
|
3759
|
+
lt = self._infer_cast_operand_type(expr.left)
|
|
3760
|
+
rt = self._infer_cast_operand_type(expr.right)
|
|
3761
|
+
if (self._CAST_FAMILY.get(lt) == "number"
|
|
3762
|
+
and self._CAST_FAMILY.get(rt) == "number"):
|
|
3763
|
+
return "number"
|
|
3764
|
+
return None
|
|
3765
|
+
|
|
3766
|
+
def _translate_cast(self, expr: ast.CastExpr) -> str:
|
|
3767
|
+
if not self.supports_cast_lowering:
|
|
3768
|
+
raise CodegenNotSupported(
|
|
3769
|
+
expr,
|
|
3770
|
+
"`(Type) expr` casts are not supported by this backend; "
|
|
3771
|
+
"use the PyTorch backend (codegen_pytorch)."
|
|
3772
|
+
)
|
|
3773
|
+
dst = expr.target_type.name if expr.target_type else None
|
|
3774
|
+
dst_family = self._CAST_FAMILY.get(dst, "geometric")
|
|
3775
|
+
src = self._infer_cast_operand_type(expr.expr)
|
|
3776
|
+
src_family = self._CAST_FAMILY.get(src, "geometric") if src else None
|
|
3777
|
+
|
|
3778
|
+
# --- text walls -------------------------------------------------
|
|
3779
|
+
if dst_family == "text" and src_family != "text":
|
|
3780
|
+
if src == "int":
|
|
3781
|
+
# strings.md § Integer formatting: `(string) n` on an
|
|
3782
|
+
# int is the integer formatter — the one number→text
|
|
3783
|
+
# cast with a real substrate implementation.
|
|
3784
|
+
return (f"_VSA.int_to_string("
|
|
3785
|
+
f"{self._translate_expr(expr.expr)})")
|
|
3786
|
+
raise CodegenNotSupported(
|
|
3787
|
+
expr,
|
|
3788
|
+
f"cannot cast to `{dst}`: only `int` values format "
|
|
3789
|
+
"(int_to_string — integer-shaped; fractional rendering is "
|
|
3790
|
+
"not built). For a literal, use `make_string(\"...\")`; "
|
|
3791
|
+
"join strings with `string_concat`."
|
|
3792
|
+
)
|
|
3793
|
+
if src_family == "text" and dst_family != "text":
|
|
3794
|
+
if dst_family == "geometric":
|
|
3795
|
+
raise CodegenNotSupported(
|
|
3796
|
+
expr,
|
|
3797
|
+
f"cannot cast a string to `{dst}`: that is the embedding "
|
|
3798
|
+
"cast, and its implementation is `embed(...)` at the "
|
|
3799
|
+
"program boundary — a String vector encodes codepoints, "
|
|
3800
|
+
"not semantics."
|
|
3801
|
+
)
|
|
3802
|
+
raise CodegenNotSupported(
|
|
3803
|
+
expr,
|
|
3804
|
+
f"cannot cast a string to `{dst}`: a String vector encodes "
|
|
3805
|
+
"packed codepoints, so its numeric axes are not a number. "
|
|
3806
|
+
"Use the string operations (string_length, char_at, ...) "
|
|
3807
|
+
"instead."
|
|
3808
|
+
)
|
|
3809
|
+
|
|
3810
|
+
inner = self._translate_expr(expr.expr)
|
|
3811
|
+
|
|
3812
|
+
# --- same family / text→text: the free relabel --------------------
|
|
3813
|
+
if src_family == dst_family:
|
|
3814
|
+
return inner
|
|
3815
|
+
|
|
3816
|
+
# --- the one genuine axis-move pair -------------------------------
|
|
3817
|
+
if src_family == "number" and dst_family == "truth":
|
|
3818
|
+
return f"_VSA.cast_number_to_truth({inner})"
|
|
3819
|
+
if src_family == "truth" and dst_family == "number":
|
|
3820
|
+
return f"_VSA.cast_truth_to_number({inner})"
|
|
3821
|
+
|
|
3822
|
+
# --- unknown source: only reject when the move/relabel choice
|
|
3823
|
+
# --- is load-bearing (truth/number targets read a single axis) ----
|
|
3824
|
+
if src_family is None and dst_family in ("truth", "number"):
|
|
3825
|
+
raise CodegenNotSupported(
|
|
3826
|
+
expr,
|
|
3827
|
+
f"cannot cast to `{dst}` here: the operand's static type "
|
|
3828
|
+
"can't be inferred, and truth/number casts need it to pick "
|
|
3829
|
+
"between relabel and axis-move. Assign the operand to a "
|
|
3830
|
+
"typed variable first."
|
|
3831
|
+
)
|
|
3832
|
+
|
|
3833
|
+
# --- every remaining pair relabels; _cnum canonicalizes a 0-d
|
|
3834
|
+
# --- number at the entry boundary and passes vectors through ------
|
|
3835
|
+
if src_family == "number" or src is None:
|
|
3836
|
+
return f"_VSA._cnum({inner})"
|
|
3837
|
+
return inner
|
|
3838
|
+
|
|
3596
3839
|
def _embed_expr_src(self, expr: ast.EmbedExpr) -> str:
|
|
3597
3840
|
"""Override point for per-backend `embed(<expr>)` lowering.
|
|
3598
3841
|
|
|
@@ -49,6 +49,15 @@ class PyTorchCodegen(Codegen):
|
|
|
49
49
|
_as_truth_vector.
|
|
50
50
|
"""
|
|
51
51
|
|
|
52
|
+
# The torch runtime carries the numeric↔truth cast helpers
|
|
53
|
+
# (cast_number_to_truth / cast_truth_to_number / _cnum), so `(Type)`
|
|
54
|
+
# cast lowering is live on this backend (types.md § Casting).
|
|
55
|
+
supports_cast_lowering = True
|
|
56
|
+
|
|
57
|
+
# The torch runtime carries the substrate String ops (make_string /
|
|
58
|
+
# string_concat), so InterpolatedString lowering is live here.
|
|
59
|
+
supports_string_runtime = True
|
|
60
|
+
|
|
52
61
|
# The numpy `Codegen` lists the immutable list ops (array_concat /
|
|
53
62
|
# array_map / array_filter) as unsupported — it has no runtime methods
|
|
54
63
|
# for them. The PyTorch runtime DOES implement them (Emma 2026-06-20),
|
|
@@ -2772,14 +2781,8 @@ class PyTorchCodegen(Codegen):
|
|
|
2772
2781
|
self._emit("return self.make_string(chr(int(codepoint)))")
|
|
2773
2782
|
self._indent -= 1
|
|
2774
2783
|
self._emit()
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
self._emit('"""True iff v is a String value (kept as `is_char` for')
|
|
2778
|
-
self._emit('backward-compat with code that pre-dated the rename to')
|
|
2779
|
-
self._emit('AXIS_STRING_FLAG; new code should use is_string)."""')
|
|
2780
|
-
self._emit("return bool(v[self.semantic_dim + self.AXIS_STRING_FLAG].item() >= 0.5)")
|
|
2781
|
-
self._indent -= 1
|
|
2782
|
-
self._emit()
|
|
2784
|
+
# `is_char` (a pure alias of is_string) retired 2026-07-08 —
|
|
2785
|
+
# zero call sites; CLAUDE.md § "Deprecate aliases aggressively".
|
|
2783
2786
|
self._emit("# ---- String runtime methods ----")
|
|
2784
2787
|
self._emit("# Encoding: AXIS_STRING_FLAG marks the vector as a String.")
|
|
2785
2788
|
self._emit("# Characters pack into the synthetic axes — char[0] at")
|
|
@@ -2828,7 +2831,16 @@ class PyTorchCodegen(Codegen):
|
|
|
2828
2831
|
self._emit()
|
|
2829
2832
|
self._emit("def make_string(self, s):")
|
|
2830
2833
|
self._indent += 1
|
|
2831
|
-
self._emit('"""Construct a String value from a Python str.
|
|
2834
|
+
self._emit('"""Construct a String value from a Python str. IDEMPOTENT on an')
|
|
2835
|
+
self._emit('already-String vector: the type-coercion pass can wrap a String-typed')
|
|
2836
|
+
self._emit("initializer in make_string even when it is already a String (e.g.")
|
|
2837
|
+
self._emit('`String g = make_string(\"hi\")`), so a double make_string must return')
|
|
2838
|
+
self._emit('the value unchanged, not `str(tensor)`-encode its repr as text."""')
|
|
2839
|
+
self._emit("if _torch.is_tensor(s) and s.ndim >= 1 and "
|
|
2840
|
+
"s.shape[-1] == self.dim and bool(self.is_string(s)):")
|
|
2841
|
+
self._indent += 1
|
|
2842
|
+
self._emit("return s")
|
|
2843
|
+
self._indent -= 1
|
|
2832
2844
|
self._emit("if not isinstance(s, str):")
|
|
2833
2845
|
self._indent += 1
|
|
2834
2846
|
self._emit("s = str(s)")
|
|
@@ -3257,6 +3269,57 @@ class PyTorchCodegen(Codegen):
|
|
|
3257
3269
|
self._emit("return v")
|
|
3258
3270
|
self._indent -= 1
|
|
3259
3271
|
self._emit()
|
|
3272
|
+
self._emit("def _i2s_pows(self):")
|
|
3273
|
+
self._indent += 1
|
|
3274
|
+
self._emit('"""Cached power-of-10 table for int_to_string digit')
|
|
3275
|
+
self._emit('extraction. Length = the dtype exactness bound (7 digits')
|
|
3276
|
+
self._emit('float32, 15 float64) — beyond it the input integer is')
|
|
3277
|
+
self._emit('already inexact in the dtype."""')
|
|
3278
|
+
self._emit("if not hasattr(self, '_i2s_pows_cache') or self._i2s_pows_cache is None:")
|
|
3279
|
+
self._indent += 1
|
|
3280
|
+
self._emit("D = 15 if self.dtype == _torch.float64 else 7")
|
|
3281
|
+
self._emit("self._i2s_pows_cache = 10.0 ** _torch.arange("
|
|
3282
|
+
"D, dtype=self.dtype, device=self.device)")
|
|
3283
|
+
self._indent -= 1
|
|
3284
|
+
self._emit("return self._i2s_pows_cache")
|
|
3285
|
+
self._indent -= 1
|
|
3286
|
+
self._emit()
|
|
3287
|
+
self._emit("def int_to_string(self, x):")
|
|
3288
|
+
self._indent += 1
|
|
3289
|
+
self._emit('"""Render an integer NUMBER as a substrate String — the')
|
|
3290
|
+
self._emit("number->string formatter (strings.md § Integer formatting).")
|
|
3291
|
+
self._emit("Digit extraction is MOD-FREE (Math.mod is banned; measured")
|
|
3292
|
+
self._emit("vector-collapse): digit_k = floor(a/10^k) - 10*floor(a/10^(k+1)).")
|
|
3293
|
+
self._emit("Leading zeros gate on the quotient-significance mask (0 renders")
|
|
3294
|
+
self._emit("'0'); negatives gate codepoint 45 into slot 0 and shift digits")
|
|
3295
|
+
self._emit("right by one — a gather by shifted index, the same VSA-native")
|
|
3296
|
+
self._emit("permutation string_concat uses. round() first: the INT contract.")
|
|
3297
|
+
self._emit("Exact within the dtype bound (7 digits float32 / 15 float64);")
|
|
3298
|
+
self._emit('beyond it output is valid-but-unspecified (input already inexact)."""')
|
|
3299
|
+
self._emit("a0 = self._scalar(x)")
|
|
3300
|
+
self._emit("neg = (a0 < 0).to(self.dtype)")
|
|
3301
|
+
self._emit("a = _torch.round(_torch.abs(a0))")
|
|
3302
|
+
self._emit("pows = self._i2s_pows()")
|
|
3303
|
+
self._emit("D = pows.shape[0]")
|
|
3304
|
+
self._emit("q = _torch.floor(a / pows)")
|
|
3305
|
+
self._emit("qn = _torch.floor(a / (pows * 10.0))")
|
|
3306
|
+
self._emit("digits = q - 10.0 * qn")
|
|
3307
|
+
self._emit("nd = _torch.clamp((q > 0).to(self.dtype).sum(), min=1.0)")
|
|
3308
|
+
self._emit("ax = self._str_axes()")
|
|
3309
|
+
self._emit("n = ax.shape[0]")
|
|
3310
|
+
self._emit("idx = _torch.arange(n, dtype=self.dtype, device=self.device)")
|
|
3311
|
+
self._emit("place = nd - 1.0 - (idx - neg)")
|
|
3312
|
+
self._emit("valid = ((place >= 0) & (place < D) & (idx - neg >= 0))"
|
|
3313
|
+
".to(self.dtype)")
|
|
3314
|
+
self._emit("gath = digits[place.clamp(0, D - 1).long()]")
|
|
3315
|
+
self._emit("out_cps = valid * (48.0 + gath) + "
|
|
3316
|
+
"(idx == 0).to(self.dtype) * neg * 45.0")
|
|
3317
|
+
self._emit("v = _torch.zeros(self.dim, dtype=self.dtype, device=self.device)")
|
|
3318
|
+
self._emit("v[self.semantic_dim + self.AXIS_STRING_FLAG] = 1.0")
|
|
3319
|
+
self._emit("v = v.index_copy(0, ax, out_cps)")
|
|
3320
|
+
self._emit("return v")
|
|
3321
|
+
self._indent -= 1
|
|
3322
|
+
self._emit()
|
|
3260
3323
|
self._emit("def string_to_python(self, v):")
|
|
3261
3324
|
self._indent += 1
|
|
3262
3325
|
self._emit('"""Decode a String value back to a Python str. This is the')
|
|
@@ -3459,6 +3522,41 @@ class PyTorchCodegen(Codegen):
|
|
|
3459
3522
|
self._emit("return self._t_from_r_cache")
|
|
3460
3523
|
self._indent -= 1
|
|
3461
3524
|
self._emit()
|
|
3525
|
+
self._emit("def _real_from_truth(self):")
|
|
3526
|
+
self._indent += 1
|
|
3527
|
+
self._emit('"""Matrix moving the truth-axis entry to the real axis')
|
|
3528
|
+
self._emit('(cached; the inverse-direction mate of _truth_from_real)."""')
|
|
3529
|
+
self._emit("if not hasattr(self, '_r_from_t_cache') or self._r_from_t_cache is None:")
|
|
3530
|
+
self._indent += 1
|
|
3531
|
+
self._emit("M = _torch.zeros((self.dim, self.dim), dtype=self.dtype, device=self.device)")
|
|
3532
|
+
self._emit("M[self.semantic_dim + self.AXIS_REAL,")
|
|
3533
|
+
self._indent += 1
|
|
3534
|
+
self._emit("self.semantic_dim + self.AXIS_TRUTH] = 1.0")
|
|
3535
|
+
self._indent -= 1
|
|
3536
|
+
self._emit("self._r_from_t_cache = M")
|
|
3537
|
+
self._indent -= 1
|
|
3538
|
+
self._emit("return self._r_from_t_cache")
|
|
3539
|
+
self._indent -= 1
|
|
3540
|
+
self._emit()
|
|
3541
|
+
self._emit("def cast_number_to_truth(self, x):")
|
|
3542
|
+
self._indent += 1
|
|
3543
|
+
self._emit('"""`(fuzzy|bool|trit) NUMBER` — the numeric→truth cast')
|
|
3544
|
+
self._emit("(types.md § Casting): the one cast pair that genuinely")
|
|
3545
|
+
self._emit("moves an axis. _cnum lifts a 0-d/host number onto AXIS_REAL")
|
|
3546
|
+
self._emit("(entry boundary), then a cached permutation-style matmul")
|
|
3547
|
+
self._emit('moves that entry to AXIS_TRUTH. Pure tensor ops throughout."""')
|
|
3548
|
+
self._emit("return self._truth_from_real() @ self._cnum(x)")
|
|
3549
|
+
self._indent -= 1
|
|
3550
|
+
self._emit()
|
|
3551
|
+
self._emit("def cast_truth_to_number(self, x):")
|
|
3552
|
+
self._indent += 1
|
|
3553
|
+
self._emit('"""`(number|int) TRUTH` — the truth→numeric cast: moves the')
|
|
3554
|
+
self._emit("AXIS_TRUTH entry to AXIS_REAL by the cached inverse-direction")
|
|
3555
|
+
self._emit('matmul. Truth values are canonical d-dim vectors (make_truth),')
|
|
3556
|
+
self._emit('which _cnum passes through unchanged."""')
|
|
3557
|
+
self._emit("return self._real_from_truth() @ self._cnum(x)")
|
|
3558
|
+
self._indent -= 1
|
|
3559
|
+
self._emit()
|
|
3462
3560
|
self._emit("CMP_SLOPE = 100.0")
|
|
3463
3561
|
self._emit()
|
|
3464
3562
|
self._emit("def gt(self, a, b):")
|
|
@@ -79,10 +79,18 @@ def _get_st_model(model: str):
|
|
|
79
79
|
file=sys.stderr,
|
|
80
80
|
flush=True,
|
|
81
81
|
)
|
|
82
|
+
import contextlib
|
|
83
|
+
import sys
|
|
84
|
+
|
|
82
85
|
from sentence_transformers import SentenceTransformer
|
|
83
86
|
|
|
84
|
-
# nomic-bert ships custom modeling code, hence trust_remote_code.
|
|
85
|
-
|
|
87
|
+
# nomic-bert ships custom modeling code, hence trust_remote_code. That load
|
|
88
|
+
# (and sentence-transformers itself) prints framework chatter to stdout —
|
|
89
|
+
# e.g. "<All keys matched successfully>" from the state-dict load — which
|
|
90
|
+
# would pollute a program's stdout (main()'s output, or --emit). Route any
|
|
91
|
+
# such load-time output to stderr, where our own notice already goes.
|
|
92
|
+
with contextlib.redirect_stdout(sys.stderr):
|
|
93
|
+
st = SentenceTransformer(hf_id, trust_remote_code=True)
|
|
86
94
|
_ST_CACHE[hf_id] = st
|
|
87
95
|
return st
|
|
88
96
|
|
|
@@ -37,7 +37,20 @@ from .fv_obligation_checker import (
|
|
|
37
37
|
range_sound_by_composition,
|
|
38
38
|
reduces_to_same_graph,
|
|
39
39
|
)
|
|
40
|
+
from .fv_loop_convergence import (
|
|
41
|
+
ASYMPTOTICALLY_STABLE,
|
|
42
|
+
MARGINALLY_STABLE,
|
|
43
|
+
UNSTABLE,
|
|
44
|
+
LoopConvergenceReport,
|
|
45
|
+
analyze_loop_recurrence,
|
|
46
|
+
)
|
|
40
47
|
from .fv_poly_bound import RangeBound, bound_polynomial_over_box
|
|
48
|
+
from .fv_sampler_convergence import (
|
|
49
|
+
SamplerConvergenceReport,
|
|
50
|
+
analyze_sampler_convergence,
|
|
51
|
+
integrate_master_equation,
|
|
52
|
+
measured_decay_rate,
|
|
53
|
+
)
|
|
41
54
|
|
|
42
55
|
__all__ = [
|
|
43
56
|
# polynomial range bounding (the §3.2 branch-range obligation)
|
|
@@ -52,4 +65,15 @@ __all__ = [
|
|
|
52
65
|
# extraction + the verifiable-fragment boundary
|
|
53
66
|
"extract_truth_polynomial",
|
|
54
67
|
"NonPolynomialResidual",
|
|
68
|
+
# loop-convergence obligation via the Z-transform poles (the §3.3 linear core)
|
|
69
|
+
"analyze_loop_recurrence",
|
|
70
|
+
"LoopConvergenceReport",
|
|
71
|
+
"ASYMPTOTICALLY_STABLE",
|
|
72
|
+
"MARGINALLY_STABLE",
|
|
73
|
+
"UNSTABLE",
|
|
74
|
+
# continuous-time convergence of the probabilistic sampler (the §7 SDE angle)
|
|
75
|
+
"analyze_sampler_convergence",
|
|
76
|
+
"SamplerConvergenceReport",
|
|
77
|
+
"integrate_master_equation",
|
|
78
|
+
"measured_decay_rate",
|
|
55
79
|
]
|