# Path: deep_verify/src/common.py
# def with_explicit_update(volatile_value):
#   def materialise(value):
# def targeted_objective(final_w, final_b, labels):
# def concave_max_binsearch(fn, lb, ub, num_iter=20):
# def _prod(lst):
# def convolution(x, kernel, padding, strides):
# def conv_transpose(y, kernel, result_shape, padding, strides):
# def avgpool_transpose(y, result_shape, kernel_shape, strides):
# def conv_broadcast(x, kernel_shape, padding, strides):
# def conv2d_broadcast(x, kernel_height, kernel_width, padding, strides):
# def conv1d_broadcast(x, kernel_length, padding, stride):
# def conv_reduce_sum(x, result_shape, padding, strides):
# def conv2d_reduce_sum(x, input_height, input_width, padding, strides):
# def conv1d_reduce_sum(x, input_length, padding, stride):


from deep_verify.src import common
from interval_bound_propagation import layer_utils
import numpy as np
import tensorflow as tf
  # Achieve this by considering W (kernel) as a new stride-1 deconvolution.
  w_offset, _ = layer_utils.materialise_conv(
      tf.reverse(w, axis=list(range(n))), None,
      input_shape=(enlarged_kernel_shape + [-1]),
      padding='VALID', strides=(n*[1]))
  # The above materialises it as a 2D tensor with shape
  # (enlarged_kernel_shape*input_channels,
  # kernel_height*kernel_width*output_channels).
  w_offset = tf.reshape(w_offset, shape=(
      [1] + enlarged_kernel_shape + [input_channels] +
      kernel_shape + [output_channels]))
  w_offset = tf.transpose(tf.reverse(w_offset, axis=list(range(1, n+1))),
                          perm=(list(range(n+2, 2*n+2)) +
                                list(range(n+2)) + [2*n+2]))
  # W_offset is now a (2N+3)D tensor with shape
  # (kernel_height, kernel_width, 1,
  # 2*kernel_height-1, 2*kernel_width-1, input_channels, output_channels).

  # Take all relevant pair-wise products of W with W_offset.
  wtw = w_offset * tf.reshape(w_s, shape=(
      kernel_shape + [input_slice_channels] + (n*[1]) + [1, output_channels]))
  # WTW is a (2N+3)D tensor with shape
  # (kernel_height, kernel_width, input_slice_channels,
  # 2*kernel_height-1, 2*kernel_width-1, input_channels, output_channels).

  # Combine with d, by performing a deconvolution.
  wtw = tf.reshape(wtw, shape=(
      kernel_shape +
      [input_slice_channels*np.prod(enlarged_kernel_shape)*input_channels,
       output_channels]))

# Path: deep_verify/src/common.py
# def with_explicit_update(volatile_value):
#   def materialise(value):
# def targeted_objective(final_w, final_b, labels):
# def concave_max_binsearch(fn, lb, ub, num_iter=20):
# def _prod(lst):
# def convolution(x, kernel, padding, strides):
# def conv_transpose(y, kernel, result_shape, padding, strides):
# def avgpool_transpose(y, result_shape, kernel_shape, strides):
# def conv_broadcast(x, kernel_shape, padding, strides):
# def conv2d_broadcast(x, kernel_height, kernel_width, padding, strides):
# def conv1d_broadcast(x, kernel_length, padding, stride):
# def conv_reduce_sum(x, result_shape, padding, strides):
# def conv2d_reduce_sum(x, input_height, input_width, padding, strides):
# def conv1d_reduce_sum(x, input_length, padding, stride):


# Path: deep_verify/src/formulations/semidefinite/gram_calcs.py
from deep_verify.src import common
from interval_bound_propagation import layer_utils
import numpy as np
import tensorflow as tf

  # Achieve this by considering W (kernel) as a new stride-1 deconvolution.
  w_offset, _ = layer_utils.materialise_conv(
      tf.reverse(w, axis=list(range(n))), None,
      input_shape=(enlarged_kernel_shape + [-1]),
      padding='VALID', strides=(n*[1]))
  # The above materialises it as a 2D tensor with shape
  # (enlarged_kernel_shape*input_channels,
  # kernel_height*kernel_width*output_channels).
  w_offset = tf.reshape(w_offset, shape=(
      [1] + enlarged_kernel_shape + [input_channels] +
      kernel_shape + [output_channels]))
  w_offset = tf.transpose(tf.reverse(w_offset, axis=list(range(1, n+1))),
                          perm=(list(range(n+2, 2*n+2)) +
                                list(range(n+2)) + [2*n+2]))
  # W_offset is now a (2N+3)D tensor with shape
  # (kernel_height, kernel_width, 1,
  # 2*kernel_height-1, 2*kernel_width-1, input_channels, output_channels).

  # Take all relevant pair-wise products of W with W_offset.
  wtw = w_offset * tf.reshape(w_s, shape=(
      kernel_shape + [input_slice_channels] + (n*[1]) + [1, output_channels]))
  # WTW is a (2N+3)D tensor with shape
  # (kernel_height, kernel_width, input_slice_channels,
  # 2*kernel_height-1, 2*kernel_width-1, input_channels, output_channels).

  # Combine with d, by performing a deconvolution.
  wtw = tf.reshape(wtw, shape=(
      kernel_shape +
      [input_slice_channels*np.prod(enlarged_kernel_shape)*input_channels,
       output_channels]))

  result = common.conv_transpose(d, wtw,