"""
Borch
=====

Borch is a universal probabilistic programing language built on top of pytorch and is
used for probabilistic modeling.

It is designed for fast experimentation and research with probabilistic models. With key
focus on beeing very flexible and expressive withouth sacreficing the usabiilty. Thus it
can be used for a wide range of models from classical hierarchical models on small data
sets to complex deep probabilistic models on large data sets.

borch separates model specification(borch) and inference(infer) into two seperate
packages for maximaple flexability and usability. However borch provides a clean
interface to use in combination with the `infer'.

Examples:
    >>> from borch.utils import torch_utils
    >>> from borch.ppl.utils import yield_named_opt_param
    >>> from borch import ppl, infer
    >>> from borch.optimizer_collection import OptimizersCollection
    >>> import borch.ppl.distributions as dist
    >>> import numpy as np
    >>> import torch
    >>> from torch import optim

    Generate random data


    >>> def generate_dataset(n=100):
    ...     x = np.linspace(0, 10, n)
    ...     y = 2*x+4+np.random.normal(0, 4, n)
    ...     return (torch.tensor(y, dtype=torch.float32),
    ... torch.tensor(x, dtype=torch.float32))
    >>> y, x = generate_dataset(100)

    Defining a linear regression model


    >>> def forward(bm, x):
    ...     bm.b = ppl.RandomVariable(dist.Normal(0, 3))
    ...     bm.a = ppl.RandomVariable(dist.Normal(0, 3))
    ...     bm.sigma = ppl.RandomVariable(dist.LogNormal(1, 1))
    ...     mu = bm.b * x + bm.a
    ...     bm.y = ppl.RandomVariable(dist.Normal(mu, bm.sigma))
    ...     return bm.y
    >>> model = ppl.Module()
    >>> model.observe(y=y)
    >>> optimizer = OptimizersCollection(optimizer=optim.Adam, lr=0.01,
    ...     amsgrad=True)

    Training loop



    >>> for e in range(2):
    ...     model.sample()
    ...     yhat = forward(model, x)
    ...     loss = infer.vi_loss(**model.pq_to_infer())
    ...     loss.backward()
    ...     optimizer.step(model.opt_parameters())
    ...     optimizer.zero_grad()
"""

from borch.ppl.random_variable import RV, RandomVariable
from borch.ppl.transformed_parameter import TP, TransformedParameter
from borch.ppl.module import Module

from borch.ppl import utils, distributions, guide
from borch.ppl.rv_metrics import all_metrics, mean_squared_error, accuracy
import borch.ppl.rv_metrics
import borch.ppl.uncertainty_measurements
