class_docstr: Brownian interval with fixed entropy.
  
  Computes increments (and optionally Levy area).
  
  To use:
  >>> bm = BrownianInterval(t0=0.0, t1=1.0, size=(4, 1), device='cuda')
  >>> bm(0., 0.5)
  tensor([[ 0.0733],
          [-0.5692],
          [ 0.1872],
          [-0.3889]], device='cuda:0')
class_docstr_tok: ["Brownian","interval","with","fixed","entropy",".","Computes","increments","(","and","optionally","Levy","area",")",".","To","use",":",">",">",">","bm","=","BrownianInterval","(","t0=0.0",",","t1=1.0",",","size=","(","4",",","1",")",",","device='cuda","'",")",">",">",">","bm","(","0.",",","0.5",")","tensor","(","[","[","0.0733","]",",","[","-0.5692","]",",","[","0.1872","]",",","[","-0.3889","]","]",",","device='cuda:0","'",")"]
code: def __call__(self, ta, tb=None, return_U=False, return_A=False):
      if tb is None:
          warnings.warn(
              f"{self.__class__.__name__} is optimised for interval-based queries, not point evaluation."
          )
          ta, tb = self._start, ta
          tb_name = "ta"
      else:
          tb_name = "tb"
      ta = float(ta)
      tb = float(tb)
      if ta < self._start:
          warnings.warn(
              f"Should have ta>=t0 but got ta={ta} and t0={self._start}."
          )
          ta = self._start
      if tb < self._start:
          warnings.warn(
              f"Should have {tb_name}>=t0 but got {tb_name}={tb} and t0={self._start}."
          )
          tb = self._start
      if ta > self._end:
          warnings.warn(
              f"Should have ta<=t1 but got ta={ta} and t1={self._end}."
          )
          ta = self._end
      if tb > self._end:
          warnings.warn(
              f"Should have {tb_name}<=t1 but got {tb_name}={tb} and t1={self._end}."
          )
          tb = self._end
      if ta > tb:
          raise RuntimeError(
              f"Query times ta={ta:.3f} and tb={tb:.3f} must respect ta <= tb."
          )
  
      if ta == tb:
          W = torch.zeros(
              self._size, dtype=self._dtype, device=self._device
          )
          H = None
          A = None
          if self._have_H:
              H = torch.zeros(
                  self._size, dtype=self._dtype, device=self._device
              )
          if self._have_A:
              size = (
                  *self._size,
                  *self._size[-1:],
              )  # not self._size[-1] as that may not exist
              A = torch.zeros(
                  size, dtype=self._dtype, device=self._device
              )
      else:
          if self._dt is None and not self._halfway_tree:
              self._num_evaluations += 1
              # We start off with "negative" num evaluations, to give us a small warm-up period at the start.
              if self._num_evaluations > 0:
                  # Compute average step size so far
                  dt = tb - ta
                  self._average_dt = (
                      dt
                      + self._average_dt * (self._num_evaluations - 1)
                  ) / self._num_evaluations
                  if self._average_dt < 0.5 * self._tree_dt:
                      # If 'dt' wasn't specified, then check the average interval length against the size of the
                      # bottom of the dependency tree. If we're below halfway then refine the tree by splitting all
                      # the bottom pieces into two.
                      self._create_dependency_tree(dt)
  
          # Find the intervals that correspond to the query. We start our search at the last interval we accessed in
          # the binary tree, as it's likely that the next query will come nearby.
          intervals = self._last_interval._loc(ta, tb)
          # Ideally we'd keep track of intervals[0] on the backward pass. Practically speaking len(intervals) tends to
          # be 1 or 2 almost always so this isn't a huge deal.
          self._last_interval = intervals[-1]
  
          W, H, A = intervals[0]._increment_and_levy_area()
          if len(intervals) > 1:
              # If we have multiple intervals then add up their increments and Levy areas.
  
              for interval in intervals[1:]:
                  Wi, Hi, Ai = interval._increment_and_levy_area()
                  if self._have_H:
                      # Aggregate H:
                      # Given s < u < t, then
                      # H_{s,t} = (term1 + term2) / (t - s)
                      # where
                      # term1 = (t - u) * (H_{u, t} + W_{s, u} / 2)
                      # term2 = (u - s) * (H_{s, u} - W_{u, t} / 2)
                      term1 = (interval._end - interval._start) * (
                          Hi + 0.5 * W
                      )
                      term2 = (interval._start - ta) * (H - 0.5 * Wi)
                      H = (term1 + term2) / (interval._end - ta)
                  if self._have_A and len(self._size) not in (0, 1):
                      # If len(self._size) in (0, 1) then we treat our scalar / single dimension as a batch
                      # dimension, so we have zero Levy area. (And these unsqueezes will result in a tensor of shape
                      # (batch, batch) which is wrong.)
  
                      # Let B_{x, y} = \int_x^y W^1_{s,u} dW^2_u.
                      # Then
                      # B_{s, t} = \int_s^t W^1_{s,u} dW^2_u
                      #          = \int_s^v W^1_{s,u} dW^2_u + \int_v^t W^1_{s,v} dW^2_u + \int_v^t W^1_{v,u} dW^2_u
                      #          = B_{s, v} + W^1_{s, v} W^2_{v, t} + B_{v, t}
                      #
                      # A is now the antisymmetric part of B, which gives the formula below.
                      A = (
                          A
                          + Ai
                          + 0.5
                          * (
                              W.unsqueeze(-1) * Wi.unsqueeze(-2)
                              - Wi.unsqueeze(-1) * W.unsqueeze(-2)
                          )
                      )
                  W = W + Wi
  
      U = None
      if self._have_H:
          U = _H_to_U(W, H, tb - ta)
  
      if return_U:
          if return_A:
              return W, U, A
          else:
              return W, U
      else:
          if return_A:
              return W, A
          else:
              return W
  
imports: import math
  import trampoline
  import warnings
  import numpy
  import torch
  from .None import brownian_base
  from ..settings import LEVY_AREA_APPROXIMATIONS
  from ..types import Scalar, Optional, Tuple, Union, Tensor
label_desc: Use image node_id 5 for calling the BrownianInterval obj's underlying member method code with example usage: obj.__call__(ta, tb, return_U, return_A) and returns: W,  U,  A, W,  U, W,  A, W