<commit_before>"""Recipes for making changes to EnergyPlus IDF files.

These are generally exposed and methods on the IDF object, e.g. `set_default_constructions(idf)`
can be called on an existing `IDF` object like ``myidf.set_default_constructions()``.

"""
from typing import List, Optional, Tuple, Union  # noqa
import warnings

from eppy.idf_msequence import Idf_MSequence  # noqa
import numpy as np

from .geom.polygons import Polygon3D
from .geom.transformations import Transformation
from .geom.vectors import Vector2D, Vector3D  # noqa

if False:
    from .idf import IDF  # noqa
if False:
    from .patches import EpBunch  # noqa


def set_default_constructions(idf):
    # type: (IDF) -> None
    """Set default constructions for surfaces in the model.

    :param idf: The IDF object.

    """
    constructions = [
        "Project Wall",
        "Project Partition",
        "Project Floor",
        "Project Flat Roof",
        "Project Ceiling",
        "Project Door",
    ]
    for construction in constructions:
        idf.newidfobject(
            "CONSTRUCTION", Name=construction, Outside_Layer="DefaultMaterial"
        )
    idf.newidfobject(
        "MATERIAL",
        Name="DefaultMaterial",
        Roughness="Rough",
        Thickness=0.1,
        Conductivity=0.1,
        Density=1000,
        Specific_Heat=1000,
    )

    idf.newidfobject(
        "CONSTRUCTION", Name="Project External Window", Outside_Layer="DefaultGlazing"
    )
    idf.newidfobject(
        "WINDOWMATERIAL:SIMPLEGLAZINGSYSTEM",
        Name="DefaultGlazing",
        UFactor=2.7,
        Solar_Heat_Gain_Coefficient=0.763,
        Visible_Transmittance=0.8,
    )

    for surface in idf.getsurfaces():
        set_default_construction(surface)
    for subsurface in idf.getsubsurfaces():
        set_default_construction(subsurface)


def set_default_construction(surface):
    # type: (EpBunch) -> None
    """Set default construction for a surface in the model.

    :param surface: A model surface.

    """
    if surface.Surface_Type.lower() == "wall":
        if surface.Outside_Boundary_Condition.lower() == "outdoors":
            surface.Construction_Name = "Project Wall"
        elif surface.Outside_Boundary_Condition.lower() == "ground":
            surface.Construction_Name = "Project Wall"
        else:
            surface.Construction_Name = "Project Partition"
    if surface.Surface_Type.lower() == "floor":
        if surface.Outside_Boundary_Condition.lower() == "ground":
            surface.Construction_Name = "Project Floor"
        else:
            surface.Construction_Name = "Project Floor"
    if surface.Surface_Type.lower() == "roof":
        surface.Construction_Name = "Project Flat Roof"
    if surface.Surface_Type.lower() == "ceiling":
        surface.Construction_Name = "Project Ceiling"
    if surface.Surface_Type == "window":
        surface.Construction_Name = "Project External Window"
    if surface.Surface_Type == "door":
        surface.Construction_Name = "Project Door"


def set_wwr(
    idf, wwr=0.2, construction=None, force=False, wwr_map=None, orientation=None
):
    # type: (IDF, Optional[float], Optional[str], Optional[bool], Optional[dict], Optional[str]) -> None
    """Set the window to wall ratio on all external walls.

    :param idf: The IDF to edit.
    :param wwr: The window to wall ratio.
    :param construction: Name of a window construction.
    :param force: True to remove all subsurfaces before setting the WWR.
    :param wwr_map: Mapping from wall orientation (azimuth) to WWR, e.g. {180: 0.25, 90: 0.2}.
    :param orientation: One of "north", "east", "south", "west". Walls within 45 degrees will be affected.

    """
    try:
        ggr = idf.idfobjects["GLOBALGEOMETRYRULES"][0]  # type: Optional[Idf_MSequence]
    except IndexError:
        ggr = None

    # check orientation
    orientations = {
        "north": 0.0,
        "east": 90.0,
        "south": 180.0,
        "west": 270.0,
        None: None,
    }
    degrees = orientations.get(orientation, None)
    external_walls = filter(
        lambda x: x.Outside_Boundary_Condition.lower() == "outdoors",
        idf.getsurfaces("wall"),
    )
    external_walls = filter(
        lambda x: _has_correct_orientation(x, degrees), external_walls
    )
    subsurfaces = idf.getsubsurfaces()
    base_wwr = wwr
    for wall in external_walls:
        # get any subsurfaces on the wall
        wall_subsurfaces = list(
            filter(lambda x: x.Building_Surface_Name == wall.Name, subsurfaces)
        )
        if not all(_is_window(wss) for wss in wall_subsurfaces) and not force:
            raise ValueError(
                'Not all subsurfaces on wall "{name}" are windows. '
                "Use `force=True` to replace all subsurfaces.".format(name=wall.Name)
            )

        if wall_subsurfaces and not construction:
            constructions = list(
                {wss.Construction_Name for wss in wall_subsurfaces if _is_window(wss)}
            )
            if len(constructions) > 1:
                raise ValueError(
                    'Not all subsurfaces on wall "{name}" have the same construction'.format(
                        name=wall.Name
                    )
                )
            construction = constructions[0]
        # remove all subsurfaces
        for ss in wall_subsurfaces:
            idf.removeidfobject(ss)
        wwr = (wwr_map or {}).get(wall.azimuth) or base_wwr
        if not wwr:
            return
        coords = window_vertices_given_wall(wall, wwr)
        window = idf.newidfobject(
            "FENESTRATIONSURFACE:DETAILED",
            Name="%s window" % wall.Name,
            Surface_Type="Window",
            Construction_Name=construction or "",
            Building_Surface_Name=wall.Name,
            View_Factor_to_Ground="autocalculate",  # from the surface angle
        )
        window.setcoords(coords, ggr)


def _has_correct_orientation(wall, orientation_degrees):
    # type: (EpBunch, Optional[float]) -> bool
    """Check that the wall has an orientation which requires WWR to be set.

    :param wall: An EpBunch representing a wall.
    :param orientation_degrees: Orientation in degrees.
    :return: True if the wall is within 45 degrees of the orientation passed, or no orientation passed.
             False if the wall is not within 45 of the orientation passed.
    """
    if not orientation_degrees:
        return True
    if abs(wall.azimuth - orientation_degrees) < 45:
        return True
    return False


def _is_window(subsurface):
    if subsurface.key.lower() in {"window", "fenestrationsurface:detailed"}:
        return True


def window_vertices_given_wall(wall, wwr):
    # type: (EpBunch, float) -> Polygon3D
    """Calculate window vertices given wall vertices and glazing ratio.

    :: For each axis:
        1) Translate the axis points so that they are centred around zero
        2) Either:
            a) Multiply the z dimension by the glazing ratio to shrink it vertically
            b) Multiply the x or y dimension by 0.995 to keep inside the surface
        3) Translate the axis points back to their original positions

    :param wall: The wall to add a window on. We expect each wall to have four vertices.
    :param wwr: Window to wall ratio.

    :returns: Window vertices bounding a vertical strip midway up the surface.

    """
    vertices = wall.coords<commit_msg>fix: correctly set wwr by orientation for north-facing walls<commit_after>"""Recipes for making changes to EnergyPlus IDF files.

These are generally exposed and methods on the IDF object, e.g. `set_default_constructions(idf)`
can be called on an existing `IDF` object like ``myidf.set_default_constructions()``.

"""
from typing import List, Optional, Tuple, Union  # noqa
import warnings

from eppy.idf_msequence import Idf_MSequence  # noqa
import numpy as np

from .geom.polygons import Polygon3D
from .geom.transformations import Transformation
from .geom.vectors import Vector2D, Vector3D  # noqa

if False:
    from .idf import IDF  # noqa
if False:
    from .patches import EpBunch  # noqa


def set_default_constructions(idf):
    # type: (IDF) -> None
    """Set default constructions for surfaces in the model.

    :param idf: The IDF object.

    """
    constructions = [
        "Project Wall",
        "Project Partition",
        "Project Floor",
        "Project Flat Roof",
        "Project Ceiling",
        "Project Door",
    ]
    for construction in constructions:
        idf.newidfobject(
            "CONSTRUCTION", Name=construction, Outside_Layer="DefaultMaterial"
        )
    idf.newidfobject(
        "MATERIAL",
        Name="DefaultMaterial",
        Roughness="Rough",
        Thickness=0.1,
        Conductivity=0.1,
        Density=1000,
        Specific_Heat=1000,
    )

    idf.newidfobject(
        "CONSTRUCTION", Name="Project External Window", Outside_Layer="DefaultGlazing"
    )
    idf.newidfobject(
        "WINDOWMATERIAL:SIMPLEGLAZINGSYSTEM",
        Name="DefaultGlazing",
        UFactor=2.7,
        Solar_Heat_Gain_Coefficient=0.763,
        Visible_Transmittance=0.8,
    )

    for surface in idf.getsurfaces():
        set_default_construction(surface)
    for subsurface in idf.getsubsurfaces():
        set_default_construction(subsurface)


def set_default_construction(surface):
    # type: (EpBunch) -> None
    """Set default construction for a surface in the model.

    :param surface: A model surface.

    """
    if surface.Surface_Type.lower() == "wall":
        if surface.Outside_Boundary_Condition.lower() == "outdoors":
            surface.Construction_Name = "Project Wall"
        elif surface.Outside_Boundary_Condition.lower() == "ground":
            surface.Construction_Name = "Project Wall"
        else:
            surface.Construction_Name = "Project Partition"
    if surface.Surface_Type.lower() == "floor":
        if surface.Outside_Boundary_Condition.lower() == "ground":
            surface.Construction_Name = "Project Floor"
        else:
            surface.Construction_Name = "Project Floor"
    if surface.Surface_Type.lower() == "roof":
        surface.Construction_Name = "Project Flat Roof"
    if surface.Surface_Type.lower() == "ceiling":
        surface.Construction_Name = "Project Ceiling"
    if surface.Surface_Type == "window":
        surface.Construction_Name = "Project External Window"
    if surface.Surface_Type == "door":
        surface.Construction_Name = "Project Door"


def set_wwr(
    idf, wwr=0.2, construction=None, force=False, wwr_map=None, orientation=None
):
    # type: (IDF, Optional[float], Optional[str], Optional[bool], Optional[dict], Optional[str]) -> None
    """Set the window to wall ratio on all external walls.

    :param idf: The IDF to edit.
    :param wwr: The window to wall ratio.
    :param construction: Name of a window construction.
    :param force: True to remove all subsurfaces before setting the WWR.
    :param wwr_map: Mapping from wall orientation (azimuth) to WWR, e.g. {180: 0.25, 90: 0.2}.
    :param orientation: One of "north", "east", "south", "west". Walls within 45 degrees will be affected.

    """
    try:
        ggr = idf.idfobjects["GLOBALGEOMETRYRULES"][0]  # type: Optional[Idf_MSequence]
    except IndexError:
        ggr = None

    # check orientation
    orientations = {
        "north": 0.0,
        "east": 90.0,
        "south": 180.0,
        "west": 270.0,
        None: None,
    }
    degrees = orientations.get(orientation, None)
    external_walls = filter(
        lambda x: x.Outside_Boundary_Condition.lower() == "outdoors",
        idf.getsurfaces("wall"),
    )
    external_walls = filter(
        lambda x: _has_correct_orientation(x, degrees), external_walls
    )
    subsurfaces = idf.getsubsurfaces()
    base_wwr = wwr
    for wall in external_walls:
        # get any subsurfaces on the wall
        wall_subsurfaces = list(
            filter(lambda x: x.Building_Surface_Name == wall.Name, subsurfaces)
        )
        if not all(_is_window(wss) for wss in wall_subsurfaces) and not force:
            raise ValueError(
                'Not all subsurfaces on wall "{name}" are windows. '
                "Use `force=True` to replace all subsurfaces.".format(name=wall.Name)
            )

        if wall_subsurfaces and not construction:
            constructions = list(
                {wss.Construction_Name for wss in wall_subsurfaces if _is_window(wss)}
            )
            if len(constructions) > 1:
                raise ValueError(
                    'Not all subsurfaces on wall "{name}" have the same construction'.format(
                        name=wall.Name
                    )
                )
            construction = constructions[0]
        # remove all subsurfaces
        for ss in wall_subsurfaces:
            idf.removeidfobject(ss)
        wwr = (wwr_map or {}).get(wall.azimuth) or base_wwr
        if not wwr:
            return
        coords = window_vertices_given_wall(wall, wwr)
        window = idf.newidfobject(
            "FENESTRATIONSURFACE:DETAILED",
            Name="%s window" % wall.Name,
            Surface_Type="Window",
            Construction_Name=construction or "",
            Building_Surface_Name=wall.Name,
            View_Factor_to_Ground="autocalculate",  # from the surface angle
        )
        window.setcoords(coords, ggr)


def _has_correct_orientation(wall, orientation_degrees):
    # type: (EpBunch, Optional[float]) -> bool
    """Check that the wall has an orientation which requires WWR to be set.

    :param wall: An EpBunch representing a wall.
    :param orientation_degrees: Orientation in degrees.
    :return: True if the wall is within 45 degrees of the orientation passed, or no orientation passed.
             False if the wall is not within 45 of the orientation passed.
    """
    if orientation_degrees is None:
        return True
    if abs(wall.azimuth - orientation_degrees) < 45:
        return True
    return False


def _is_window(subsurface):
    if subsurface.key.lower() in {"window", "fenestrationsurface:detailed"}:
        return True


def window_vertices_given_wall(wall, wwr):
    # type: (EpBunch, float) -> Polygon3D
    """Calculate window vertices given wall vertices and glazing ratio.

    :: For each axis:
        1) Translate the axis points so that they are centred around zero
        2) Either:
            a) Multiply the z dimension by the glazing ratio to shrink it vertically
            b) Multiply the x or y dimension by 0.995 to keep inside the surface
        3) Translate the axis points back to their original positions

    :param wall: The wall to add a window on. We expect each wall to have four vertices.
    :param wwr: Window to wall ratio.

    :returns: Window vertices bounding a vertical strip midway up the surface.

    """
    vertices = wall.coords
{'fraction_non_alphanumeric': 0.09059931850070155, 'fraction_numerical': 0.008953029999331863, 'mean_word_length': 2.578293091082955, 'pattern_counts': {'":': 26, '<': 5, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3994304', 'n_tokens_mistral': 4545, 'n_tokens_neox': 4289, 'n_words': 1318}