Skip to content

Result

Container for posterior samples returned by all samplers.

result = sampler.run(problem, x0=[...])
result = result.discard(1000)      # remove burn-in

# Summary statistics
result.mean()                      # posterior mean, shape (n_params,)
result.std()                       # posterior std, shape (n_params,)
result.cov()                       # posterior covariance matrix
result.quantile([0.025, 0.975])    # credible interval

# Raw data
result.samples                     # ndarray, shape (n_samples, n_params)
result.log_posteriors              # log p(theta | y), shape (n_samples,)
result.log_evidence                # set by TMCMC; None for MCMC samplers

# Diagnostics
result.ess()                       # effective sample size, shape (n_params,)
result.autocorr(max_lag=100)       # ACF, shape (max_lag+1, n_params)

# Posterior predictive
pp = result.posterior_predictive(forward_model, n_eval=500)
pp.mean()                          # shape (n_obs,)
pp.plot_bands(x=..., y_obs=...)

# Plots
result.plot_trace()
result.plot_marginals()
result.plot_corner(style="corner", true_values=[...])
result.plot_autocorr(max_lag=80)

Result

Container for posterior samples produced by a sampler.

Parameters:

Name Type Description Default
samples (ndarray, shape(n_samples, n_params))
required
log_posteriors (ndarray, shape(n_samples))

Omit it to wrap a chain you produced yourself with the step functions; everything except log-posterior-based output still works.

None
param_names list of str
None
acceptance_rate float
None
Source code in mcmckit/core/result.py
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
class Result:
    """Container for posterior samples produced by a sampler.

    Parameters
    ----------
    samples : np.ndarray, shape (n_samples, n_params)
    log_posteriors : np.ndarray, shape (n_samples,), optional
        Omit it to wrap a chain you produced yourself with the step
        functions; everything except log-posterior-based output still works.
    param_names : list of str, optional
    acceptance_rate : float, optional
    """

    def __init__(self, samples, log_posteriors=None, param_names=None,
                 acceptance_rate=None, log_evidence=None):
        self.samples = np.asarray(samples)
        self.log_posteriors = None if log_posteriors is None else np.asarray(log_posteriors)
        self.param_names = param_names
        self.acceptance_rate = acceptance_rate
        self.log_evidence = log_evidence  # set by TMCMC; None for MCMC samplers

    # ------------------------------------------------------------------
    # Summary statistics
    # ------------------------------------------------------------------

    def mean(self):
        return np.mean(self.samples, axis=0)

    def std(self):
        return np.std(self.samples, axis=0)

    def cov(self):
        """Posterior covariance, always shape ``(n_params, n_params)``.

        ``np.cov`` collapses to a 0-d scalar for a single parameter, which makes
        ``result.cov()[0, 0]`` raise and stops the result being passed straight
        to a sampler's ``initial_cov``. Keep it 2-D so one-parameter problems
        behave like every other size.
        """
        c = np.cov(self.samples.T)
        return np.atleast_2d(c)

    def quantile(self, q):
        return np.quantile(self.samples, q, axis=0)

    def select(self, indices):
        """Return a new Result containing only a subset of parameters.

        Parameters
        ----------
        indices : array-like of int or str
            Parameter indices (int) or names (str) to keep.

        Returns
        -------
        Result

        Examples
        --------
        ::

            r_sub = result.select([0, 2, 6, 9])          # by index
            r_sub = result.select(["k1", "k3", "k7"])    # by name
        """
        idx = []
        for i in indices:
            if isinstance(i, str):
                if self.param_names is None:
                    raise ValueError("param_names not set; use integer indices.")
                idx.append(self.param_names.index(i))
            else:
                idx.append(int(i))
        names = [self.param_names[i] for i in idx] if self.param_names else None
        return Result(self.samples[:, idx], self.log_posteriors,
                      param_names=names, acceptance_rate=self.acceptance_rate)

    def discard(self, n):
        """Return a new Result with the first n samples removed (burn-in).

        Parameters
        ----------
        n : int
            Number of initial samples to discard.
        """
        if n >= len(self.samples):
            raise ValueError(f"Cannot discard {n} samples from a chain of length {len(self.samples)}.")
        return Result(
            samples=self.samples[n:],
            log_posteriors=None if self.log_posteriors is None else self.log_posteriors[n:],
            param_names=self.param_names,
            acceptance_rate=self.acceptance_rate,
        )

    # ------------------------------------------------------------------
    # Diagnostics
    # ------------------------------------------------------------------

    def ess(self):
        """Effective sample size per parameter.

        Returns
        -------
        np.ndarray, shape (n_params,)

        See Also
        --------
        mcmckit.core.diagnostics.ess
        """
        from .diagnostics import ess as _ess
        return _ess(self.samples)

    def autocorr(self, max_lag=100):
        """Normalised autocorrelation function for each parameter.

        Parameters
        ----------
        max_lag : int
            Maximum lag to compute.

        Returns
        -------
        np.ndarray, shape (max_lag + 1, n_params)

        See Also
        --------
        mcmckit.core.diagnostics.autocorr
        """
        from .diagnostics import autocorr as _autocorr
        return _autocorr(self.samples, max_lag=max_lag)

    def plot_autocorr(self, max_lag=100, title=None):
        """Plot the autocorrelation function for each parameter.

        Parameters
        ----------
        max_lag : int
            Maximum lag to display.
        title : str, optional

        Returns
        -------
        matplotlib Figure
        """
        import matplotlib.pyplot as plt

        acf = self.autocorr(max_lag=max_lag)
        lags = np.arange(acf.shape[0])
        n_params = acf.shape[1]
        names = self.param_names or [f"theta[{i}]" for i in range(n_params)]

        ncols = min(n_params, 3)
        nrows = (n_params + ncols - 1) // ncols
        fig, axes = plt.subplots(nrows, ncols, figsize=(4 * ncols, 2.5 * nrows),
                                 squeeze=False, constrained_layout=True)
        axes_flat = axes.flatten()

        for i in range(n_params):
            ax = axes_flat[i]
            ax.bar(lags, acf[:, i], width=1.0, color="steelblue", alpha=0.7)
            ax.axhline(0, color="black", lw=0.8)
            ax.set_xlabel("lag")
            ax.set_ylabel("ACF")
            ax.set_title(names[i])

        for j in range(n_params, len(axes_flat)):
            axes_flat[j].set_visible(False)

        if title is not None:
            fig.suptitle(title)
        return fig

    # ------------------------------------------------------------------
    # Visualisation (requires matplotlib)
    # ------------------------------------------------------------------

    def plot_trace(self, title=None, **kwargs):
        import matplotlib.pyplot as plt

        n_params = self.samples.shape[1]
        names = self.param_names or [f"theta[{i}]" for i in range(n_params)]

        fig, axes = plt.subplots(n_params, 1, figsize=(10, 2.5 * n_params), squeeze=False,
                                 constrained_layout=True)
        for i, ax in enumerate(axes[:, 0]):
            ax.plot(self.samples[:, i], lw=0.7, **kwargs)
            ax.set_ylabel(names[i])
        axes[-1, 0].set_xlabel("iteration")
        if title is not None:
            fig.suptitle(title)
        return fig

    def plot_marginals(self, bins=40, title=None, **kwargs):
        import matplotlib.pyplot as plt

        n_params = self.samples.shape[1]
        names = self.param_names or [f"theta[{i}]" for i in range(n_params)]

        ncols = min(n_params, 3)
        nrows = (n_params + ncols - 1) // ncols
        fig, axes = plt.subplots(nrows, ncols, figsize=(4 * ncols, 3 * nrows), squeeze=False,
                                 constrained_layout=True)
        axes_flat = axes.flatten()

        for i in range(n_params):
            axes_flat[i].hist(self.samples[:, i], bins=bins, density=True, **kwargs)
            axes_flat[i].set_xlabel(names[i])
            axes_flat[i].set_ylabel("density")

        for j in range(n_params, len(axes_flat)):
            axes_flat[j].set_visible(False)

        if title is not None:
            fig.suptitle(title)
        return fig

    def plot_corner(
        self,
        style="corner",
        bins=30,
        kde_grid=80,
        levels=6,
        true_values=None,
        title=None,
        scatter_kwargs=None,
        hist_kwargs=None,
        kde_kwargs=None,
    ):
        """Corner / pair plot of posterior samples.

        Parameters
        ----------
        style : str
            Layout style:

            ``"corner"`` *(default)*
                Diagonal = histogram + KDE. Lower triangle = 2D KDE contours.
                Upper triangle = empty.

            ``"scatter"``
                Diagonal = histogram. Lower triangle = scatter plot.
                Upper triangle = empty.

            ``"full"``
                Diagonal = histogram + KDE. Lower triangle = scatter.
                Upper triangle = 2D KDE contours.

            ``"kde"``
                Diagonal = KDE only. All off-diagonal = 2D KDE contours.

        bins : int
            Number of histogram bins for diagonal panels.
        kde_grid : int
            Grid resolution for 2D KDE evaluation.
        levels : int
            Number of contour levels for 2D KDE panels.
        true_values : array-like, optional
            True / reference parameter values. Shown as a vertical line on
            diagonal panels and a cross on off-diagonal panels.
        scatter_kwargs : dict, optional
            Passed to ``ax.scatter`` for scatter panels.
        hist_kwargs : dict, optional
            Passed to ``ax.hist`` for diagonal panels.
        kde_kwargs : dict, optional
            Passed to ``ax.contourf`` for KDE contour panels.
        """
        import matplotlib.pyplot as plt

        _scatter_kw = dict(s=1, alpha=0.3, color="steelblue", rasterized=True)
        _scatter_kw.update(scatter_kwargs or {})
        _hist_kw = dict(bins=bins, density=True, color="steelblue", alpha=0.6)
        _hist_kw.update(hist_kwargs or {})
        _kde_kw = dict(levels=levels, cmap="Blues")
        _kde_kw.update(kde_kwargs or {})

        valid = {"corner", "scatter", "full", "kde"}
        if style not in valid:
            raise ValueError(f"style must be one of {valid}, got {style!r}")

        true_values = np.asarray(true_values) if true_values is not None else None

        n = self.samples.shape[1]
        names = self.param_names or [f"theta[{i}]" for i in range(n)]
        means = self.mean()
        stds = self.std()

        fig, axes = plt.subplots(n, n, figsize=(2.5 * n, 2.5 * n), constrained_layout=True)
        if n == 1:
            axes = np.array([[axes]])

        for row in range(n):
            for col in range(n):
                ax = axes[row, col]
                xi = self.samples[:, col]
                yi = self.samples[:, row]

                if row == col:
                    # --- diagonal ---
                    if style == "kde":
                        xg, zg = _kde1d(xi)
                        ax.plot(xg, zg, color="steelblue", lw=1.5)
                        ax.fill_between(xg, zg, alpha=0.25, color="steelblue")
                    else:
                        ax.hist(xi, **_hist_kw)
                        if style in ("corner", "full"):
                            xg, zg = _kde1d(xi)
                            ax.plot(xg, zg, color="navy", lw=1.2)

                    # mean ± std as title
                    ax.set_title(f"{means[col]:.3g} ± {stds[col]:.3g}", fontsize=7, pad=2)

                    # true value: vertical line
                    if true_values is not None:
                        ax.axvline(true_values[col], color="crimson", lw=1.2, ls="--", zorder=5)

                elif row > col:
                    # --- lower triangle ---
                    if style in ("scatter", "full"):
                        ax.scatter(xi, yi, **_scatter_kw)
                    else:  # "corner" or "kde"
                        xx, yy, zz = _kde2d(xi, yi, grid_size=kde_grid)
                        ax.contourf(xx, yy, zz, **_kde_kw)
                        ax.contour(xx, yy, zz, levels=levels, colors="navy", linewidths=0.5, alpha=0.6)

                    # true value: cross
                    if true_values is not None:
                        ax.plot(true_values[col], true_values[row], marker="+",
                                color="crimson", ms=8, mew=1.5, zorder=5, ls="none")

                else:
                    # --- upper triangle ---
                    if style in ("corner", "scatter"):
                        ax.set_visible(False)
                        continue
                    else:  # "full" or "kde"
                        xx, yy, zz = _kde2d(xi, yi, grid_size=kde_grid)
                        ax.contourf(xx, yy, zz, **_kde_kw)
                        ax.contour(xx, yy, zz, levels=levels, colors="navy", linewidths=0.5, alpha=0.6)

                    # true value: cross
                    if true_values is not None:
                        ax.plot(true_values[col], true_values[row], marker="+",
                                color="crimson", ms=8, mew=1.5, zorder=5, ls="none")

                # axis labels on edges only
                if row == n - 1:
                    ax.set_xlabel(names[col], fontsize=8)
                else:
                    ax.set_xticklabels([])

                if col == 0 and row != 0:
                    ax.set_ylabel(names[row], fontsize=8)
                else:
                    ax.set_yticklabels([])

                ax.tick_params(labelsize=7)

        if title is not None:
            fig.suptitle(title)
        return fig

    # ------------------------------------------------------------------
    # Posterior predictive
    # ------------------------------------------------------------------

    def posterior_predictive(self, forward_model, n_eval=None):
        """Evaluate the forward model at posterior samples.

        Parameters
        ----------
        forward_model : callable
            ``f(theta) -> array-like, shape (n_obs,)``.
        n_eval : int, optional
            Number of posterior samples to evaluate.  ``None`` (default)
            evaluates all samples.  For expensive forward models (e.g. FEM),
            use a smaller ``n_eval`` — see ``_subsample_indices`` for the
            extension point to plug in smarter sampling strategies.

        Returns
        -------
        PosteriorPredictive
        """
        idx = _subsample_indices(len(self.samples), n_eval)
        theta_sub = self.samples[idx]
        preds = np.array([np.asarray(forward_model(t), dtype=float).ravel()
                          for t in theta_sub])
        return PosteriorPredictive(preds, theta_sub,
                                   param_names=self.param_names)

    # ------------------------------------------------------------------
    # Dunder
    # ------------------------------------------------------------------

    def as_prior(self, method: str, discard: int = 0):
        """Convert this result into a prior for the next sequential update step.

        Parameters
        ----------
        method : {'gaussian', 'kde'}
            Density estimation method.  ``'gaussian'`` fits a multivariate
            normal and is fast in any dimension.  ``'kde'`` fits a
            non-parametric kernel density estimate (recommended for ≤ 8
            parameters when the posterior is non-Gaussian or multimodal).
        discard : int
            Number of initial samples to discard as burn-in before fitting.

        Returns
        -------
        PosteriorPrior
            A callable that evaluates ``log p(theta)`` and supports
            ``sample(n)`` for use with TMCMC.

        Examples
        --------
        ::

            prior2 = result1.as_prior(method='gaussian', discard=1000)
            problem2 = mc.Problem(prior=prior2, likelihood=ll_new)
            result2 = mc.DRAM(n_samples=20_000, initial_cov=prior2.cov).run(
                problem2, x0=prior2.mean
            )
        """
        from .sequential import PosteriorPrior
        samples = self.discard(discard).samples
        return PosteriorPrior(samples, method=method)

    def __repr__(self):
        n, d = self.samples.shape
        ar = f", acceptance_rate={self.acceptance_rate:.3f}" if self.acceptance_rate is not None else ""
        return f"Result(n_samples={n}, n_params={d}{ar})"

Methods:

cov

cov()

Posterior covariance, always shape (n_params, n_params).

np.cov collapses to a 0-d scalar for a single parameter, which makes result.cov()[0, 0] raise and stops the result being passed straight to a sampler's initial_cov. Keep it 2-D so one-parameter problems behave like every other size.

Source code in mcmckit/core/result.py
def cov(self):
    """Posterior covariance, always shape ``(n_params, n_params)``.

    ``np.cov`` collapses to a 0-d scalar for a single parameter, which makes
    ``result.cov()[0, 0]`` raise and stops the result being passed straight
    to a sampler's ``initial_cov``. Keep it 2-D so one-parameter problems
    behave like every other size.
    """
    c = np.cov(self.samples.T)
    return np.atleast_2d(c)

select

select(indices)

Return a new Result containing only a subset of parameters.

Parameters:

Name Type Description Default
indices array-like of int or str

Parameter indices (int) or names (str) to keep.

required

Returns:

Type Description
Result

Examples:

::

r_sub = result.select([0, 2, 6, 9])          # by index
r_sub = result.select(["k1", "k3", "k7"])    # by name
Source code in mcmckit/core/result.py
def select(self, indices):
    """Return a new Result containing only a subset of parameters.

    Parameters
    ----------
    indices : array-like of int or str
        Parameter indices (int) or names (str) to keep.

    Returns
    -------
    Result

    Examples
    --------
    ::

        r_sub = result.select([0, 2, 6, 9])          # by index
        r_sub = result.select(["k1", "k3", "k7"])    # by name
    """
    idx = []
    for i in indices:
        if isinstance(i, str):
            if self.param_names is None:
                raise ValueError("param_names not set; use integer indices.")
            idx.append(self.param_names.index(i))
        else:
            idx.append(int(i))
    names = [self.param_names[i] for i in idx] if self.param_names else None
    return Result(self.samples[:, idx], self.log_posteriors,
                  param_names=names, acceptance_rate=self.acceptance_rate)

discard

discard(n)

Return a new Result with the first n samples removed (burn-in).

Parameters:

Name Type Description Default
n int

Number of initial samples to discard.

required
Source code in mcmckit/core/result.py
def discard(self, n):
    """Return a new Result with the first n samples removed (burn-in).

    Parameters
    ----------
    n : int
        Number of initial samples to discard.
    """
    if n >= len(self.samples):
        raise ValueError(f"Cannot discard {n} samples from a chain of length {len(self.samples)}.")
    return Result(
        samples=self.samples[n:],
        log_posteriors=None if self.log_posteriors is None else self.log_posteriors[n:],
        param_names=self.param_names,
        acceptance_rate=self.acceptance_rate,
    )

ess

ess()

Effective sample size per parameter.

Returns:

Type Description
(ndarray, shape(n_params))
See Also

mcmckit.core.diagnostics.ess

Source code in mcmckit/core/result.py
def ess(self):
    """Effective sample size per parameter.

    Returns
    -------
    np.ndarray, shape (n_params,)

    See Also
    --------
    mcmckit.core.diagnostics.ess
    """
    from .diagnostics import ess as _ess
    return _ess(self.samples)

autocorr

autocorr(max_lag=100)

Normalised autocorrelation function for each parameter.

Parameters:

Name Type Description Default
max_lag int

Maximum lag to compute.

100

Returns:

Type Description
(ndarray, shape(max_lag + 1, n_params))
See Also

mcmckit.core.diagnostics.autocorr

Source code in mcmckit/core/result.py
def autocorr(self, max_lag=100):
    """Normalised autocorrelation function for each parameter.

    Parameters
    ----------
    max_lag : int
        Maximum lag to compute.

    Returns
    -------
    np.ndarray, shape (max_lag + 1, n_params)

    See Also
    --------
    mcmckit.core.diagnostics.autocorr
    """
    from .diagnostics import autocorr as _autocorr
    return _autocorr(self.samples, max_lag=max_lag)

plot_autocorr

plot_autocorr(max_lag=100, title=None)

Plot the autocorrelation function for each parameter.

Parameters:

Name Type Description Default
max_lag int

Maximum lag to display.

100
title str
None

Returns:

Type Description
matplotlib Figure
Source code in mcmckit/core/result.py
def plot_autocorr(self, max_lag=100, title=None):
    """Plot the autocorrelation function for each parameter.

    Parameters
    ----------
    max_lag : int
        Maximum lag to display.
    title : str, optional

    Returns
    -------
    matplotlib Figure
    """
    import matplotlib.pyplot as plt

    acf = self.autocorr(max_lag=max_lag)
    lags = np.arange(acf.shape[0])
    n_params = acf.shape[1]
    names = self.param_names or [f"theta[{i}]" for i in range(n_params)]

    ncols = min(n_params, 3)
    nrows = (n_params + ncols - 1) // ncols
    fig, axes = plt.subplots(nrows, ncols, figsize=(4 * ncols, 2.5 * nrows),
                             squeeze=False, constrained_layout=True)
    axes_flat = axes.flatten()

    for i in range(n_params):
        ax = axes_flat[i]
        ax.bar(lags, acf[:, i], width=1.0, color="steelblue", alpha=0.7)
        ax.axhline(0, color="black", lw=0.8)
        ax.set_xlabel("lag")
        ax.set_ylabel("ACF")
        ax.set_title(names[i])

    for j in range(n_params, len(axes_flat)):
        axes_flat[j].set_visible(False)

    if title is not None:
        fig.suptitle(title)
    return fig

plot_corner

plot_corner(style='corner', bins=30, kde_grid=80, levels=6, true_values=None, title=None, scatter_kwargs=None, hist_kwargs=None, kde_kwargs=None)

Corner / pair plot of posterior samples.

Parameters:

Name Type Description Default
style str

Layout style:

"corner" (default) Diagonal = histogram + KDE. Lower triangle = 2D KDE contours. Upper triangle = empty.

"scatter" Diagonal = histogram. Lower triangle = scatter plot. Upper triangle = empty.

"full" Diagonal = histogram + KDE. Lower triangle = scatter. Upper triangle = 2D KDE contours.

"kde" Diagonal = KDE only. All off-diagonal = 2D KDE contours.

'corner'
bins int

Number of histogram bins for diagonal panels.

30
kde_grid int

Grid resolution for 2D KDE evaluation.

80
levels int

Number of contour levels for 2D KDE panels.

6
true_values array - like

True / reference parameter values. Shown as a vertical line on diagonal panels and a cross on off-diagonal panels.

None
scatter_kwargs dict

Passed to ax.scatter for scatter panels.

None
hist_kwargs dict

Passed to ax.hist for diagonal panels.

None
kde_kwargs dict

Passed to ax.contourf for KDE contour panels.

None
Source code in mcmckit/core/result.py
def plot_corner(
    self,
    style="corner",
    bins=30,
    kde_grid=80,
    levels=6,
    true_values=None,
    title=None,
    scatter_kwargs=None,
    hist_kwargs=None,
    kde_kwargs=None,
):
    """Corner / pair plot of posterior samples.

    Parameters
    ----------
    style : str
        Layout style:

        ``"corner"`` *(default)*
            Diagonal = histogram + KDE. Lower triangle = 2D KDE contours.
            Upper triangle = empty.

        ``"scatter"``
            Diagonal = histogram. Lower triangle = scatter plot.
            Upper triangle = empty.

        ``"full"``
            Diagonal = histogram + KDE. Lower triangle = scatter.
            Upper triangle = 2D KDE contours.

        ``"kde"``
            Diagonal = KDE only. All off-diagonal = 2D KDE contours.

    bins : int
        Number of histogram bins for diagonal panels.
    kde_grid : int
        Grid resolution for 2D KDE evaluation.
    levels : int
        Number of contour levels for 2D KDE panels.
    true_values : array-like, optional
        True / reference parameter values. Shown as a vertical line on
        diagonal panels and a cross on off-diagonal panels.
    scatter_kwargs : dict, optional
        Passed to ``ax.scatter`` for scatter panels.
    hist_kwargs : dict, optional
        Passed to ``ax.hist`` for diagonal panels.
    kde_kwargs : dict, optional
        Passed to ``ax.contourf`` for KDE contour panels.
    """
    import matplotlib.pyplot as plt

    _scatter_kw = dict(s=1, alpha=0.3, color="steelblue", rasterized=True)
    _scatter_kw.update(scatter_kwargs or {})
    _hist_kw = dict(bins=bins, density=True, color="steelblue", alpha=0.6)
    _hist_kw.update(hist_kwargs or {})
    _kde_kw = dict(levels=levels, cmap="Blues")
    _kde_kw.update(kde_kwargs or {})

    valid = {"corner", "scatter", "full", "kde"}
    if style not in valid:
        raise ValueError(f"style must be one of {valid}, got {style!r}")

    true_values = np.asarray(true_values) if true_values is not None else None

    n = self.samples.shape[1]
    names = self.param_names or [f"theta[{i}]" for i in range(n)]
    means = self.mean()
    stds = self.std()

    fig, axes = plt.subplots(n, n, figsize=(2.5 * n, 2.5 * n), constrained_layout=True)
    if n == 1:
        axes = np.array([[axes]])

    for row in range(n):
        for col in range(n):
            ax = axes[row, col]
            xi = self.samples[:, col]
            yi = self.samples[:, row]

            if row == col:
                # --- diagonal ---
                if style == "kde":
                    xg, zg = _kde1d(xi)
                    ax.plot(xg, zg, color="steelblue", lw=1.5)
                    ax.fill_between(xg, zg, alpha=0.25, color="steelblue")
                else:
                    ax.hist(xi, **_hist_kw)
                    if style in ("corner", "full"):
                        xg, zg = _kde1d(xi)
                        ax.plot(xg, zg, color="navy", lw=1.2)

                # mean ± std as title
                ax.set_title(f"{means[col]:.3g} ± {stds[col]:.3g}", fontsize=7, pad=2)

                # true value: vertical line
                if true_values is not None:
                    ax.axvline(true_values[col], color="crimson", lw=1.2, ls="--", zorder=5)

            elif row > col:
                # --- lower triangle ---
                if style in ("scatter", "full"):
                    ax.scatter(xi, yi, **_scatter_kw)
                else:  # "corner" or "kde"
                    xx, yy, zz = _kde2d(xi, yi, grid_size=kde_grid)
                    ax.contourf(xx, yy, zz, **_kde_kw)
                    ax.contour(xx, yy, zz, levels=levels, colors="navy", linewidths=0.5, alpha=0.6)

                # true value: cross
                if true_values is not None:
                    ax.plot(true_values[col], true_values[row], marker="+",
                            color="crimson", ms=8, mew=1.5, zorder=5, ls="none")

            else:
                # --- upper triangle ---
                if style in ("corner", "scatter"):
                    ax.set_visible(False)
                    continue
                else:  # "full" or "kde"
                    xx, yy, zz = _kde2d(xi, yi, grid_size=kde_grid)
                    ax.contourf(xx, yy, zz, **_kde_kw)
                    ax.contour(xx, yy, zz, levels=levels, colors="navy", linewidths=0.5, alpha=0.6)

                # true value: cross
                if true_values is not None:
                    ax.plot(true_values[col], true_values[row], marker="+",
                            color="crimson", ms=8, mew=1.5, zorder=5, ls="none")

            # axis labels on edges only
            if row == n - 1:
                ax.set_xlabel(names[col], fontsize=8)
            else:
                ax.set_xticklabels([])

            if col == 0 and row != 0:
                ax.set_ylabel(names[row], fontsize=8)
            else:
                ax.set_yticklabels([])

            ax.tick_params(labelsize=7)

    if title is not None:
        fig.suptitle(title)
    return fig

posterior_predictive

posterior_predictive(forward_model, n_eval=None)

Evaluate the forward model at posterior samples.

Parameters:

Name Type Description Default
forward_model callable

f(theta) -> array-like, shape (n_obs,).

required
n_eval int

Number of posterior samples to evaluate. None (default) evaluates all samples. For expensive forward models (e.g. FEM), use a smaller n_eval — see _subsample_indices for the extension point to plug in smarter sampling strategies.

None

Returns:

Type Description
PosteriorPredictive
Source code in mcmckit/core/result.py
def posterior_predictive(self, forward_model, n_eval=None):
    """Evaluate the forward model at posterior samples.

    Parameters
    ----------
    forward_model : callable
        ``f(theta) -> array-like, shape (n_obs,)``.
    n_eval : int, optional
        Number of posterior samples to evaluate.  ``None`` (default)
        evaluates all samples.  For expensive forward models (e.g. FEM),
        use a smaller ``n_eval`` — see ``_subsample_indices`` for the
        extension point to plug in smarter sampling strategies.

    Returns
    -------
    PosteriorPredictive
    """
    idx = _subsample_indices(len(self.samples), n_eval)
    theta_sub = self.samples[idx]
    preds = np.array([np.asarray(forward_model(t), dtype=float).ravel()
                      for t in theta_sub])
    return PosteriorPredictive(preds, theta_sub,
                               param_names=self.param_names)

as_prior

as_prior(method: str, discard: int = 0)

Convert this result into a prior for the next sequential update step.

Parameters:

Name Type Description Default
method (gaussian, kde)

Density estimation method. 'gaussian' fits a multivariate normal and is fast in any dimension. 'kde' fits a non-parametric kernel density estimate (recommended for ≤ 8 parameters when the posterior is non-Gaussian or multimodal).

'gaussian'
discard int

Number of initial samples to discard as burn-in before fitting.

0

Returns:

Type Description
PosteriorPrior

A callable that evaluates log p(theta) and supports sample(n) for use with TMCMC.

Examples:

::

prior2 = result1.as_prior(method='gaussian', discard=1000)
problem2 = mc.Problem(prior=prior2, likelihood=ll_new)
result2 = mc.DRAM(n_samples=20_000, initial_cov=prior2.cov).run(
    problem2, x0=prior2.mean
)
Source code in mcmckit/core/result.py
def as_prior(self, method: str, discard: int = 0):
    """Convert this result into a prior for the next sequential update step.

    Parameters
    ----------
    method : {'gaussian', 'kde'}
        Density estimation method.  ``'gaussian'`` fits a multivariate
        normal and is fast in any dimension.  ``'kde'`` fits a
        non-parametric kernel density estimate (recommended for ≤ 8
        parameters when the posterior is non-Gaussian or multimodal).
    discard : int
        Number of initial samples to discard as burn-in before fitting.

    Returns
    -------
    PosteriorPrior
        A callable that evaluates ``log p(theta)`` and supports
        ``sample(n)`` for use with TMCMC.

    Examples
    --------
    ::

        prior2 = result1.as_prior(method='gaussian', discard=1000)
        problem2 = mc.Problem(prior=prior2, likelihood=ll_new)
        result2 = mc.DRAM(n_samples=20_000, initial_cov=prior2.cov).run(
            problem2, x0=prior2.mean
        )
    """
    from .sequential import PosteriorPrior
    samples = self.discard(discard).samples
    return PosteriorPrior(samples, method=method)