Skip to content
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

IDF.from_example_files uses the as_version argument to pick from the correct folder of example files (based on the EnergyPlus version) #506

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f6b9c53
adds IDF.copy() and IDF.saveas(inplace=True) (#254)
Dec 3, 2021
6ec65a1
Adjusts svg repr to the min/max values of the schedule (#255)
Dec 3, 2021
0ef37ee
Graceful warning when Slab or Basement program is not found
samuelduchesne Dec 3, 2021
6d78dee
Adds KeyBoardInterupt to IDF Thread
samuelduchesne Dec 8, 2021
53db076
catches more variations of unit name
samuelduchesne Dec 8, 2021
2453bd4
Adds ability to scale a schedule
samuelduchesne Dec 8, 2021
3ec77f9
Fixes fallback limits for Schedule.plot2d() when Type is not defined
samuelduchesne Dec 9, 2021
8718452
Type can be specified in Schedule.from_values constructor
samuelduchesne Dec 9, 2021
fb3b741
plot2d is prettier by default
samuelduchesne Dec 9, 2021
ef2331e
more Typing
samuelduchesne Dec 9, 2021
ac47fa1
Return existing object when new_object is there (#257)
Dec 10, 2021
b4b9a38
Adds ability to replace schedule values without affecting the full lo…
samuelduchesne Dec 20, 2021
9e677dd
more robust IDF.name property
samuelduchesne Jan 20, 2022
818f0ed
Keep sim files when error occurs (#276)
Feb 1, 2022
2e21b69
updates requests requirement from ~=2.25.1 to >=2.26 (#292)
zberzolla Apr 19, 2022
423ff41
Merge branch 'main' into develop
samuelduchesne Jul 21, 2024
88cd43a
Merge branch 'hotfix/v2.18.2' into develop
samuelduchesne Jul 21, 2024
f8f29b0
typo
samuelduchesne Sep 13, 2024
eb91e4b
this
samuelduchesne Sep 13, 2024
4325fdb
gitsubmodule as https
samuelduchesne Sep 13, 2024
ec51575
p
samuelduchesne Sep 13, 2024
a1e1ce6
pp
samuelduchesne Sep 13, 2024
26f54aa
this
samuelduchesne Sep 13, 2024
3286240
error
samuelduchesne Sep 13, 2024
44f49a8
catch outfile
samuelduchesne Sep 13, 2024
bef3e4b
logging
samuelduchesne Sep 13, 2024
fb1efb0
Revert "logging"
samuelduchesne Sep 13, 2024
a3fd384
keep
samuelduchesne Sep 13, 2024
dd136f7
as model
samuelduchesne Sep 13, 2024
d5876a4
better logging
samuelduchesne Sep 13, 2024
c1d163b
allow setting `as_version` in `IDF.from_example_files` to load exampl…
samuelduchesne Sep 13, 2024
e1884d0
Trigger Build
samuelduchesne Sep 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "geomeppy"]
path = geomeppy
url = git@github.com:samuelduchesne/geomeppy.git
url = https://github.com/samuelduchesne/geomeppy.git
26 changes: 16 additions & 10 deletions archetypal/eplus_interface/basement.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ def run(self):
# get version from IDF object or by parsing the IDF file for it

# Move files into place
# copy "%wthrfile%.epw" in.epw
self.epw = self.idf.epw.copy(self.run_dir / "in.epw").expand()
self.idfname = Path(self.idf.savecopy(self.run_dir / "in.idf")).expand()
self.idd = self.idf.iddname.copy(self.run_dir).expand()
Expand All @@ -60,7 +59,7 @@ def run(self):
basemenet_exe = shutil.which("Basement", path=self.eplus_home)
if basemenet_exe is None:
log(
f"The Basement program could not be found at " f"'{self.eplus_home}",
f"The Basement program could not be found at '{self.eplus_home}'",
lg.WARNING,
)
return
Expand Down Expand Up @@ -90,7 +89,7 @@ def run(self):
self.msg_callback(f"Weather File: {self.epw}")

# Run Slab Program
with logging_redirect_tqdm(loggers=[lg.getLogger(self.idf.name)]):
with logging_redirect_tqdm(loggers=[lg.getLogger("archetypal")]):
with tqdm(
unit_scale=True,
miniters=1,
Expand All @@ -109,10 +108,16 @@ def run(self):
"Begin Basement Temperature Calculation processing . . ."
)

for line in self.p.stdout:
self.msg_callback(line.decode("utf-8").strip("\n"))
# Read stdout line by line
for line in iter(self.p.stdout.readline, b""):
decoded_line = line.decode("utf-8").strip()
self.msg_callback(decoded_line)
progress.update()

# Process stderr after stdout is fully read
stderr = self.p.stderr.read()
stderr_lines = stderr.decode("utf-8").splitlines()

# We explicitly close stdout
self.p.stdout.close()

Expand All @@ -121,20 +126,21 @@ def run(self):

# Communicate callbacks
if self.cancelled:
self.msg_callback("RunSlab cancelled")
self.msg_callback("Basement cancelled")
# self.cancelled_callback(self.std_out, self.std_err)
else:
if self.p.returncode == 0:
self.msg_callback(
"RunSlab completed in {:,.2f} seconds".format(
"Basement completed in {:,.2f} seconds".format(
time.time() - start_time
)
)
self.success_callback()
for line in self.p.stderr:
self.msg_callback(line.decode("utf-8"))
for line in stderr_lines:
self.msg_callback(line)
else:
self.msg_callback("RunSlab failed")
self.msg_callback("Basement failed")
self.msg_callback("\n".join(stderr_lines), level=lg.ERROR)
self.failure_callback()

def msg_callback(self, *args, **kwargs):
Expand Down
84 changes: 51 additions & 33 deletions archetypal/eplus_interface/slab.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import shutil
import subprocess
import time
from io import StringIO
from threading import Thread

from packaging.version import Version
Expand All @@ -12,7 +13,6 @@
from tqdm.contrib.logging import logging_redirect_tqdm

from archetypal.eplus_interface.exceptions import EnergyPlusProcessError
from archetypal.eplus_interface.version import EnergyPlusVersion
from archetypal.utils import log


Expand Down Expand Up @@ -41,59 +41,66 @@ def __init__(self, idf, tmp):
@property
def cmd(self):
"""Get the command."""
cmd_path = Path(shutil.which("Slab", path=self.run_dir))
return [cmd_path]
# if platform is windows
return [self.slabexe]

def run(self):
"""Wrapper around the EnergyPlus command line interface."""
"""Wrapper around the Slab command line interface."""
self.cancelled = False
# get version from IDF object or by parsing the IDF file for it

# Move files into place
self.epw = self.idf.epw.copy(self.run_dir / "in.epw").expand()
self.idfname = Path(self.idf.savecopy(self.run_dir / "in.idf")).expand()
self.idd = self.idf.iddname.copy(self.run_dir).expand()

# Get executable using shutil.which (determines the extension based on
# the platform, eg: .exe. And copy the executable to tmp
# Get executable using shutil.which
slab_exe = shutil.which("Slab", path=self.eplus_home)
if slab_exe is None:
log(
f"The Slab program could not be found at " f"'{self.eplus_home}'",
f"The Slab program could not be found at '{self.eplus_home}'",
lg.WARNING,
)
return
self.slabexe = Path(slab_exe).copy(self.run_dir)
else:
slab_exe = (self.eplus_home / slab_exe).expand()
self.slabexe = slab_exe
self.slabidd = (self.eplus_home / "SlabGHT.idd").copy(self.run_dir)
self.outfile = self.idf.name

# The GHTin.idf file is copied from the self.include list (added by
# ExpandObjects. If self.include is empty, no need to run Slab.
# The GHTin.idf file is copied from the self.include list
self.include = [Path(file).copy(self.run_dir) for file in self.idf.include]
if not self.include:
self.cleanup_callback()
return

# Run Slab Program
with logging_redirect_tqdm(loggers=[lg.getLogger(self.idf.name)]):
with logging_redirect_tqdm(loggers=[lg.getLogger("archetypal")]):
with tqdm(
unit_scale=True,
miniters=1,
desc=f"RunSlab #{self.idf.position}-{self.idf.name}",
desc=f"{self.slabexe} #{self.idf.position}-{self.idf.name}",
position=self.idf.position,
) as progress:
self.p = subprocess.Popen(
self.cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True, # can use shell
cwd=self.run_dir.abspath(),
shell=False,
cwd=self.run_dir,
)
start_time = time.time()
self.msg_callback("Begin Slab Temperature Calculation processing . . .")
for line in self.p.stdout:
self.msg_callback(line.decode("utf-8").strip("\n"))

# Read stdout line by line
for line in iter(self.p.stdout.readline, b""):
decoded_line = line.decode("utf-8").strip()
self.msg_callback(decoded_line)
progress.update()

# Process stderr after stdout is fully read
stderr = self.p.stderr.read()
stderr_lines = stderr.decode("utf-8").splitlines()

# We explicitly close stdout
self.p.stdout.close()

Expand All @@ -102,20 +109,20 @@ def run(self):

# Communicate callbacks
if self.cancelled:
self.msg_callback("RunSlab cancelled")
# self.cancelled_callback(self.std_out, self.std_err)
self.msg_callback("Slab cancelled")
else:
if self.p.returncode == 0:
self.msg_callback(
"RunSlab completed in {:,.2f} seconds".format(
"Slab completed in {:,.2f} seconds".format(
time.time() - start_time
)
)
self.success_callback()
for line in self.p.stderr:
self.msg_callback(line.decode("utf-8"))
for line in stderr_lines:
self.msg_callback(line)
else:
self.msg_callback("RunSlab failed")
self.msg_callback("Slab failed", level=lg.ERROR)
self.msg_callback("\n".join(stderr_lines), level=lg.ERROR)
self.failure_callback()

def msg_callback(self, *args, **kwargs):
Expand All @@ -124,16 +131,27 @@ def msg_callback(self, *args, **kwargs):

def success_callback(self):
"""Parse surface temperature and append to IDF file."""
temp_schedule = self.run_dir / "SLABSurfaceTemps.txt"
if temp_schedule.exists():
with open(self.idf.idfname, "a") as outfile:
with open(temp_schedule) as infile:
next(infile) # Skipping first line
next(infile) # Skipping second line
for line in infile:
outfile.write(line)
# invalidate attributes dependant on idfname, since it has changed
self.idf._reset_dependant_vars("idfname")
for temp_schedule in self.run_dir.glob("SLABSurfaceTemps*"):
if temp_schedule.exists():
slab_models = self.idf.__class__(
StringIO(open(temp_schedule, "r").read()),
file_version=self.idf.file_version,
as_version=self.idf.as_version,
prep_outputs=False,
)
# Loop on all objects and using self.newidfobject
added_objects = []
for sequence in slab_models.idfobjects.values():
if sequence:
for obj in sequence:
data = obj.to_dict()
key = data.pop("key")
added_objects.append(
self.idf.newidfobject(key=key.upper(), **data)
)
del slab_models # remove loaded_string model
else:
self.msg_callback("No SLABSurfaceTemps.txt file found.", level=lg.ERROR)
self.cleanup_callback()

def cleanup_callback(self):
Expand Down
12 changes: 6 additions & 6 deletions archetypal/idfclass/idf.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,10 @@ def from_example_files(cls, example_name, epw=None, **kwargs):
from pathlib import Path as Pathlib

example_name = Path(example_name)
example_files_dir: Path = (
EnergyPlusVersion.current().current_install_dir / "ExampleFiles"
eplus_version = EnergyPlusVersion(
kwargs.get("as_version", EnergyPlusVersion.current())
)
example_files_dir: Path = eplus_version.current_install_dir / "ExampleFiles"
try:
file = next(
iter(Pathlib(example_files_dir).rglob(f"{example_name.stem}.idf"))
Expand All @@ -399,9 +400,7 @@ def from_example_files(cls, example_name, epw=None, **kwargs):
epw = Path(epw)

if not epw.exists():
dir_weather_data_ = (
EnergyPlusVersion.current().current_install_dir / "WeatherData"
)
dir_weather_data_ = eplus_version.current_install_dir / "WeatherData"
try:
epw = next(
iter(Pathlib(dir_weather_data_).rglob(f"{epw.stem}.epw"))
Expand Down Expand Up @@ -1485,7 +1484,8 @@ def simulate(self, force=False, **kwargs):
except KeyboardInterrupt:
slab_thread.stop()
finally:
tmp.rmtree(ignore_errors=True)
if not self.keep_data_err:
tmp.rmtree(ignore_errors=True)
e = slab_thread.exception
if e is not None:
raise e
Expand Down
4 changes: 2 additions & 2 deletions archetypal/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,13 @@ def get_logger(level=None, name=None, filename=None, log_dir=None):
todays_date = dt.datetime.today().strftime("%Y_%m_%d")

if not log_dir:
log_dir = settings.logs_folder
log_dir: Path = settings.logs_folder

log_filename = log_dir / "{}_{}.log".format(filename, todays_date)

# if the logs folder does not already exist, create it
if not log_dir.exists():
log_dir.makedirs_p()
os.mkdir(log_dir)
# create file handler and log formatter and set them up
formatter = lg.Formatter(
"%(asctime)s [%(process)d] %(levelname)s - %(name)s - %(" "message)s"
Expand Down
15 changes: 15 additions & 0 deletions tests/test_idfclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ def test_copy_saveas(self, idf_model, tmp_path):
# assert saveas returns another object
assert idf_copy.saveas(tmp_path / "in.idf", inplace=False) is not idf_copy

def test_copy_saveas(self, idf_model, tmp_path):
"""Test making a copy of self and two ways of saving as (inplace or not)."""
idf_copy = idf_model.copy() # make a copy of self

assert idf_copy is not idf_model

# assert saveas modifies self inplace.
id_before = id(idf_copy)
idf_copy.saveas(tmp_path / "in.idf", inplace=True)
id_after = id(idf_copy)
assert id_after == id_before

# assert saveas returns another object
assert idf_copy.saveas(tmp_path / "in.idf", inplace=False) is not idf_copy

def test_default_version_none(self):
file = (
"tests/input_data/necb/NECB 2011-FullServiceRestaurant-NECB HDD "
Expand Down
Loading