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

Bounds Handling in Linear Tree #152

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
13 changes: 13 additions & 0 deletions src/omlt/linear_tree/lt_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def __init__(
self.__model = lt_regressor
self.__scaling_object = scaling_object

is_scaled = True
# Process input bounds to insure scaled input bounds exist for formulations
if scaled_input_bounds is None:
if unscaled_input_bounds is not None and scaling_object is not None:
Expand All @@ -72,13 +73,15 @@ def __init__(
# input bounds = unscaled input bounds
elif unscaled_input_bounds is not None and scaling_object is None:
scaled_input_bounds = unscaled_input_bounds
is_scaled = False
elif unscaled_input_bounds is None:
raise ValueError(
"Input Bounds needed to represent linear trees as MIPs"
)

self.__unscaled_input_bounds = unscaled_input_bounds
self.__scaled_input_bounds = scaled_input_bounds
self.__is_scaled = is_scaled

self.__splits, self.__leaves, self.__thresholds = _parse_tree_data(
lt_regressor, scaled_input_bounds
Expand All @@ -97,6 +100,16 @@ def scaled_input_bounds(self):
"""Returns dict containing scaled input bounds"""
return self.__scaled_input_bounds

@property
def unscaled_input_bounds(self):
"""Returns dict containing unscaled input bounds"""
return self.__unscaled_input_bounds

@property
def is_scaled(self):
"""Returns bool indicating whether model is scaled"""
return self.__is_scaled

@property
def splits(self):
"""Returns dict containing split information"""
Expand Down
45 changes: 29 additions & 16 deletions src/omlt/linear_tree/lt_formulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@ def _add_gdp_formulation_to_block(

"""
leaves = model_definition.leaves
input_bounds = model_definition.scaled_input_bounds
scaled_input_bounds = model_definition.scaled_input_bounds
unscaled_input_bounds = model_definition.unscaled_input_bounds
n_inputs = model_definition.n_inputs

# The set of leaves and the set of features
Expand All @@ -242,17 +243,25 @@ def _add_gdp_formulation_to_block(

# Use the input_bounds and the linear models in the leaves to calculate
# the lower and upper bounds on the output variable. Required for Pyomo.GDP
output_bounds = _build_output_bounds(model_definition, input_bounds)
scaled_output_bounds = _build_output_bounds(model_definition, scaled_input_bounds)
unscaled_output_bounds = _build_output_bounds(
model_definition, unscaled_input_bounds
)

# Ouptuts are automatically scaled based on whether inputs are scaled
block.outputs.setub(output_bounds[1])
block.outputs.setlb(output_bounds[0])
block.scaled_outputs.setub(output_bounds[1])
block.scaled_outputs.setlb(output_bounds[0])

block.intermediate_output = pe.Var(
tree_ids, bounds=(output_bounds[0], output_bounds[1])
)
block.outputs.setub(unscaled_output_bounds[1])
block.outputs.setlb(unscaled_output_bounds[0])
block.scaled_outputs.setub(scaled_output_bounds[1])
block.scaled_outputs.setlb(scaled_output_bounds[0])

if model_definition.is_scaled is True:
block.intermediate_output = pe.Var(
tree_ids, bounds=(scaled_output_bounds[0], scaled_output_bounds[1])
)
else:
block.intermediate_output = pe.Var(
tree_ids, bounds=(unscaled_output_bounds[0], unscaled_output_bounds[1])
)

# Create a disjunct for each leaf containing the bound constraints
# and the linear model expression.
Expand Down Expand Up @@ -302,7 +311,8 @@ def _add_hybrid_formulation_to_block(block, model_definition, input_vars, output
output_vars -- output variable of the linear tree model
"""
leaves = model_definition.leaves
input_bounds = model_definition.scaled_input_bounds
scaled_input_bounds = model_definition.scaled_input_bounds
unscaled_input_bounds = model_definition.unscaled_input_bounds
n_inputs = model_definition.n_inputs

# The set of trees
Expand All @@ -318,13 +328,16 @@ def _add_hybrid_formulation_to_block(block, model_definition, input_vars, output

# Use the input_bounds and the linear models in the leaves to calculate
# the lower and upper bounds on the output variable. Required for Pyomo.GDP
output_bounds = _build_output_bounds(model_definition, input_bounds)
scaled_output_bounds = _build_output_bounds(model_definition, scaled_input_bounds)
unscaled_output_bounds = _build_output_bounds(
model_definition, unscaled_input_bounds
)

# Ouptuts are automatically scaled based on whether inputs are scaled
block.outputs.setub(output_bounds[1])
block.outputs.setlb(output_bounds[0])
block.scaled_outputs.setub(output_bounds[1])
block.scaled_outputs.setlb(output_bounds[0])
block.outputs.setub(scaled_output_bounds[1])
block.outputs.setlb(scaled_output_bounds[0])
block.scaled_outputs.setub(unscaled_output_bounds[1])
block.scaled_outputs.setlb(unscaled_output_bounds[0])

# Create the intermeditate variables. z is binary that indicates which leaf
# in tree t is returned. intermediate_output is the output of tree t and
Expand Down
Loading