Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replacing assert statements with Exceptions #137

Merged
merged 21 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/omlt/gbt/gbt_formulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,23 @@
node_mask = (nodes_tree_ids == tree_id) & (nodes_node_ids == branch_node_id)
feature_id = nodes_feature_ids[node_mask]
branch_value = nodes_values[node_mask]
assert len(feature_id) == 1 and len(branch_value) == 1
if len(branch_value) != 1:
raise ValueError(

Check warning on line 201 in src/omlt/gbt/gbt_formulation.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/gbt/gbt_formulation.py#L201

Added line #L201 was not covered by tests
f"The given tree_id and branch_node_id do not uniquely identify a branch value."
)
if len(feature_id) != 1:
raise ValueError(

Check warning on line 205 in src/omlt/gbt/gbt_formulation.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/gbt/gbt_formulation.py#L205

Added line #L205 was not covered by tests
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
)
assert len(branch_y_idx) == 1
if len(branch_y_idx) != 1:
raise ValueError(

Check warning on line 214 in src/omlt/gbt/gbt_formulation.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/gbt/gbt_formulation.py#L214

Added line #L214 was not covered by tests
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
26 changes: 19 additions & 7 deletions src/omlt/gbt/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,36 @@
def _model_num_inputs(model):
"""Returns the number of input variables"""
graph = model.graph
assert len(graph.input) == 1
if len(graph.input) != 1:
raise ValueError(

Check warning on line 60 in src/omlt/gbt/model.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/gbt/model.py#L60

Added line #L60 was not covered by tests
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
assert len(graph.output) == 1
if len(graph.output) != 1:
raise ValueError(

Check warning on line 70 in src/omlt/gbt/model.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/gbt/model.py#L70

Added line #L70 was not covered by tests
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
for dim in tensor_type.shape.dim:
if dim.dim_value is not None and dim.dim_value > 0:
assert size is None
size = dim.dim_value
assert size is not 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
]
if len(dim_values) == 1:
size = dim_values[0]
elif dim_values == []:
raise ValueError(f"Tensor {tensor} has no positive dimensions.")

Check warning on line 88 in src/omlt/gbt/model.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/gbt/model.py#L88

Added line #L88 was not covered by tests
else:
raise ValueError(f"Tensor {tensor} has multiple positive dimensions.")

Check warning on line 90 in src/omlt/gbt/model.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/gbt/model.py#L90

Added line #L90 was not covered by tests
return size
186 changes: 143 additions & 43 deletions src/omlt/io/onnx_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,17 @@
dim_value = 1
size.append(dim.dim_value)
dim_value *= dim.dim_value
assert dim_value is not None
if dim_value is None:
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)

assert network_input is not None
if network_input is None:
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 @@ -109,11 +113,14 @@
# Now connect inputs to the current node
for input in node_inputs:
self._nodes[input][2].append(node.name)
else:
assert node.op_type == "Constant"
elif node.op_type == "Constant":
for output in node.output:
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}".'
)

# traverse graph
self._node_stack = list(inputs)
Expand Down Expand Up @@ -169,34 +176,54 @@

def _consume_dense_nodes(self, node, next_nodes):
"""Starting from a MatMul node, consume nodes to form a dense Ax + b node."""
assert node.op_type == "MatMul"
assert len(node.input) == 2
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."
)
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."
)

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

assert len(next_nodes) == 1
if len(next_nodes) != 1:
raise ValueError(

Check warning on line 193 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L193

Added line #L193 was not covered by tests
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]]
assert type_ == "node"
assert node.op_type == "Add"
if type_ != "node":
raise TypeError(f"Expected a node next, got a {type_} instead.")

Check warning on line 200 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L200

Added line #L200 was not covered by tests
if node.op_type != "Add":
raise ValueError(

Check warning on line 202 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L202

Added line #L202 was not covered by tests
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
assert len(node.input) == 2
[in_0, in_1] = list(node.input)

if in_0 in self._initializers:
node_biases = self._initializers[in_0]
else:
assert in_1 in self._initializers
elif in_1 in self._initializers:
node_biases = self._initializers[in_1]
else:
raise ValueError(f"Node inputs were not found in graph initializers.")

Check warning on line 215 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L215

Added line #L215 was not covered by tests

assert len(node_weights.shape) == 2
assert node_weights.shape[1] == node_biases.shape[0]
assert len(node.output) == 1
if len(node_weights.shape) != 2:
raise ValueError(f"Node weights must be a 2-dimensional matrix.")

Check warning on line 218 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L218

Added line #L218 was not covered by tests
if node_weights.shape[1] != node_biases.shape[0]:
raise ValueError(

Check warning on line 220 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L220

Added line #L220 was not covered by tests
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(

Check warning on line 224 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L224

Added line #L224 was not covered by tests
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 @@ -226,8 +253,14 @@

def _consume_gemm_dense_nodes(self, node, next_nodes):
"""Starting from a Gemm node, consume nodes to form a dense aAB + bC node."""
assert node.op_type == "Gemm"
assert len(node.input) == 3
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."
)
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."
)

attr = _collect_attributes(node)
alpha = attr["alpha"]
Expand Down Expand Up @@ -275,8 +308,15 @@
Starting from a Conv node, consume nodes to form a convolution node with
(optional) activation function.
"""
assert node.op_type == "Conv"
assert len(node.input) in [2, 3]
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."
)
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."
)

if len(node.input) == 2:
[in_0, in_1] = list(node.input)
in_2 = None
Expand All @@ -295,18 +335,43 @@
attr = _collect_attributes(node)

strides = attr["strides"]

# check only kernel shape and stride are set
# everything else is not supported
assert biases.shape == (out_channels,)
assert in_channels == input_output_size[0]
assert attr["kernel_shape"] == kernel_shape
assert attr["dilations"] == [1, 1]
assert attr["group"] == 1
if "pads" in attr:
assert not np.any(attr["pads"]) # pads all zero
assert len(kernel_shape) == len(strides)
assert len(input_output_size) == len(kernel_shape) + 1
if attr["kernel_shape"] != kernel_shape:
raise ValueError(

Check warning on line 340 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L340

Added line #L340 was not covered by tests
f"Kernel shape attribute {attr['kernel_shape']} does not match initialized kernel shape {kernel_shape}."
)
if len(kernel_shape) != len(strides):
raise ValueError(

Check warning on line 344 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L344

Added line #L344 was not covered by tests
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(

Check warning on line 348 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L348

Added line #L348 was not covered by tests
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(

Check warning on line 354 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L354

Added line #L354 was not covered by tests
f"Biases shape {biases.shape} must match output weights channels {(out_channels,)}."
)
if in_channels != input_output_size[0]:
raise ValueError(

Check warning on line 358 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L358

Added line #L358 was not covered by tests
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(

Check warning on line 364 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L364

Added line #L364 was not covered by tests
f"{node} has non-identity dilations ({attr['dilations']}). This is not supported."
)
if attr["group"] != 1:
raise ValueError(

Check warning on line 368 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L368

Added line #L368 was not covered by tests
f"{node} has multiple groups ({attr['group']}). This is not supported."
)
if "pads" in attr and np.any(attr["pads"]):
raise ValueError(

Check warning on line 372 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L372

Added line #L372 was not covered by tests
f"{node} has non-zero pads ({attr['pads']}). This is not supported."
)

# generate new nodes for the node output
padding = 0
Expand All @@ -326,7 +391,10 @@

# convolute image one channel at the time
# expect 2d image with channels
assert len(input_output_size) == 3
if len(input_output_size) != 3:
raise ValueError(

Check warning on line 395 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L395

Added line #L395 was not covered by tests
f"Expected a 2D image with channels, got {input_output_size}."
)

conv_layer = ConvLayer2D(
input_output_size,
Expand All @@ -343,8 +411,14 @@

def _consume_reshape_nodes(self, node, next_nodes):
"""Parse a Reshape node."""
assert node.op_type == "Reshape"
assert len(node.input) == 2
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."
)
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."
)
[in_0, in_1] = list(node.input)
input_layer = self._node_map[in_0]
new_shape = self._constants[in_1]
Expand All @@ -358,23 +432,34 @@
Starting from a MaxPool node, consume nodes to form a pooling node with
(optional) activation function.
"""
assert node.op_type in _POOLING_OP_TYPES
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."
)
pool_func_name = "max"

# ONNX network should not contain indices output from MaxPool - not supported by OMLT
assert len(node.output) == 1
if len(node.output) != 1:
raise ValueError(

Check warning on line 443 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L443

Added line #L443 was not covered by tests
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."
)

assert len(node.input) == 1
input_layer, transformer = self._node_input_and_transformer(node.input[0])
input_output_size = _get_input_output_size(input_layer, transformer)

# currently only support 2D image with channels.
if len(input_output_size) == 4:
# 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
assert input_output_size[0] == 1
if input_output_size[0] != 1:
raise ValueError(

Check warning on line 459 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L459

Added line #L459 was not covered by tests
f"{node.name} has {input_output_size[0]} batches, only a single batch is supported."
)
input_output_size = input_output_size[1:]
assert len(input_output_size) == 3

in_channels = input_output_size[0]

Expand All @@ -385,11 +470,26 @@

# check only kernel shape, stride, storage order are set
# everything else is not supported
assert ("dilations" not in attr) or (attr["dilations"] == [1, 1])
assert ("pads" not in attr) or (not np.any(attr["pads"]))
assert ("auto_pad" not in attr) or (attr["auto_pad"] == "NOTSET")
assert len(kernel_shape) == len(strides)
assert len(input_output_size) == len(kernel_shape) + 1
if "dilations" in attr and attr["dilations"] != [1, 1]:
raise ValueError(

Check warning on line 474 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L474

Added line #L474 was not covered by tests
f"{node.name} has non-identity dilations ({attr['dilations']}). This is not supported."
)
if "pads" in attr and np.any(attr["pads"]):
raise ValueError(

Check warning on line 478 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L478

Added line #L478 was not covered by tests
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(

Check warning on line 482 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L482

Added line #L482 was not covered by tests
f"{node.name} has autopad set ({attr['auto_pad']}). This is not supported."
)
if len(kernel_shape) != len(strides):
raise ValueError(

Check warning on line 486 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L486

Added line #L486 was not covered by tests
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(

Check warning on line 490 in src/omlt/io/onnx_parser.py

View check run for this annotation

Codecov / codecov/patch

src/omlt/io/onnx_parser.py#L490

Added line #L490 was not covered by tests
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
Loading