Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

An AI agent that acts as a compiler for computational models. It transpiles between probabilistic programming languages (PyMC, Stan), deep learning frameworks (JAX, PyTorch), and compiles to optimized Rust — with numerical validation at every step.

**[Read the blog post →](https://twiecki.io/blog/2026/03/10/transalchemy/)**
**[Read the blog post →](https://twiecki.io/blog/2026/03/10/alchemize/)**

## How it works

Expand Down Expand Up @@ -88,7 +88,7 @@ uv sync # or: pip install -e .

```python
import pymc as pm
from transalchemy import compile_model
from alchemize import compile_model

with pm.Model() as model:
mu = pm.Normal("mu", 0, 10)
Expand Down Expand Up @@ -133,7 +133,7 @@ python examples/mingpt_to_rust.py
## Architecture

```
transalchemy/
alchemize/
├── exporter.py # Extract parameters, transforms, logp graph from pm.Model()
├── compiler.py # Agentic loop: Claude API → Rust code → build → validate
├── stan_exporter.py # Extract Stan model context via BridgeStan
Expand Down Expand Up @@ -166,7 +166,7 @@ compiled_models/ # Pre-compiled models (normal, linreg, hierarchical, GP, ...
The same agentic architecture works for language-to-language translation. Claude generates PyMC code, validates logp against BridgeStan reference values, and iterates until the models match numerically. Used to translate all 120 models from [posteriordb](https://github.com/stan-dev/posteriordb) from Stan to PyMC.

```python
from transalchemy import transpile_stan_to_pymc
from alchemize import transpile_stan_to_pymc

stan_code = """
data { int<lower=0> N; array[N] real y; }
Expand All @@ -188,7 +188,7 @@ The same agentic architecture generalizes to deep learning frameworks. Claude tr

```python
import jax.numpy as jnp
from transalchemy import transpile_jax_to_pytorch
from alchemize import transpile_jax_to_pytorch

def forward(params, x):
x = jax.nn.relu(x @ params["w1"] + params["b1"])
Expand All @@ -206,7 +206,7 @@ if result.success:

```python
import torch.nn as nn
from transalchemy import transpile_pytorch_to_jax
from alchemize import transpile_pytorch_to_jax

class MLP(nn.Module):
def __init__(self):
Expand All @@ -231,7 +231,7 @@ The agent uses the same agentic architecture with tools: `write_code` → `cargo

```python
import torch.nn as nn
from transalchemy import transpile_pytorch_to_rust
from alchemize import transpile_pytorch_to_rust

class MLP(nn.Module):
def __init__(self):
Expand Down
58 changes: 29 additions & 29 deletions transalchemy/__init__.py → alchemize/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,94 +8,94 @@
import importlib
from typing import TYPE_CHECKING

from transalchemy.jax_exporter import (
from alchemize.jax_exporter import (
JaxModelExporter,
export_jax_model,
)

# JAX ↔ PyTorch (no heavy deps at import time)
from transalchemy.jax_pytorch_transpiler import (
from alchemize.jax_pytorch_transpiler import (
TranspileResult,
transpile_jax_to_pytorch,
transpile_pytorch_to_jax,
)
from transalchemy.pytorch_exporter import (
from alchemize.pytorch_exporter import (
PytorchModelExporter,
export_pytorch_model,
)
from transalchemy.pytorch_rust_transpiler import (
from alchemize.pytorch_rust_transpiler import (
RustTranspileResult,
transpile_pytorch_to_rust,
)

# PyMC/Stan imports are lazy — they pull in heavy deps (pymc, bridgestan)
if TYPE_CHECKING:
from transalchemy.analysis import (
from alchemize.analysis import (
plot_optimization_progress,
plot_timeline,
plot_waterfall,
print_summary,
)
from transalchemy.compiler import (
from alchemize.compiler import (
OptimizationEvent,
compile_model,
optimize_model,
)
from transalchemy.exporter import (
from alchemize.exporter import (
ModelContext,
RustModelExporter,
export_model,
)
from transalchemy.stan_compiler import (
from alchemize.stan_compiler import (
StanCompilationResult,
compile_stan_model,
)
from transalchemy.stan_exporter import (
from alchemize.stan_exporter import (
StanModelContext,
StanModelExporter,
export_stan_model,
)
from transalchemy.stan_to_pymc import StanToPyMCResult, transpile_stan_to_pymc
from alchemize.stan_to_pymc import StanToPyMCResult, transpile_stan_to_pymc


def __getattr__(name: str):
"""Lazy import for PyMC/Stan components."""
_lazy_imports = {
"ModelContext": ("transalchemy.exporter", "ModelContext"),
"RustModelExporter": ("transalchemy.exporter", "RustModelExporter"),
"export_model": ("transalchemy.exporter", "export_model"),
"compile_model": ("transalchemy.compiler", "compile_model"),
"optimize_model": ("transalchemy.compiler", "optimize_model"),
"OptimizationEvent": ("transalchemy.compiler", "OptimizationEvent"),
"ModelContext": ("alchemize.exporter", "ModelContext"),
"RustModelExporter": ("alchemize.exporter", "RustModelExporter"),
"export_model": ("alchemize.exporter", "export_model"),
"compile_model": ("alchemize.compiler", "compile_model"),
"optimize_model": ("alchemize.compiler", "optimize_model"),
"OptimizationEvent": ("alchemize.compiler", "OptimizationEvent"),
"plot_optimization_progress": (
"transalchemy.analysis",
"alchemize.analysis",
"plot_optimization_progress",
),
"plot_waterfall": ("transalchemy.analysis", "plot_waterfall"),
"plot_timeline": ("transalchemy.analysis", "plot_timeline"),
"print_summary": ("transalchemy.analysis", "print_summary"),
"StanModelContext": ("transalchemy.stan_exporter", "StanModelContext"),
"StanModelExporter": ("transalchemy.stan_exporter", "StanModelExporter"),
"export_stan_model": ("transalchemy.stan_exporter", "export_stan_model"),
"plot_waterfall": ("alchemize.analysis", "plot_waterfall"),
"plot_timeline": ("alchemize.analysis", "plot_timeline"),
"print_summary": ("alchemize.analysis", "print_summary"),
"StanModelContext": ("alchemize.stan_exporter", "StanModelContext"),
"StanModelExporter": ("alchemize.stan_exporter", "StanModelExporter"),
"export_stan_model": ("alchemize.stan_exporter", "export_stan_model"),
"compile_stan_model": (
"transalchemy.stan_compiler",
"alchemize.stan_compiler",
"compile_stan_model",
),
"StanCompilationResult": (
"transalchemy.stan_compiler",
"alchemize.stan_compiler",
"StanCompilationResult",
),
"transpile_stan_to_pymc": (
"transalchemy.stan_to_pymc",
"alchemize.stan_to_pymc",
"transpile_stan_to_pymc",
),
"StanToPyMCResult": ("transalchemy.stan_to_pymc", "StanToPyMCResult"),
"StanToPyMCResult": ("alchemize.stan_to_pymc", "StanToPyMCResult"),
}
if name in _lazy_imports:
module_path, attr = _lazy_imports[name]
module = importlib.import_module(module_path)
return getattr(module, attr)
raise AttributeError(f"module 'transalchemy' has no attribute {name!r}")
raise AttributeError(f"module 'alchemize' has no attribute {name!r}")


__all__ = [
Expand Down Expand Up @@ -137,6 +137,6 @@ def __getattr__(name: str):

def to_nutpie(compile_result, model):
"""Convert a CompilationResult to a nutpie-compatible model. Lazy import."""
from transalchemy.nutpie_bridge import to_nutpie as _to_nutpie
from alchemize.nutpie_bridge import to_nutpie as _to_nutpie

return _to_nutpie(compile_result, model)
4 changes: 2 additions & 2 deletions transalchemy/analysis.py → alchemize/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

Usage:
# From a CompilationResult:
from transalchemy.analysis import plot_optimization_progress
from alchemize.analysis import plot_optimization_progress
fig = plot_optimization_progress(result)

# From a results.tsv file:
Expand All @@ -21,7 +21,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from transalchemy.compiler import CompilationResult
from alchemize.compiler import CompilationResult


@dataclass
Expand Down
File renamed without changes.
12 changes: 6 additions & 6 deletions transalchemy/cli.py → alchemize/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def _transpile(


@click.group()
@click.version_option(package_name="transalchemy")
@click.version_option(package_name="alchemize")
def cli():
"""Transalchemy: AI-powered transpilation between computational frameworks."""

Expand Down Expand Up @@ -210,10 +210,10 @@ def convert(

\b
Examples:
transalchemy convert model.stan --to pymc
transalchemy convert train.py --to jax
transalchemy convert model.py --to pytorch
cat model.stan | transalchemy convert --to pymc
alchemize convert model.stan --to pymc
alchemize convert train.py --to jax
alchemize convert model.py --to pytorch
cat model.stan | alchemize convert --to pymc
"""
# Read input
if input_file:
Expand All @@ -224,7 +224,7 @@ def convert(
filename = "stdin"
else:
raise click.UsageError(
"No input file provided and no data on stdin. Usage: transalchemy convert <file> --to <framework>"
"No input file provided and no data on stdin. Usage: alchemize convert <file> --to <framework>"
)

target = _normalize_framework(target)
Expand Down
2 changes: 1 addition & 1 deletion transalchemy/compiler.py → alchemize/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import numpy as np
import pymc as pm

from transalchemy.exporter import RustModelExporter
from alchemize.exporter import RustModelExporter

_SKILLS_DIR = Path(__file__).parent / "skills"

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

import numpy as np

from transalchemy.formatting import format_python_code as _format_python
from transalchemy.jax_exporter import ModelContext
from alchemize.formatting import format_python_code as _format_python
from alchemize.jax_exporter import ModelContext

_SKILLS_DIR = Path(__file__).parent / "skills"

Expand Down Expand Up @@ -717,7 +717,7 @@ def transpile_jax_to_pytorch(
Returns:
TranspileResult with generated PyTorch code and validation status.
"""
from transalchemy.jax_exporter import JaxModelExporter
from alchemize.jax_exporter import JaxModelExporter

api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
Expand Down Expand Up @@ -810,7 +810,7 @@ def transpile_pytorch_to_jax(
Returns:
TranspileResult with generated JAX code and validation status.
"""
from transalchemy.pytorch_exporter import PytorchModelExporter
from alchemize.pytorch_exporter import PytorchModelExporter

api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
Expand Down
6 changes: 3 additions & 3 deletions transalchemy/nutpie_bridge.py → alchemize/nutpie_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Usage:
import pymc as pm
from transalchemy import compile_model
from transalchemy.nutpie_bridge import to_nutpie
from alchemize import compile_model
from alchemize.nutpie_bridge import to_nutpie

with pm.Model() as model:
...
Expand All @@ -24,7 +24,7 @@
import numpy as np
import pymc as pm

from transalchemy.compiler import CompilationResult
from alchemize.compiler import CompilationResult


def _build_shared_lib(build_dir: Path) -> Path:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import numpy as np

from transalchemy.jax_exporter import ModelContext, TensorInfo, ValidationPoint
from alchemize.jax_exporter import ModelContext, TensorInfo, ValidationPoint


class PytorchModelExporter:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

import numpy as np

from transalchemy.jax_exporter import ModelContext, ValidationPoint
from alchemize.jax_exporter import ModelContext, ValidationPoint

_SKILLS_DIR = Path(__file__).parent / "skills"

Expand Down Expand Up @@ -1050,7 +1050,7 @@ def transpile_pytorch_to_rust(
if backend not in ("pure", "burn"):
raise ValueError(f"backend must be 'pure' or 'burn', got {backend!r}")

from transalchemy.pytorch_exporter import PytorchModelExporter
from alchemize.pytorch_exporter import PytorchModelExporter

api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import numpy as np

from transalchemy.stan_exporter import StanModelExporter
from alchemize.stan_exporter import StanModelExporter

_SKILLS_DIR = Path(__file__).parent / "skills"

Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions transalchemy/stan_to_pymc.py → alchemize/stan_to_pymc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import numpy as np

from transalchemy.formatting import format_python_code as _format_python
from alchemize.formatting import format_python_code as _format_python

_SKILLS_DIR = Path(__file__).parent / "skills"

Expand Down Expand Up @@ -294,7 +294,7 @@ def transpile_stan_to_pymc(
if verbose:
print("Extracting reference values via BridgeStan...")
t0 = time.time()
from transalchemy.stan_exporter import StanModelExporter
from alchemize.stan_exporter import StanModelExporter

exporter = StanModelExporter(stan_code, data=data)
ctx = exporter.context
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/run_benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
and stores convergence diagnostics (ESS, Rhat).

Usage:
cd /path/to/transalchemy
cd /path/to/alchemize
uv run python benchmarks/run_benchmarks.py [--models normal,linreg] [--n-repeats 3] [--output results.json]
"""

Expand Down Expand Up @@ -553,7 +553,7 @@ def benchmark_rust(

try:
sys.path.insert(0, str(PROJECT_ROOT))
from transalchemy.nutpie_bridge import _ensure_ffi_setup, _load_logp_fn
from alchemize.nutpie_bridge import _ensure_ffi_setup, _load_logp_fn

# Compilation: build the shared library
t_compile_start = time.perf_counter()
Expand Down
2 changes: 1 addition & 1 deletion examples/01_normal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import numpy as np
import pymc as pm

from transalchemy import compile_model
from alchemize import compile_model

# Generate synthetic data
np.random.seed(42)
Expand Down
2 changes: 1 addition & 1 deletion examples/02_linear_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import numpy as np
import pymc as pm

from transalchemy import compile_model
from alchemize import compile_model

# Generate synthetic data
np.random.seed(42)
Expand Down
Loading
Loading