VBMC#
Note
The VBMC class implements the Variational Bayesian Monte Carlo (VBMC) algorithm.
VBMC computes a variational approximation of the full posterior and a lower bound on the log normalization constant (log marginal likelihood or log model evidence) for a provided unnormalized log posterior.
To perform inference, first initialize a VBMC object and then call vbmc.optimize() on the instance.
By default VBMC assumes noiseless evaluations of the log posterior, but noisy likelihoods can also be handled. See PyVBMC Example 6: Noisy log-likelihood evaluations for more details.
See below for more details on the VBMC class methods and interface. The primary entry-points for users are the VBMC class, which initializes the algorithm, and the VariationalPosterior class, which represents the returned variational solution. The Basic options may also be useful.
The iteration history (vbmc.iteration_history) keeps each iteration’s Gaussian process surrogate as its training data and hyperparameters, without the posterior factors; vbmc.get_gp(iteration) returns the surrogate of an iteration with its posterior restored, ready for prediction.
Reusing evaluations and charging initialization work#
Use precomputed_evaluations to give VBMC target evaluations that are
already available when the run starts. These observations are independent of
the starting point and plausible box: they neither become additional rows of
x0 nor change the plausible bounds. Pass (X, y) for an exact target or
one with unknown noise, where X has shape (N, D) in the target’s
original input coordinates and y has shape (N,):
vbmc = VBMC(
log_density,
x0,
lower_bounds,
upper_bounds,
plausible_lower_bounds,
plausible_upper_bounds,
precomputed_evaluations=(X, y),
)
The values in y must be outputs of log_density at the corresponding
rows of X. When prior or log_prior is supplied separately,
log_density and y contain log-likelihood values; VBMC adds the
deterministic log-prior once. Precomputed points must lie strictly inside the
hard bounds, but they need not lie inside the plausible box.
For a target with user-provided noise estimates, set
options["specify_target_noise"] and pass (X, y, y_sd). y_sd has
shape (N,) and contains positive standard deviations of the log-density
observations, not uncertainties in X. Repeated rows of X remain
independent noisy observations and are pooled by inverse-variance weighting:
vbmc = VBMC(
noisy_log_density,
x0,
lower_bounds,
upper_bounds,
plausible_lower_bounds,
plausible_upper_bounds,
precomputed_evaluations=(X, y, y_sd),
options={"specify_target_noise": True},
)
Precomputed observations do not count as fresh target calls. By default they
also carry no cost for the current run, which is appropriate for evaluations
obtained during earlier work. If preparing this run incurred target or
derivative work, pass its nonnegative integer function-equivalent charge as
initialization_cost. The charge is independent of the number of retained
observations and counts once against the total max_fun_evals allowance:
vbmc = VBMC(
log_density,
x0,
lower_bounds,
upper_bounds,
plausible_lower_bounds,
plausible_upper_bounds,
precomputed_evaluations=(X, y),
initialization_cost=13,
options={"max_fun_evals": 100},
)
Here VBMC can make at most 87 fresh target calls.
results["func_count"] remains the literal number of fresh calls made by
VBMC. When initialization_cost is positive,
results["evaluation_budget"] additionally reports the
function-equivalent limit, initialization, new_calls and used
total. Zero-cost runs retain the ordinary result and display without this
additional budget report.
Performance calibration#
New runs reuse compatible machine-local chunk settings produced by an
explicit calibrate campaign. If no compatible record
exists, they use historical defaults. Optimization never starts a campaign.
Use the performance_calibration option to select "cached" (default),
"off", or an explicit CalibrationProfile.
Settings are fixed before the first relevant kernel call and survive final
boost and save/resume. A resolved save rejects load-time "cached" or
different explicit settings; start a new run to use a different profile.
Startup tips#
With show_tips=True (default), a new run may print one tip before the
iteration display. Resumed and continued runs do not show tips. PyVBMC may use
the same space for a performance-calibration suggestion or an old-release
reminder.
Set show_tips=False to disable tips and the old-release reminder. Set
display="off" to suppress tips and both reminders. Pass either setting in
the options dictionary when constructing VBMC. The
FAQ also
describes the old-release reminder.
- class pyvbmc.VBMC(log_density: callable, x0: ndarray = None, lower_bounds: ndarray = None, upper_bounds: ndarray = None, plausible_lower_bounds: ndarray = None, plausible_upper_bounds: ndarray = None, options: dict = None, options_path: PathLike | None = None, prior: Prior = None, log_prior: callable = None, sample_prior: callable = None, seed=None, *, precomputed_evaluations=None, initialization_cost=0)[source]#
Posterior and model inference via Variational Bayesian Monte Carlo (VBMC).
VBMC computes a variational approximation of the full posterior and a lower bound on the normalization constant (marginal likelihood or model evidence) for a provided unnormalized log posterior.
Initialize a
VBMCobject to set up the inference problem, then runoptimize(). See the examples for more details.- Parameters:
- log_densitycallable or pyvbmc.pymc.PyMCTarget
A given target log-posterior or log-likelihood, or a constructed
pyvbmc.pymc.PyMCTarget. APyMCTargetsupplies its target function, starting point, bounds, setup evaluations, and setup cost; explicitly supplied constructor arguments override its starting point, plausible bounds, evaluations, and cost. Supplied hard bounds must equaltarget.lbandtarget.ub, which are in the target’s flat coordinates: the support of a variable the adapter leaves untransformed, and infinite for a variable that keeps its PyMC transform. An additionalpriororlog_priorcannot be combined with aPyMCTarget. For an ordinary callable, iflog_priorisNone,log_densityaccepts inputxand returns the value of the target log-joint, that is, the unnormalized log-posterior density atx. Iflog_prioris notNone,log_densityshould return the unnormalized log-likelihood. In either case, ifoptions["specify_target_noise"]is true,log_densityshould return a tuple where the first element is the noisy log-density, and the second is an estimate of the standard deviation of the noise. Ifoptions["vectorized_target"]is true, it instead receives a NumPy array with shape(N, D)and returns values with shape(N,)or(N, 1). A noisy vectorized target returns a pair of arrays with one value and positive standard deviation per row. The initial design is evaluated in one batch; subsequent calls contain one point, with shape(1, D). A separately supplied prior is still evaluated one point at a time.- x0array_like, optional
Starting point for the inference. Ideally
x0is a point in the proximity of the mode of the posterior. One starting point is a row of D coordinates (a 1-D array or a list; for D = 1, also a number), and several are the rows of an array of shape(n0, D)or a list of lists. Default isNone, in which case the number of variables is read from the plausible bounds, one of which then needs one entry per variable.- lower_bounds, upper_boundsnp.ndarray, optional
lower_bounds(LB) andupper_bounds(UB) define a set of strict lower and upper bounds for the coordinate vector, x, so that the posterior has support on LB < x < UB. If scalars, the bound is replicated in each dimension. UseNonefor LB and UB if no bounds exist. Set LB [i] = -infand UB [i] =infif the i-th coordinate is unbounded (while other coordinates may be bounded). Note that if LB and UB contain unbounded variables, the respective values of PLB and PUB need to be specified (see below), by defaultNone.- plausible_lower_bounds, plausible_upper_boundsnp.ndarray, optional
Specifies a set of
plausible_lower_bounds(PLB) andplausible_upper_bounds(PUB) such that LB < PLB < PUB < UB. Both PLB and PUB need to be finite. PLB and PUB represent a “plausible” range, which should denote a region of high posterior probability mass. If scalars, the bound is replicated in each dimension. Among other things, the plausible box is used to draw initial samples and to set priors over hyperparameters of the algorithm. When in doubt, we found that setting PLB and PUB using the topmost ~68% percentile range of the prior (e.g, mean +/- 1 SD for a Gaussian prior) works well in many cases (but note that additional information might afford a better guess). Both are by default None.- optionsdict, optional
Additional options can be passed as a dict. Please refer to the VBMC options page for the default options. If no
optionsare passed, the default options are used.- options_pathpathlike, optional
Additional options can also be specified by a .ini file. The
optionsdict keyword takes precedence over options set in the .ini file.options_pathshould be either absolute or relative to the pyvbmc/vbmc/ directory.- prior, optional
An optional separate prior. It can be a
pyvbmc.priors.Priorsubclass, an appropriatescipy.statsdistribution, or a list of one-dimensional PyVBMCPriorand/or one-dimensional continuousscipy.statsdistributions. (see the documentation on priors for more details). Ifprioris not None, the argumentlog_densityis assumed to represent the log-likelihood (otherwise it is assumed to represent the log-joint). The hard boundslower_boundsandupper_boundsmust lie inside the support of the prior. Within them the prior is used as it is given: the model evidence reported is that of the prior restricted to the hard bounds, not that of a prior truncated to them and normalized again.- log_priorcallable, optional
An optional separate log-prior function, which should accept a single argument x and return the log-density of the prior at x. If
log_prioris notNone, the argumentlog_densityis assumed to represent the log-likelihood (otherwise it is assumed to represent the log-joint).- sample_priorcallable, optional
An optional function which accepts a single argument n and returns an array of samples from the prior, of shape (n, D), where D is the problem dimension. Currently unused.
- seedNone, int, SeedSequence or np.random.Generator, optional
Seed (or generator) for the random generator
vbmc.rngused by this instance, by its variational posterior and by the GP hyperparameter fit, for reproducible runs. Anything accepted bynumpy.random.default_rng. IfNone(default), a generator is derived from NumPy’s global random state, so that callingnp.random.seedbeforehand still makes a run reproducible; deriving it draws four integers from that state and so advances it, which a construction with a seed or a generator does not do. Every random draw of a run comes fromvbmc.rng, and after the construction NumPy’s global random state is neither read nor written. Draws fromvbmc.rngbeforeoptimize(e.g.vbmc.vp.sample) change the run like any other change of the generator’s state. This seed does not control random draws made bylog_density; seed a stochastic target separately.- precomputed_evaluationstuple, optional
Evaluations available before the run, independently of
x0. Pass(X, y)for exact or unknown-noise targets, or(X, y, y_sd)whenoptions["specify_target_noise"]is true.Xhas shape(N, D)in the target’s original coordinates and the other arrays have shape(N,). If a separate prior is supplied,ycontains log-likelihood values; VBMC adds the prior once. (The values ofoptions["f_vals"], given atx0, are log-joint values, with the prior already added.) Inputs are copied to float64 storage. Exact duplicate points must agree within four float64 ULPs at their value scale and are retained once; noisy repeats remain independent observations and are pooled by the logger.- initialization_costint, optional
Nonnegative function-equivalent cost incurred while preparing this run. It is independent of the number of precomputed rows and counts once against
options["max_fun_evals"], leaving the difference for fresh target calls. Default is zero.
- Attributes:
- rngnp.random.Generator
The random generator used by this instance. It is shared with
vbmc.vpand with the variational posteriors returned byoptimize.targetpyvbmc.pymc.PyMCTarget or NonePyMC target supplied directly at construction, if any.
- x0_orignp.ndarray, shape (n0, D)
The starting points in the coordinates the caller gave them in.
x0holds the same points in the inference space the instance was constructed with, which a later warp of that space leaves behind.
- Raises:
- ValueError
When neither x0 or (plausible_lower_bounds and plausible_upper_bounds) are specified.
- ValueError
When various checks for the bounds (LB, UB, PLB, PUB) of VBMC fail.
- ValueError
When an option value,
precomputed_evaluationsorinitialization_costis refused, wheninitialization_costleaves too few evaluations ofmax_fun_evalsfor the initial design, when the hard bounds reach outside the support ofprior, or when at leastfun_eval_startstarting points are given and the firstfun_eval_startof them take one value in a coordinate.- NotImplementedError
When the options select a feature of MATLAB VBMC that is not ported (
noise_shaping,acq_hedge, agp_hyp_samplerother than"slicesample", an acquisition function that asks for the MCMC step of the importance sampler).
References
[1]Huggins, B., Li, C., Tobaben, M., Aarnos, M., & Acerbi, L. (2023). “PyVBMC: Efficient Bayesian inference in Python”. Journal of Open Source Software 8(86), 5428, https://doi.org/10.21105/joss.05428.
[2]Acerbi, L. (2018). “Variational Bayesian Monte Carlo”. In Advances in Neural Information Processing Systems 31 (NeurIPS 2018), pp. 8213-8223.
[3]Acerbi, L. (2020). “Variational Bayesian Monte Carlo with Noisy Likelihoods”. In Advances in Neural Information Processing Systems 33 (NeurIPS 2020).
Examples
For VBMC usage examples, please look up the Jupyter notebook tutorials in the PyVBMC documentation: https://acerbilab.org/pyvbmc/_examples/pyvbmc_example_1_basic_usage.html
Previously computed target values can seed the logger without becoming starting points or changing the plausible box. Preparation performed for the current run can be charged against the total function-equivalent budget independently of the number of retained observations:
cached = (X_evaluated, log_density_values) vbmc = VBMC( log_density, x0, lower_bounds, upper_bounds, plausible_lower_bounds, plausible_upper_bounds, precomputed_evaluations=cached, initialization_cost=setup_cost, options={"max_fun_evals": 100}, )
- determine_best_vp(max_idx: int = None, safe_sd: float = None, frac_back: float = None, rank_criterion_flag: bool = None)[source]#
Return the best VariationalPosterior found during the optimization of VBMC as well as its ELBO, ELBO_SD and the index of the iteration.
Called without the selection arguments, the method uses the run’s options, so that on a finished run it returns the iteration the run selected.
- Parameters:
- max_idxint, optional
Check up to this iteration, by default None which means last iter.
- safe_sdfloat, optional
Penalization for uncertainty: the multiple of the ELBO_SD subtracted from the ELBO to give the ELCBO. By default None, which means the option
best_safe_sd(5 unless set).- frac_backfloat, optional
If no past stable iteration, go back up to this fraction of iterations. Used only without the ranking criterion. By default None, which means the option
best_frac_back(0.25 unless set).- rank_criterion_flagbool, optional
If True use new ranking criterion method to pick best solution. It finds a solution that combines ELCBO, stability, and recency. By default None, which means the option
rank_criterion(True unless set).
- Returns:
- vpVariationalPosterior
A copy of the VariationalPosterior of the selected iteration, carrying that iteration’s stability flag in its statistics. It shares the random generator with the recorded posterior, as every copy of a VariationalPosterior does, and the recorded posterior itself is left untouched.
- elbofloat
The ELBO of the iteration with the best VariationalPosterior.
- elbo_sdfloat
The ELBO_SD of the iteration with the best VariationalPosterior.
- idx_bestint
The index of the iteration with the best VariationalPosterior.
Notes
A NaN ELCBO (
elbo - safe_sd * elbo_sd) or reliability index ranks last in the ranking criterion, and a NaN ELCBO is skipped without it. If the ELCBO of every candidate iteration is NaN, the last iteration considered is selected, which with the defaultmax_idxis the posterior the run ended on.
- final_boost(vp: VariationalPosterior, gp: GP)[source]#
Perform a final boost of variational components.
- Parameters:
- vpVariationalPosterior
The VariationalPosterior that should be boosted.
- gpgpyreg.GP
The Gaussian process the VariationalPosterior was fitted with.
- Returns:
- vpVariationalPosterior
The VariationalPosterior resulting from the final boost.
- elbofloat
The ELBO of the VariationalPosterior resulting from the final boost.
- elbo_sdfloat
The ELBO_SD of the VariationalPosterior resulting from the final boost.
- changed_flagbool
Whether the returned VariationalPosterior is the boosted one: False when no boost was needed and when the guard rejected the boosted posterior.
- Raises:
- ValueError
If the option
tol_elcbo_boostis neither None nor a finite nonnegative number; or, withvariable_meansoff, whengphas fewer training inputs thanvphas components.- RuntimeError
If, with the guard on, neither
vpnor the boosted posterior has a finite ELBO and a finite nonnegative ELBO SD.
Notes
A boost is needed when
vphas fewer components than the boost gives it (min_final_componentswithvariable_meanson, the number of training inputs ofgpwith it off), or whenns_ent_boostorns_ent_fine_boostchanges the corresponding entropy sample size.With the option
tol_elcbo_boostset (0.1 by default), a guard keeps the boosted posterior only if neither its ELBO nor its ELBO minus five times its ELBO SD falls more thantol_elcbo_boostbelow that ofvp; otherwise it warns and returnsvp. A boosted posterior whose ELBO or ELBO SD is not finite, or whose SD is negative, is rejected in the same way, and one whose statistics are valid is kept when those ofvpare not. Withtol_elcbo_boost=Nonethe boosted posterior is always returned. The guard compares the optimizer’s stored pre- and post-boost ELBO and GP-based ELBO SD. It performs no diagnostic rescoring, and the SD does not include Monte Carlo uncertainty from the entropy estimate.The boost optimizes with warm-up over, without pruning of components, with the entropy sample sizes of the boost options (
ns_ent_boostand its kin), with the stochastic optimization allowed its ceiling of 10000 iterations whatevermax_iter_stochasticsays and, under the guard, without the weight penalty (weight_penalty). It runs on copies of the options and of the optimization state; neither the instance’s options nor its optimization state is changed.With
variable_meansoff the components of the boosted posterior sit at the training inputs ofgp, one each, andmin_final_componentsdoes not apply.
- get_gp(iteration: int)[source]#
Return the Gaussian process surrogate of a recorded iteration.
The iteration history stores each iteration’s GP without its posterior factors: the training data, the hyperparameter samples and the model (covariance, mean and noise functions, hyperparameter priors and bounds), but not the Cholesky factors and the other per-sample quantities that a GP prediction needs, which would make the history grow with the square of the training-set size. This method returns a copy of that record with the factors recomputed from the stored data and hyperparameters. They are a deterministic function of those, so the returned GP is the one the algorithm held at that iteration. The stored record is left without them, and a record that does carry its factors (an instance saved by a version of PyVBMC that stored complete GPs) is copied as it is.
- Parameters:
- iterationint
The iteration whose GP to return, from 0 to the last recorded iteration.
- Returns:
- gpgpyreg.GP
The Gaussian process of that iteration, ready for prediction.
- Raises:
- ValueError
If no GP has been recorded (the instance has not been optimized), or if
iterationis not the index of a recorded iteration.
- classmethod load(file, new_options=None, iteration=None, set_random_state=False)[source]#
Load a VBMC instance from a file.
Note
A saved instance holds Python bytecode: always that of the options whose value is a function (
ns_ent,k_fun_maxand the like), and that of the target function, and of the function that PyVBMC builds from the target and a separate prior, whenever they cannot be pickled by name (a lambda, a function defined in a script or inside another function). Bytecode belongs to the minor version of Python that wrote the file. Under another minor version the file can be loaded and inspected (the variational posterior, the iteration history and the evaluated points are data), but the run should not be continued or saved again there: both reach that bytecode, which can end the interpreter. To move a result between Python versions, save the variational posterior on its own (vp.save); its file holds no bytecode.Loading a run made with
VBMC(target)from aPyMCTargetchecks the arrays that the target could not copy and evaluates its log density once, at the starting point, against the value recorded during setup. If the target no longer matches the run, loading warns; the run’s results remain valid, andoptimizerefuses to continue the run.- Parameters:
- filepath-like
The file name or path to read from. Default file extension .pkl will be added if no extension is specified.
- new_optionsdict or None
Options to apply to the loaded run. This is useful for increasing
max_fun_evalsormax_iterbefore continuing. Names and values are checked as they are during construction. Options used only to construct a run, such asinteger_vars,gp_mean_funandwarmup, cannot be changed on load; start a new run to change them. Supplying their stored value is allowed. Changingns_gp_maxupdates GP hyperparameter sampling unless sampling has already stopped in the stable regime. Options with no effect, andnoise_sizewhen the target supplies its own noise estimate, produce the same warnings as at construction. See the changelog for compatibility details about files saved by earlier releases.- iterationint or None
The iteration at which to initialize the stored VBMC instance. Default is None, meaning initialize to the last recorded iteration.
- set_random_statebool
Whether to restore the state of the instance’s generator (
vbmc.rng) stored at the specified iteration, for reproducibility. Default False, in which casevbmc.rngcontinues from the state it had when the file was saved. Files saved beforevbmc.rngexisted only stored NumPy’s global random state; loading them withset_random_state=Truerestores it, derives a fresh generator from it (leaving the global state where it was saved), and warns. Files saved while the GP hyperparameter fit still drew from the global state also hold that state; it is ignored, so a run resumed from such a file does not replay the original exactly.
- Returns:
- vbmcVBMC
The loaded VBMC instance, initialized at the specified iteration.
- Raises:
- ValueError
If the specified
iterationis less than zero or larger than the last stored iteration; if an option name or value is invalid; or ifnew_optionstries to change an option that is fixed at construction.- NotImplementedError
If the options select a feature of MATLAB VBMC that is not ported (
noise_shaping,acq_hedge, agp_hyp_samplerother than"slicesample", an acquisition function that asks for the MCMC step of the importance sampler).- OSError
If the file cannot be found, or cannot be opened for other reasons.
- optimize()[source]#
Run inference on an initialized
VBMCobject.VBMC computes a variational approximation of the full posterior and the ELBO (evidence lower bound), a lower bound on the log normalization constant (log marginal likelihood or log model evidence) for the provided unnormalized log posterior.
- Returns:
- vpVariationalPosterior
The
VariationalPosteriorcomputed by VBMC.- resultsdict
A dictionary with the following information about the run:
"success_flag"(bool): whether the returned posterior was marked stable."message"(str): reason the optimization stopped."elbo"(float): estimated evidence lower bound for the returned posterior."elbo_sd"(float): estimated standard deviation of the ELBO."iterations"(int): number of iterations performed."func_count"(int): number of fresh target evaluations, counting each row of a vectorized call."best_iter"(int): zero-based index of the iteration whose posterior was selected for return, before any final boost."train_set_size"(int): number of observations represented in that iteration’s training set; pooled repeated observations are counted separately."components"(int): number of mixture components in the returned posterior."r_index"(float): reliability index atbest_iter."convergence_status"(str):"probable"if the selected iteration was stable, otherwise"no"."problem_type"(str):"bounded"if any hard bound is finite, otherwise"unconstrained"."function"(str): string representation of the function logged by VBMC."algorithm"(str): algorithm name."performance_calibration"(dict): serialized calibration profile used by the run."version"(strorNone): installed PyVBMC version, orNoneif package metadata is unavailable."rng_state"(dict): state ofvbmc.rngafter the run."overhead"(float): reserved field, currentlynp.nan.
When at least one precomputed observation was supplied, the dictionary also contains
"precomputed_observations"(int), the number supplied, and"precomputed_locations"(int), the number of distinct input points among them.When
initialization_costis positive, it also contains"evaluation_budget"(dict), with these entries:"unit"(str):"function_equivalent_evaluations"."limit"(intorfloat): the totalmax_fun_evalsallowance, possiblynp.inf."initialization"(int): the initialization charge."new_calls"(int): fresh target evaluations, equal tofunc_count."used"(int): initialization charge plus fresh target evaluations.
- Raises:
- ValueError
If no fresh function-equivalent budget remains for the run.
- RuntimeError
If the
PyMCTargetof a run made withVBMC(target)no longer matches the run: an array that the model reads inside the body of apytensor.scanloop or of anOpFromGraphwas changed in place, or, for a loaded run, the target no longer computed the log density recorded during setup. Restoring the array lets a run that was not loaded continue; otherwise, build a new target and run to fit the changed data.
Notes
Every inference draw of the run comes from
vbmc.rng(see theseedparameter); NumPy’s global random state is neither read nor written. Optional startup tips use a separate private random stream.
- save(file, overwrite=False)[source]#
Save the VBMC instance to a file.
Note
A saved instance holds Python bytecode: always that of the options whose value is a function (
ns_ent,k_fun_maxand the like), and that of the target function, and of the function that PyVBMC builds from the target and a separate prior, whenever they cannot be pickled by name (a lambda, a function defined in a script or inside another function). Bytecode belongs to the minor version of Python that wrote the file. Under another minor version the file can be loaded and inspected (the variational posterior, the iteration history and the evaluated points are data), but the run should not be continued or saved again there: both reach that bytecode, which can end the interpreter. To move a result between Python versions, save the variational posterior on its own (vp.save); its file holds no bytecode.Saving a run made with
VBMC(target)whosePyMCTargetno longer matches the run (an array that the model reads inside the body of apytensor.scanloop or of anOpFromGraphwas changed in place, or loading marked the run) warns: the file records the target as it is, andoptimizerefuses to continue the run once it is loaded.- Parameters:
- filepath-like
The file name or path to write to. Default file extension .pkl will be added if no extension is specified.
- overwritebool
Whether to allow overwriting existing files. Default False.
- Raises:
- FileExistsError
If the file already exists and
overwriteis False.- OSError
If the file cannot be opened for other reasons (e.g., the directory is not found, the disk is full, etc.).
- property target#
PyMC target supplied directly at construction, if any.
The lower-level functions used by the algorithm are listed under VBMC algorithm helpers.