# Path: difflogic/nn/neural_logic/modules/dimension.py
# class Expander(nn.Module):
#   """Capture a free variable into predicates, implemented by broadcast."""
# 
#   def __init__(self, dim):
#     super().__init__()
#     self.dim = dim
# 
#   def forward(self, inputs, n=None):
#     if self.dim == 0:
#       assert n is not None
#     elif n is None:
#       n = inputs.size(self.dim)
#     dim = self.dim + 1
#     return broadcast(inputs.unsqueeze(dim), dim, n)
# 
#   def get_output_dim(self, input_dim):
#     return input_dim
# 
# class Reducer(nn.Module):
#   """Reduce out a variable via quantifiers (exists/forall), implemented by max/min-pooling."""
# 
#   def __init__(self, dim, exclude_self=True, exists=True):
#     super().__init__()
#     self.dim = dim
#     self.exclude_self = exclude_self
#     self.exists = exists
# 
#   def forward(self, inputs):
#     shape = inputs.size()
#     inp0, inp1 = inputs, inputs
#     if self.exclude_self:
#       mask = exclude_mask(inputs, cnt=self.dim, dim=-1 - self.dim)
#       inp0 = mask_value(inputs, mask, 0.0)
#       inp1 = mask_value(inputs, mask, 1.0)
# 
#     if self.exists:
#       shape = shape[:-2] + (shape[-1] * 2,)
#       exists = torch.max(inp0, dim=-2)[0]
#       forall = torch.min(inp1, dim=-2)[0]
#       return torch.stack((exists, forall), dim=-1).view(shape)
# 
#     shape = shape[:-2] + (shape[-1],)
#     return torch.max(inp0, dim=-2)[0].view(shape)
# 
#   def get_output_dim(self, input_dim):
#     if self.exists:
#       return input_dim * 2
#     return input_dim
# 
# class Permutation(nn.Module):
#   """Create r! new predicates by permuting the axies for r-arity predicates."""
# 
#   def __init__(self, dim):
#     super().__init__()
#     self.dim = dim
# 
#   def forward(self, inputs):
#     if self.dim <= 1:
#       return inputs
#     nr_dims = len(inputs.size())
#     # Assume the last dim is channel.
#     index = tuple(range(nr_dims - 1))
#     start_dim = nr_dims - 1 - self.dim
#     assert start_dim > 0
#     res = []
#     for i in itertools.permutations(index[start_dim:]):
#       p = index[:start_dim] + i + (nr_dims - 1,)
#       res.append(inputs.permute(p))
#     return torch.cat(res, dim=-1)
# 
#   def get_output_dim(self, input_dim):
#     mul = 1
#     for i in range(self.dim):
#       mul *= i + 1
#     return input_dim * mul
# 
# Path: difflogic/nn/neural_logic/modules/neural_logic.py
# class LogicInference(InferenceBase):
#   """MLP layer with sigmoid activation."""
# 
#   def __init__(self, input_dim, output_dim, hidden_dim):
#     super().__init__(input_dim, output_dim, hidden_dim)
#     self.layer.add_module(str(len(self.layer)), nn.Sigmoid())


import torch
import torch.nn as nn
from jacinle.logging import get_logger
from .modules.dimension import Expander, Reducer, Permutation
from .modules.neural_logic import LogicInference
  """

  def __init__(
      self,
      breadth,
      input_dims,
      output_dims,
      logic_hidden_dim,
      exclude_self=True,
      residual=False,
  ):
    super().__init__()
    assert breadth > 0, 'Does not support breadth <= 0.'
    if breadth > 3:
      logger.warn(
          'Using LogicLayer with breadth > 3 may cause speed and memory issue.')

    self.max_order = breadth
    self.residual = residual

    input_dims = _get_tuple_n(input_dims, self.max_order + 1, int)
    output_dims = _get_tuple_n(output_dims, self.max_order + 1, int)

    self.logic, self.dim_perms, self.dim_expanders, self.dim_reducers = [
        nn.ModuleList() for _ in range(4)
    ]
    for i in range(self.max_order + 1):
      # collect current_dim from group i-1, i and i+1.
      current_dim = input_dims[i]
      if i > 0:

# Path: difflogic/nn/neural_logic/modules/dimension.py
# class Expander(nn.Module):
#   """Capture a free variable into predicates, implemented by broadcast."""
# 
#   def __init__(self, dim):
#     super().__init__()
#     self.dim = dim
# 
#   def forward(self, inputs, n=None):
#     if self.dim == 0:
#       assert n is not None
#     elif n is None:
#       n = inputs.size(self.dim)
#     dim = self.dim + 1
#     return broadcast(inputs.unsqueeze(dim), dim, n)
# 
#   def get_output_dim(self, input_dim):
#     return input_dim
# 
# class Reducer(nn.Module):
#   """Reduce out a variable via quantifiers (exists/forall), implemented by max/min-pooling."""
# 
#   def __init__(self, dim, exclude_self=True, exists=True):
#     super().__init__()
#     self.dim = dim
#     self.exclude_self = exclude_self
#     self.exists = exists
# 
#   def forward(self, inputs):
#     shape = inputs.size()
#     inp0, inp1 = inputs, inputs
#     if self.exclude_self:
#       mask = exclude_mask(inputs, cnt=self.dim, dim=-1 - self.dim)
#       inp0 = mask_value(inputs, mask, 0.0)
#       inp1 = mask_value(inputs, mask, 1.0)
# 
#     if self.exists:
#       shape = shape[:-2] + (shape[-1] * 2,)
#       exists = torch.max(inp0, dim=-2)[0]
#       forall = torch.min(inp1, dim=-2)[0]
#       return torch.stack((exists, forall), dim=-1).view(shape)
# 
#     shape = shape[:-2] + (shape[-1],)
#     return torch.max(inp0, dim=-2)[0].view(shape)
# 
#   def get_output_dim(self, input_dim):
#     if self.exists:
#       return input_dim * 2
#     return input_dim
# 
# class Permutation(nn.Module):
#   """Create r! new predicates by permuting the axies for r-arity predicates."""
# 
#   def __init__(self, dim):
#     super().__init__()
#     self.dim = dim
# 
#   def forward(self, inputs):
#     if self.dim <= 1:
#       return inputs
#     nr_dims = len(inputs.size())
#     # Assume the last dim is channel.
#     index = tuple(range(nr_dims - 1))
#     start_dim = nr_dims - 1 - self.dim
#     assert start_dim > 0
#     res = []
#     for i in itertools.permutations(index[start_dim:]):
#       p = index[:start_dim] + i + (nr_dims - 1,)
#       res.append(inputs.permute(p))
#     return torch.cat(res, dim=-1)
# 
#   def get_output_dim(self, input_dim):
#     mul = 1
#     for i in range(self.dim):
#       mul *= i + 1
#     return input_dim * mul
# 
# Path: difflogic/nn/neural_logic/modules/neural_logic.py
# class LogicInference(InferenceBase):
#   """MLP layer with sigmoid activation."""
# 
#   def __init__(self, input_dim, output_dim, hidden_dim):
#     super().__init__(input_dim, output_dim, hidden_dim)
#     self.layer.add_module(str(len(self.layer)), nn.Sigmoid())


# Path: difflogic/nn/neural_logic/layer.py
import torch
import torch.nn as nn
from jacinle.logging import get_logger
from .modules.dimension import Expander, Reducer, Permutation
from .modules.neural_logic import LogicInference

  """

  def __init__(
      self,
      breadth,
      input_dims,
      output_dims,
      logic_hidden_dim,
      exclude_self=True,
      residual=False,
  ):
    super().__init__()
    assert breadth > 0, 'Does not support breadth <= 0.'
    if breadth > 3:
      logger.warn(
          'Using LogicLayer with breadth > 3 may cause speed and memory issue.')

    self.max_order = breadth
    self.residual = residual

    input_dims = _get_tuple_n(input_dims, self.max_order + 1, int)
    output_dims = _get_tuple_n(output_dims, self.max_order + 1, int)

    self.logic, self.dim_perms, self.dim_expanders, self.dim_reducers = [
        nn.ModuleList() for _ in range(4)
    ]
    for i in range(self.max_order + 1):
      # collect current_dim from group i-1, i and i+1.
      current_dim = input_dims[i]
      if i > 0:

        expander = Expander(i - 1)