-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add all the new stuff that didn't get comitted earlier
- Loading branch information
1 parent
67f2f57
commit 8e63da1
Showing
31 changed files
with
18,683 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from dataclasses import dataclass | ||
from typing import Callable | ||
|
||
from alfalfa_worker.lib.models import Point | ||
|
||
|
||
@dataclass | ||
class AlfalfaPoint: | ||
point: Point | ||
handle: int | ||
converter: Callable[[float], float] = lambda x: x |
84 changes: 84 additions & 0 deletions
84
alfalfa_worker/jobs/openstudio/lib/workflow/measures/alfalfa_python_environment/measure.py
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,84 @@ | ||
"""insert your copyright here. | ||
# see the URL below for information on how to write OpenStudio measures | ||
# http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/ | ||
""" | ||
|
||
import os | ||
from pathlib import Path | ||
|
||
import openstudio | ||
|
||
|
||
class AlfalfaPythonEnvironment(openstudio.measure.EnergyPlusMeasure): | ||
"""An EnergyPlusMeasure.""" | ||
|
||
def name(self): | ||
"""Returns the human readable name. | ||
Measure name should be the title case of the class name. | ||
The measure name is the first contact a user has with the measure; | ||
it is also shared throughout the measure workflow, visible in the OpenStudio Application, | ||
PAT, Server Management Consoles, and in output reports. | ||
As such, measure names should clearly describe the measure's function, | ||
while remaining general in nature | ||
""" | ||
return "AlfalfaPythonEnvironment" | ||
|
||
def description(self): | ||
"""Human readable description. | ||
The measure description is intended for a general audience and should not assume | ||
that the reader is familiar with the design and construction practices suggested by the measure. | ||
""" | ||
return "Add alfalfa python environment to IDF" | ||
|
||
def modeler_description(self): | ||
"""Human readable description of modeling approach. | ||
The modeler description is intended for the energy modeler using the measure. | ||
It should explain the measure's intent, and include any requirements about | ||
how the baseline model must be set up, major assumptions made by the measure, | ||
and relevant citations or references to applicable modeling resources | ||
""" | ||
return "Add python script path to IDF" | ||
|
||
def arguments(self, workspace: openstudio.Workspace): | ||
"""Prepares user arguments for the measure. | ||
Measure arguments define which -- if any -- input parameters the user may set before running the measure. | ||
""" | ||
args = openstudio.measure.OSArgumentVector() | ||
|
||
return args | ||
|
||
def run( | ||
self, | ||
workspace: openstudio.Workspace, | ||
runner: openstudio.measure.OSRunner, | ||
user_arguments: openstudio.measure.OSArgumentMap, | ||
): | ||
"""Defines what happens when the measure is run.""" | ||
super().run(workspace, runner, user_arguments) # Do **NOT** remove this line | ||
|
||
if not (runner.validateUserArguments(self.arguments(workspace), user_arguments)): | ||
return False | ||
|
||
run_dir = os.getenv("RUN_DIR") | ||
if run_dir: | ||
venv_dir = Path(run_dir) / '.venv' | ||
if venv_dir.exists(): | ||
python_paths = openstudio.IdfObject(openstudio.IddObjectType("PythonPlugin:SearchPaths")) | ||
python_paths.setString(0, "Alfalfa Virtual Environment Path") | ||
python_paths.setString(1, 'No') | ||
python_paths.setString(2, 'No') | ||
python_paths.setString(3, 'No') | ||
python_paths.setString(4, str(venv_dir / 'lib' / 'python3.12' / 'site-packages')) | ||
|
||
workspace.addObject(python_paths) | ||
|
||
return True | ||
|
||
|
||
# register the measure to be used by the application | ||
AlfalfaPythonEnvironment().registerWithApplication() |
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,122 @@ | ||
import threading | ||
from ctypes import c_wchar_p | ||
from datetime import datetime | ||
from multiprocessing import Manager, Process | ||
from time import time | ||
|
||
from alfalfa_worker.jobs.step_run_base import StepRunBase | ||
from alfalfa_worker.lib.constants import DATETIME_FORMAT | ||
from alfalfa_worker.lib.job import message | ||
from alfalfa_worker.lib.job_exception import ( | ||
JobExceptionExternalProcess, | ||
JobExceptionMessageHandler | ||
) | ||
from alfalfa_worker.lib.utils import exc_to_str | ||
|
||
|
||
class StepRunProcess(StepRunBase): | ||
|
||
def __init__(self, run_id: str, realtime: bool, timescale: int, external_clock: bool, start_datetime: str, end_datetime: str, **kwargs) -> None: | ||
super().__init__(run_id, realtime, timescale, external_clock, start_datetime, end_datetime) | ||
self.manager = Manager() | ||
self.advance_event = self.manager.Event() | ||
self.running_event = self.manager.Event() | ||
self.stop_event = self.manager.Event() | ||
self.error_event = self.manager.Event() | ||
self.error_log = self.manager.Value(c_wchar_p, '') | ||
self.simulation_process: Process | ||
self.subprocess: bool = False | ||
self.timestamp = self.manager.Value(c_wchar_p, '') | ||
|
||
def initialize_simulation(self) -> None: | ||
self.simulation_process = Process(target=StepRunProcess._start_simulation_process, args=(self,)) | ||
self.simulation_process.start() | ||
|
||
self._wait_for_event(self.running_event, self.options.start_timeout, desired_event_set=True) | ||
self.update_run_time() | ||
|
||
def set_run_time(self, sim_time: datetime): | ||
if self.subprocess: | ||
self.timestamp.value = sim_time.strftime(DATETIME_FORMAT) | ||
else: | ||
return super().set_run_time(sim_time) | ||
|
||
def update_run_time(self) -> None: | ||
if self.subprocess: | ||
super().update_run_time() | ||
else: | ||
self.set_run_time(datetime.strptime(self.timestamp.value, DATETIME_FORMAT)) | ||
|
||
def _start_simulation_process(self) -> None: | ||
self.subprocess = True | ||
try: | ||
return self.start_simulation_process() | ||
except Exception: | ||
self.catch_exception() | ||
|
||
def start_simulation_process(self) -> None: | ||
raise NotImplementedError | ||
|
||
def handle_process_error(self) -> None: | ||
if self.simulation_process.is_alive(): | ||
self.simulation_process.kill() | ||
raise JobExceptionExternalProcess(self.error_log.value) | ||
|
||
def catch_exception(self, notes: list[str]) -> None: | ||
if self.subprocess: | ||
exception_log = exc_to_str() | ||
self.error_log.value = exception_log | ||
if len(notes) > 0: | ||
self.error_log.value += "\n\n" + '\n'.join(notes) | ||
self.error_event.set() | ||
|
||
def check_simulation_stop_conditions(self) -> bool: | ||
return not self.simulation_process.is_alive() | ||
|
||
def check_for_errors(self): | ||
exit_code = self.simulation_process.exitcode | ||
if exit_code: | ||
raise JobExceptionExternalProcess(f"Simulation process exited with non-zero exit code: {exit_code}") | ||
|
||
def _wait_for_event(self, event: threading.Event, timeout: float, desired_event_set: bool = False): | ||
wait_until = time() + timeout | ||
while (event.is_set() != desired_event_set | ||
and time() < wait_until | ||
and self.simulation_process.is_alive() | ||
and not self.error_event.is_set()): | ||
self.check_for_errors() | ||
if desired_event_set: | ||
event.wait(1) | ||
if self.error_event.is_set(): | ||
self.handle_process_error() | ||
if not self.simulation_process.is_alive(): | ||
self.check_for_errors() | ||
raise JobExceptionExternalProcess("Simulation process exited without returning an error") | ||
if time() > wait_until: | ||
self.simulation_process.kill() | ||
raise TimeoutError("Timedout waiting for simulation process to toggle event") | ||
|
||
@message | ||
def advance(self) -> None: | ||
self.logger.info(f"Advance called at {self.run.sim_time}") | ||
if self.advance_event.is_set(): | ||
raise JobExceptionMessageHandler("Cannot advance, simulation is already advancing") | ||
self.advance_event.set() | ||
self._wait_for_event(self.advance_event, timeout=self.options.advance_timeout, desired_event_set=False) | ||
self.update_run_time() | ||
|
||
@message | ||
def stop(self): | ||
self.logger.info("Stop called, stopping") | ||
if not self.stop_event.is_set(): | ||
stop_start = time() | ||
self.stop_event.set() | ||
while (self.simulation_process.is_alive() | ||
and time() - stop_start < self.options.stop_timeout | ||
and not self.error_event.is_set()): | ||
pass | ||
if time() - stop_start > self.options.stop_timeout and self.simulation_process.is_alive(): | ||
self.simulation_process.kill() | ||
raise JobExceptionExternalProcess("Simulation process stopped responding and was killed.") | ||
if self.error_event.is_set(): | ||
self.handle_process_error() |
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 @@ | ||
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' |
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,36 @@ | ||
class JobException(Exception): | ||
pass | ||
|
||
|
||
class JobExceptionMessageHandler(JobException): | ||
"""Thrown when there is an exception that occurs in an message handler. | ||
This is caught and reported back to the caller via redis.""" | ||
|
||
|
||
class JobExceptionInvalidModel(JobException): | ||
"""Thrown when working on a model. | ||
ex: missing osw""" | ||
|
||
|
||
class JobExceptionInvalidRun(JobException): | ||
"""Thrown when working on run. | ||
ex. run does not have necessary files""" | ||
|
||
|
||
class JobExceptionExternalProcess(JobException): | ||
"""Thrown when an external process throws an error. | ||
ex. E+ can't run idf""" | ||
|
||
|
||
class JobExceptionFailedValidation(JobException): | ||
"""Thrown when the job fails validation for any reason. | ||
ex. file that should have been generated was not""" | ||
|
||
|
||
class JobExceptionSimulation(JobException): | ||
"""Thrown when there is a simulation issue. | ||
ex. Simulation falls too far behind in timescale run""" | ||
|
||
|
||
class JobExceptionTimeout(JobException): | ||
"""Thrown when a timeout is triggered in the job""" |
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,17 @@ | ||
from logging import Handler, LogRecord | ||
|
||
from alfalfa_worker.lib.alfalfa_connections_manager import ( | ||
AlafalfaConnectionsManager | ||
) | ||
from alfalfa_worker.lib.models import Run | ||
|
||
|
||
class RedisLogHandler(Handler): | ||
def __init__(self, run: Run, level: int | str = 0) -> None: | ||
super().__init__(level) | ||
connections_manager = AlafalfaConnectionsManager() | ||
self.redis = connections_manager.redis | ||
self.run = run | ||
|
||
def emit(self, record: LogRecord) -> None: | ||
self.redis.rpush(f"run:{self.run.ref_id}:log", self.format(record)) |
19 changes: 19 additions & 0 deletions
19
tests/integration/broken_models/small_office/bad_callback.osw
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,19 @@ | ||
{ | ||
"seed_file" : "small_office.osm", | ||
"weather_file": "USA_OH_Dayton-Wright.Patterson.AFB.745700_TMY3.epw", | ||
"measure_paths": [ | ||
"./measures/" | ||
], | ||
"file_paths": [ | ||
"./weather/" | ||
], | ||
"run_directory": "./run/", | ||
"steps" : [ | ||
{ | ||
"measure_dir_name" : "python_bad_callback", | ||
"name" : "PythonEMS", | ||
"description" : "Add python EMS to IDF", | ||
"modeler_description" : "Add python EMS to IDF", | ||
} | ||
] | ||
} |
19 changes: 19 additions & 0 deletions
19
tests/integration/broken_models/small_office/bad_constructor.osw
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,19 @@ | ||
{ | ||
"seed_file" : "small_office.osm", | ||
"weather_file": "USA_OH_Dayton-Wright.Patterson.AFB.745700_TMY3.epw", | ||
"measure_paths": [ | ||
"./measures/" | ||
], | ||
"file_paths": [ | ||
"./weather/" | ||
], | ||
"run_directory": "./run/", | ||
"steps" : [ | ||
{ | ||
"measure_dir_name" : "python_bad_constructor", | ||
"name" : "PythonEMS", | ||
"description" : "Add python EMS to IDF", | ||
"modeler_description" : "Add python EMS to IDF", | ||
} | ||
] | ||
} |
19 changes: 19 additions & 0 deletions
19
tests/integration/broken_models/small_office/bad_module_class.osw
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,19 @@ | ||
{ | ||
"seed_file" : "small_office.osm", | ||
"weather_file": "USA_OH_Dayton-Wright.Patterson.AFB.745700_TMY3.epw", | ||
"measure_paths": [ | ||
"./measures/" | ||
], | ||
"file_paths": [ | ||
"./weather/" | ||
], | ||
"run_directory": "./run/", | ||
"steps" : [ | ||
{ | ||
"measure_dir_name" : "python_bad_module_class", | ||
"name" : "PythonEMS", | ||
"description" : "Add python EMS to IDF", | ||
"modeler_description" : "Add python EMS to IDF", | ||
} | ||
] | ||
} |
19 changes: 19 additions & 0 deletions
19
tests/integration/broken_models/small_office/bad_module_name.osw
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,19 @@ | ||
{ | ||
"seed_file" : "small_office.osm", | ||
"weather_file": "USA_OH_Dayton-Wright.Patterson.AFB.745700_TMY3.epw", | ||
"measure_paths": [ | ||
"./measures/" | ||
], | ||
"file_paths": [ | ||
"./weather/" | ||
], | ||
"run_directory": "./run/", | ||
"steps" : [ | ||
{ | ||
"measure_dir_name" : "python_bad_module_name", | ||
"name" : "PythonEMS", | ||
"description" : "Add python EMS to IDF", | ||
"modeler_description" : "Add python EMS to IDF", | ||
} | ||
] | ||
} |
Oops, something went wrong.