Skip to content

Commit

Permalink
Fixing yet more lint issues
Browse files Browse the repository at this point in the history
  • Loading branch information
jezsadler committed Nov 3, 2023
1 parent 733364c commit 6b82771
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 113 deletions.
12 changes: 9 additions & 3 deletions src/omlt/gbt/gbt_formulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,22 @@ def _branching_y(tree_id, branch_node_id):
feature_id = nodes_feature_ids[node_mask]
branch_value = nodes_values[node_mask]
if len(branch_value) != 1:
raise ValueError(f"The given tree_id and branch_node_id do not uniquely identify a branch value.")
raise ValueError(
f"The given tree_id and branch_node_id do not uniquely identify a branch value."
)
if len(feature_id) != 1:
raise ValueError(f"The given tree_id and branch_node_id do not uniquely identify a feature.")
raise ValueError(
f"The given tree_id and branch_node_id do not uniquely identify a feature."
)
feature_id = feature_id[0]
branch_value = branch_value[0]
(branch_y_idx,) = np.where(
branch_value_by_feature_id[feature_id] == branch_value
)
if len(branch_y_idx) != 1:
raise ValueError(f"The given tree_id and branch_node_id do not uniquely identify a branch index.")
raise ValueError(
f"The given tree_id and branch_node_id do not uniquely identify a branch index."
)
return block.y[feature_id, branch_y_idx[0]]

def _sum_of_z_l(tree_id, start_node_id):
Expand Down
14 changes: 11 additions & 3 deletions src/omlt/gbt/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,31 @@ def _model_num_inputs(model):
"""Returns the number of input variables"""
graph = model.graph
if len(graph.input) != 1:
raise ValueError(f"Model graph input field is multi-valued {graph.input}. A single value is required.")
raise ValueError(
f"Model graph input field is multi-valued {graph.input}. A single value is required."
)
return _tensor_size(graph.input[0])


def _model_num_outputs(model):
"""Returns the number of output variables"""
graph = model.graph
if len(graph.output) != 1:
raise ValueError(f"Model graph output field is multi-valued {graph.output}. A single value is required.")
raise ValueError(
f"Model graph output field is multi-valued {graph.output}. A single value is required."
)
return _tensor_size(graph.output[0])


def _tensor_size(tensor):
"""Returns the size of an input tensor"""
tensor_type = tensor.type.tensor_type
size = None
dim_values = [dim.dim_value for dim in tensor_type.shape.dim if dim.dim_value is not None and dim.dim_value > 0]
dim_values = [
dim.dim_value
for dim in tensor_type.shape.dim
if dim.dim_value is not None and dim.dim_value > 0
]
if len(dim_values) == 1:
size = dim_values[0]
elif dim_values == []:
Expand Down
130 changes: 97 additions & 33 deletions src/omlt/io/onnx_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,16 @@ def parse_network(self, graph, scaling_object, input_bounds):
size.append(dim.dim_value)
dim_value *= dim.dim_value
if dim_value is None:
raise ValueError(f"All dimensions in graph \"{graph.name}\" input tensor have 0 value.")
raise ValueError(
f'All dimensions in graph "{graph.name}" input tensor have 0 value.'
)
assert network_input is None
network_input = InputLayer(size)
self._node_map[input.name] = network_input
network.add_layer(network_input)

if network_input is None:
raise ValueError(f"No valid input layer found in graph \"{graph.name}\".")
raise ValueError(f'No valid input layer found in graph "{graph.name}".')

self._nodes = nodes
self._nodes_by_output = nodes_by_output
Expand Down Expand Up @@ -116,7 +118,9 @@ def parse_network(self, graph, scaling_object, input_bounds):
value = _parse_constant_value(node)
self._constants[output] = value
else:
raise ValueError(f"Nodes must have inputs or have op_type \"Constant\". Node \"{node.name}\" has no inputs and op_type \"{node.op_type}\".")
raise ValueError(
f'Nodes must have inputs or have op_type "Constant". Node "{node.name}" has no inputs and op_type "{node.op_type}".'
)

# traverse graph
self._node_stack = list(inputs)
Expand Down Expand Up @@ -173,23 +177,31 @@ def _visit_node(self, node, next_nodes):
def _consume_dense_nodes(self, node, next_nodes):
"""Starting from a MatMul node, consume nodes to form a dense Ax + b node."""
if node.op_type != "MatMul":
raise ValueError(f"{node.name} is a {node.op_type} node, only MatMul nodes can be used as starting points for consumption.")
raise ValueError(
f"{node.name} is a {node.op_type} node, only MatMul nodes can be used as starting points for consumption."
)
if len(node.input) != 2:
raise ValueError(f"{node.name} input has {len(node.input)} dimensions, only nodes with 2 input dimensions can be used as starting points for consumption.")
raise ValueError(
f"{node.name} input has {len(node.input)} dimensions, only nodes with 2 input dimensions can be used as starting points for consumption."
)

[in_0, in_1] = list(node.input)
input_layer, transformer = self._node_input_and_transformer(in_0)
node_weights = self._initializers[in_1]

if len(next_nodes) != 1:
raise ValueError(f"Next nodes must have length 1, {next_nodes} has length {len(next_nodes)}")
raise ValueError(
f"Next nodes must have length 1, {next_nodes} has length {len(next_nodes)}"
)

# expect 'Add' node ahead
type_, node, maybe_next_nodes = self._nodes[next_nodes[0]]
if type_ != "node":
raise TypeError(f"Expected a node next, got a {type_} instead.")
if node.op_type != "Add":
raise ValueError(f"The first node to be consumed, {node.name}, is a {node.op_type} node. Only Add nodes are supported.")
raise ValueError(
f"The first node to be consumed, {node.name}, is a {node.op_type} node. Only Add nodes are supported."
)

# extract biases
next_nodes = maybe_next_nodes
Expand All @@ -205,9 +217,13 @@ def _consume_dense_nodes(self, node, next_nodes):
if len(node_weights.shape) != 2:
raise ValueError(f"Node weights must be a 2-dimensional matrix.")
if node_weights.shape[1] != node_biases.shape[0]:
raise ValueError(f"Node weights has {node_weights.shape[1]} columns; node biases has {node_biases.shape[0]} rows. These must be equal.")
raise ValueError(
f"Node weights has {node_weights.shape[1]} columns; node biases has {node_biases.shape[0]} rows. These must be equal."
)
if len(node.output) != 1:
raise ValueError(f"Node output is {node.output} but should be a single value.")
raise ValueError(
f"Node output is {node.output} but should be a single value."
)


input_output_size = _get_input_output_size(input_layer, transformer)
Expand Down Expand Up @@ -239,9 +255,13 @@ def _consume_dense_nodes(self, node, next_nodes):
def _consume_gemm_dense_nodes(self, node, next_nodes):
"""Starting from a Gemm node, consume nodes to form a dense aAB + bC node."""
if node.op_type != "Gemm":
raise ValueError(f"{node.name} is a {node.op_type} node, only Gemm nodes can be used as starting points for consumption.")
raise ValueError(
f"{node.name} is a {node.op_type} node, only Gemm nodes can be used as starting points for consumption."
)
if len(node.input) != 3:
raise ValueError(f"{node.name} input has {len(node.input)} dimensions, only nodes with 3 input dimensions can be used as starting points for consumption.")
raise ValueError(
f"{node.name} input has {len(node.input)} dimensions, only nodes with 3 input dimensions can be used as starting points for consumption."
)

attr = _collect_attributes(node)
alpha = attr["alpha"]
Expand Down Expand Up @@ -290,9 +310,13 @@ def _consume_conv_nodes(self, node, next_nodes):
(optional) activation function.
"""
if node.op_type != "Conv":
raise ValueError(f"{node.name} is a {node.op_type} node, only Conv nodes can be used as starting points for consumption.")
raise ValueError(
f"{node.name} is a {node.op_type} node, only Conv nodes can be used as starting points for consumption."
)
if len(node.input) not in [2,3]:
raise ValueError(f"{node.name} input has {len(node.input)} dimensions, only nodes with 2 or 3 input dimensions can be used as starting points for consumption.")
raise ValueError(
f"{node.name} input has {len(node.input)} dimensions, only nodes with 2 or 3 input dimensions can be used as starting points for consumption."
)

if len(node.input) == 2:
[in_0, in_1] = list(node.input)
Expand All @@ -314,25 +338,41 @@ def _consume_conv_nodes(self, node, next_nodes):
strides = attr["strides"]
# check only kernel shape and stride are set
if attr["kernel_shape"] != kernel_shape:
raise ValueError(f"Kernel shape attribute {attr['kernel_shape']} does not match initialized kernel shape {kernel_shape}.")
raise ValueError(
f"Kernel shape attribute {attr['kernel_shape']} does not match initialized kernel shape {kernel_shape}."
)
if len(kernel_shape) != len(strides):
raise ValueError(f"Initialized kernel shape {kernel_shape} has {len(kernel_shape)} dimensions. Strides attribute has {len(strides)} dimensions. These must be equal.")
raise ValueError(
f"Initialized kernel shape {kernel_shape} has {len(kernel_shape)} dimensions. Strides attribute has {len(strides)} dimensions. These must be equal."
)
if len(input_output_size) != len(kernel_shape) + 1:
raise ValueError(f"Input/output size ({input_output_size}) must have one more dimension than initialized kernel shape ({kernel_shape}).")
raise ValueError(
f"Input/output size ({input_output_size}) must have one more dimension than initialized kernel shape ({kernel_shape})."
)

# Check input, output have correct dimensions
if biases.shape != (out_channels,):
raise ValueError(f"Biases shape {biases.shape} must match output weights channels {(out_channels,)}.")
raise ValueError(
f"Biases shape {biases.shape} must match output weights channels {(out_channels,)}."
)
if in_channels != input_output_size[0]:
raise ValueError(f"Input/output size ({input_output_size}) first dimension must match input weights channels ({in_channels}).")
raise ValueError(
f"Input/output size ({input_output_size}) first dimension must match input weights channels ({in_channels})."
)

# Other attributes are not supported
if "dilations" in attr and attr["dilations"] != [1, 1]:
raise ValueError(f"{node} has non-identity dilations ({attr['dilations']}). This is not supported.")
raise ValueError(
f"{node} has non-identity dilations ({attr['dilations']}). This is not supported."
)
if attr["group"] != 1:
raise ValueError(f"{node} has multiple groups ({attr['group']}). This is not supported.")
raise ValueError(
f"{node} has multiple groups ({attr['group']}). This is not supported."
)
if "pads" in attr and np.any(attr["pads"]):
raise ValueError(f"{node} has non-zero pads ({attr['pads']}). This is not supported.")
raise ValueError(
f"{node} has non-zero pads ({attr['pads']}). This is not supported."
)

# generate new nodes for the node output
padding = 0
Expand All @@ -353,7 +393,9 @@ def _consume_conv_nodes(self, node, next_nodes):
# convolute image one channel at the time
# expect 2d image with channels
if len(input_output_size) != 3:
raise ValueError(f"Expected a 2D image with channels, got {input_output_size}.")
raise ValueError(
f"Expected a 2D image with channels, got {input_output_size}."
)

conv_layer = ConvLayer2D(
input_output_size,
Expand All @@ -371,9 +413,13 @@ def _consume_conv_nodes(self, node, next_nodes):
def _consume_reshape_nodes(self, node, next_nodes):
"""Parse a Reshape node."""
if node.op_type != "Reshape":
raise ValueError(f"{node.name} is a {node.op_type} node, only Reshape nodes can be used as starting points for consumption.")
raise ValueError(
f"{node.name} is a {node.op_type} node, only Reshape nodes can be used as starting points for consumption."
)
if len(node.input) != 2:
raise ValueError(f"{node.name} input has {len(node.input)} dimensions, only nodes with 2 input dimensions can be used as starting points for consumption.")
raise ValueError(
f"{node.name} input has {len(node.input)} dimensions, only nodes with 2 input dimensions can be used as starting points for consumption."
)
[in_0, in_1] = list(node.input)
input_layer = self._node_map[in_0]
new_shape = self._constants[in_1]
Expand All @@ -388,14 +434,20 @@ def _consume_pool_nodes(self, node, next_nodes):
(optional) activation function.
"""
if node.op_type not in _POOLING_OP_TYPES:
raise ValueError(f"{node.name} is a {node.op_type} node, only MaxPool nodes can be used as starting points for consumption.")
raise ValueError(
f"{node.name} is a {node.op_type} node, only MaxPool nodes can be used as starting points for consumption."
)
pool_func_name = "max"

# ONNX network should not contain indices output from MaxPool - not supported by OMLT
if len(node.output) != 1:
raise ValueError(f"The ONNX contains indices output from MaxPool. This is not supported by OMLT.")
raise ValueError(
f"The ONNX contains indices output from MaxPool. This is not supported by OMLT."
)
if len(node.input) != 1:
raise ValueError(f"{node.name} input has {len(node.input)} dimensions, only nodes with 1 input dimension can be used as starting points for consumption.")
raise ValueError(
f"{node.name} input has {len(node.input)} dimensions, only nodes with 1 input dimension can be used as starting points for consumption."
)

input_layer, transformer = self._node_input_and_transformer(node.input[0])
input_output_size = _get_input_output_size(input_layer, transformer)
Expand All @@ -405,7 +457,9 @@ def _consume_pool_nodes(self, node, next_nodes):
# this means there is an extra dimension for number of batches
# batches not supported, so only accept if they're not there or there is only 1 batch
if input_output_size[0] != 1:
raise ValueError(f"{node.name} has {input_output_size[0]} batches, only a single batch is supported.")
raise ValueError(
f"{node.name} has {input_output_size[0]} batches, only a single batch is supported."
)
input_output_size = input_output_size[1:]

in_channels = input_output_size[0]
Expand All @@ -418,15 +472,25 @@ def _consume_pool_nodes(self, node, next_nodes):
# check only kernel shape, stride, storage order are set
# everything else is not supported
if "dilations" in attr and attr["dilations"] != [1, 1]:
raise ValueError(f"{node.name} has non-identity dilations ({attr['dilations']}). This is not supported.")
raise ValueError(
f"{node.name} has non-identity dilations ({attr['dilations']}). This is not supported."
)
if "pads" in attr and np.any(attr["pads"]):
raise ValueError(f"{node.name} has non-zero pads ({attr['pads']}). This is not supported.")
raise ValueError(
f"{node.name} has non-zero pads ({attr['pads']}). This is not supported."
)
if ("auto_pad" in attr) and (attr["auto_pad"] != "NOTSET"):
raise ValueError(f"{node.name} has autopad set ({attr['auto_pad']}). This is not supported.")
raise ValueError(
f"{node.name} has autopad set ({attr['auto_pad']}). This is not supported."
)
if len(kernel_shape) != len(strides):
raise ValueError(f"Kernel shape {kernel_shape} has {len(kernel_shape)} dimensions. Strides attribute has {len(strides)} dimensions. These must be equal.")
raise ValueError(
f"Kernel shape {kernel_shape} has {len(kernel_shape)} dimensions. Strides attribute has {len(strides)} dimensions. These must be equal."
)
if len(input_output_size) != len(kernel_shape) + 1:
raise ValueError(f"Input/output size ({input_output_size}) must have one more dimension than kernel shape ({kernel_shape}).")
raise ValueError(
f"Input/output size ({input_output_size}) must have one more dimension than kernel shape ({kernel_shape})."
)

output_shape_wrapper = math.floor
if "ceil_mode" in attr and attr["ceil_mode"] == 1:
Expand Down
Loading

0 comments on commit 6b82771

Please sign in to comment.