Data to posterior · language 0.8

Read a dataset, compile a model, and run it

Fit the same normal-mean posterior from a CSV file through the Java builder and a Stan-inspired .jdm script, then choose among in-memory, cached, and ahead-of-time compilation.

Inspired by Stan; documented subset

Step 1

Read an observed column from CSV

The checked-in examples/data/normal-observations.csv begins with a y header followed by one numeric observation per row. The executable companion provides a small reader:

Path csv = Paths.get("examples/data/normal-observations.csv");
double[] y = McmcDataIngestionExamples.readNumericColumn(csv, "y");

That reader is dependency-free and rejects missing or nonnumeric cells. It is not a general RFC-style CSV parser: use a dedicated CSV library for quoted delimiters and then pass the resulting primitive arrays to JDistlib.

Step 2 · script frontend

Load and compile a .jdm file with data

Path modelFile = Paths.get(
    "examples/models/41-normal-csv-mean.jdm");
String source = new String(
    Files.readAllBytes(modelFile), StandardCharsets.UTF_8);

Map<String, double[]> data =
    new LinkedHashMap<String, double[]>();
data.put("N", new double[] {y.length});
data.put("y", y);

CompiledModelScript compiled =
    ModelScript.compile(source, data);
BayesianModel scriptedModel = compiled.model();

ModelScript.compile parses the source, validates names, dimensions, integer values and bounds, binds a defensive copy of the supplied data, lowers the program to a BayesianModel, and creates the generated-quantities evaluator. Scalars use one-element arrays because the data boundary has one uniform Java type.

Step 3 · Java frontend

Feed the same array to ModelBuilder

BayesianModel javaModel = new ModelBuilder()
    .data("y", y)
    .parameter("mu", Constraints.real(), 0.0)
    .factor("mu prior", new String[] {"mu"},
        ModelFactors.normalPrior("mu", 0.0, 10.0))
    .factor("observations", new String[] {"y", "mu"},
        ModelFactors.normalObservations("y", "mu", 1.0))
    .build();

The Java and script routes produce the same model abstraction. Use Java for custom likelihood code or APIs outside the script subset; use scripts when a compact textual model is easier to review or deploy.

Step 4

Check the gradient and run four NUTS chains

GradientCheckResult check = Gradients.check(
    scriptedModel, scriptedModel.initialState(), 1e-5, 1e-5);
if (!check.passed()) throw new IllegalStateException(check.message());

SamplingOptions options = SamplingOptions.builder()
    .warmupIterations(500)
    .sampleIterations(1000)
    .targetAcceptance(0.85)
    .build();

double[][] initial = {{-1}, {-0.25}, {0.25}, {1}};
ChainResult[] chains = Chains.parallel(
    new NoUTurnSampler(), scriptedModel, initial,
    options, 2026082701L, 4);

McmcDiagnosticReport report = McmcDiagnostics.analyze(
    new String[] {"mu"}, chains);

Diagnostics, graphs, checkpoints, and exports do not care which frontend created the model. Generate a posterior prediction with compiled.generate(draw, random).

Step 5

Choose one of three compilation modes

In-memory reference compiler

CompiledModelScript model =
    ModelScript.compile(source, data);

Portable and usually sufficient.

JDK-backed cache

try (LoadedGeneratedModel loaded =
    ModelCompilationCache.compile(source,
        Paths.get("build/model-cache"))) {
  CompiledModelScript model =
      loaded.factory().compile(data);
}

Generates, invokes javac, hashes, caches, and loads an isolated wrapper. A JDK is required.

Ahead-of-time command line

gradlew.bat jar

java -cp build\libs\* ^
  jdistlib.inference.lang.ModelScriptCli ^
  examples\models\41-normal-csv-mean.jdm ^
  com.example.NormalCsvMean ^
  build\generated\com\example\NormalCsvMean.java

javac -cp build\libs\* ^
  -d build\generated-classes ^
  build\generated\com\example\NormalCsvMean.java

ModelScriptCli validates the script and writes a Java class implementing GeneratedModelFactory. It does not invoke javac, bind data, or sample. Instantiate com.example.NormalCsvMean and call compile(data) after placing the library and generated classes on the runtime classpath.

Language additions

Use scalar locals and control flow where expansion is clearer

model {
  real total = 0;
  for (n in 1:N) {
    if (y[n] >= 0)
      total += normal_lpdf(y[n] | mu, 1);
    else
      total += normal_lpdf(y[n] | mu, 2);
  }
  int pass = 0;
  while (pass < 2) {
    total += 0;
    pass += 1;
  }
  target += total;
}

The model block supports initialized scalar locals, scoped blocks, assignments, comparisons and boolean expressions, if/else, integer-range for, and guarded while. The language also provides a broad scalar math catalog and more than thirty scalar probability families. Consult the supported-surface reference before porting a model.

Complete examples

Run the code instead of copying fragments

See McmcDataIngestionExamples.java, ModelScriptCompilationExamples.java, the sample CSV, all fifty-one JDistlib scripts, and forty-one ordinary Stan fixtures. Continue with the v0.8.3 containers tutorial.