diff --git a/.jchat.json b/.jchat.json deleted file mode 100644 index 2a51e57..0000000 --- a/.jchat.json +++ /dev/null @@ -1 +0,0 @@ -{"model":"gpt-4-1106-preview","messages":[{"role":"system","content":"You are an polymath who is an expert is all scientific and engineering fields. You are able to leverage knowledge in other domains to solve the problem at hand. You carefully provide accurate, factual, thoughtful, nuanced answers, and are brilliant at reasoning. If you think there might not be a correct answer, you say so. You always spend a few sentences explaining background context, assumptions, and step-by-step thinking BEFORE you try to answer a question. Dont be verbose in your answers, but do provide details and examples where it might help the explanation. When showing code, minimise vertical space, and do not include comments or docstrings."},{"role":"user","content":"how to calculate the view factor between two concentric sphere, with diameter of 1 and 2 meters."},{"role":"assistant","content":"The view factor (also known as the configuration factor, shape factor, or form factor) is a dimensionless quantity used in radiation heat transfer calculations. It represents the fraction of the radiation leaving a surface that is intercepted by another surface. For two concentric spheres, the view factor can be determined by using the reciprocity relationship and the fact that a sphere completely encloses its view.\n\nView factor from the inner sphere (1 m diameter) to the outer sphere (2 m diameter) can be considered as a special case where the entire radiation leaving the inner surface will be intercepted by the outer surface. This is because the inner sphere is entirely enclosed by the outer sphere, and thus cannot \"see\" anything other than the outer sphere. In this case, the view factor from the smaller sphere to the larger sphere (F_1-2) is simply 1.\n\nTo calculate the view factor from the outer sphere to the inner sphere (F_2-1), we can use the reciprocity theorem. According to the reciprocity theorem:\n\nA_1 * F_1-2 = A_2 * F_2-1\n\nwhere A_1 is the surface area of the inner sphere, A_2 is the surface area of the outer sphere.\n\nGiven the diameters, the radius of the inner sphere (r_1) is 0.5 m and the radius of the outer sphere (r_2) is 1 m.\n\nThe surface areas (A) can be calculated for each sphere using the formula for the surface area of a sphere (A = 4 * pi * r^2):\n\nA_1 = 4 * pi * (0.5)^2\nA_1 = 4 * pi * 0.25\nA_1 = pi square meters\n\nA_2 = 4 * pi * (1)^2\nA_2 = 4 * pi square meters\n\nGiven that F_1-2 = 1, we can now use the reciprocity relation to solve for F_2-1:\n\npi * 1 = 4 * pi * F_2-1\n\nNow, solving for F_2-1:\n\nF_2-1 = pi / (4 * pi)\nF_2-1 = 1 / 4\n\nSo the view factor from the outer sphere to the inner sphere (F_2-1) is 0.25, or 25%. This means that 25% of the radiation leaving the outer sphere will be intercepted by the inner sphere, while the remaining 75% is lost to the surroundings.\n\nIn summary:\nF_1-2 = 1 (or 100%)\nF_2-1 = 0.25 (or 25%)"}],"stream":true} diff --git a/frads/__init__.py b/frads/__init__.py index 4284d33..733a06a 100755 --- a/frads/__init__.py +++ b/frads/__init__.py @@ -50,12 +50,6 @@ import logging -from .ep2rad import epmodel_to_radmodel - -from .eplus import load_energyplus_model, EnergyPlusSetup, ep_datetime_parser - -from .eplus_model import EnergyPlusModel - from .matrix import ( load_matrix, load_binary_matrix, @@ -94,10 +88,6 @@ Gap, Gas, GlazingSystem, - AIR, - ARGON, - KRYPTON, - XENON, ) __version__ = "1.2.1" @@ -106,15 +96,10 @@ __all__ = [ - "AIR", - "ARGON", - "EnergyPlusModel", - "EnergyPlusSetup", "FivePhaseMethod", "Gap", "Gas", "GlazingSystem", - "KRYPTON", "Matrix", "SensorSender", "SkyReceiver", @@ -128,15 +113,11 @@ "WeaData", "WeaMetaData", "WorkflowConfig", - "XENON", "create_glazing_system", - "ep_datetime_parser", - "epmodel_to_radmodel", "gen_grid", "gen_perez_sky", "genskymtx", "load_binary_matrix", - "load_energyplus_model", "load_matrix", "matrix_multiply_rgb", "parse_epw", diff --git a/frads/cli.py b/frads/cli/gen.py similarity index 61% rename from frads/cli.py rename to frads/cli/gen.py index 6d347ef..c3f002e 100644 --- a/frads/cli.py +++ b/frads/cli/gen.py @@ -1,405 +1,23 @@ """ -Executive command-line program for Radiance matrix-based simulation. +gen cli """ - import argparse -import configparser import logging -import os from pathlib import Path import sys -from typing import Dict, List, Optional -import numpy as np +from frads.matrix import load_matrix, Matrix, SensorSender, SkyReceiver, SurfaceSender, SurfaceReceiver, SunMatrix, SunReceiver, ViewSender +from frads.room import create_south_facing_room +from frads.utils import unpack_primitives, parse_polygon, gen_grid, primitive_normal, write_hdrs +from frads.window import PaneRGB, get_glazing_primitive +from frads import ncp +import pywincalc as pwc +from pyradiance import lib import pyradiance as pr -from pyradiance import SamplingParameters, lib +from pyradiance.param import SamplingParameters from pyradiance import param as rparam -from pyradiance import model as rmodel -import pywincalc as pwc - -from frads import ncp -from frads.eplus import load_energyplus_model -from frads.ep2rad import epmodel_to_radmodel -from frads.room import make_room -from frads.window import PaneRGB, get_glazing_primitive -from frads.matrix import ( - load_matrix, - Matrix, - SensorSender, - SkyReceiver, - SurfaceSender, - SurfaceReceiver, - SunMatrix, - SunReceiver, - ViewSender, -) -from frads.methods import ( - TwoPhaseMethod, - ThreePhaseMethod, - FivePhaseMethod, - WorkflowConfig, -) -from frads.utils import ( - gen_grid, - parse_polygon, - primitive_normal, - unpack_primitives, - write_hdrs, - write_ep_rad_model, -) - - -logger: logging.Logger = logging.getLogger("frads") - - -def parse_vu(vu_str: str) -> Optional[rmodel.View]: - """Parse view string into a View object. - - Args: - vu_str: view parameters as a string - - Returns: - A view object - """ - - if vu_str.strip() == "": - return - args_list = vu_str.strip().split() - vparser = argparse.ArgumentParser() - vparser = rparam.add_view_args(vparser) - vparser.add_argument("-x", type=int) - vparser.add_argument("-y", type=int) - args, _ = vparser.parse_known_args(args_list) - if args.vf is not None: - args, _ = vparser.parse_known_args( - args.vf.readline().strip().split(), namespace=args - ) - args.vf.close() - if None in (args.vp, args.vd): - raise ValueError("Invalid view") - view = rmodel.View( - position=args.vp, - direction=args.vd, - vtype=args.vt[-1], - horiz=args.vh, - vert=args.vv, - vfore=args.vo, - vaft=args.va, - hoff=args.vs, - voff=args.vl, - ) - if args.x is not None: - view.xres = args.x - if args.y is not None: - view.yres = args.y - return view - - -def parse_mrad_config(cfg_path: Path) -> Dict[str, dict]: - """ - Parse mrad configuration file. - Args: - cfg_path: path to the configuration file - Returns: - A dictionary of configuration in a format - that can be used by methods.WorkflowConfig - """ - if not cfg_path.is_file(): - raise FileNotFoundError(cfg_path) - config = configparser.ConfigParser( - allow_no_value=False, - inline_comment_prefixes="#", - interpolation=configparser.ExtendedInterpolation(), - converters={ - "path": lambda x: Path(x.strip()), - "paths": lambda x: [Path(i) for i in x.split()], - "spaths": lambda x: x.split(), - # "options": parse_opt, - "options": rparam.parse_rtrace_args, - "view": parse_vu, - }, - ) - config.read(Path(__file__).parent / "data" / "mrad_default.cfg") - config.read(cfg_path) - # Convert config to dict - config_dict = {} - for section in config.sections(): - config_dict[section] = {} - for key, val in config.items(section): - config_dict[section][key] = val - config_dict["settings"] = {**config_dict["SimControl"], **config_dict["Site"]} - for k, v in config_dict["settings"].items(): - # Convert sampling parameters string to list - if k.endswith("_matrix"): - config_dict["settings"][k] = v.split() - config_dict["settings"]["separate_direct"] = config["SimControl"].getboolean( - "separate_direct" - ) - config_dict["settings"]["overwrite"] = config["SimControl"].getboolean( - "overwrite", False - ) - config_dict["settings"]["save_matrices"] = config["SimControl"].getboolean( - "save_matrices", True - ) - config_dict["model"] = { - "scene": {}, - "materials": {}, - "windows": {}, - "views": {}, - "sensors": {}, - } - config_dict["model"]["scene"]["files"] = config["Model"].getspaths("scene") - config_dict["model"]["materials"]["files"] = config["Model"].getspaths("material") - config_dict["model"]["materials"]["matrices"] = { - k.stem: {"matrix_file": k} for k in config["Model"].getpaths("window_xmls") - } - for wpath, xpath in zip( - config["Model"].getpaths("windows"), config["Model"].getpaths("window_xmls") - ): - config_dict["model"]["windows"][wpath.stem] = { - "file": str(wpath), - "matrix_file": xpath.stem, - } - if (grid_files := config["RaySender"].getspaths("grid_points")) is not None: - for gfile in grid_files: - name = gfile.stem - with open(gfile) as f: - config_dict["model"]["sensors"][name] = { - "data": [[float(v) for v in l.split()] for l in f.readlines()] - } - elif (grid_paths := config["RaySender"].getpaths("grid_surface")) is not None: - for gpath in grid_paths: - name: str = gpath.stem - # Take the first polygon primitive - gprimitives = unpack_primitives(gpath) - surface_polygon = None - for prim in gprimitives: - if prim.ptype == "polygon": - surface_polygon = parse_polygon(prim) - break - if surface_polygon is None: - raise ValueError(f"No polygon found in {gpath}") - config_dict["model"]["sensors"][name] = { - "data": gen_grid( - surface_polygon, - config["RaySender"].getfloat("grid_height"), - config["RaySender"].getfloat("grid_spacing"), - ) - } - views = [i for i in config["RaySender"] if i.startswith("view")] - for vname in views: - if (view := config["RaySender"].getview("view")) is not None: - config_dict["model"]["views"][vname] = { - "view": " ".join(view.args()), - "xres": view.xres, - "yres": view.yres, - } - del ( - config_dict["SimControl"], - config_dict["Site"], - config_dict["Model"], - config_dict["RaySender"], - ) - return config_dict - - -def mrad_init(args: argparse.Namespace) -> None: - """Initiate mrad operation. - - Args: - args: argparse.Namespace - Returns: - None - """ - - def get_file_list(paths: List[Path], ext): - file_list: List[str] = [] - for path in paths: - if not path.is_dir(): - file_list.append(str(path)) - else: - file_list.extend(sorted(path.glob(f"*.{ext}"))) - return file_list - - config = configparser.ConfigParser(allow_no_value=False) - simcontrol = { - "# vmx_opt": "", - "# dmx_opt": "", - "# smx_basis": "", - "# overwrite": "", - "# separate_direct": "", - } - site = {} - model = {} - raysender = {} - if args.wea_path is not None: - if not args.wea_path.is_file(): - raise FileNotFoundError(args.wea_path) - site["wea_path"] = args.wea_path - elif args.epw_path is not None: - if not args.epw_path.is_file(): - raise FileNotFoundError(args.epw_path) - site["epw_path"] = args.epw_path - model["name"] = args.name - if args.grid is not None: - raysender["grid_surface"] = args.grid[0] - raysender["grid_spacing"] = args.grid[1] - raysender["grid_height"] = args.grid[2] - material_list = get_file_list(args.material, "mat") - object_list: List[str] = get_file_list(args.object, "rad") - model["scene"] = "\n".join(object_list) - model["material"] = "\n".join(material_list) - if args.window is not None: - window_list = get_file_list(args.window, "rad") - model["windows"] = "\n".join(window_list) - if args.bsdf is not None: - xml_list = get_file_list(args.bsdf, "xml") - if len(window_list) != len(xml_list): - raise ValueError("Number of window and xml files not the same") - model["window_xmls"] = "\n".join(xml_list) - templ_config = { - "SimControl": simcontrol, - "Site": site, - "Model": model, - "RaySender": raysender, - } - config.read_dict(templ_config) - with open(f"{args.name}.cfg", "w", encoding="utf-8") as rdr: - config.write(rdr) - - -def mrad_run(args: argparse.Namespace) -> None: - """Call mtxmethod to carry out the actual simulation.""" - config_dict = parse_mrad_config(args.cfg) - if config_dict["settings"]["name"] == "": - config_dict["settings"]["name"] = args.cfg.stem - wconfig = WorkflowConfig.from_dict(config_dict) - workflow = None - if method := wconfig.settings.method: - if method.startswith(("2", "two")): - logger.info("Using two-phase simulation") - workflow = TwoPhaseMethod(wconfig) - elif method.startswith(("3", "three")): - logger.info("Using three-phase simulation") - workflow = ThreePhaseMethod(wconfig) - elif method.startswith(("5", "five")): - logger.info("Using five-phase simulation") - workflow = FivePhaseMethod(wconfig) - else: - # Use 3- or 5-phase methods if we have - # window groups and bsdf defined - if len(wconfig.model.windows) > 1: - if wconfig.settings.separate_direct: - logger.info("Using five-phase simulation") - workflow = FivePhaseMethod(wconfig) - else: - logger.info("Using three-phase simulation") - workflow = ThreePhaseMethod(wconfig) - else: - logger.info("Using two-phase method") - workflow = TwoPhaseMethod(wconfig) - if workflow is None: - raise ValueError("No simulation method found") - workflow.generate_matrices() - for vname, view in wconfig.model.views.items(): - res = workflow.calculate_view_from_wea(vname) - write_hdrs(res, view.xres, view.yres, outdir=os.path.join("Results", vname)) - for sensor in wconfig.model.sensors: - res = workflow.calculate_sensor_from_wea(sensor) - np.savetxt(os.path.join("Results", f"{sensor}.txt"), res) - - -def mrad() -> None: - """mrad entry point: parse arugments for init and run subprograms.""" - parser = argparse.ArgumentParser( - prog="mrad", - description="Mrad is an executive program for Radiance matrix-based simulation methods.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - subparser = parser.add_subparsers() - # Parse arguments for init subprogram - parser_init = subparser.add_parser("init") - parser_init.set_defaults(func=mrad_init) - egroup = parser_init.add_mutually_exclusive_group(required=True) - egroup.add_argument("-a", "--wea_path", metavar="wea_path", type=Path) - egroup.add_argument("-e", "--epw_path", metavar="epw_path", type=Path) - parser_init.add_argument("-n", "--name", metavar="model_name", default="default") - parser_init.add_argument( - "-o", - "--object", - required=True, - metavar="object", - nargs="+", - type=Path, - help="Objects to include, can include wildcards", - ) - parser_init.add_argument( - "-m", - "--material", - required=True, - metavar="material", - nargs="+", - type=Path, - help="Material files to include, can include wildcards", - ) - parser_init.add_argument( - "-w", - "--window", - nargs="+", - metavar="window", - type=Path, - help="Window files to include, these are gonna get parsed into window groups", - ) - parser_init.add_argument( - "-x", - "--bsdf", - nargs="+", - metavar="bsdf", - type=Path, - help="xml file paths to include for each window file", - ) - parser_init.add_argument( - "-g", - "--grid", - nargs=3, - metavar=("surface path", "grid spacing", "grid height"), - help="Grid file path, grid spacing and height", - ) - parser_init.add_argument( - "-u", "--view", metavar="view", help="Grid file path, grid spacing and height" - ) - # Parse arguments for run subprogram - parser_run = subparser.add_parser("run") - parser_run.set_defaults(func=mrad_run) - parser_run.add_argument("cfg", type=Path, help="configuration file path") - parser.add_argument( - "-v", - "--verbose", - action="count", - default=0, - help=( - "Verbose mode: -v=Debug, -vv=Info, -vvv=Warning, " - "-vvvv=Error, -vvvvv=Critical" - ), - ) - args = parser.parse_args() - # Setup logger - formatter = logging.Formatter( - "%(asctime)s-%(name)s-%(levelname)s-%(message)s", "%m-%d %H:%M:%S" - ) - console_handler = logging.StreamHandler() - _level = args.verbose * 10 - logger.setLevel(_level) - console_handler.setLevel(_level) - console_handler.setFormatter(formatter) - logger.addHandler(console_handler) - if args.verbose > 1: - sys.tracebacklimit = 0 - if len(sys.argv) == 1: - parser.print_help(sys.stderr) - sys.exit(1) - args.func(args) +logger = logging.getLogger(__name__) def glaze(args) -> None: """Command-line program for generating BRTDfunc for glazing system.""" @@ -414,8 +32,8 @@ def glaze(args) -> None: photopic_wvl = range(380, 781, 10) for pane in panes: hemi = { - d.wavelength - * 1e3: ( + int(float(d.wavelength + * 1e3)): ( d.direct_component.transmittance_front, d.direct_component.transmittance_back, d.direct_component.reflectance_front, @@ -438,8 +56,8 @@ def glaze(args) -> None: else: coated_rgb = rb_rgb glass_rgb = rf_rgb - pane_rgb.append(PaneRGB(pane, coated_rgb, glass_rgb, tf_rgb)) - print(get_glazing_primitive(pane_rgb)) + pane_rgb.append(PaneRGB(coated_rgb, glass_rgb, tf_rgb, pane.coated_side)) + print(get_glazing_primitive("default", pane_rgb)) def gengrid(args) -> None: @@ -466,20 +84,6 @@ def gengrid(args) -> None: print(grid_str) -def epjson2rad_cmd() -> None: - """Command-line interface to converting epjson to rad.""" - parser = argparse.ArgumentParser() - parser.add_argument("fpath", type=Path) - parser.add_argument("-run", action="store_true", default=False) - args = parser.parse_args() - epmodel = load_energyplus_model(args.fpath) - if "FenestrationSurface:Detailed" not in epmodel.epjs: - raise ValueError("No windows found in this model") - zones = epmodel_to_radmodel(epmodel) - for zone in zones: - write_ep_rad_model(f"{zone}.rad", zones[zone]) - - def genmtx_pts_sky(args) -> None: """Generate a point to sky matrix.""" with open(args.pts, "r", encoding="ascii") as rdr: @@ -837,6 +441,7 @@ def gen() -> None: parser_room.add_argument("-n", dest="name", help="Model name", default="model") parser_room.add_argument( "-t", dest="facade_thickness", metavar="Facade thickness", type=float + , default=0, ) parser_room.add_argument("-r", "--rotate", type=float) @@ -1087,56 +692,24 @@ def gen() -> None: args.func(args) -# def rpxop() -> None: -# """Operate on input directories given a operation type.""" -# parser = argparse.ArgumentParser(prog="rpxop", description="Batch image processing") -# subparser = parser.add_subparsers() -# parser_dcts = subparser.add_parser("dctimestep") -# parser_dcts.set_defaults(func=mtxmult.batch_dctimestep) -# parser_dcts.add_argument("mtx", nargs="+", type=Path, help="input matrices") -# parser_dcts.add_argument("sky", type=Path, help="sky files directory") -# parser_dcts.add_argument("out", type=Path, help="output directory") -# parser_dcts.add_argument("-n", type=int, help="number of processors to use") -# parser_pcomb = subparser.add_parser("pcomb") -# parser_pcomb.set_defaults(func=mtxmult.batch_pcomb) -# parser_pcomb.add_argument( -# "inp", type=str, nargs="+", help="list of inputs, e.g., inp1 + inp2.hdr" -# ) -# parser_pcomb.add_argument("out", type=Path, help="output directory") -# parser_pcomb.add_argument("-n", type=int, help="number of processors to use") -# args = parser.parse_args() -# if args.func == mtxmult.batch_pcomb: -# inp = [Path(i) for i in args.inp[::2]] -# for i in inp: -# if not i.exists(): -# raise FileNotFoundError(i) -# ops = args.inp[1::2] -# args.func(inp, ops, args.out, nproc=args.n) -# elif args.func == mtxmult.batch_dctimestep: -# for i in args.mtx: -# if not i.exists(): -# raise FileNotFoundError(i) -# args.func(args.mtx, args.sky, args.out, nproc=args.n) - - def genradroom(args) -> None: """Commandline interface for generating a generic room. Resulting Radiance .rad files will be written to a local Objects directory, which will be created if not existed before. """ - aroom = make_room( + aroom = create_south_facing_room( args.width, args.depth, args.flrflr, args.flrclg, - args.window, swall_thickness=args.facade_thickness, + wpd=args.window, ) name = args.name objdir = Path("Objects") objdir.mkdir(exist_ok=True) with open(objdir / f"materials_{name}.mat", "w", encoding="ascii") as wtr: - for prim in aroom.materials.values(): + for prim in aroom.materials: wtr.write(str(prim) + "\n") with open(objdir / f"ceiling_{name}.rad", "w", encoding="ascii") as wtr: for prim in aroom.ceiling.primitives: @@ -1156,5 +729,4 @@ def genradroom(args) -> None: with open( objdir / f"window_{idx:02d}_{name}.rad", "w", encoding="ascii" ) as wtr: - for prim in srf.primitives: - wtr.write(str(prim) + "\n") + wtr.write(str(srf.primitive) + "\n") diff --git a/frads/cli/mrad.py b/frads/cli/mrad.py new file mode 100644 index 0000000..7666457 --- /dev/null +++ b/frads/cli/mrad.py @@ -0,0 +1,365 @@ +""" +mrad cli +""" +import argparse +import configparser +import logging +import os +from pathlib import Path +from typing import Dict, Optional, List +import sys + +from ..utils import unpack_primitives, parse_polygon, gen_grid, write_hdrs +from ..methods import WorkflowConfig, TwoPhaseMethod, ThreePhaseMethod, FivePhaseMethod +import numpy as np +from pyradiance import model as rmodel +from pyradiance import param as rparam + +logger = logging.getLogger(__name__) + +def parse_vu(vu_str: str) -> Optional[rmodel.View]: + """Parse view string into a View object. + + Args: + vu_str: view parameters as a string + + Returns: + A view object + """ + + if vu_str.strip() == "": + return + args_list = vu_str.strip().split() + vparser = argparse.ArgumentParser() + vparser = rparam.add_view_args(vparser) + vparser.add_argument("-x", type=int) + vparser.add_argument("-y", type=int) + args, _ = vparser.parse_known_args(args_list) + if args.vf is not None: + args, _ = vparser.parse_known_args( + args.vf.readline().strip().split(), namespace=args + ) + args.vf.close() + if None in (args.vp, args.vd): + raise ValueError("Invalid view") + view = rmodel.View( + position=args.vp, + direction=args.vd, + vtype=args.vt[-1], + horiz=args.vh, + vert=args.vv, + vfore=args.vo, + vaft=args.va, + hoff=args.vs, + voff=args.vl, + ) + if args.x is not None: + view.xres = args.x + if args.y is not None: + view.yres = args.y + return view + + +def parse_mrad_config(cfg_path: Path) -> Dict[str, dict]: + """ + Parse mrad configuration file. + Args: + cfg_path: path to the configuration file + Returns: + A dictionary of configuration in a format + that can be used by methods.WorkflowConfig + """ + if not cfg_path.is_file(): + raise FileNotFoundError(cfg_path) + config = configparser.ConfigParser( + allow_no_value=False, + inline_comment_prefixes="#", + interpolation=configparser.ExtendedInterpolation(), + converters={ + "path": lambda x: Path(x.strip()), + "paths": lambda x: [Path(i) for i in x.split()], + "spaths": lambda x: x.split(), + # "options": parse_opt, + "options": rparam.parse_rtrace_args, + "view": parse_vu, + }, + ) + config.read(Path(__file__).parent / "data" / "mrad_default.cfg") + config.read(cfg_path) + # Convert config to dict + config_dict = {} + for section in config.sections(): + config_dict[section] = {} + for key, val in config.items(section): + config_dict[section][key] = val + config_dict["settings"] = {**config_dict["SimControl"], **config_dict["Site"]} + for k, v in config_dict["settings"].items(): + # Convert sampling parameters string to list + if k.endswith("_matrix"): + config_dict["settings"][k] = v.split() + config_dict["settings"]["separate_direct"] = config["SimControl"].getboolean( + "separate_direct" + ) + config_dict["settings"]["overwrite"] = config["SimControl"].getboolean( + "overwrite", False + ) + config_dict["settings"]["save_matrices"] = config["SimControl"].getboolean( + "save_matrices", True + ) + config_dict["model"] = { + "scene": {}, + "materials": {}, + "windows": {}, + "views": {}, + "sensors": {}, + } + config_dict["model"]["scene"]["files"] = config["Model"].getspaths("scene") + config_dict["model"]["materials"]["files"] = config["Model"].getspaths("material") + config_dict["model"]["materials"]["matrices"] = { + k.stem: {"matrix_file": k} for k in config["Model"].getpaths("window_xmls") + } + for wpath, xpath in zip( + config["Model"].getpaths("windows"), config["Model"].getpaths("window_xmls") + ): + config_dict["model"]["windows"][wpath.stem] = { + "file": str(wpath), + "matrix_file": xpath.stem, + } + if (grid_files := config["RaySender"].getspaths("grid_points")) is not None: + for gfile in grid_files: + name = gfile.stem + with open(gfile) as f: + config_dict["model"]["sensors"][name] = { + "data": [[float(v) for v in l.split()] for l in f.readlines()] + } + elif (grid_paths := config["RaySender"].getpaths("grid_surface")) is not None: + for gpath in grid_paths: + name: str = gpath.stem + # Take the first polygon primitive + gprimitives = unpack_primitives(gpath) + surface_polygon = None + for prim in gprimitives: + if prim.ptype == "polygon": + surface_polygon = parse_polygon(prim) + break + if surface_polygon is None: + raise ValueError(f"No polygon found in {gpath}") + config_dict["model"]["sensors"][name] = { + "data": gen_grid( + surface_polygon, + config["RaySender"].getfloat("grid_height"), + config["RaySender"].getfloat("grid_spacing"), + ) + } + views = [i for i in config["RaySender"] if i.startswith("view")] + for vname in views: + if (view := config["RaySender"].getview("view")) is not None: + config_dict["model"]["views"][vname] = { + "view": " ".join(view.args()), + "xres": view.xres, + "yres": view.yres, + } + del ( + config_dict["SimControl"], + config_dict["Site"], + config_dict["Model"], + config_dict["RaySender"], + ) + return config_dict + + +def mrad_init(args: argparse.Namespace) -> None: + """Initiate mrad operation. + + Args: + args: argparse.Namespace + Returns: + None + """ + + def get_file_list(paths: List[Path], ext): + file_list: List[str] = [] + for path in paths: + if not path.is_dir(): + file_list.append(str(path)) + else: + file_list.extend(sorted(path.glob(f"*.{ext}"))) + return file_list + + config = configparser.ConfigParser(allow_no_value=False) + simcontrol = { + "# vmx_opt": "", + "# dmx_opt": "", + "# smx_basis": "", + "# overwrite": "", + "# separate_direct": "", + } + site = {} + model = {} + raysender = {} + if args.wea_path is not None: + if not args.wea_path.is_file(): + raise FileNotFoundError(args.wea_path) + site["wea_path"] = args.wea_path + elif args.epw_path is not None: + if not args.epw_path.is_file(): + raise FileNotFoundError(args.epw_path) + site["epw_path"] = args.epw_path + model["name"] = args.name + if args.grid is not None: + raysender["grid_surface"] = args.grid[0] + raysender["grid_spacing"] = args.grid[1] + raysender["grid_height"] = args.grid[2] + material_list = get_file_list(args.material, "mat") + object_list: List[str] = get_file_list(args.object, "rad") + model["scene"] = "\n".join(object_list) + model["material"] = "\n".join(material_list) + if args.window is not None: + window_list = get_file_list(args.window, "rad") + model["windows"] = "\n".join(window_list) + if args.bsdf is not None: + xml_list = get_file_list(args.bsdf, "xml") + if len(window_list) != len(xml_list): + raise ValueError("Number of window and xml files not the same") + model["window_xmls"] = "\n".join(xml_list) + templ_config = { + "SimControl": simcontrol, + "Site": site, + "Model": model, + "RaySender": raysender, + } + config.read_dict(templ_config) + with open(f"{args.name}.cfg", "w", encoding="utf-8") as rdr: + config.write(rdr) + + +def mrad_run(args: argparse.Namespace) -> None: + """Call mtxmethod to carry out the actual simulation.""" + config_dict = parse_mrad_config(args.cfg) + config_dict["settings"]["name"] = config_dict["settings"].get("name", args.cfg.stem) + wconfig = WorkflowConfig.from_dict(config_dict) + workflow = None + if method := wconfig.settings.method: + if method.startswith(("2", "two")): + logger.info("Using two-phase simulation") + workflow = TwoPhaseMethod(wconfig) + elif method.startswith(("3", "three")): + logger.info("Using three-phase simulation") + workflow = ThreePhaseMethod(wconfig) + elif method.startswith(("5", "five")): + logger.info("Using five-phase simulation") + workflow = FivePhaseMethod(wconfig) + else: + # Use 3- or 5-phase methods if we have + # window groups and bsdf defined + if len(wconfig.model.windows) > 1: + if wconfig.settings.separate_direct: + logger.info("Using five-phase simulation") + workflow = FivePhaseMethod(wconfig) + else: + logger.info("Using three-phase simulation") + workflow = ThreePhaseMethod(wconfig) + else: + logger.info("Using two-phase method") + workflow = TwoPhaseMethod(wconfig) + if workflow is None: + raise ValueError("No simulation method found") + workflow.generate_matrices() + for vname, view in wconfig.model.views.items(): + res = workflow.calculate_view_from_wea(vname) + write_hdrs(res, view.xres, view.yres, outdir=os.path.join("Results", vname)) + for sensor in wconfig.model.sensors: + res = workflow.calculate_sensor_from_wea(sensor) + np.savetxt(os.path.join("Results", f"{sensor}.txt"), res) + + +def mrad() -> None: + """mrad entry point: parse arugments for init and run subprograms.""" + parser = argparse.ArgumentParser( + prog="mrad", + description="Mrad is an executive program for Radiance matrix-based simulation methods.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + subparser = parser.add_subparsers() + # Parse arguments for init subprogram + parser_init = subparser.add_parser("init") + parser_init.set_defaults(func=mrad_init) + egroup = parser_init.add_mutually_exclusive_group(required=True) + egroup.add_argument("-a", "--wea_path", metavar="wea_path", type=Path) + egroup.add_argument("-e", "--epw_path", metavar="epw_path", type=Path) + parser_init.add_argument("-n", "--name", metavar="model_name", default="default") + parser_init.add_argument( + "-o", + "--object", + required=True, + metavar="object", + nargs="+", + type=Path, + help="Objects to include, can include wildcards", + ) + parser_init.add_argument( + "-m", + "--material", + required=True, + metavar="material", + nargs="+", + type=Path, + help="Material files to include, can include wildcards", + ) + parser_init.add_argument( + "-w", + "--window", + nargs="+", + metavar="window", + type=Path, + help="Window files to include, these are gonna get parsed into window groups", + ) + parser_init.add_argument( + "-x", + "--bsdf", + nargs="+", + metavar="bsdf", + type=Path, + help="xml file paths to include for each window file", + ) + parser_init.add_argument( + "-g", + "--grid", + nargs=3, + metavar=("surface path", "grid spacing", "grid height"), + help="Grid file path, grid spacing and height", + ) + parser_init.add_argument( + "-u", "--view", metavar="view", help="Grid file path, grid spacing and height" + ) + # Parse arguments for run subprogram + parser_run = subparser.add_parser("run") + parser_run.set_defaults(func=mrad_run) + parser_run.add_argument("cfg", type=Path, help="configuration file path") + parser.add_argument( + "-v", + "--verbose", + action="count", + default=0, + help=( + "Verbose mode: -v=Debug, -vv=Info, -vvv=Warning, " + "-vvvv=Error, -vvvvv=Critical" + ), + ) + args = parser.parse_args() + # Setup logger + formatter = logging.Formatter( + "%(asctime)s-%(name)s-%(levelname)s-%(message)s", "%m-%d %H:%M:%S" + ) + console_handler = logging.StreamHandler() + _level = args.verbose * 10 + logger.setLevel(_level) + console_handler.setLevel(_level) + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) + if args.verbose > 1: + sys.tracebacklimit = 0 + if len(sys.argv) == 1: + parser.print_help(sys.stderr) + sys.exit(1) + args.func(args) diff --git a/frads/eplus/__init__.py b/frads/eplus/__init__.py new file mode 100644 index 0000000..f23c1be --- /dev/null +++ b/frads/eplus/__init__.py @@ -0,0 +1,5 @@ +from .eplus import EnergyPlusSetup, load_energyplus_model +from .eplus_model import EnergyPlusModel +from .ep2rad import epmodel_to_radmodel + +__all__ = ['EnergyPlusSetup', 'EnergyPlusModel', 'epmodel_to_radmodel', 'load_energyplus_model'] diff --git a/frads/ep2rad.py b/frads/eplus/ep2rad.py similarity index 99% rename from frads/ep2rad.py rename to frads/eplus/ep2rad.py index 0e924fa..a7ff08b 100755 --- a/frads/ep2rad.py +++ b/frads/eplus/ep2rad.py @@ -13,7 +13,7 @@ polygon_center, ) from frads.utils import polygon_primitive, gen_grid -from frads.eplus_model import EnergyPlusModel +from .eplus_model import EnergyPlusModel from epmodel.epmodel import ( BuildingSurfaceDetailed, FenestrationSurfaceDetailed, diff --git a/frads/eplus.py b/frads/eplus/eplus.py similarity index 99% rename from frads/eplus.py rename to frads/eplus/eplus.py index 194663f..be19331 100644 --- a/frads/eplus.py +++ b/frads/eplus/eplus.py @@ -11,12 +11,12 @@ import tempfile from epmodel import epmodel as epm -from frads.ep2rad import epmodel_to_radmodel +from .ep2rad import epmodel_to_radmodel from frads.methods import ( WorkflowConfig, ThreePhaseMethod, ) -from frads.eplus_model import EnergyPlusModel +from .eplus_model import EnergyPlusModel import numpy as np from pyenergyplus.api import EnergyPlusAPI diff --git a/frads/eplus_model.py b/frads/eplus/eplus_model.py similarity index 100% rename from frads/eplus_model.py rename to frads/eplus/eplus_model.py diff --git a/frads/window.py b/frads/window.py index b2c8490..a3429fd 100644 --- a/frads/window.py +++ b/frads/window.py @@ -8,21 +8,15 @@ import pywincalc as pwc -AIR = pwc.PredefinedGasType.AIR -KRYPTON = pwc.PredefinedGasType.KRYPTON -XENON = pwc.PredefinedGasType.XENON -ARGON = pwc.PredefinedGasType.ARGON - - @dataclass class PaneRGB: """Pane color data object. Attributes: - measured_data: measured data as a PaneProperty object. coated_rgb: Coated side RGB. glass_rgb: Non-coated side RGB. trans_rgb: Transmittance RGB. + coated_side: Coated side of the pane. """ coated_rgb: Tuple[float, float, float] diff --git a/pyproject.toml b/pyproject.toml index e10b3d9..6bc87fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,8 @@ license = {"file"= "license.txt"} requires-python = ">= 3.8" [project.scripts] -gen = "frads.cli:gen" -mrad = "frads.cli:mrad" +gen = "frads.cli.gen:gen" +mrad = "frads.cli.mrad:mrad" [tool.setuptools.dynamic] version = {attr = "frads.__version__"} diff --git a/test/test_ep2rad.py b/test/test_ep2rad.py index d072f96..dde1dc2 100644 --- a/test/test_ep2rad.py +++ b/test/test_ep2rad.py @@ -1,8 +1,7 @@ """ """ # from frads import epjson_to_rad -from frads import epmodel_to_radmodel -from frads.eplus import load_energyplus_model +from frads.eplus import epmodel_to_radmodel, load_energyplus_model def test_cfscase(resources_dir): diff --git a/test/test_methods.py b/test/test_methods.py index 5589027..cd6773f 100644 --- a/test/test_methods.py +++ b/test/test_methods.py @@ -2,8 +2,7 @@ from frads.methods import TwoPhaseMethod, ThreePhaseMethod, WorkflowConfig from frads.window import create_glazing_system, Gap, Gas -from frads.ep2rad import epmodel_to_radmodel -from frads.eplus import load_energyplus_model +from frads.eplus import epmodel_to_radmodel, load_energyplus_model import pytest from pyenergyplus.dataset import ref_models, weather_files import pyradiance as pr diff --git a/test/test_window.py b/test/test_window.py index 40e9362..44eb4cc 100644 --- a/test/test_window.py +++ b/test/test_window.py @@ -1,6 +1,6 @@ import os from pathlib import Path -from frads.window import create_glazing_system, Gas, Gap, GlazingSystem, AIR, ARGON +from frads.window import create_glazing_system, Gas, Gap, GlazingSystem import pytest