Skip to content

Commit

Permalink
Merge remote-tracking branch 'j-bryan/teal' into inno_work
Browse files Browse the repository at this point in the history
  • Loading branch information
joshua-cogliati-inl committed Feb 14, 2024
2 parents 1de277d + 920177c commit 9b877b5
Show file tree
Hide file tree
Showing 24 changed files with 699 additions and 17 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ write_inner.py
.ravenStatus
romMeta.xml

# Ignore built executables
build

# Allow these files
!FORCE_logo-bw.png
!FORCE_logo-color.png


# python stuff
__pycache__
*.pyc
*.pyc
28 changes: 20 additions & 8 deletions package/heron.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,26 @@
# limitations under the License.
import re
import sys
import os
from HERON.src.main import main
from ui import run_from_gui
from utils import add_local_bin_to_path


if __name__ == '__main__':
script_path = os.path.dirname(sys.argv[0])
local_path = os.path.join(script_path,"local","bin")
if os.path.exists(local_path):
os.environ['PATH'] += (os.pathsep+local_path)
print("PATH",os.environ['PATH'], "local_path", local_path)
from HERON.src.main import main
import argparse

sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())
parser = argparse.ArgumentParser(description='HERON')
parser.add_argument('-w', action='store_true', default=False, required=False, help='Run in the GUI')
parser.add_argument('file', nargs='?', help='Case file to run')
args = parser.parse_args()

# Adds the "local/bin" directory to the system path in order to find ipopt and other executables
add_local_bin_to_path()

if args.file:
sys.argv = [sys.argv[0], args.file]
if args.w or not args.file: # if asked to or if no file is passed, run the GUI
run_from_gui(main)
else:
main()
4 changes: 0 additions & 4 deletions package/print_loc.py

This file was deleted.

23 changes: 22 additions & 1 deletion package/raven_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,26 @@
"""
import sys
from ravenframework.Driver import main
from ui import run_from_gui
from utils import add_local_bin_to_path


if __name__ == '__main__':
sys.exit(main(True))
import argparse

parser = argparse.ArgumentParser(description='RAVEN')
parser.add_argument('-w', action='store_true', default=False, required=False,help='Run in the GUI')
parser.add_argument('file', nargs='?', help='Case file to run')
args = parser.parse_args()

# Adds the "local/bin" directory to the system path in order to find ipopt and other executables
add_local_bin_to_path()

if args.file:
sys.argv = [sys.argv[0], args.file]
if args.w or not args.file: # if asked to or if no file is passed, run the GUI
# run_from_gui(lambda: sys.exit(main(True)))
run_from_gui(main, checkLibraries=True)
# run_from_gui(main)
else:
sys.exit(main(True))
1 change: 1 addition & 0 deletions package/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ install_requires =
raven-framework
teal-ravenframework
heron-ravenframework
ipopt
21 changes: 18 additions & 3 deletions package/setup.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import sys
import platform
import os
from cx_Freeze import setup, Executable, build_exe

import HERON.templates.write_inner


build_exe_options = {
"packages": ["ravenframework","msgpack","ray","crow_modules","AMSC","sklearn","pyomo","HERON"],
"packages": ["ravenframework","msgpack","ray","crow_modules","AMSC","sklearn","pyomo","HERON","TEAL","pyarrow","netCDF4","cftime"],
"includes": ["ray.thirdparty_files.colorama","ray.autoscaler._private","pyomo.common.plugins","HERON.templates.template_driver"],
"include_files": [(HERON.templates.write_inner.__file__,"lib/HERON/templates/write_inner.py")],
"include_msvcr": True,
}

# Some files must be included manually for the Windows build
if platform.system().lower() == "windows":
# netCDF4 .dll files get missed by cx_Freeze
# ipopt executable must be included manually
netCDF4_libs_path = os.path.join(os.path.dirname(sys.executable), "lib", "site-packages", "netCDF4.libs")
build_exe_options["include_files"] += [
("Ipopt-3.14.13-win64-msvs2019-md/","local/bin/Ipopt-3.14.13-win64-msvs2019-md"), # FIXME: Point to the correct location for ipopt executable
(netCDF4_libs_path,"lib/netCDF4")
]
# Include the Microsoft Visual C++ Runtime
build_exe_options["include_msvcr"] = True


setup(
name="force",
version="0.1",
description="FORCE package",
executables=[Executable(script="raven_framework.py",icon="raven_64.ico"),
Executable(script="heron.py",icon="heron_64.ico")],
Executable(script="heron.py",icon="heron_64.ico"),
Executable(script="teal.py",icon="teal_64.ico")],
options={"build_exe": build_exe_options},
)
46 changes: 46 additions & 0 deletions package/teal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env python
# Copyright 2017 Battelle Energy Alliance, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Created on Feb 8, 2024
@author: j-bryan (Jacob Bryan)
Runs the TEAL package as a standalone application.
"""
import sys
from TEAL.src.CashFlow_ExtMod import TEALmain
from ui import run_from_gui


if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='RAVEN')
parser.add_argument('-w', action='store_true', default=False, required=False,help='Run in the GUI')
parser.add_argument('-iXML', nargs=1, required=False, help='XML CashFlow input file name', metavar='inp_file')
parser.add_argument('-iINP', nargs=1, required=False, help='CashFlow input file name with the input variable list', metavar='inp_file')
parser.add_argument('-o', nargs=1, required=False, help='Output file name', metavar='out_file')
args = parser.parse_args()

# Remove the -w argument from sys.argv so it doesn't interfere with TEAL's argument parsing
if args.w:
sys.argv.remove('-w')

# If the -w argument is present or any of the other arguments are missing, run the GUI
if args.w or not args.iXML or not args.iINP or not args.o:
print('Running TEAL in GUI mode')
run_from_gui(TEALmain)
else:
print('Running TEAL in command line mode')
sys.exit(TEALmain())
1 change: 1 addition & 0 deletions package/ui/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .main import run_from_gui
1 change: 1 addition & 0 deletions package/ui/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .main import Controller
109 changes: 109 additions & 0 deletions package/ui/controllers/file_selection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from typing import Optional
import os
import tkinter as tk
import argparse
from collections import namedtuple


class FileSpec:
""" Input/output file specification for a package. """
def __init__(self,
arg_name: str,
description: str,
default_filename: Optional[str] = None,
is_output: bool = False,
file_type: Optional[str] = None):
"""
Constructor
@In, arg_name, str, optional, the argument flag for the file
@In, description, str, the description of the file
@In, default_filename, str, optional, the default filename
@In, is_output, bool, optional, whether the file is an output file
@In, file_type, str, optional, the type of file
@Out, None
"""
self.arg_name = arg_name
self.description = description
self.default_filename = default_filename
self.is_output = is_output
self.file_type = file_type

def add_to_parser(self, parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"""
Adds the file specification to the parser.
@In, parser, argparse.ArgumentParser, the parser
@Out, parser, argparse.ArgumentParser, the parser with the file specification added
"""
if self.arg_name.startswith('-'):
parser.add_argument(self.arg_name, nargs=1, required=False, default=self.default_filename,
help=self.description)
else: # positional argument
parser.add_argument(self.arg_name, nargs='?', default=self.default_filename, help=self.description)
return parser


class FileSelectionController:
""" Controller for the file selection widget. """
_file_selection_specs = {
'teal': [FileSpec('-iXML', 'XML File', file_type='xml'),
FileSpec('-iINP','CashFlow File'),
FileSpec('-o', 'Output File', is_output=True)],
'ravenframework': [FileSpec('filename', 'RAVEN XML File', file_type='xml')],
'heron': [FileSpec('filename', 'HERON Input File', file_type='xml')]
}

def __init__(self, model, view):
"""
Constructor
@In, model, Model, the model
@In, view, FileSelection, the view
@Out, None
"""
self.file_selection = view

# Create the file selectors, adding any files specified from the command line
model_package_name = model.get_package_name().strip().lower()
self._file_specs = self._file_selection_specs[model_package_name]
cli_args = self._parse_cli_args()
for spec in self._file_specs:
filename = cli_args.get(spec.arg_name, spec.default_filename)
if isinstance(filename, (list, tuple)): # argparse returns items in a tuple sometimes
filename = filename[0]
self.file_selection.add_file_selector(spec.description, filename, spec.file_type, spec.is_output)

def get_files(self):
"""
Gets the files selected by the user and returns them as a list along with their
corresponding argument flags, if any.
@In, None
@Out, files, list, a list of files and their corresponding argument flags, if any
"""
files = []
for spec in self._file_specs:
# Get the filename from the file selector
filename = self.file_selection.file_selectors[spec.description].get_filename()
# Add the filename with its corresponding argument flag to the list
if not os.path.exists(filename) and spec.arg_name != '-o':
raise FileNotFoundError(f"File {filename} not found")
if spec.arg_name.startswith('-'): # flag argument
files.extend([spec.arg_name, filename])
else: # positional argument
files.append(filename)
return files

def _parse_cli_args(self) -> dict[str]:
"""
Parse arguments provided from the command line
@In, None
@Out, args, dict, the parsed arguments
"""
parser = argparse.ArgumentParser()
for spec in self._file_specs:
parser = spec.add_to_parser(parser)
args = vars(parser.parse_args())
# Any arguments with flags will have had the '-' stripped off. It'll be helpful to know which
# arguments were specified with flags, so we'll add the '-' back to the keys.
for key in list(args.keys()):
if f'-{key}' in [spec.arg_name for spec in self._file_specs]:
args[f'-{key}'] = args.pop(key)
return args
36 changes: 36 additions & 0 deletions package/ui/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import sys

from .file_selection import FileSelectionController
from .status import StatusController
from .text_output import TextOutputController


class Controller:
def __init__(self, model, view):
self.model = model
self.view = view

# Initialize controllers
self.file_selection_controller = FileSelectionController(self.model, self.view.frames["file_selection"])
self.status_panel_controller = StatusController(self.model, self.view.frames["status_panel"])
self.text_output_controller = TextOutputController(self.model, self.view.frames["text_output"])

# Bind the run button to the model
self.view.frames["run_abort"].run_button.config(command=self.run_model)
# Bind the abort button to closing the window
self.view.frames["run_abort"].abort_button.config(command=self.view.quit)

# Bind Ctrl-C to closing the window for convenvience
self.view.root.bind('<Control-c>', lambda cmd: self.view.root.destroy())

def run_model(self):
# Construct sys.argv from the file selectors
sys.argv = [sys.argv[0]] + self.file_selection_controller.get_files()
print('sys.argv:', sys.argv)
# Start the model
self.model.start()
# Status update loop
self.view.frames["status_panel"].after(100, self.status_panel_controller.update_status)

def start(self):
self.view.mainloop()
25 changes: 25 additions & 0 deletions package/ui/controllers/status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import tkinter as tk
import datetime


class StatusController(tk.Frame):
""" Controller for the status panel. """
def __init__(self, model, view):
self.model = model
self.view = view

def set_status(self, status):
self.view.status.set(f'Status: {status}')

def set_timer(self, time):
self.view.timer.set(f'Time: {time}')

def update_status(self):
self.set_status('Running' if self.model.is_alive() else 'Idle')
time_elapsed = round(self.model.get_execution_time())
self.set_timer(f'{datetime.timedelta(seconds=time_elapsed)}')
self.view.update()
if self.model.is_alive():
self.view.after(100, self.update_status)
else:
self.set_status('Idle')
Loading

0 comments on commit 9b877b5

Please sign in to comment.