Skip to content

Commit 1c72d6a

Browse files
committed
Add option for info.txt file
1 parent f0f3f69 commit 1c72d6a

File tree

6 files changed

+103
-68
lines changed

6 files changed

+103
-68
lines changed

docs/Contribute.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,36 @@ Read [`docs/Graphs.md`](./Graphs.md) to see learn to add graphs.
4444
## Modifying the database
4545

4646
Read [`docs/Database.md`](./Database.md) to learn about the database.
47+
48+
## Outputting results
49+
50+
Once the model is solved, the `post_solve()` function in each module is called.
51+
Within the `post_solve()` function you may
52+
53+
- Call `write_table()` to create
54+
a .csv file with data from the solution (see existing modules for examples).
55+
56+
- Call `add_info()` (from `utilities/result_info.py`) to add a line
57+
of information to the `outputs/info.txt` file. `add_info()` can also be added to `graph()`.
58+
59+
### Example
60+
61+
```python
62+
from switch_model.utilities.results_info import add_info
63+
from switch_model.reporting import write_table
64+
import os
65+
...
66+
def post_solve(instance, outdir):
67+
...
68+
# This will add the a line to info.txt in the outputs folder
69+
add_info("Some important value", instance.important_value)
70+
...
71+
# This will create my_table.csv
72+
write_table(
73+
instance,
74+
instance.TIMEPOINTS, # Set that the values function will iterate over
75+
output_file=os.path.join(outdir, "my_table.csv"),
76+
headings=("timepoint", "some value"),
77+
values=lambda m, t: (t, m.some_value[t])
78+
)
79+
```

switch_model/generators/core/dispatch.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,10 @@ def graph_curtailment_per_tech(tools):
696696
title="Percent of total dispatchable capacity curtailed",
697697
)
698698
# Plot
699-
df.plot(ax=ax, kind="line", color=tools.get_colors(), xlabel="Period")
699+
color = tools.get_colors()
700+
kwargs = dict() if color is None else dict(color=color)
701+
df.plot(ax=ax, kind="line", xlabel="Period", **kwargs)
702+
700703
# Set the y-axis to use percent
701704
ax.yaxis.set_major_formatter(tools.mplt.ticker.PercentFormatter(1.0))
702705
# Horizontal line at 100%

switch_model/reporting/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
2020
"""
2121
from __future__ import print_function
22-
from switch_model.utilities import string_types
22+
from switch_model.utilities import string_types, add_info
2323
from switch_model.utilities.scaling import get_unscaled_var
2424

2525
dependencies = "switch_model.financials"
@@ -235,8 +235,10 @@ def get_value(obj):
235235

236236

237237
def save_total_cost_value(instance, outdir):
238+
total_cost = round(value(instance.SystemCost), ndigits=2)
239+
add_info("Total Cost", f"$ {total_cost}")
238240
with open(os.path.join(outdir, "total_cost.txt"), "w") as fh:
239-
fh.write("{}\n".format(round(value(instance.SystemCost), ndigits=2)))
241+
fh.write(f"{total_cost}\n")
240242

241243

242244
def save_cost_components(m, outdir):

switch_model/solve.py

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@
1010

1111
import sys, os, shlex, re, inspect, textwrap, types, pickle, traceback, gc
1212
import warnings
13+
import datetime
14+
import platform
1315

14-
from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import DirectOrPersistentSolver
16+
from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import (
17+
DirectOrPersistentSolver,
18+
)
1519

1620
import switch_model
1721
from switch_model.utilities import (
@@ -22,13 +26,14 @@
2226
LogOutput,
2327
warn,
2428
query_yes_no,
25-
create_info_file,
2629
get_module_list,
2730
add_module_args,
2831
_ScaledVariable,
32+
add_git_info,
2933
)
3034
from switch_model.upgrade import do_inputs_need_upgrade, upgrade_inputs
3135
from switch_model.tools.graphing import graph
36+
from switch_model.utilities.results_info import save_info, add_info, ResultsInfoSection
3237

3338

3439
def main(
@@ -118,6 +123,9 @@ def debug(type, value, tb):
118123
if not os.path.isdir(model.options.outputs_dir):
119124
raise IOError("Directory specified for prior solution does not exist.")
120125

126+
add_info("Host name", platform.node(), section=ResultsInfoSection.GENERAL)
127+
add_git_info()
128+
121129
# get a list of modules to iterate through
122130
iterate_modules = get_iteration_list(model)
123131

@@ -229,8 +237,23 @@ def debug(type, value, tb):
229237
graph.main(args=["--overwrite"])
230238

231239
total_time = start_to_end_timer.step_time_as_str()
232-
create_info_file(
233-
getattr(instance.options, "outputs_dir", "outputs"), run_time=total_time
240+
add_info("Total run time", total_time, section=ResultsInfoSection.GENERAL)
241+
242+
add_info(
243+
"End date",
244+
datetime.datetime.now().strftime("%Y-%m-%d"),
245+
section=ResultsInfoSection.GENERAL,
246+
)
247+
add_info(
248+
"End time",
249+
datetime.datetime.now().strftime("%H:%M:%S"),
250+
section=ResultsInfoSection.GENERAL,
251+
)
252+
253+
save_info(
254+
os.path.join(
255+
getattr(instance.options, "outputs_dir", "outputs"), "info.txt"
256+
)
234257
)
235258

236259
if instance.options.verbose:
@@ -924,7 +947,9 @@ def solve(model):
924947
keepfiles=model.options.keepfiles,
925948
tee=model.options.tee,
926949
symbolic_solver_labels=model.options.symbolic_solver_labels,
927-
save_results=model.options.save_solution if isinstance(solver, DirectOrPersistentSolver) else None,
950+
save_results=model.options.save_solution
951+
if isinstance(solver, DirectOrPersistentSolver)
952+
else None,
928953
)
929954

930955
if model.options.warm_start is not None:
@@ -996,35 +1021,6 @@ def solve(model):
9961021
)
9971022
raise RuntimeError("Infeasible model")
9981023

999-
# Raise an error if the solver failed to produce a solution
1000-
# Note that checking for results.solver.status in {SolverStatus.ok,
1001-
# SolverStatus.warning} is not enough because with a warning there will
1002-
# sometimes be a solution and sometimes not.
1003-
# Note: the results object originally contains values for model components
1004-
# in results.solution.variable, etc., but pyomo.solvers.solve erases it via
1005-
# result.solution.clear() after calling model.solutions.load_from() with it.
1006-
# load_from() loads values into the model.solutions._entry, so we check there.
1007-
# (See pyomo.PyomoModel.ModelSolutions.add_solution() for the code that
1008-
# actually creates _entry).
1009-
# Another option might be to check that model.solutions[-1].status (previously
1010-
# result.solution.status, but also cleared) is in
1011-
# pyomo.opt.SolutionStatus.['optimal', 'bestSoFar', 'feasible', 'globallyOptimal', 'locallyOptimal'],
1012-
# but this seems pretty foolproof (if undocumented).
1013-
if len(model.solutions[-1]._entry["variable"]) == 0:
1014-
# no solution returned
1015-
print("Solver terminated without a solution.")
1016-
print(" Solver Status: ", results.solver.status)
1017-
print(" Solution Status: ", model.solutions[-1].status)
1018-
print(" Termination Condition: ", results.solver.termination_condition)
1019-
if (
1020-
model.options.solver == "glpk"
1021-
and results.solver.termination_condition == TerminationCondition.other
1022-
):
1023-
print(
1024-
"Hint: glpk has been known to classify infeasible problems as 'other'."
1025-
)
1026-
raise RuntimeError("Solver failed to find an optimal solution.")
1027-
10281024
# Report any warnings; these are written to stderr so users can find them in
10291025
# error logs (e.g. on HPC systems). These can occur, e.g., if solver reaches
10301026
# time limit or iteration limit but still returns a valid solution

switch_model/utilities/__init__.py

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@
77
from __future__ import print_function
88

99
import os, types, sys, argparse, time, datetime, traceback, subprocess, platform
10+
import warnings
1011

1112
import switch_model.__main__ as main
1213
from pyomo.environ import *
1314
from pyomo.core.base.set import UnknownSetDimen
1415
from pyomo.dataportal import DataManagerFactory
1516
from pyomo.dataportal.plugins.csv_table import CSVTable
17+
18+
from switch_model.utilities.results_info import add_info, ResultsInfoSection
1619
from switch_model.utilities.scaling import _ScaledVariable, _get_unscaled_expression
1720
import pyomo.opt
18-
import yaml
1921

2022
# Define string_types (same as six.string_types). This is useful for
2123
# distinguishing between strings and other iterables.
@@ -950,16 +952,6 @@ def query_yes_no(question, default="yes"):
950952
sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n")
951953

952954

953-
def load_config():
954-
"""Read the config.yaml configuration file"""
955-
if not os.path.isfile("config.yaml"):
956-
raise Exception(
957-
"config.yaml does not exist. Try running 'switch new' to auto-create it."
958-
)
959-
with open("config.yaml") as f:
960-
return yaml.load(f, Loader=yaml.FullLoader)
961-
962-
963955
def run_command(command):
964956
return (
965957
subprocess.check_output(command.split(" "), cwd=os.path.dirname(__file__))
@@ -968,28 +960,14 @@ def run_command(command):
968960
)
969961

970962

971-
def get_git_hash():
972-
return run_command("git rev-parse HEAD")
973-
974-
975-
def get_git_branch():
976-
return run_command("git rev-parse --abbrev-ref HEAD")
977-
978-
979-
def create_info_file(output_path, run_time=None):
980-
content = ""
981-
content += f"End date: {datetime.datetime.now().strftime('%Y-%m-%d')}\n"
982-
content += f"End time: {datetime.datetime.now().strftime('%H:%M:%S')}\n"
963+
def add_git_info():
983964
try:
984-
content += f"SWITCH Git Commit Hash: {get_git_hash()}\n"
985-
content += f"SWITCH Git Branch: {get_git_branch()}\n"
965+
commit_num = run_command("git rev-parse HEAD")
966+
branch = run_command("git rev-parse --abbrev-ref HEAD")
967+
add_info("Git Commit", commit_num, section=ResultsInfoSection.GENERAL)
968+
add_info("Git Branch", branch, section=ResultsInfoSection.GENERAL)
986969
except:
987-
print("Warning: failed to get commit hash or branch for info.txt.")
988-
if run_time is not None:
989-
content += f"Run time: {run_time}\n"
990-
content += f"Host name: {platform.node()}\n"
991-
with open(os.path.join(output_path, "info.txt"), "w") as f:
992-
f.write(content)
970+
warnings.warn("Failed to get Git Branch or Commit Hash for info.txt.")
993971

994972

995973
def get_module_list(args=None, include_solve_module=True):
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import enum
2+
3+
4+
class ResultsInfoSection(enum.Enum):
5+
GENERAL = "1. General"
6+
RESULTS = "2. Results"
7+
8+
9+
info = {}
10+
11+
12+
def add_info(name: str, value="", section=ResultsInfoSection.RESULTS):
13+
if section not in info:
14+
info[section] = []
15+
16+
info[section].append(str(name) + ": " + str(value))
17+
18+
19+
def save_info(filepath):
20+
with open(filepath, "w") as f:
21+
for section, rows in sorted(info.items(), key=lambda x: x[0].value):
22+
f.write(f"##########\n" f"{section.value}\n" f"##########\n\n")
23+
f.write("\n".join(rows) + "\n\n")

0 commit comments

Comments
 (0)