diff --git a/src/omlt/linear_tree/lt_definition.py b/src/omlt/linear_tree/lt_definition.py index 6bd26c8f..799bd9d6 100644 --- a/src/omlt/linear_tree/lt_definition.py +++ b/src/omlt/linear_tree/lt_definition.py @@ -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: @@ -72,6 +73,7 @@ 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" @@ -79,6 +81,7 @@ def __init__( 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 @@ -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""" diff --git a/src/omlt/linear_tree/lt_formulation.py b/src/omlt/linear_tree/lt_formulation.py index 4f83e7f3..6bbb42d7 100644 --- a/src/omlt/linear_tree/lt_formulation.py +++ b/src/omlt/linear_tree/lt_formulation.py @@ -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 @@ -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. @@ -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 @@ -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