class_docstr: Implements "DeepZ" for relu.
  
  | Paper link:  https://papers.nips.cc/paper/2018/file/f2f446980d8e971ef3da97af089481c3-Paper.pdf
class_docstr_tok: ["Implements","``","DeepZ","''","for","relu",".","|","Paper","link",":","https",":","\/\/papers.nips.cc\/paper\/2018\/file\/f2f446980d8e971ef3da97af089481c3-Paper.pdf"]
docstr: Implements "DeepZ" for relu.
  
  :param x: input zonotope
  :return x: zonotope after application of the relu. May have grown in dimension if crossing relus occur.
docstr_tok: ["Implements","``","DeepZ","''","for","relu",".",":","param","x",":","input","zonotope",":","return","x",":","zonotope","after","application","of","the","relu",".","May","have","grown","in","dimension","if","crossing","relus","occur","."]
code: def zonotope_relu(self, x: "torch.Tensor") -> "torch.Tensor":
      """
      Implements "DeepZ" for relu.
  
      :param x: input zonotope
      :return x: zonotope after application of the relu. May have grown in dimension if crossing relus occur.
      """
      original_shape = x.shape
      x = x.reshape((x.shape[0], -1))
  
      # compute lower bounds
      lbs = self.compute_lb(cent=x[0], eps=x[1:])
  
      # compute upper bounds
      ubs = self.compute_ub(cent=x[0], eps=x[1:])
      slope = torch.div(ubs, (ubs - lbs))
  
      index_cent_vector = torch.zeros((x.shape[0], 1)).to(self.device)
      index_cent_vector[0] = 1
  
      cent_update = (slope * lbs) / 2
      cent_update = torch.tile(cent_update, (x.shape[0], 1))
  
      # find where we have a crossing relu
      bools = torch.logical_and(lbs < 0, ubs > 0)
  
      # where we have a crossing relu, update the terms. Else, do not change input.
      x = torch.where(
          bools, x * slope - cent_update * index_cent_vector, x
      )
  
      # where we have a feature that is < 0, relu always returns 0
      zeros = torch.from_numpy(np.zeros(1).astype("float32")).to(
          self.device
      )
      x = torch.where(ubs < 0, zeros, x)
  
      # vector containing all the (potential) new error terms. We will need to 1) select the ones
      # we need and zero out the rest. And 2) Shape the errors into the correct matrix.
      new_vector = torch.unsqueeze(-1 * ((slope * lbs) / 2), dim=0)
  
      # indexing_matrix is the shape we want the error terms to be.
      indexing_matrix = np.zeros((torch.sum(bools), x.shape[1]))
  
      tmp_crossing_relu = torch.logical_and(lbs < 0, ubs > 0)
      crossing_relu_index = 0
      for j, crossing_relu in enumerate(tmp_crossing_relu):
          if crossing_relu:
              indexing_matrix[crossing_relu_index, j] = 1
              crossing_relu_index += 1
  
      indexing_matrix_tensor = torch.from_numpy(
          indexing_matrix.astype("float32")
      ).to(self.device)
  
      # where there is a crossing ReLU, select the error terms, else zero the vector.
      new_vector = torch.where(bools, new_vector, zeros)
      # tile the error vector to the correct shape and select the terms we want.
      # crossing_relu_index at this point is also the same as the number of crossing relus
      new_vector = torch.tile(new_vector, (crossing_relu_index, 1))
      new_vector = new_vector * indexing_matrix_tensor
  
      # add the new error terms to the zonotope.
      x = torch.cat((x, new_vector))
  
      if len(original_shape) > 2:
          x = x.reshape(
              (
                  -1,
                  original_shape[1],
                  original_shape[2],
                  original_shape[3],
              )
          )
      return x
  
imports: from typing import Tuple, Union
  import numpy
  import torch
label_desc: Use image node_id 5 for calling the ZonoReLU obj's underlying member method code with example usage: obj.zonotope_relu(x) and returns: x