Skip to content

Commit

Permalink
Change names of several arguments, change rtd.
Browse files Browse the repository at this point in the history
Refactor deprecation utility
  • Loading branch information
PaulScheerRLI committed Sep 20, 2024
1 parent 3a06d0a commit a91647a
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 88 deletions.
14 changes: 7 additions & 7 deletions data/examples/simba.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ scenario_name = example
##### Paths #####
### Input and output files and paths ###
# Input file containing trip information (required)
input_schedule = data/examples/trips_example.csv
schedule_path = data/examples/trips_example.csv
# Output files are stored here (defaults to: data/sim_outputs)
# Attention: In Windows the path-length is limited to 256 characters!
# Deactivate storage of output by setting output_directory = null
output_directory = data/output/
output_path = data/output/
# Electrified stations (required)
electrified_stations = data/examples/electrified_stations.json
electrified_stations_path = data/examples/electrified_stations.json
# Vehicle types (defaults to: ./data/examples/vehicle_types.json)
vehicle_types = data/examples/vehicle_types.json
vehicle_types_path = data/examples/vehicle_types.json
# Path to station data with stations heights
# (Optional: needed if mileage in vehicle types not constant and inclination should be considered)
station_data_path = data/examples/all_stations.csv
Expand All @@ -23,11 +23,11 @@ outside_temperature_over_day_path = data/examples/default_temp_winter.csv
# (Optional: needed if mileage in vehicle types not constant)
level_of_loading_over_day_path = data/examples/default_level_of_loading_over_day.csv
# Path to configuration file for the station optimization. Only needed for mode "station_optimization"
optimizer_config = data/examples/default_optimizer.cfg
optimizer_config_path = data/examples/default_optimizer.cfg
# Cost parameters (needed if cost_calculation flag is set to true, see Flag section below)
cost_parameters_file = data/examples/cost_params.json
cost_parameters_path = data/examples/cost_params.json
# Path to rotation filter
rotation_filter = data/examples/rotation_filter.csv
rotation_filter_path = data/examples/rotation_filter.csv

##### Modes #####
### Specify how you want to simulate the scenario ###
Expand Down
2 changes: 2 additions & 0 deletions docs/source/modes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ To make use of this feature the parameters in the optimizer.cfg have to be set.
decision_tree_path = data/last_optimization.pickle
save_decision_tree = True

.. _optimizer_config:

Optimizer Configuration
###################################
The functionality of the optimizer is controlled through the optimizer.cfg specified in the simba.cfg used for calling SimBA.
Expand Down
16 changes: 12 additions & 4 deletions docs/source/simulation_parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,19 @@ The example (data/simba.cfg) contains parameter descriptions which are explained
- Optional: no default given
- string
- scenario identifier, appended to output directory name and report file names
* - input_schedule
* - schedule_path
- Mandatory: no default given
- Path as string
- Input file containing :ref:`schedule` information
* - Output_directory
* - output_path
- Data/sim_outputs
- Path as string
- Output files are stored here; set to null to deactivate
* - electrified_stations_path
- ./data/examples/vehicle_types.json
- Path as string
- Path to Electrified stations data
* - vehicle_types
* - vehicle_types_path
- ./data/examples/vehicle_types.json
- Path as string
- Path to :ref:`vehicle_types`
Expand All @@ -60,10 +60,18 @@ The example (data/simba.cfg) contains parameter descriptions which are explained
- Optional: no default given
- Path as string
- Path to :ref:`level_of_loading`
* - cost_parameters_file
* - cost_parameters_path
- Optional: no default given
- Path as string
- Path to :ref:`cost_params`
* - optimizer_config_path
- Optional: no default given
- Path as string
- Path to station optimizer config :ref:`optimizer_config`
* - rotation_filter_path
- Optional: no default given
- Path as string
- Path to rotation filter json
* - mode
- ['sim', 'report']
- List of modes is any order in range of ['sim', 'neg_depb_to_oppb', 'neg_oppb_to_depb', 'service_optimization', 'report']
Expand Down
12 changes: 6 additions & 6 deletions simba/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
dir_name = time_str + '_' + args.scenario_name
else:
dir_name = time_str
if args.output_directory is not None:
args.output_directory = Path(args.output_directory) / dir_name
if args.output_path is not None:
args.output_path = Path(args.output_path) / dir_name
# create subfolder for specific sim results with timestamp.
# if folder doesn't exist, create folder.
# needs to happen after set_options_from_config since
# args.output_directory can be overwritten by config
args.output_directory_input = args.output_directory / "input_data"
# args.output_path can be overwritten by config
args.output_directory_input = args.output_path / "input_data"
try:
args.output_directory_input.mkdir(parents=True, exist_ok=True)
except NotADirectoryError:
# can't create new directory (may be write protected): no output
args.output_directory = None
if args.output_directory is not None:
args.output_path = None
if args.output_path is not None:
# copy input files to output to ensure reproducibility
copy_list = [args.config, args.electrified_stations_path, args.vehicle_types_path]
if "station_optimization" in args.mode:
Expand Down
5 changes: 2 additions & 3 deletions simba/data_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,13 @@ def __init__(self):
def fill_with_args(self, args: argparse.Namespace) -> 'DataContainer':
""" Fill DataContainer with data from file_paths defined in args.
:param args: Arguments containing paths for input_schedule, vehicle_types_path,
:param args: Arguments containing paths for schedule_path, vehicle_types_path,
electrified_stations_path, cost_parameters_path, outside_temperature_over_day_path,
level_of_loading_over_day_path, station_data_path
:return: self
"""

return self.fill_with_paths(
trips_file_path=args.input_schedule,
trips_file_path=args.schedule_path,
vehicle_types_path=args.vehicle_types_path,
electrified_stations_path=args.electrified_stations_path,
cost_parameters_path=args.cost_parameters_path,
Expand Down
16 changes: 8 additions & 8 deletions simba/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ def from_datacontainer(cls, data: DataContainer, args):
if vars(args).get("check_rotation_consistency"):
# check rotation expectations
inconsistent_rotations = cls.check_consistency(schedule)
if inconsistent_rotations and args.output_directory is not None:
if inconsistent_rotations and args.output_path is not None:
# write errors to file
filepath = args.output_directory / "inconsistent_rotations.csv"
filepath = args.output_path / "inconsistent_rotations.csv"
with open(filepath, "w", encoding='utf-8') as f:
for rot_id, e in inconsistent_rotations.items():
f.write(f"Rotation {rot_id}: {e}\n")
Expand Down Expand Up @@ -223,7 +223,7 @@ def run(self, args, mode="distributed"):
For external usage the core run functionality is accessible through this function.
It allows for defining a custom-made assign_vehicles method for the schedule.
:param args: used arguments are rotation_filter, path to rotation ids,
:param args: used arguments are rotation_filter_path, path to rotation ids,
and rotation_filter_variable that sets mode (options: include, exclude)
:type args: argparse.Namespace
:param mode: SpiceEV strategy name
Expand Down Expand Up @@ -800,7 +800,7 @@ def get_total_distance(self):
def rotation_filter(self, args, rf_list=[]):
""" Edits rotations according to args.rotation_filter_variable.
:param args: used arguments are rotation_filter, path to rotation ids,
:param args: used arguments are rotation_filter_path, path to rotation ids,
and rotation_filter_variable that sets mode (options: include, exclude)
:type args: argparse.Namespace
:param rf_list: rotation filter list with strings of rotation ids (default is None)
Expand All @@ -812,18 +812,18 @@ def rotation_filter(self, args, rf_list=[]):
# cast rotations in filter to string
rf_list = [str(i) for i in rf_list]

if args.rotation_filter is None and not rf_list:
if args.rotation_filter_path is None and not rf_list:
warnings.warn("Rotation filter variable is enabled but file and list are not used.")
return

if args.rotation_filter:
if args.rotation_filter_path:
# read out rotations from file (one rotation ID per line)
try:
with open(args.rotation_filter, encoding='utf-8') as f:
with open(args.rotation_filter_path, encoding='utf-8') as f:
for line in f:
rf_list.append(line.strip())
except FileNotFoundError:
warnings.warn(f"Path to rotation filter {args.rotation_filter} is invalid.")
warnings.warn(f"Path to rotation filter {args.rotation_filter_path} is invalid.")
# no file, no change
return
# filter out rotations in self.rotations
Expand Down
8 changes: 4 additions & 4 deletions simba/simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def modes_simulation(schedule, scenario, args):
if scenario is not None and scenario.step_i > 0:
# generate plot of failed scenario
args.mode = args.mode[:i] + ["ABORTED"]
if args.output_directory is None:
if args.output_path is None:
create_results_directory(args, i+1)
if not args.skip_plots:
report.generate_plots(scenario, args)
Expand Down Expand Up @@ -260,7 +260,7 @@ def split_negative_depb(schedule, scenario, args, _i):

@staticmethod
def report(schedule, scenario, args, i):
if args.output_directory is None:
if args.output_path is None:
return schedule, scenario

# create report based on all previous modes
Expand Down Expand Up @@ -288,12 +288,12 @@ def create_results_directory(args, i):
:type i: int
"""

if args.output_directory is None:
if args.output_path is None:
return

prior_reports = sum([m.count('report') for m in args.mode[:i]])
report_name = f"report_{prior_reports+1}"
args.results_directory = args.output_directory.joinpath(report_name)
args.results_directory = args.output_path.joinpath(report_name)
args.results_directory.mkdir(parents=True, exist_ok=True)
# save used modes in report version
used_modes = ['sim'] + [m for m in args.mode[:i] if m not in ['sim', 'report']]
Expand Down
87 changes: 55 additions & 32 deletions simba/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def cast_float_or_none(val: any) -> any:
def setup_logging(args, time_str):
""" Setup logging.
:param args: command line arguments. Used: logfile, loglevel, output_directory
:param args: command line arguments. Used: logfile, loglevel, output_path
:type args: argparse.Namespace
:param time_str: log file name if args.logfile is not given
:type time_str: str
Expand All @@ -272,13 +272,14 @@ def setup_logging(args, time_str):
console = logging.StreamHandler()
console.setLevel(log_level)
log_handlers = [console]
if args.logfile is not None and args.output_directory is not None:

if args.logfile is not None and args.output_path is not None:
# optionally to file in output dir
if args.logfile:
log_name = args.logfile
else:
log_name = f"{time_str}.log"
log_path = args.output_directory / log_name
log_path = args.output_path / log_name
print(f"Writing log to {log_path}")
file_logger = logging.FileHandler(log_path, encoding='utf-8')
log_level_file = vars(logging).get((args.loglevel_file or args.loglevel).upper())
Expand Down Expand Up @@ -340,28 +341,41 @@ def get_buffer_time(trip, default=0):


def replace_deprecated_arguments(args):
if args.electrified_stations is not None:
assert args.electrified_stations_path is None, \
"Multiple electrified stations are not supported."
logging.warning("The parameter 'electrified_stations' is deprecated. "
"Use 'electrified_stations_path 'instead.")
args.electrified_stations_path = args.electrified_stations
del args.electrified_stations

if args.vehicle_types is not None:
logging.warning("The parameter 'vehicle_types' is deprecated. "
"Use 'vehicle_types_path 'instead. The value of args.vehicle_types_path "
f"{args.vehicle_types_path} is replaced with {args.vehicle_types}.")
args.vehicle_types_path = args.vehicle_types
del args.vehicle_types

if args.cost_parameters_file is not None:
assert args.cost_parameters_path is None, \
"Multiple cost parameters files are not supported."
logging.warning("The parameter 'cost_parameters_file' is deprecated. "
"Use 'cost_parameters_path 'instead.")
args.cost_parameters_path = args.cost_parameters_file
del args.cost_parameters_file
# handling of args with default values
# Pairs of deprecated names and new names
deprecated_names = [
("input_schedule", "schedule_path"),
("vehicle_types", "vehicle_types_path"),
("output_directory", "output_path"),
]
for old, new in deprecated_names:
if vars(args)[old] is not None:
logging.warning(
f"Parameter '{old}' is deprecated. Use '{new}' instead. The value of args.{new}: "
f"{args.__getattribute__(new)} is replaced with {args.__getattribute__(old)} .")
# Replace value of current name with value of deprecated name
args.__setattr__(new, args.__getattribute__(old))
# delete deprecated name
args.__delattr__(old)

# Pairs of deprecated names and new names and verbose name
deprecated_names = [
("electrified_stations", "electrified_stations_path", "electrified stations paths"),
("cost_parameters_file", "cost_parameters_path", "costs parameter paths"),
("rotation_filter", "rotation_filter_path", "rotation filter paths"),
("optimizer_config", "optimizer_config_path", "station optimizer config paths"),
]

for old, new, description in deprecated_names:
if vars(args)[old] is not None:
assert vars(args)[new] is None, \
f"Multiple {description} are not supported. Found values for {old} and {new}."
logging.warning(f"The parameter '{old}' is deprecated. "
f"Use '{new}' instead.")
# Replace value of current name with value of deprecated name
args.__setattr__(new, args.__getattribute__(old))
# delete deprecated name
args.__delattr__(old)
return args


Expand Down Expand Up @@ -389,7 +403,7 @@ def get_args():
# rename special options
args.timing = args.eta

mandatory_arguments = ["input_schedule", "electrified_stations_path"]
mandatory_arguments = ["schedule_path", "electrified_stations_path"]
missing = [a for a in mandatory_arguments if vars(args).get(a) is None]
if missing:
raise Exception("The following arguments are required: {}".format(", ".join(missing)))
Expand All @@ -403,9 +417,9 @@ def get_parser():
parser.add_argument('--scenario-name', help='Identifier of scenario, appended to results')

# #### Paths #####
parser.add_argument('--input-schedule',
parser.add_argument('--schedule-path',
help='Path to CSV file containing all trips of schedule to be analyzed')
parser.add_argument('--output-directory', default="data/sim_outputs",
parser.add_argument('--output-path', default="data/sim_outputs",
help='Location where all simulation outputs are stored')
parser.add_argument('--electrified-stations-path', help='include electrified_stations json')
parser.add_argument('--vehicle-types-path', default="data/examples/vehicle_types.json",
Expand All @@ -421,7 +435,7 @@ def get_parser():
level of loading in case they are not in trips.csv")
parser.add_argument('--cost-parameters-path', default=None,
help='include cost parameters json, needed if cost_calculation==True')
parser.add_argument('--rotation-filter', default=None,
parser.add_argument('--rotation-filter-path', default=None,
help='Use json data with rotation ids')

# #### Modes #####
Expand Down Expand Up @@ -456,6 +470,9 @@ def get_parser():
parser.add_argument('--create-scenario-file', help='Write scenario.json to file')
parser.add_argument('--create-trips-in-report', action='store_true',
help='Write a trips.csv during report mode')
parser.add_argument('--optimizer-config-path', default=None,
help="For station_optimization an optimizer_config is needed. \
Input a path to an .cfg file or use the default_optimizer.cfg")
parser.add_argument('--rotation-filter-variable', default=None,
choices=[None, 'include', 'exclude'],
help='set mode for filtering schedule rotations')
Expand Down Expand Up @@ -553,19 +570,25 @@ def get_parser():
parser.add_argument('--include-price-csv-option', '-po', metavar=('KEY', 'VALUE'),
nargs=2, default=[], action='append',
help='append additional argument to price signals')
parser.add_argument('--optimizer_config', default=None,
help="For station_optimization an optimizer_config is needed. \
Input a path to an .cfg file or use the default_optimizer.cfg")
parser.add_argument('--time-windows', metavar='FILE',
help='use peak load windows to force lower power '
'during times of high grid load')

# Deprecated options for downwards compatibility
parser.add_argument('--input-schedule', default=None,
help='Deprecated use "schedule-path" instead')
parser.add_argument('--electrified-stations', default=None,
help='Deprecated use "electrified-stations-path" instead')
parser.add_argument('--vehicle-types', default=None,
help='Deprecated use "vehicle-types-path" instead')
parser.add_argument('--cost-parameters-file', default=None,
help='Deprecated use "cost-parameters-path" instead')
parser.add_argument('--rotation-filter', default=None,
help='Deprecated use "rotation-filter-path" instead')
parser.add_argument('--output-directory', default=None,
help='Deprecated use "output-path" instead')
parser.add_argument('--optimizer_config', default=None,
help='Deprecated use "optimizer-config-path" instead')

parser.add_argument('--config', help='Use config file to set arguments')
return parser
2 changes: 1 addition & 1 deletion tests/test_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def test_example_cfg(self, tmp_path):
# provide path to input data
src_text = src_text.replace("data/examples", str(EXAMPLE_PATH))
# write output to tmp
src_text = re.sub(r"output_directory.+", f"output_directory = {str(tmp_path.as_posix())}",
src_text = re.sub(r"output_path.+", f"output_path = {str(tmp_path.as_posix())}",
src_text)
dst = tmp_path / "simba.cfg"
# don't show plots. spaces are optional, so use regex
Expand Down
Loading

0 comments on commit a91647a

Please sign in to comment.