Skip to content

Commit

Permalink
Avoid computing max criterion when there is none (#950)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-zakir authored Oct 23, 2024
1 parent c042044 commit 68412f8
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cucumber-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ runs:
run: |
export PATH="${{ inputs.mpi_path }}:$PATH"
cd tests/end_to_end
behave --tags ${{ inputs.tags }} cucumber/${{ inputs.feature }} --no-capture
behave --tags ${{ inputs.tags }} cucumber/${{ inputs.feature }} --no-capture --no-capture-stderr --format pretty
4 changes: 4 additions & 0 deletions data_test/mini_instance_MIP/adequacy_criterion.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#justt for test: area N0 doesn't make sense in .mps of the current directory
patterns:
- area: "N0"
criterion: 1
17 changes: 10 additions & 7 deletions src/cpp/benders/benders_mpi/BendersMpiOuterLoop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,16 @@ void BendersMpiOuterLoop::UpdateOuterLoopMaxCriterionArea() {
auto criterions_end =
_data.outer_loop_current_iteration_data.outer_loop_criterion.cend();
auto max_criterion_it = std::max_element(criterions_begin, criterions_end);
_data.outer_loop_current_iteration_data.max_criterion = *max_criterion_it;
auto max_criterion_index = std::distance(criterions_begin, max_criterion_it);
_data.outer_loop_current_iteration_data.max_criterion_area =
criterion_computation_.getOuterLoopInputData()
.OuterLoopData()[max_criterion_index]
.Pattern()
.GetBody();
if (max_criterion_it != criterions_end) {
_data.outer_loop_current_iteration_data.max_criterion = *max_criterion_it;
auto max_criterion_index =
std::distance(criterions_begin, max_criterion_it);
_data.outer_loop_current_iteration_data.max_criterion_area =
criterion_computation_.getOuterLoopInputData()
.OuterLoopData()[max_criterion_index]
.Pattern()
.GetBody();
}
}

void BendersMpiOuterLoop::InitializeProblems() {
Expand Down
10 changes: 9 additions & 1 deletion tests/end_to_end/cucumber/features/outer_loop_tests.feature
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@ Feature: outer loop tests
And the expected overall cost is 92.70005
And the solution is
| variable | value |
| G_p_max_0_0 | 2.900004 |
| G_p_max_0_0 | 2.900004 |

@fast @short @outerloop
Scenario: a non outer loop study e.g with non-consistent adequacy_criterion file and with un-formatted mps (unnamed mps)
Given the study path is "data_test/mini_instance_MIP"
When I run outer loop with 1 proc(s) and "options_default.json" as option file
Then the simulation takes less than 5 seconds
And the simulation succeeds
And LOLD.txt and PositiveUnsuppliedEnergy.txt files are full of zeros
69 changes: 57 additions & 12 deletions tests/end_to_end/cucumber/steps/steps.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import math
import os
import subprocess
from pathlib import Path
Expand All @@ -15,11 +16,11 @@ def study_path_is(context, string):
string.replace("/", os.sep))


def build_outer_loop_command(context, n: int):
def build_outer_loop_command(context, n: int, option_file: str = "options.json"):
command = get_mpi_command(allow_run_as_root=context.allow_run_as_root, nproc=n)
exe_path = Path(get_conf("DEFAULT_INSTALL_DIR")) / get_conf("OUTER_LOOP")
command.append(str(exe_path))
command.append("options.json")
command.append(option_file)
return command


Expand All @@ -30,29 +31,39 @@ def build_launch_command(study_dir: str, method: str, nproc: int, in_memory: boo
return command


def read_outputs(output_path):
def read_json_file(output_path):
with open(output_path, 'r') as file:
outputs = json.load(file)
return outputs


def read_file(output_path):
with open(output_path, 'r') as file:
outputs = file.readlines()
return outputs


@when('I run outer loop with {n:d} proc(s) and "{option_file}" as option file')
@when('I run outer loop with {n:d} proc(s)')
def run_outer_loop(context, n):
def run_outer_loop(context, n, option_file: str = "options.json"):
context.allow_run_as_root = get_conf("allow_run_as_root")
command = build_outer_loop_command(context, n)
command = build_outer_loop_command(context, n, option_file)
print(f"Running command: {command}")
old_cwd = os.getcwd()
lp_path = Path(context.study_path) / "lp"

lp_path = Path(context.study_path) / "lp" if (Path(context.study_path) / "lp").exists() else Path(
context.study_path)

os.chdir(lp_path)
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
out, err = process.communicate()
print(out)
print("*****")
print(err)
process.communicate()
context.return_code = process.returncode
context.outputs = read_outputs(Path("..") / "expansion" / "out.json")
options = read_json_file(option_file)
output_file_path = options["JSON_FILE"]
context.outputs = read_json_file(output_file_path)
context.loss_of_load_file = (Path(options["OUTPUTROOT"]) / "LOLD.txt").absolute()
context.positive_unsupplied_energy_file = (Path(options["OUTPUTROOT"]) / "PositiveUnsuppliedEnergy.txt").absolute()

os.chdir(old_cwd)


Expand All @@ -62,7 +73,7 @@ def run_antares_xpansion(context, method, n):
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
out, err = process.communicate()
context.return_code = process.returncode
context.outputs = read_outputs(Path(get_results_file_path_from_logs(out)))
context.outputs = read_json_file(Path(get_results_file_path_from_logs(out)))


@then("the simulation takes less than {seconds:d} seconds")
Expand Down Expand Up @@ -97,6 +108,40 @@ def check_solution(context):
assert_dict_allclose(context.outputs["solution"]["values"], expected_solution)


def is_column_full_of_zeros(filename, column_index, abs_tol=1e-9):
with open(filename, 'r') as file:
# Skip the header
next(file)

# Check each line in the file
for line in file:
columns = line.split()

# Ensure column exists
if column_index >= len(columns):
print(f"Error: Missing column at index {column_index} in line: {line.strip()}")
return False

try:
value = float(columns[column_index])
except (ValueError, IndexError):
print(f"Error parsing line: {line.strip()}")
return False

# Use math.isclose to compare to zero with tolerance
if not math.isclose(value, 0.0, abs_tol=abs_tol):
print(f"Error {value} is not close to 0")
return False

return True


@then("LOLD.txt and PositiveUnsuppliedEnergy.txt files are full of zeros")
def check_other_outputs(context):
assert (is_column_full_of_zeros(context.loss_of_load_file, 2))
assert (is_column_full_of_zeros(context.positive_unsupplied_energy_file, 2))


def get_results_file_path_from_logs(logs: bytes) -> str:
for line in logs.splitlines():
if b'Optimization results available in : ' in line:
Expand Down

0 comments on commit 68412f8

Please sign in to comment.