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

Including CI process changes #9

Merged
merged 29 commits into from
Jun 24, 2024
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Fixing mypy typing errors
  • Loading branch information
jezsadler committed Jun 24, 2024
commit 040c858112936134d05bf6f15dd471e46ce13e63
2 changes: 1 addition & 1 deletion src/omlt/__init__.py
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@
"""

from omlt._version import __version__
from omlt.block import OmltBlock
from omlt.block import OmltBlock # type: ignore[attr-defined]
from omlt.scaling import OffsetScaling

__all__ = [
5 changes: 3 additions & 2 deletions src/omlt/formulation.py
Original file line number Diff line number Diff line change
@@ -63,7 +63,6 @@ class _PyomoFormulation(_PyomoFormulationInterface):
"""

def __init__(self):
super().__init__()
self.__block = None

def _set_block(self, block):
@@ -76,7 +75,9 @@ def block(self):
The underlying block containing the constraints / variables for this
formulation.
"""
return self.__block()
if self.__block is not None:
return self.__block()
return None


def scalar_or_tuple(x):
3 changes: 2 additions & 1 deletion src/omlt/gbt/gbt_formulation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import collections
from typing import Any

import numpy as np
import pyomo.environ as pe
@@ -158,7 +159,7 @@ def add_formulation_to_block(block, model_definition, input_vars, output_vars):
domain=pe.Reals,
)

branch_value_by_feature_id = {}
branch_value_by_feature_id: dict[int, Any] = {}
branch_value_by_feature_id = collections.defaultdict(list)

for f in feature_ids:
8 changes: 4 additions & 4 deletions src/omlt/gbt/model.py
Original file line number Diff line number Diff line change
@@ -44,15 +44,15 @@ def scaling_object(self):
"""Return an instance of the scaling object supporting the ScalingInterface."""
return self.__scaling_object

@scaling_object.setter
def scaling_object(self, scaling_object):
self.__scaling_object = scaling_object

@property
def scaled_input_bounds(self):
"""Return a list of tuples of lower and upper bounds of tree ensemble inputs."""
return self.__scaled_input_bounds

@scaling_object.setter
def scaling_object(self, scaling_object):
self.__scaling_object = scaling_object


def _model_num_inputs(model):
"""Returns the number of input variables."""
4 changes: 2 additions & 2 deletions src/omlt/io/keras/keras_reader.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from tensorflow import keras

from omlt.neuralnet.layer import DenseLayer, InputLayer
from omlt.neuralnet.layer import DenseLayer, InputLayer, Layer
from omlt.neuralnet.network_definition import NetworkDefinition


@@ -43,7 +43,7 @@ def load_keras_sequential(
unscaled_input_bounds=unscaled_input_bounds,
)

prev_layer = InputLayer([n_inputs])
prev_layer: Layer = InputLayer([n_inputs])
net.add_layer(prev_layer)

for layer in nn.layers:
2 changes: 1 addition & 1 deletion src/omlt/io/onnx.py
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ def load_onnx_neural_network_with_bounds(filename):
onnx_model = onnx.load(filename)
input_bounds_filename = Path(f"{filename}.bounds.json")
input_bounds = None
if input_bounds_filename.exists:
if input_bounds_filename.exists():
input_bounds = load_input_bounds(input_bounds_filename)

return load_onnx_neural_network(onnx_model, input_bounds=input_bounds)
17 changes: 9 additions & 8 deletions src/omlt/io/onnx_parser.py
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@
ATTR_TENSOR = 4
ATTR_INTS = 7


class NetworkParser:
"""Network Parser.

@@ -41,31 +42,31 @@ def __init__(self):

def _reset_state(self):
self._graph = None
self._initializers = None
self._constants = None
self._nodes = None
self._initializers = {}
self._constants = {}
self._nodes = {}
self._nodes_by_output = None
self._inputs = None
self._outputs = None
self._node_stack = None
self._node_map = None
self._node_stack = []
self._node_map = {}

def parse_network(self, graph, scaling_object, input_bounds):
self._reset_state()
self._graph = graph

# initializers contain constant data
initializers = {}
initializers: dict[str, Any] = {}
for initializer in self._graph.initializer:
initializers[initializer.name] = numpy_helper.to_array(initializer)

self._initializers = initializers

# Build graph
nodes = {}
nodes: dict[str, tuple[str, Any, list[Any]]] = {}
nodes_by_output = {}
inputs = set()
outputs = set()
outputs: set[Any] = set()
self._node_map = {}

network = NetworkDefinition(
4 changes: 2 additions & 2 deletions src/omlt/io/torch_geometric/torch_geometric_reader.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

import numpy as np

from omlt.neuralnet.layer import DenseLayer, GNNLayer, InputLayer
from omlt.neuralnet.layer import DenseLayer, GNNLayer, InputLayer, Layer
from omlt.neuralnet.network_definition import NetworkDefinition


@@ -150,7 +150,7 @@ def load_torch_geometric_sequential(
unscaled_input_bounds=unscaled_input_bounds,
)

prev_layer = InputLayer([n_inputs])
prev_layer: Layer = InputLayer([n_inputs])
net.add_layer(prev_layer)

operations = []
8 changes: 4 additions & 4 deletions src/omlt/linear_tree/lt_definition.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

import lineartree
import numpy as np

@@ -178,9 +180,7 @@ def _find_all_children_leaves(split, splits_dict, leaves_dict):
# For each leaf, check if the parents appear in the list of children
# splits (all_splits). If so, it must be a leaf of the argument split

return [
leaf for leaf in leaves_dict if leaves_dict[leaf]["parent"] in all_splits
]
return [leaf for leaf in leaves_dict if leaves_dict[leaf]["parent"] in all_splits]


def _find_n_inputs(leaves):
@@ -341,7 +341,7 @@ def _parse_tree_data(model, input_bounds):

# For each variable that appears in the tree, go through all the splits
# and record its splitting threshold
splitting_thresholds = {}
splitting_thresholds: dict[int, Any] = {}
for split in splits:
var = splits[split]["col"]
splitting_thresholds[var] = {}
3 changes: 2 additions & 1 deletion src/omlt/neuralnet/activations/__init__.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
variable, and :math:`y` denotes post-activation variable.

"""
from typing import Any

from .linear import linear_activation_constraint, linear_activation_function
from .relu import ComplementarityReLUActivation, bigm_relu_activation_constraint
@@ -25,7 +26,7 @@
"tanh": tanh_activation_function,
}

NON_INCREASING_ACTIVATIONS = []
NON_INCREASING_ACTIVATIONS: list[Any] = []

__all__ = [
"linear_activation_constraint",
4 changes: 2 additions & 2 deletions src/omlt/scaling.py
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@
expressions to the Pyomo model for the inputs and outputs of an ML model. An
implementation of a common scaling approach is included with `OffsetScaling`.
"""

import abc
from typing import Any


class ScalingInterface(abc.ABC):
@@ -28,7 +28,7 @@ def get_unscaled_output_expressions(self, scaled_output_vars):
# pragma: no cover


def convert_to_dict(x):
def convert_to_dict(x: Any) -> dict[Any, Any]:
if isinstance(x, dict):
return dict(x)
return dict(enumerate(x))
13 changes: 6 additions & 7 deletions tests/neuralnet/test_keras.py
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@
from omlt.io import load_keras_sequential

from conftest import get_neural_network_data
from omlt.block import OmltBlock
from omlt import OmltBlock
from omlt.formulation import _PyomoFormulation
from omlt.neuralnet import FullSpaceNNFormulation, ReducedSpaceNNFormulation
from omlt.neuralnet.activations import ComplementarityReLUActivation
from omlt.scaling import OffsetScaling
@@ -32,10 +33,9 @@ def _test_keras_linear_131(keras_fname, *, reduced_space=False):
m = pyo.ConcreteModel()
m.neural_net_block = OmltBlock()
if reduced_space:
formulation = ReducedSpaceNNFormulation(net)
m.neural_net_block.build_formulation(ReducedSpaceNNFormulation(net))
else:
formulation = FullSpaceNNFormulation(net)
m.neural_net_block.build_formulation(formulation)
m.neural_net_block.build_formulation(FullSpaceNNFormulation(net))

nn_outputs = nn.predict(x=x_test)
for d in range(len(x_test)):
@@ -104,10 +104,9 @@ def _test_keras_linear_big(keras_fname, *, reduced_space=False):
m = pyo.ConcreteModel()
m.neural_net_block = OmltBlock()
if reduced_space:
formulation = ReducedSpaceNNFormulation(net)
m.neural_net_block.build_formulation(ReducedSpaceNNFormulation(net))
else:
formulation = FullSpaceNNFormulation(net)
m.neural_net_block.build_formulation(formulation)
m.neural_net_block.build_formulation(FullSpaceNNFormulation(net))

nn_outputs = nn.predict(x=x_test)
for d in range(len(x_test)):
2 changes: 1 addition & 1 deletion tests/neuralnet/test_network_definition.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
import numpy as np
import pyomo.environ as pyo
import pytest
from omlt.block import OmltBlock
from omlt import OmltBlock
from omlt.neuralnet.layer import DenseLayer, InputLayer
from omlt.neuralnet.network_definition import NetworkDefinition
from omlt.neuralnet.nn_formulation import FullSpaceNNFormulation
29 changes: 11 additions & 18 deletions tests/neuralnet/test_nn_formulation.py
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
import pyomo.environ as pyo
import pytest
from omlt import OmltBlock
from omlt.formulation import _PyomoFormulation
from omlt.neuralnet import (
FullSpaceNNFormulation,
FullSpaceSmoothNNFormulation,
@@ -31,6 +32,12 @@
from omlt.neuralnet.layers.reduced_space import reduced_space_dense_layer
from pyomo.contrib.fbbt import interval

formulations = {
"FullSpace": FullSpaceNNFormulation,
"ReducedSpace": ReducedSpaceNNFormulation,
"relu": ReluPartitionFormulation,
}

NEAR_EQUAL = 1e-6
FULLSPACE_SMOOTH_VARS = 15
FULLSPACE_SMOOTH_CONSTRAINTS = 14
@@ -41,6 +48,7 @@
THREE_NODE_VARS = 81
THREE_NODE_CONSTRAINTS = 120


def two_node_network(activation, input_value):
"""Two node network.

@@ -370,12 +378,7 @@ def _test_formulation_added_extra_input(network_formulation):
"""network_formulation can be:'FullSpace', 'ReducedSpace', 'relu'."""
net, y = two_node_network("linear", -2.0)
extra_input = InputLayer([1])
if network_formulation == "FullSpace":
formulation = FullSpaceNNFormulation(net)
elif network_formulation == "ReducedSpace":
formulation = ReducedSpaceNNFormulation(net)
elif network_formulation == "relu":
formulation = ReluPartitionFormulation(net)
formulation: _PyomoFormulation = formulations[network_formulation](net)
net.add_layer(extra_input)
expected_msg = "Multiple input layers are not currently supported."
with pytest.raises(ValueError, match=expected_msg):
@@ -386,12 +389,7 @@ def _test_formulation_build_extra_input(network_formulation):
"""network_formulation can be:'FullSpace', 'ReducedSpace', 'relu'."""
net, y = two_node_network("linear", -2.0)
extra_input = InputLayer([1])
if network_formulation == "FullSpace":
formulation = FullSpaceNNFormulation(net)
elif network_formulation == "ReducedSpace":
formulation = ReducedSpaceNNFormulation(net)
elif network_formulation == "relu":
formulation = ReluPartitionFormulation(net)
formulation: _PyomoFormulation = formulations[network_formulation](net)
net.add_layer(extra_input)
m = pyo.ConcreteModel()
m.neural_net_block = OmltBlock()
@@ -410,12 +408,7 @@ def _test_formulation_added_extra_output(network_formulation):
weights=np.array([[1.0, 0.0], [5.0, 1.0]]),
biases=np.array([3.0, 4.0]),
)
if network_formulation == "FullSpace":
formulation = FullSpaceNNFormulation(net)
elif network_formulation == "ReducedSpace":
formulation = ReducedSpaceNNFormulation(net)
elif network_formulation == "relu":
formulation = ReluPartitionFormulation(net)
formulation: _PyomoFormulation = formulations[network_formulation](net)
net.add_layer(extra_output)
net.add_edge(list(net.layers)[-2], extra_output)
expected_msg = "Multiple output layers are not currently supported."
12 changes: 6 additions & 6 deletions tests/neuralnet/test_onnx.py
Original file line number Diff line number Diff line change
@@ -59,8 +59,8 @@ def obj(mdl):
SolverFactory("cbc").solve(model, tee=False)

x_s = (x - scale_x[0]) / scale_x[1]
x_s = np.array([[x_s]], dtype=np.float32)
outputs = net_regression.run(None, {"dense_input:0": x_s})
x_s_arr = np.array([[x_s]], dtype=np.float32)
outputs = net_regression.run(None, {"dense_input:0": x_s_arr})
y_s = outputs[0][0, 0]
y = y_s * scale_y[1] + scale_y[0]

@@ -102,8 +102,8 @@ def obj(mdl):
SolverFactory("cbc").solve(model, tee=False)

x_s = (x - scale_x[0]) / scale_x[1]
x_s = np.array([[x_s]], dtype=np.float32)
outputs = net_regression.run(None, {"dense_input:0": x_s})
x_s_arr = np.array([[x_s]], dtype=np.float32)
outputs = net_regression.run(None, {"dense_input:0": x_s_arr})
y_s = outputs[0][0, 0]
y = y_s * scale_y[1] + scale_y[0]

@@ -146,8 +146,8 @@ def obj(mdl):
SolverFactory("ipopt").solve(model, tee=False)

x_s = (x - scale_x[0]) / scale_x[1]
x_s = np.array([[x_s]], dtype=np.float32)
outputs = net_regression.run(None, {"dense_2_input:0": x_s})
x_s_arr = np.array([[x_s]], dtype=np.float32)
outputs = net_regression.run(None, {"dense_2_input:0": x_s_arr})
y_s = outputs[0][0, 0]
y = y_s * scale_y[1] + scale_y[0]

2 changes: 1 addition & 1 deletion tests/neuralnet/test_relu.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import numpy as np
import pyomo.environ as pyo
import pytest
from omlt.block import OmltBlock
from omlt import OmltBlock
from omlt.dependencies import onnx_available
from omlt.neuralnet import (
FullSpaceNNFormulation,
Loading