class_docstr: EXPERIMENTAL This is not a :class:`~pyro.distributions.Distribution`, but
  acts as a transposed version of :class:`CoalescentTimesWithRate` making the
  elements of ``rate_grid`` independent and thus compatible with ``plate``
  and ``poutine.markov``. For non-batched inputs the following are all
  equivalent likelihoods::
  
      # Version 1.
      pyro.sample("coalescent",
                  CoalescentTimesWithRate(leaf_times, rate_grid),
                  obs=coal_times)
  
      # Version 2. using pyro.plate
      likelihood = CoalescentRateLikelihood(leaf_times, coal_times, len(rate_grid))
      with pyro.plate("time", len(rate_grid)):
          pyro.factor("coalescent", likelihood(rate_grid))
  
      # Version 3. using pyro.markov
      likelihood = CoalescentRateLikelihood(leaf_times, coal_times, len(rate_grid))
      for t in pyro.markov(range(len(rate_grid))):
          pyro.factor("coalescent_{}".format(t), likelihood(rate_grid[t], t))
  
  The third version is useful for e.g.
  :class:`~pyro.infer.smcfilter.SMCFilter` where ``rate_grid`` might be
  computed sequentially.
  
  :param torch.Tensor leaf_times: Tensor of times of sampling events, i.e.
      leaf nodes in the phylogeny. These can be arbitrary real numbers with
      arbitrary order and duplicates.
  :param torch.Tensor coal_times: A tensor of coalescent times. These denote
      sets of size ``leaf_times.size(-1) - 1`` along the trailing dimension
      and should be sorted along that dimension.
  :param int duration: Size of the rate grid, ``rate_grid.size(-1)``.
class_docstr_tok: ["EXPERIMENTAL","This","is","not","a",":","class",":","`","~pyro.distributions.Distribution","`",",","but","acts","as","a","transposed","version","of",":","class",":","`","CoalescentTimesWithRate","`","making","the","elements","of","``","rate_grid","``","independent","and","thus","compatible","with","``","plate","``","and","``","poutine.markov","``",".","For","non-batched","inputs","the","following","are","all","equivalent","likelihoods",":",":","#","Version","1.","pyro.sample","(","``","coalescent","''",",","CoalescentTimesWithRate","(","leaf_times",",","rate_grid",")",",","obs=coal_times",")","#","Version","2.","using","pyro.plate","likelihood","=","CoalescentRateLikelihood","(","leaf_times",",","coal_times",",","len","(","rate_grid",")",")","with","pyro.plate","(","``","time","''",",","len","(","rate_grid",")",")",":","pyro.factor","(","``","coalescent","''",",","likelihood","(","rate_grid",")",")","#","Version","3.","using","pyro.markov","likelihood","=","CoalescentRateLikelihood","(","leaf_times",",","coal_times",",","len","(","rate_grid",")",")","for","t","in","pyro.markov","(","range","(","len","(","rate_grid",")",")",")",":","pyro.factor","(","``","coalescent_","{","}","''",".format","(","t",")",",","likelihood","(","rate_grid","[","t","]",",","t",")",")","The","third","version","is","useful","for","e.g",".",":","class",":","`","~pyro.infer.smcfilter.SMCFilter","`","where","``","rate_grid","``","might","be","computed","sequentially",".",":","param","torch.Tensor","leaf_times",":","Tensor","of","times","of","sampling","events",",","i.e",".","leaf","nodes","in","the","phylogeny",".","These","can","be","arbitrary","real","numbers","with","arbitrary","order","and","duplicates",".",":","param","torch.Tensor","coal_times",":","A","tensor","of","coalescent","times",".","These","denote","sets","of","size","``","leaf_times.size","(","-1",")","-","1","``","along","the","trailing","dimension","and","should","be","sorted","along","that","dimension",".",":","param","int","duration",":","Size","of","the","rate","grid",",","``","rate_grid.size","(","-1",")","``","."]
code: def __init__(
      self, leaf_times, coal_times, duration, *, validate_args=None
  ):
      assert leaf_times.size(-1) == 1 + coal_times.size(-1)
      assert isinstance(duration, int) and duration >= 2
      if (
          validate_args is True
          or validate_args is None
          and is_validation_enabled
      ):
          constraint = CoalescentTimesConstraint(
              leaf_times, ordered=False
          )
          if not constraint.check(coal_times).all():
              raise ValueError("Invalid (leaf_times, coal_times)")
  
      phylogeny = _make_phylogeny(leaf_times, coal_times)
      batch_shape = phylogeny.times.shape[:-1]
      new_zeros = leaf_times.new_zeros
      new_ones = leaf_times.new_ones
  
      # Construct linear part from intervals of survival outside of [0,duration].
      times = phylogeny.times.clamp(max=0)
      intervals = times[..., 1:] - times[..., :-1]
      pre_linear = (phylogeny.binomial[..., :-1] * intervals).sum(
          -1, keepdim=True
      )
      times = phylogeny.times.clamp(min=duration)
      intervals = times[..., 1:] - times[..., :-1]
      post_linear = (phylogeny.binomial[..., :-1] * intervals).sum(
          -1, keepdim=True
      )
      self._linear = torch.cat(
          [
              pre_linear,
              new_zeros(pre_linear.shape[:-1] + (duration - 2,)),
              post_linear,
          ],
          dim=-1,
      )
  
      # Construct linear part from intervals of survival within [0, duration].
      times = phylogeny.times.clamp(min=0, max=duration)
      sparse_diff = (
          phylogeny.binomial[..., :-1] - phylogeny.binomial[..., 1:]
      )
      dense_diff = new_zeros(batch_shape + (1 + duration,))
      _interpolate_scatter_add_(dense_diff, times[..., 1:], sparse_diff)
      self._linear += (
          dense_diff.flip([-1]).cumsum(-1)[..., :-1].flip([-1])
      )
  
      # Construct const and log part from coalescent events.
      coal_index = (
          coal_times.floor().clamp(min=0, max=duration - 1).long()
      )
      self._const = new_zeros(batch_shape + (duration,))
      self._const.scatter_add_(
          -1, coal_index, phylogeny.coal_binomial.log()
      )
      self._log = new_zeros(batch_shape + (duration,))
      self._log.scatter_add_(-1, coal_index, new_ones(coal_index.shape))
  
imports: import functools
  import weakref
  from collections import namedtuple
  import torch
  from torch.distributions import constraints
  from pyro.distributions.util import broadcast_shape, is_validation_enabled
  from pyro.ops.special import safe_log
  from .torch_distribution import TorchDistribution
label_desc: Use image node_id 1 to create a new CoalescentRateLikelihood object  with example: obj = CoalescentRateLikelihood(leaf_times, coal_times, duration)