-
Notifications
You must be signed in to change notification settings - Fork 22
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
Define glpk launcher and expose command line and api option #112
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
7276e76
feat: wip additional solver option
254e950
chore: move launchers to separate file
ebddb01
fix: ignore threads if not supported
144cb45
chore: move some logic to a function
1fe18fc
chore: improve user experience
8a36ba5
fix: need to set threads, even if ignored
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import os | ||
from time import time | ||
|
||
import pandas as pd | ||
|
||
from pyreisejl.utility.helpers import ( | ||
InvalidDateArgument, | ||
InvalidInterval, | ||
extract_date_limits, | ||
sec2hms, | ||
validate_time_format, | ||
validate_time_range, | ||
) | ||
|
||
|
||
class Launcher: | ||
"""Parent class for solver-specific scenario launchers. | ||
|
||
:param str start_date: start date of simulation as 'YYYY-MM-DD HH:MM:SS', | ||
where HH, MM, and SS are optional. | ||
:param str end_date: end date of simulation as 'YYYY-MM-DD HH:MM:SS', | ||
where HH, MM, and SS are optional. | ||
:param int interval: length of each interval in hours | ||
:param str input_dir: directory with input data | ||
:raises InvalidDateArgument: if start_date is posterior to end_date | ||
:raises InvalidInterval: if the interval doesn't evently divide the given date range | ||
""" | ||
|
||
def __init__(self, start_date, end_date, interval, input_dir): | ||
"""Constructor.""" | ||
# extract time limits from 'demand.csv' | ||
with open(os.path.join(input_dir, "demand.csv")) as profile: | ||
min_ts, max_ts, freq = extract_date_limits(profile) | ||
|
||
dates = pd.date_range(start=min_ts, end=max_ts, freq=freq) | ||
|
||
start_ts = validate_time_format(start_date) | ||
end_ts = validate_time_format(end_date, end_date=True) | ||
|
||
# make sure the dates are within the time frame we have data for | ||
validate_time_range(start_ts, min_ts, max_ts) | ||
validate_time_range(end_ts, min_ts, max_ts) | ||
|
||
if start_ts > end_ts: | ||
raise InvalidDateArgument( | ||
f"The start date ({start_ts}) cannot be after the end date ({end_ts})." | ||
) | ||
|
||
# Julia starts at 1 | ||
start_index = dates.get_loc(start_ts) + 1 | ||
end_index = dates.get_loc(end_ts) + 1 | ||
|
||
# Calculate number of intervals | ||
ts_range = end_index - start_index + 1 | ||
if ts_range % interval > 0: | ||
raise InvalidInterval( | ||
"This interval does not evenly divide the given date range." | ||
) | ||
self.start_index = start_index | ||
self.interval = interval | ||
self.n_interval = int(ts_range / interval) | ||
self.input_dir = input_dir | ||
print("Validation complete!") | ||
|
||
def _print_settings(self): | ||
print("Launching scenario with parameters:") | ||
print( | ||
{ | ||
"interval": self.interval, | ||
"n_interval": self.n_interval, | ||
"start_index": self.start_index, | ||
"input_dir": self.input_dir, | ||
"execute_dir": self.execute_dir, | ||
"threads": self.threads, | ||
} | ||
) | ||
|
||
def launch_scenario(self): | ||
# This should be defined in sub-classes | ||
raise NotImplementedError | ||
|
||
|
||
class GLPKLauncher(Launcher): | ||
def launch_scenario(self, execute_dir=None, threads=None, solver_kwargs=None): | ||
"""Launches the scenario. | ||
|
||
:param None/str execute_dir: directory for execute data. None defaults to an | ||
execute folder that will be created in the input directory | ||
:param None/int threads: number of threads to use. | ||
:param None/dict solver_kwargs: keyword arguments to pass to solver (if any). | ||
:return: (*int*) runtime of scenario in seconds | ||
""" | ||
self.execute_dir = execute_dir | ||
self.threads = threads | ||
self._print_settings() | ||
print("INFO: threads not supported by GLPK, ignoring") | ||
|
||
from julia.api import Julia | ||
|
||
Julia(compiled_modules=False) | ||
from julia import GLPK # noqa: F401 | ||
from julia import REISE | ||
|
||
start = time() | ||
REISE.run_scenario( | ||
interval=self.interval, | ||
n_interval=self.n_interval, | ||
start_index=self.start_index, | ||
inputfolder=self.input_dir, | ||
outputfolder=self.execute_dir, | ||
optimizer_factory=GLPK.Optimizer, | ||
) | ||
end = time() | ||
|
||
runtime = round(end - start) | ||
hours, minutes, seconds = sec2hms(runtime) | ||
print(f"Run time: {hours}:{minutes:02d}:{seconds:02d}") | ||
|
||
return runtime | ||
|
||
|
||
class GurobiLauncher(Launcher): | ||
def launch_scenario(self, execute_dir=None, threads=None, solver_kwargs=None): | ||
"""Launches the scenario. | ||
|
||
:param None/str execute_dir: directory for execute data. None defaults to an | ||
execute folder that will be created in the input directory | ||
:param None/int threads: number of threads to use. | ||
:param None/dict solver_kwargs: keyword arguments to pass to solver (if any). | ||
:return: (*int*) runtime of scenario in seconds | ||
""" | ||
self.execute_dir = execute_dir | ||
self.threads = threads | ||
self._print_settings() | ||
# Import these within function because there is a lengthy compilation step | ||
from julia.api import Julia | ||
|
||
Julia(compiled_modules=False) | ||
from julia import Gurobi # noqa: F401 | ||
from julia import REISE | ||
|
||
start = time() | ||
REISE.run_scenario_gurobi( | ||
interval=self.interval, | ||
n_interval=self.n_interval, | ||
start_index=self.start_index, | ||
inputfolder=self.input_dir, | ||
outputfolder=self.execute_dir, | ||
threads=self.threads, | ||
) | ||
end = time() | ||
|
||
runtime = round(end - start) | ||
hours, minutes, seconds = sec2hms(runtime) | ||
print(f"Run time: {hours}:{minutes:02d}:{seconds:02d}") | ||
|
||
return runtime | ||
|
||
|
||
_launch_map = {"gurobi": GurobiLauncher, "glpk": GLPKLauncher} | ||
|
||
|
||
def get_available_solvers(): | ||
return list(_launch_map.keys()) | ||
|
||
|
||
def get_launcher(solver): | ||
if solver is None: | ||
return GurobiLauncher | ||
if solver.lower() not in _launch_map.keys(): | ||
raise ValueError("Invalid solver") | ||
return _launch_map[solver] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to maximize our DRY, we could define a
round_and_print_runtime
method of theLauncher
class and then callreturn self.round_and_print_runtime(start, end)
, but it's not really necessary.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I wanted to reuse the code here but since it's interleaved it felt like the result would be harder to read. If we end up adding more solvers I would want to do something about it.