# Path: interval_bound_propagation/src/bounds.py
# class AbstractBounds(object):
# class IntervalBounds(AbstractBounds):
#   def __init__(self):
#   def convert(cls, bounds):
#   def shape(self):
#   def concretize(self):
#   def _raise_not_implemented(self, name):
#   def apply_linear(self, wrapper, w, b):  # pylint: disable=unused-argument
#   def apply_conv1d(self, wrapper, w, b, padding, stride):  # pylint: disable=unused-argument
#   def apply_conv2d(self, wrapper, w, b, padding, strides):  # pylint: disable=unused-argument
#   def apply_increasing_monotonic_fn(self, wrapper, fn, *args, **parameters):  # pylint: disable=unused-argument
#   def apply_piecewise_monotonic_fn(self, wrapper, fn, boundaries, *args):  # pylint: disable=unused-argument
#   def apply_batch_norm(self, wrapper, mean, variance, scale, bias, epsilon):  # pylint: disable=unused-argument
#   def apply_batch_reshape(self, wrapper, shape):  # pylint: disable=unused-argument
#   def apply_softmax(self, wrapper):  # pylint: disable=unused-argument
#   def update_cache_op(self):
#   def enable_caching(self):
#   def _set_up_cache(self):
#   def _cache_with_update_op(self, tensor):
#   def __init__(self, lower, upper):
#   def lower(self):
#   def upper(self):
#   def shape(self):
#   def __iter__(self):
#   def convert(cls, bounds):
#   def apply_linear(self, wrapper, w, b):
#   def apply_conv1d(self, wrapper, w, b, padding, stride):
#   def apply_conv2d(self, wrapper, w, b, padding, strides):
#   def _affine(self, w, b, fn, **kwargs):
#   def apply_increasing_monotonic_fn(self, wrapper, fn, *args, **parameters):
#   def apply_piecewise_monotonic_fn(self, wrapper, fn, boundaries, *args):
#   def apply_batch_norm(self, wrapper, mean, variance, scale, bias, epsilon):
#   def apply_batch_reshape(self, wrapper, shape):
#   def apply_softmax(self, wrapper):
#   def _set_up_cache(self):
# 
# Path: interval_bound_propagation/src/relative_bounds.py
# class RelativeIntervalBounds(basic_bounds.AbstractBounds):
#   def __init__(self, lower_offset, upper_offset, nominal):
#   def lower_offset(self):
#   def upper_offset(self):
#   def nominal(self):
#   def lower(self):
#   def upper(self):
#   def shape(self):
#   def convert(cls, bounds):
#   def apply_batch_reshape(self, wrapper, shape):
#   def apply_linear(self, wrapper, w, b):
#   def apply_conv1d(self, wrapper, w, b, padding, stride):
#   def apply_conv2d(self, wrapper, w, b, padding, strides):
#   def apply_increasing_monotonic_fn(self, wrapper, fn, *args, **parameters):
#   def apply_batch_norm(self, wrapper, mean, variance, scale, bias, epsilon):
#   def _set_up_cache(self):
# def _maxpool_bounds(module, kernel_shape, strides, lb_in, ub_in,
#                     nominal_in, nominal_out):
# def _activation_bounds(nl_fun, lb_in, ub_in, nominal_in, parameters=None):


import collections
import sonnet as snt
import tensorflow.compat.v1 as tf
from absl import logging
from interval_bound_propagation.src import bounds as basic_bounds
from interval_bound_propagation.src import relative_bounds
# limitations under the License.

"""Fast-Lin symbolic bound calculation for common neural network layers.

The Fast-Lin algorithm expresses lower and upper bounds of each layer of
a neural network as a symbolic linear expression in the input neurons,
relaxing the ReLU layers to retain linearity at the expense of tightness.

Reference: "Towards Fast Computation of Certified Robustness for ReLU Networks",
https://arxiv.org/pdf/1804.09699.pdf.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function




# Holds the linear expressions serving as bounds.
# w: [batch_size, input_size, output_shape] storing the weights.
# b: [batch_size, output_shape] storing the bias.
# lower: [batch_size, input_size] storing the lower bounds on inputs.
# upper: [batch_size, input_size] storing the upper bounds on inputs.
# `lower` and `upper` tensors are always flattened representations of the
# original inputs.
LinearExpression = collections.namedtuple(
    'LinearExpression', ['w', 'b', 'lower', 'upper'])



# Path: interval_bound_propagation/src/bounds.py
# class AbstractBounds(object):
# class IntervalBounds(AbstractBounds):
#   def __init__(self):
#   def convert(cls, bounds):
#   def shape(self):
#   def concretize(self):
#   def _raise_not_implemented(self, name):
#   def apply_linear(self, wrapper, w, b):  # pylint: disable=unused-argument
#   def apply_conv1d(self, wrapper, w, b, padding, stride):  # pylint: disable=unused-argument
#   def apply_conv2d(self, wrapper, w, b, padding, strides):  # pylint: disable=unused-argument
#   def apply_increasing_monotonic_fn(self, wrapper, fn, *args, **parameters):  # pylint: disable=unused-argument
#   def apply_piecewise_monotonic_fn(self, wrapper, fn, boundaries, *args):  # pylint: disable=unused-argument
#   def apply_batch_norm(self, wrapper, mean, variance, scale, bias, epsilon):  # pylint: disable=unused-argument
#   def apply_batch_reshape(self, wrapper, shape):  # pylint: disable=unused-argument
#   def apply_softmax(self, wrapper):  # pylint: disable=unused-argument
#   def update_cache_op(self):
#   def enable_caching(self):
#   def _set_up_cache(self):
#   def _cache_with_update_op(self, tensor):
#   def __init__(self, lower, upper):
#   def lower(self):
#   def upper(self):
#   def shape(self):
#   def __iter__(self):
#   def convert(cls, bounds):
#   def apply_linear(self, wrapper, w, b):
#   def apply_conv1d(self, wrapper, w, b, padding, stride):
#   def apply_conv2d(self, wrapper, w, b, padding, strides):
#   def _affine(self, w, b, fn, **kwargs):
#   def apply_increasing_monotonic_fn(self, wrapper, fn, *args, **parameters):
#   def apply_piecewise_monotonic_fn(self, wrapper, fn, boundaries, *args):
#   def apply_batch_norm(self, wrapper, mean, variance, scale, bias, epsilon):
#   def apply_batch_reshape(self, wrapper, shape):
#   def apply_softmax(self, wrapper):
#   def _set_up_cache(self):
# 
# Path: interval_bound_propagation/src/relative_bounds.py
# class RelativeIntervalBounds(basic_bounds.AbstractBounds):
#   def __init__(self, lower_offset, upper_offset, nominal):
#   def lower_offset(self):
#   def upper_offset(self):
#   def nominal(self):
#   def lower(self):
#   def upper(self):
#   def shape(self):
#   def convert(cls, bounds):
#   def apply_batch_reshape(self, wrapper, shape):
#   def apply_linear(self, wrapper, w, b):
#   def apply_conv1d(self, wrapper, w, b, padding, stride):
#   def apply_conv2d(self, wrapper, w, b, padding, strides):
#   def apply_increasing_monotonic_fn(self, wrapper, fn, *args, **parameters):
#   def apply_batch_norm(self, wrapper, mean, variance, scale, bias, epsilon):
#   def _set_up_cache(self):
# def _maxpool_bounds(module, kernel_shape, strides, lb_in, ub_in,
#                     nominal_in, nominal_out):
# def _activation_bounds(nl_fun, lb_in, ub_in, nominal_in, parameters=None):


# Path: interval_bound_propagation/src/fastlin.py
import collections
import sonnet as snt
import tensorflow.compat.v1 as tf
from absl import logging
from interval_bound_propagation.src import bounds as basic_bounds
from interval_bound_propagation.src import relative_bounds

# limitations under the License.

"""Fast-Lin symbolic bound calculation for common neural network layers.

The Fast-Lin algorithm expresses lower and upper bounds of each layer of
a neural network as a symbolic linear expression in the input neurons,
relaxing the ReLU layers to retain linearity at the expense of tightness.

Reference: "Towards Fast Computation of Certified Robustness for ReLU Networks",
https://arxiv.org/pdf/1804.09699.pdf.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function




# Holds the linear expressions serving as bounds.
# w: [batch_size, input_size, output_shape] storing the weights.
# b: [batch_size, output_shape] storing the bias.
# lower: [batch_size, input_size] storing the lower bounds on inputs.
# upper: [batch_size, input_size] storing the upper bounds on inputs.
# `lower` and `upper` tensors are always flattened representations of the
# original inputs.
LinearExpression = collections.namedtuple(
    'LinearExpression', ['w', 'b', 'lower', 'upper'])



class SymbolicBounds(basic_bounds.AbstractBounds):