a
    ;ghb|                     @   sd  d Z ddlmZ ddlZddlZddlmZ ddlmZ	 ddl
Z
ddlmZ ddlmZmZmZmZmZ ddlmZmZ g dZG d	d
 d
eZG dd deZG dd deZG dd deZG dd deZG dd deZG dd deZG dd deZG dd deZG dd deZ G dd deZ!G dd  d eZ"G d!d" d"eZ#G d#d$ d$eZ$dS )%u  
This provides a small set of effect handlers in NumPyro that are modeled
after Pyro's `poutine <http://docs.pyro.ai/en/stable/poutine.html>`_ module.
For a tutorial on effect handlers more generally, readers are encouraged to
read `Poutine: A Guide to Programming with Effect Handlers in Pyro
<http://pyro.ai/examples/effect_handlers.html>`_. These simple effect handlers
can be composed together or new ones added to enable implementation of custom
inference utilities and algorithms.

**Example**

As an example, we are using :class:`~numpyro.handlers.seed`, :class:`~numpyro.handlers.trace`
and :class:`~numpyro.handlers.substitute` handlers to define the `log_likelihood` function below.
We first create a logistic regression model and sample from the posterior distribution over
the regression parameters using :func:`~numpyro.infer.MCMC`. The `log_likelihood` function
uses effect handlers to run the model by substituting sample sites with values from the posterior
distribution and computes the log density for a single data point. The `log_predictive_density`
function computes the log likelihood for each draw from the joint posterior and aggregates the
results for all the data points, but does so by using JAX's auto-vectorize transform called
`vmap` so that we do not need to loop over all the data points.



.. doctest::

   >>> import jax.numpy as jnp
   >>> from jax import random, vmap
   >>> from jax.scipy.special import logsumexp
   >>> import numpyro
   >>> import numpyro.distributions as dist
   >>> from numpyro import handlers
   >>> from numpyro.infer import MCMC, NUTS

   >>> N, D = 3000, 3
   >>> def logistic_regression(data, labels):
   ...     coefs = numpyro.sample('coefs', dist.Normal(jnp.zeros(D), jnp.ones(D)))
   ...     intercept = numpyro.sample('intercept', dist.Normal(0., 10.))
   ...     logits = jnp.sum(coefs * data + intercept, axis=-1)
   ...     return numpyro.sample('obs', dist.Bernoulli(logits=logits), obs=labels)

   >>> data = random.normal(random.PRNGKey(0), (N, D))
   >>> true_coefs = jnp.arange(1., D + 1.)
   >>> logits = jnp.sum(true_coefs * data, axis=-1)
   >>> labels = dist.Bernoulli(logits=logits).sample(random.PRNGKey(1))

   >>> num_warmup, num_samples = 1000, 1000
   >>> mcmc = MCMC(NUTS(model=logistic_regression), num_warmup=num_warmup, num_samples=num_samples)
   >>> mcmc.run(random.PRNGKey(2), data, labels)  # doctest: +SKIP
   sample: 100%|██████████| 1000/1000 [00:00<00:00, 1252.39it/s, 1 steps of size 5.83e-01. acc. prob=0.85]
   >>> mcmc.print_summary()  # doctest: +SKIP


                      mean         sd       5.5%      94.5%      n_eff       Rhat
       coefs[0]       0.96       0.07       0.85       1.07     455.35       1.01
       coefs[1]       2.05       0.09       1.91       2.20     332.00       1.01
       coefs[2]       3.18       0.13       2.96       3.37     320.27       1.00
      intercept      -0.03       0.02      -0.06       0.00     402.53       1.00

   >>> def log_likelihood(rng_key, params, model, *args, **kwargs):
   ...     model = handlers.substitute(handlers.seed(model, rng_key), params)
   ...     model_trace = handlers.trace(model).get_trace(*args, **kwargs)
   ...     obs_node = model_trace['obs']
   ...     return obs_node['fn'].log_prob(obs_node['value'])

   >>> def log_predictive_density(rng_key, params, model, *args, **kwargs):
   ...     n = list(params.values())[0].shape[0]
   ...     log_lk_fn = vmap(lambda rng_key, params: log_likelihood(rng_key, params, model, *args, **kwargs))
   ...     log_lk_vals = log_lk_fn(random.split(rng_key, n), params)
   ...     return jnp.sum(logsumexp(log_lk_vals, 0) - jnp.log(n))

   >>> print(log_predictive_density(random.PRNGKey(2), mcmc.get_samples(),
   ...       logistic_regression, data, labels))  # doctest: +SKIP
   -874.89813
    )OrderedDictN)random)	COERCIONS)_PYRO_STACKCondIndepStackFrame	Messengerapply_stackplate)find_stack_levelnot_jax_tracer)blockcollapse	conditioninfer_configliftmaskreparamreplayscalescopeseed
substitutetracedoc                       s0   e Zd ZdZ fddZdd Zdd Z  ZS )r   a  
    Returns a handler that records the inputs and outputs at primitive calls
    inside `fn`.

    **Example:**

    .. doctest::

       >>> from jax import random
       >>> import numpyro
       >>> import numpyro.distributions as dist
       >>> from numpyro.handlers import seed, trace
       >>> import pprint as pp

       >>> def model():
       ...     numpyro.sample('a', dist.Normal(0., 1.))

       >>> exec_trace = trace(seed(model, random.PRNGKey(0))).get_trace()
       >>> pp.pprint(exec_trace)  # doctest: +SKIP
       OrderedDict([('a',
                     {'args': (),
                      'fn': <numpyro.distributions.continuous.Normal object at 0x7f9e689b1eb8>,
                      'is_observed': False,
                      'kwargs': {'rng_key': DeviceArray([0, 0], dtype=uint32)},
                      'name': 'a',
                      'type': 'sample',
                      'value': DeviceArray(-0.20584235, dtype=float32)})])
    c                    s   t t|   t | _| jS N)superr   	__enter__r   self	__class__ `/mounts/lovelace/software/anaconda3/envs/metaDMG/lib/python3.9/site-packages/numpyro/handlers.pyr      s    ztrace.__enter__c                 C   sN   d|vrd S |d dv r8|d | j v r8J d|d | | j |d < d S )Nnametype)sampledeterministicz8all sites must have unique names but got `{}` duplicated)r   formatcopyr   msgr!   r!   r"   postprocess_message   s    
ztrace.postprocess_messagec                 O   s   | |i | | j S )z
        Run the wrapped callable and return the recorded trace.

        :param `*args`: arguments to the callable.
        :param `**kwargs`: keyword arguments to the callable.
        :return: `OrderedDict` containing the execution trace.
        )r   r   argskwargsr!   r!   r"   	get_trace   s    ztrace.get_trace)__name__
__module____qualname____doc__r   r+   r/   __classcell__r!   r!   r   r"   r   t   s   r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )r   a  
    Given a callable `fn` and an execution trace `trace`,
    return a callable which substitutes `sample` calls in `fn` with
    values from the corresponding site names in `trace`.

    :param fn: Python callable with NumPyro primitives.
    :param trace: an OrderedDict containing execution metadata.

    **Example:**

    .. doctest::

       >>> from jax import random
       >>> import numpyro
       >>> import numpyro.distributions as dist
       >>> from numpyro.handlers import replay, seed, trace

       >>> def model():
       ...     numpyro.sample('a', dist.Normal(0., 1.))

       >>> exec_trace = trace(seed(model, random.PRNGKey(0))).get_trace()
       >>> print(exec_trace['a']['value'])  # doctest: +SKIP
       -0.20584235
       >>> replayed_trace = trace(replay(model, exec_trace)).get_trace()
       >>> print(exec_trace['a']['value'])  # doctest: +SKIP
       -0.20584235
       >>> assert replayed_trace['a']['value'] == exec_trace['a']['value']
    Nc                    s&   |d usJ || _ tt| | d S r   )r   r   r   __init__)r   fnr   r   r!   r"   r5      s    zreplay.__init__c                 C   s   |d dv r|d | j v r|d }|d dv r|| j v r| j | }|d dkrz|d dkrjtd| d|d |d< d S |d rd S |d d	ks|d rtd| d
|d |d< |d |d< d S )Nr$   )r%   r	   r#   r	   zSite z must be a plate in trace.valueis_observedr%   z must be sampled in trace.infer)r   RuntimeError)r   r*   r#   Z	guide_msgr!   r!   r"   process_message   s    
zreplay.process_message)NNr0   r1   r2   r3   r5   r;   r4   r!   r!   r   r"   r      s   r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )r   a  
    Given a callable `fn`, return another callable that selectively hides
    primitive sites  where `hide_fn` returns True from other effect handlers
    on the stack.

    :param callable fn: Python callable with NumPyro primitives.
    :param callable hide_fn: function which when given a dictionary containing
        site-level metadata returns whether it should be blocked.
    :param list hide: list of site names to hide.
    :param list expose_types: list of site types to expose, e.g. `['param']`.

    **Example:**

    .. doctest::

       >>> from jax import random
       >>> import numpyro
       >>> from numpyro.handlers import block, seed, trace
       >>> import numpyro.distributions as dist

       >>> def model():
       ...     a = numpyro.sample('a', dist.Normal(0., 1.))
       ...     return numpyro.sample('b', dist.Normal(a, 1.))

       >>> model = seed(model, random.PRNGKey(0))
       >>> block_all = block(model)
       >>> block_a = block(model, lambda site: site['name'] == 'a')
       >>> trace_block_all = trace(block_all).get_trace()
       >>> assert not {'a', 'b'}.intersection(trace_block_all.keys())
       >>> trace_block_a =  trace(block_a).get_trace()
       >>> assert 'a' not in trace_block_a
       >>> assert 'b' in trace_block_a
    Nc                    s^   |d ur|| _ n:d ur(fdd| _ n" d ur@ fdd| _ n
dd | _ tt| | d S )Nc                    s   |  d v S )Nr#   getr*   )hider!   r"   <lambda>      z block.__init__.<locals>.<lambda>c                    s   |  d vS )Nr$   r=   r?   )expose_typesr!   r"   rA     rB   c                 S   s   dS )NTr!   r?   r!   r!   r"   rA     rB   )hide_fnr   r   r5   )r   r6   rD   r@   rC   r   )rC   r@   r"   r5     s    
zblock.__init__c                 C   s   |  |rd|d< d S )NTstop)rD   r)   r!   r!   r"   r;     s    
zblock.process_message)NNNNr<   r!   r!   r   r"   r      s   "r   c                       sD   e Zd ZdZdZ fddZdd Z fddZ fd	d
Z  Z	S )r   a4  
    EXPERIMENTAL Collapses all sites in the context by lazily sampling and
    attempting to use conjugacy relations. If no conjugacy is known this will
    fail. Code using the results of sample sites must be written to accept
    Funsors rather than Tensors. This requires ``funsor`` to be installed.
    Nc                    sH   t jd u r2dd l}ddlm} |d |dt _t j|i | d S )Nr   )CoerceDistributionToFunsorjax)r   _coercefunsorZfunsor.distributionrF   Zset_backendr   r5   )r   r-   r.   rI   rF   r   r!   r"   r5   !  s    


zcollapse.__init__c                 C   s\   ddl m} |d dkrX|d d u r0|d |d< t|d |sPt|d t|frXd|d	< d S )
Nr   )Funsorr$   r%   r7   r#   r6   TrE   )Zfunsor.termsrJ   
isinstancestr)r   r*   rJ   r!   r!   r"   r;   *  s     zcollapse.process_messagec                    s*   t dd tD | _t| j t  S )Nc                 s   s   | ]}t |tr|jV  qd S r   )rK   r	   r#   ).0hr!   r!   r"   	<genexpr>5  s   z%collapse.__enter__.<locals>.<genexpr>)	frozensetr   preserved_platesr   appendrH   r   r   r   r   r!   r"   r   4  s
    
zcollapse.__enter__c                    sN  dd l }t }|| ju sJ t ||| |d ur:d S g }g }t }| j D ]\}	}
|
d dkrhqR|
d sz|	|	 dd |
d D }|
|
d |j|}|
d	 }t|ts|
|
d	 |jd	 |}|	||d
 |tdd |
d D O }qR|sJ d|| j }|jj|jj|jj|t||B |d}|d }	t|	|j d S )Nr   r$   r%   r8   c                 S   s   i | ]}|j |jqS r!   )dimr#   rM   fr!   r!   r"   
<dictcomp>N  rB   z%collapse.__exit__.<locals>.<dictcomp>cond_indep_stackr6   r7   )r7   c                 s   s   | ]}|j V  qd S r   )r#   rT   r!   r!   r"   rO   T  rB   z$collapse.__exit__.<locals>.<genexpr>znothing to collapse)Z	eliminateplates)rI   r   poprH   r   __exit__rP   r   itemsrR   Z	to_funsorRealrK   rL   inputsrQ   Zsum_productopsZ	logaddexpaddnumpyrofactordata)r   exc_type	exc_value	tracebackrI   rH   Zreduced_varsZlog_prob_termsrX   r#   siteZdim_to_namer6   r7   Zreduced_platesZlog_probr   r!   r"   rZ   ;  s@    



zcollapse.__exit__)
r0   r1   r2   r3   rH   r5   r;   r   rZ   r4   r!   r!   r   r"   r     s   	
r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )r   a"  
    Conditions unobserved sample sites to values from `data` or `condition_fn`.
    Similar to :class:`~numpyro.handlers.substitute` except that it only affects
    `sample` sites and changes the `is_observed` property to `True`.

    :param fn: Python callable with NumPyro primitives.
    :param dict data: dictionary of `numpy.ndarray` values keyed by
       site names.
    :param condition_fn: callable that takes in a site dict and returns
       a numpy array or `None` (in which case the handler has no side
       effect).

    **Example:**

    .. doctest::

       >>> from jax import random
       >>> import numpyro
       >>> from numpyro.handlers import condition, seed, substitute, trace
       >>> import numpyro.distributions as dist

       >>> def model():
       ...     numpyro.sample('a', dist.Normal(0., 1.))

       >>> model = seed(model, random.PRNGKey(0))
       >>> exec_trace = trace(condition(model, {'a': -1})).get_trace()
       >>> assert exec_trace['a']['value'] == -1
       >>> assert exec_trace['a']['is_observed']
    Nc                    sB   || _ || _tdd ||fD dkr.tdtt| | d S )Nc                 s   s   | ]}|d uV  qd S r   r!   rM   xr!   r!   r"   rO     rB   z%condition.__init__.<locals>.<genexpr>   z8Only one of `data` or `condition_fn` should be provided.)condition_fnrb   sum
ValueErrorr   r   r5   )r   r6   rb   rj   r   r!   r"   r5     s    zcondition.__init__c                 C   s   |d dks| ddrl|d dkrh| jd urF|d d d| jf | jd urh|d d d| jf d S | jd ur| j |d	 }n
| |}|d ur||d
< d|d< d S )Nr$   r%   _control_flow_doneFcontrol_flowr.   substitute_stackr   r#   r7   Tr8   )r>   rb   rR   rj   r   r*   r7   r!   r!   r"   r;     s    



zcondition.process_message)NNNr<   r!   r!   r   r"   r   b  s   	r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )r   a  
    Given a callable `fn` that contains NumPyro primitive calls
    and a callable `config_fn` taking a trace site and returning a dictionary,
    updates the value of the infer kwarg at a sample site to config_fn(site).

    :param fn: a stochastic function (callable containing NumPyro primitive calls)
    :param config_fn: a callable taking a site and returning an infer dict
    Nc                    s   t  | || _d S r   )r   r5   	config_fn)r   r6   rq   r   r!   r"   r5     s    zinfer_config.__init__c                 C   s$   |d dv r |d  | | d S )Nr$   )r%   r9   )updaterq   r)   r!   r!   r"   r;     s    zinfer_config.process_message)NNr<   r!   r!   r   r"   r     s   	r   c                       sB   e Zd ZdZd fdd	Z fddZ fddZd	d
 Z  ZS )r   a-  
    Given a stochastic function with ``param`` calls and a prior distribution,
    create a stochastic function where all param calls are replaced by sampling from prior.
    Prior should be a distribution or a dict of names to distributions.

    Consider the following NumPyro program:

        >>> import numpyro
        >>> import numpyro.distributions as dist
        >>> from numpyro.handlers import lift
        >>>
        >>> def model(x):
        ...     s = numpyro.param("s", 0.5)
        ...     z = numpyro.sample("z", dist.Normal(x, s))
        ...     return z ** 2
        >>> lifted_model = lift(model, prior={"s": dist.Exponential(0.3)})

    ``lift`` makes ``param`` statements behave like ``sample`` statements
    using the distributions in ``prior``.  In this example, site `s` will now behave
    as if it was replaced with ``s = numpyro.sample("s", dist.Exponential(0.3))``.

    :param fn: function whose parameters will be lifted to random values
    :param prior: prior function in the form of a Distribution or a dict of Distributions
    Nc                    s   t  | || _i | _d S r   )r   r5   prior_samples_cache)r   r6   rs   r   r!   r"   r5     s    zlift.__init__c                    s   i | _ t  S r   )rt   r   r   r   r   r!   r"   r     s    zlift.__enter__c                    s   i | _ t j|i |S r   )rt   r   rZ   r,   r   r!   r"   rZ     s    zlift.__exit__c                 C   s   |d dkrd S |d }t | jtr0| j|n| j}t |tjjrd|d< ||d< d|d< |d d	d |d d
dd|d< g |d< |di |d< nd S || jv r| j| d |d< d|d< d|d< n|| j|< d|d< d S )Nr$   paramr#   r%   r6   r!   r-   r.   rng_keysample_shape)rv   rw   Zintermediatesr9   r7   Tr8   rE   F)rK   rs   dictr>   r`   distributionsDistributionrt   )r   r*   r#   r6   r!   r!   r"   r;     s(    



zlift.process_message)NN)	r0   r1   r2   r3   r5   r   rZ   r;   r4   r!   r!   r   r"   r     s
   r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )	r   z
    This messenger masks out some of the sample statements elementwise.

    :param mask: a boolean or a boolean-valued array for masking elementwise log
        probability of sample sites (`True` includes a site, `False` excludes a site).
    NTc                    s,   t |dkrtd|| _t | d S )Nboolz`mask` should be a bool array.)jnpZresult_typerl   r   r   r5   )r   r6   r   r   r!   r"   r5      s    zmask.__init__c                 C   sX   |d dkr@|d dkr<|d d u r*| j n| j |d @ |d< d S |d  | j |d< d S )Nr$   r%   inspectr   r6   )r   r)   r!   r!   r"   r;     s    zmask.process_message)NTr<   r!   r!   r   r"   r     s   r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )r   a  
    Reparametrizes each affected sample site into one or more auxiliary sample
    sites followed by a deterministic transformation [1].

    To specify reparameterizers, pass a ``config`` dict or callable to the
    constructor.  See the :mod:`numpyro.infer.reparam` module for available
    reparameterizers.

    Note some reparameterizers can examine the ``*args,**kwargs`` inputs of
    functions they affect; these reparameterizers require using
    ``handlers.reparam`` as a decorator rather than as a context manager.

    [1] Maria I. Gorinova, Dave Moore, Matthew D. Hoffman (2019)
        "Automatic Reparameterisation of Probabilistic Programs"
        https://arxiv.org/pdf/1906.03028.pdf

    :param config: Configuration, either a dict mapping site name to
        :class:`~numpyro.infer.reparam.Reparam` ,
        or a function mapping site to
        :class:`~numpyro.infer.reparam.Reparam` or None.
    :type config: dict or callable
    Nc                    s,   t |tst|sJ || _t | d S r   )rK   rx   callableconfigr   r5   )r   r6   r   r   r!   r"   r5   )  s    zreparam.__init__c                 C   s   |d dkrd S t | jtr.| j|d }n
| |}|d u rDd S ||d |d |d \}}|d ur|d u rd|d< ||d< t| D ]}|dvr||= qd S |d d u rd|d	< ||d< ||d< d S )
Nr$   r%   r#   r6   r7   r&   )r$   r#   r7   Tr8   )rK   r   rx   r>   listkeys)r   r*   r   Znew_fnr7   keyr!   r!   r"   r;   .  s(    
zreparam.process_message)NNr<   r!   r!   r   r"   r     s   r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )	r   an  
    This messenger rescales the log probability score.

    This is typically used for data subsampling or for stratified sampling of data
    (e.g. in fraud detection where negatives vastly outnumber positives).

    :param scale: a positive scaling factor that is broadcastable to the shape
        of log probability.
    :type scale: float or numpy.ndarray
    N      ?c                    s8   t |r"tt|dr"td|| _t | d S )Nr   z$'scale' argument should be positive.)r   npanyZ
less_equalrl   r   r   r5   )r   r6   r   r   r!   r"   r5   V  s
    zscale.__init__c                 C   s:   |d dvrd S | dd u r$| jn| j|d  |d< d S )Nr$   )ru   r%   r	   r   )r>   r   r)   r!   r!   r"   r;   ]  s     zscale.process_message)Nr   r<   r!   r!   r   r"   r   J  s   r   c                       s0   e Zd ZdZd
dd fddZdd	 Z  ZS )r   a-  
    This handler prepend a prefix followed by a divider to the name of sample sites.

    **Example:**

    .. doctest::

        >>> import numpyro
        >>> import numpyro.distributions as dist
        >>> from numpyro.handlers import scope, seed, trace

        >>> def model():
        ...     with scope(prefix="a"):
        ...         with scope(prefix="b", divider="."):
        ...             return numpyro.sample("x", dist.Bernoulli(0.5))
        ...
        >>> assert "a/b.x" in trace(seed(model, 0)).get_trace()

    :param fn: Python callable with NumPyro primitives.
    :param str prefix: a string to prepend to sample names
    :param str divider: a string to join the prefix and sample name; default to `'/'`
    :param list hide_types: an optional list of side types to skip renaming.
    N /)
hide_typesc                   s.   || _ || _|d u rg n|| _t | d S r   )prefixdividerr   r   r5   )r   r6   r   r   r   r   r!   r"   r5     s    zscope.__init__c                    sf   | dr4|d  jvr4 j  j |d  |d< | drbd jvrb fdd|d D |d< d S )Nr#   r$   rW   r	   c                    s.   g | ]&}t  j  j |j |j|jqS r!   )r   r   r   r#   rS   size)rM   ir   r!   r"   
<listcomp>  s   z)scope.process_message.<locals>.<listcomp>)r>   r   r   r   r)   r!   r   r"   r;     s    
zscope.process_message)Nr   r   r<   r!   r!   r   r"   r   f  s   r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )r   a  
    JAX uses a functional pseudo random number generator that requires passing
    in a seed :func:`~jax.random.PRNGKey` to every stochastic function. The
    `seed` handler allows us to initially seed a stochastic function with a
    :func:`~jax.random.PRNGKey`. Every call to the :func:`~numpyro.handlers.sample`
    primitive inside the function results in a splitting of this initial seed
    so that we use a fresh seed for each subsequent call without having to
    explicitly pass in a `PRNGKey` to each `sample` call.

    :param fn: Python callable with NumPyro primitives.
    :param rng_seed: a random number generator seed.
    :type rng_seed: int, jnp.ndarray scalar, or jax.random.PRNGKey

    .. note::

        Unlike in Pyro, `numpyro.sample` primitive cannot be used without wrapping
        it in seed handler since there is no global random state. As such,
        users need to use `seed` as a contextmanager to generate samples from
        distributions or as a decorator for their model callable (See below).

    **Example:**

    .. doctest::

       >>> from jax import random
       >>> import numpyro
       >>> import numpyro.handlers
       >>> import numpyro.distributions as dist

       >>> # as context manager
       >>> with handlers.seed(rng_seed=1):
       ...     x = numpyro.sample('x', dist.Normal(0., 1.))

       >>> def model():
       ...     return numpyro.sample('y', dist.Normal(0., 1.))

       >>> # as function decorator (/modifier)
       >>> y = handlers.seed(model, rng_seed=1)()
       >>> assert x == y
    Nc                    s   t |ts&t |tjtjfr0t|s0t|}t |tjtjfrX|jtj	krX|jdksjt
dt||| _tt| | d S )N)   zIncorrect type for rng_seed: {})rK   intr   Zndarrayr|   shaper   ZPRNGKeyZdtypeZuint32	TypeErrorr'   r$   rv   r   r   r5   )r   r6   Zrng_seedr   r!   r"   r5     s    


zseed.__init__c                 C   sb   |d dkr$|d s$|d d d u s0|d dv r^|d d ur@d S t | j\| _}||d d< d S )Nr$   r%   r8   r.   rv   )Zprng_keyr	   rn   r7   )r   splitrv   )r   r*   Zrng_key_sampler!   r!   r"   r;     s    

zseed.process_message)NNr<   r!   r!   r   r"   r     s   )r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )r   a  
    Given a callable `fn` and a dict `data` keyed by site names
    (alternatively, a callable `substitute_fn`), return a callable
    which substitutes all primitive calls in `fn` with values from
    `data` whose key matches the site name. If the site name
    is not present in `data`, there is no side effect.

    If a `substitute_fn` is provided, then the value at the site is
    replaced by the value returned from the call to `substitute_fn`
    for the given site.

    .. note:: This handler is mainly used for internal algorithms.
        For conditioning a generative model on observed data, please
        use the :class:`condition` handler.

    :param fn: Python callable with NumPyro primitives.
    :param dict data: dictionary of `numpy.ndarray` values keyed by
        site names.
    :param substitute_fn: callable that takes in a site dict and returns
        a numpy array or `None` (in which case the handler has no side
        effect).

    **Example:**

    .. doctest::

       >>> from jax import random
       >>> import numpyro
       >>> from numpyro.handlers import seed, substitute, trace
       >>> import numpyro.distributions as dist

       >>> def model():
       ...     numpyro.sample('a', dist.Normal(0., 1.))

       >>> model = seed(model, random.PRNGKey(0))
       >>> exec_trace = trace(substitute(model, {'a': -1})).get_trace()
       >>> assert exec_trace['a']['value'] == -1
    Nc                    sB   || _ || _tdd ||fD dkr.tdtt| | d S )Nc                 s   s   | ]}|d uV  qd S r   r!   rg   r!   r!   r"   rO     rB   z&substitute.__init__.<locals>.<genexpr>ri   z9Only one of `data` or `substitute_fn` should be provided.)substitute_fnrb   rk   rl   r   r   r5   )r   r6   rb   r   r   r!   r"   r5     s    zsubstitute.__init__c                 C   s   |d dvs| ddrl|d dkrh| jd urF|d d d| jf | jd urh|d d d| jf d S | jd ur| j |d	 }n
| |}|d ur||d
< d S )Nr$   )r%   ru   Zmutabler	   rm   Frn   r.   ro   r   r#   r7   )r>   rb   rR   r   rp   r!   r!   r"   r;     s     



zsubstitute.process_message)NNNr<   r!   r!   r   r"   r     s   '	r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )r   a  
    Given a stochastic function with some sample statements and a dictionary
    of values at names, set the return values of those sites equal to the values
    as if they were hard-coded to those values and introduce fresh sample sites
    with the same names whose values do not propagate.

    Composes freely with :func:`~numpyro.handlers.condition` to represent
    counterfactual distributions over potential outcomes. See Single World
    Intervention Graphs [1] for additional details and theory.

    This is equivalent to replacing `z = numpyro.sample("z", ...)` with `z = 1.`
    and introducing a fresh sample site `numpyro.sample("z", ...)` whose value is
    not used elsewhere.

    **References:**

    1. *Single World Intervention Graphs: A Primer*,
       Thomas Richardson, James Robins

    :param fn: a stochastic function (callable containing Pyro primitive calls)
    :param data: a ``dict`` mapping sample site names to interventions

    **Example:**

    .. doctest::

      >>> import jax.numpy as jnp
      >>> import numpyro
      >>> from numpyro.handlers import do, trace, seed
      >>> import numpyro.distributions as dist
      >>> def model(x):
      ...     s = numpyro.sample("s", dist.LogNormal())
      ...     z = numpyro.sample("z", dist.Normal(x, s))
      ...     return z ** 2
      >>> intervened_model = handlers.do(model, data={"z": 1.})
      >>> with trace() as exec_trace:
      ...     z_square = seed(intervened_model, 0)(1)
      >>> assert exec_trace['z']['value'] != 1.
      >>> assert not exec_trace['z']['is_observed']
      >>> assert not exec_trace['z'].get('stop', None)
      >>> assert z_square == 1
    Nc                    s(   || _ tt| | _tt| | d S r   )rb   rL   id_intervener_idr   r   r5   )r   r6   rb   r   r!   r"   r5   J  s    zdo.__init__c                 C   s   |d dkrd S | dd | jkr| j |d d ur| dd d urbtjd|d tt d | j|d< | }t	| | j |d }|d d |d< ||d< d	|d
< d	|d< d S )Nr$   r%   r   r#   zaAttempting to intervene on variable {} multiple times,this is almost certainly incorrect behavior)
stacklevelZ__CFr7   Tr8   rE   )
r>   r   rb   warningswarnr'   RuntimeWarningr
   r(   r   )r   r*   Znew_msgZinterventionr!   r!   r"   r;   O  s,    
zdo.process_message)NNr<   r!   r!   r   r"   r     s   +r   )%r3   collectionsr   r   numpyr   rG   r   Z	jax.numpyr|   r`   Z"numpyro.distributions.distributionr   Znumpyro.primitivesr   r   r   r   r	   Znumpyro.utilr
   r   __all__r   r   r   r   r   r   r   r   r   r   r   r   r   r   r!   r!   r!   r"   <module>   s0   K;53K=F9,EG