import networkx as nx
from omlt.neuralnet.layer import Layer
[docs]class NetworkDefinition:
def __init__(self, scaling_object=None, scaled_input_bounds=None):
"""
Create a network definition object used to create the neural network
formulation in Pyomo
Args:
scaling_object : ScalingInterface or None
A scaling object to specify the scaling parameters for the
neural network inputs and outputs. If None, then no
scaling is performed.
scaled_input_bounds : dict or None
A dict that contains the bounds on the scaled variables (the
direct inputs to the neural network). If None, then no bounds
are specified.
"""
self.__layers_by_id = dict()
self.__graph = nx.DiGraph()
self.__scaling_object = scaling_object
self.__scaled_input_bounds = scaled_input_bounds
[docs] def add_layer(self, layer):
"""
Add a layer to the network.
Parameters
----------
layer : Layer
the layer to add to the network
"""
layer_id = id(layer)
self.__layers_by_id[layer_id] = layer
self.__graph.add_node(layer_id)
[docs] def add_edge(self, from_layer, to_layer):
"""
Add an edge between two layers.
Parameters
----------
from_layer : Layer
the layer with the outbound connection
to_layer : Layer
the layer with the inbound connection
"""
id_to = id(to_layer)
id_from = id(from_layer)
assert id_to in self.__layers_by_id
assert id_from in self.__layers_by_id
self.__graph.add_edge(id_from, id_to)
@property
def scaling_object(self):
"""Return an instance of the scaling object that supports the ScalingInterface"""
return self.__scaling_object
@property
def scaled_input_bounds(self):
"""Return a dict of tuples containing lower and upper bounds of neural network inputs"""
return self.__scaled_input_bounds
@property
def input_layers(self):
"""Return an iterator over the input layers"""
for layer_id, in_degree in self.__graph.in_degree():
if in_degree == 0:
yield self.__layers_by_id[layer_id]
@property
def input_nodes(self):
"""An alias for input_layers"""
return self.input_layers
@property
def output_layers(self):
"""Return an iterator over the output layer"""
for layer_id, out_degree in self.__graph.out_degree():
if out_degree == 0:
yield self.__layers_by_id[layer_id]
@property
def output_nodes(self):
"""An alias for output_layers"""
return self.output_layers
[docs] def layer(self, layer_id):
"""Return the layer with the given id"""
return self.__layers_by_id[layer_id]
@property
def layers(self):
"""Return an iterator over all the layers"""
for layer_id in nx.topological_sort(self.__graph):
yield self.__layers_by_id[layer_id]
[docs] def predecessors(self, layer):
"""Return an iterator over the layers with outbound connections into the layer"""
if isinstance(layer, Layer):
layer = id(layer)
for node_id in self.__graph.predecessors(layer):
yield self.__layers_by_id[node_id]
[docs] def successors(self, layer):
"""Return an iterator over the layers with an inbound connection from the layer"""
if isinstance(layer, Layer):
layer = id(layer)
for node_id in self.__graph.successors(layer):
yield self.__layers_by_id[node_id]
def __str__(self):
return f"NetworkDefinition(num_layers={len(self.__layers_by_id)})"