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

reimplement material enrichment and not only material overwrite #676

Open
DaJansenGit opened this issue Jul 3, 2024 · 1 comment
Open

Comments

@DaJansenGit
Copy link
Member

DaJansenGit commented Jul 3, 2024

sim_setting.layers_and_materials = LOD.full is currently not supported anymore

@DaJansenGit
Copy link
Member Author

Here is the old relevant code:

EnrichMaterial Task:

import ast
import re

from bim2sim.kernel.decision import ListDecision, DecisionBunch
from bim2sim.elements.base_elements import Material
from bim2sim.elements.bps_elements import Layer, LayerSet, Building

from bim2sim.tasks.base import ITask
from bim2sim.utilities.common_functions import get_material_templates, \
    translate_deep, filter_elements, get_type_building_elements


class EnrichMaterial(ITask):
    """Enriches material properties that were recognized as invalid
    LOD.layers = Medium & Full"""

    reads = ('elements', 'invalid',)
    touches = ('elements',)

    def __init__(self, playground):
        super().__init__(playground)
        self.enriched_elements = {}
        self.template_layer_set = {}
        self.template_materials = {}

    def run(self, elements: dict, invalid: dict):
        buildings = filter_elements(elements, Building)
        templates = yield from self.get_templates_for_buildings(
            buildings, self.playground.sim_settings)
        if not templates:
            self.logger.warning(
                "Tried to run enrichment for layers structure and materials, "
                "but no fitting templates were found. "
                "Please check your settings.")
            return elements,
        resumed = self.get_resumed_material_templates()
        for invalid_inst in invalid.values():
            yield from self.enrich_invalid_element(invalid_inst, resumed,
                                                   templates)
        self.logger.info("enriched %d invalid materials",
                         len(self.enriched_elements))
        elements = self.update_elements(elements, self.enriched_elements)
        # TODO currently, old materials and layser sets still exist, clean this
        return elements,

    def get_templates_for_buildings(
            self, buildings, sim_settings):
        """get templates for building"""
        templates = {}
        construction_type = sim_settings.construction_class_walls
        windows_construction_type = sim_settings.construction_class_windows
        if not buildings:
            raise ValueError(
                "No buildings found, without a building no template can be"
                " assigned and enrichment can't proceed.")
        for building in buildings:
            if sim_settings.year_of_construction_overwrite:
                building.year_of_construction = \
                    int(sim_settings.year_of_construction_overwrite)
            if not building.year_of_construction:
                year_decision = building.request('year_of_construction')
                yield DecisionBunch([year_decision])
            year_of_construction = int(building.year_of_construction.m)
            templates[building] = self.get_template_for_year(
                year_of_construction, construction_type,
                windows_construction_type)
        return templates

    def get_template_for_year(self, year_of_construction, construction_type,
                              windows_construction_type):
        element_templates = get_type_building_elements()
        bldg_template = {}
        for element_type, years_dict in element_templates.items():
            if len(years_dict) == 1:
                template_options = years_dict[list(years_dict.keys())[0]]
            else:
                template_options = None
                for i, template in years_dict.items():
                    years = ast.literal_eval(i)
                    if years[0] <= year_of_construction <= years[1]:
                        template_options = element_templates[element_type][i]
                        break
            if len(template_options) == 1:
                bldg_template[element_type] = \
                    template_options[list(template_options.keys())[0]]
            else:
                if element_type == 'Window':
                    try:
                        bldg_template[element_type] = \
                            template_options[windows_construction_type]
                    except KeyError:
                        # select last available window construction type if
                        # the selected/default window type is not available
                        # for the given year. The last construction type is
                        # selected, since the first construction type may be a
                        # single pane wood frame window and should not be
                        # used as new default construction.
                        new_window_construction_type = \
                            list(template_options.keys())[-1]
                        self.logger.warning(
                            "The window_construction_type %s is not available "
                            "for year_of_construction %i. Using the "
                            "window_construction_type %s instead.",
                            windows_construction_type, year_of_construction,
                            new_window_construction_type)
                        bldg_template[element_type] = \
                            template_options[new_window_construction_type]
                else:
                    bldg_template[element_type] = \
                        template_options[construction_type]
        return bldg_template

    def enrich_invalid_element(self, invalid_element, resumed, templates):
        """enrich invalid element"""
        # TODO when material lod = low --> don't enrich layerssets, just create
        #  fresh ones from template. Maybe don't even create materials and
        #  layersets from IFC in the first place
        #
        if type(invalid_element) is Layer:
            enriched_element = yield from self.enrich_layer(
                invalid_element, resumed, templates)
            self.enriched_elements[invalid_element.guid] = enriched_element

        elif type(invalid_element) is LayerSet:
            enriched_element = self.enrich_layer_set(invalid_element, resumed,
                                                      templates)
            self.enriched_elements[invalid_element.guid] = enriched_element
        else:
            self.enrich_element(invalid_element, resumed, templates)

    def enrich_layer(self, invalid_layer, resumed, templates):
        """enrich layer"""
        invalid_layer_sets = [layer_set for layer_set in
                              invalid_layer.to_layerset]
        type_invalid_elements = self.get_invalid_elements_type(
            invalid_layer_sets)
        if len(type_invalid_elements) == 1:
            specific_element_template = templates[list(
                templates.keys())[0]][type_invalid_elements[0]]
            resumed_names = list(set(
                layer['material']['name'] for layer in
                specific_element_template['layer'].values()))
        else:
            resumed_names = list(resumed.keys())
        layer = Layer()
        layer.thickness = invalid_layer.thickness
        material_name = invalid_layer.material.name
        if material_name in self.template_materials:
            material = self.template_materials[material_name]
        else:
            specific_template = yield from self.get_material_template(
                material_name, resumed_names, resumed)
            material = self.create_material_from_template(specific_template)
            self.template_materials[material_name] = material
        material.parents.append(layer)
        layer.material = material
        for layer_set in invalid_layer_sets:
            layer.to_layerset.append(layer_set)
            layer_set.layers[layer_set.layers.index(invalid_layer)] = layer
        return layer

    @staticmethod
    def get_invalid_elements_type(layer_sets):
        """get invalid elements"""
        invalid_elements = []
        for layer_set in layer_sets:
            for parent in layer_set.parents:
                element_type = type(parent).__name__
                if element_type not in invalid_elements:
                    invalid_elements.append(element_type)
        return invalid_elements

    @classmethod
    def get_material_template(cls, material_name: str, resumed_names: list,
                              resumed: dict) -> [list, str]:
        """get list of matching materials
        if material has no matches, more common name necessary"""
        material = re.sub(r'[^\w]*?[0-9]', '', material_name)
        material_options = cls.get_matches_list(
            material, resumed_names) if material_name else []
        if len(material_options) == 1:
            selected_material = material_options[0]
        else:
            selected_material = yield from cls.material_search(material_options,
                                                               material_name)
        return resumed[selected_material]

    def enrich_layer_set(self, invalid_element, resumed, templates):
        """enrich layer set"""
        type_invalid_elements = self.get_invalid_elements_type(
            [invalid_element])[0]
        layer_set, add_enrichment = self.layer_set_search(
            type_invalid_elements, templates, resumed)
        for parent in invalid_element.parents:
            layer_set.parents.append(parent)
            parent.layerset = layer_set
            self.additional_element_enrichment(parent,
                                                add_enrichment)
        return layer_set

    def enrich_element(self, invalid_element, resumed, templates):
        """enrich element"""
        type_invalid_element = type(invalid_element).__name__
        # Handle disaggregated classes
        if "Disaggregated" in type_invalid_element:
            type_invalid_element = type_invalid_element.replace(
                "Disaggregated", "")
        if type_invalid_element == "InnerFloor":
            type_invalid_element = "Floor"
        layer_set, add_enrichment = self.layer_set_search(type_invalid_element,
                                                          templates, resumed)

        layer_set.parents.append(invalid_element)
        invalid_element.layerset = layer_set
        self.additional_element_enrichment(invalid_element, add_enrichment)
        # return element

    def layer_set_search(self, type_invalid_element, templates, resumed):
        """Search for layer set.

        Args:
            type_invalid_element:
            templates:
            resumed:

        Returns:
            layer_set: bim2sim LayerSet instance
            ele_enrichment_info (dict): additional enrichment information that
             needs to be attached to the element and not the LayerSet
        """

        if type_invalid_element in self.template_layer_set:
            layer_set, ele_enrichment_info = self.template_layer_set[
                type_invalid_element].values()
        else:
            specific_template = templates[
                list(templates.keys())[0]][type_invalid_element]
            ele_enrichment_info = {key: info for key, info in
                              specific_template.items()
                              if type(info) not in [list, dict]}
            layer_set = self.create_layer_set_from_template(resumed,
                                                            specific_template)
            self.template_layer_set[type_invalid_element] = {
                'layer_set': layer_set,
                'add_enrichment': ele_enrichment_info}
        return layer_set, ele_enrichment_info

    @staticmethod
    def additional_element_enrichment(invalid_element, add_enrichment):
        # TODO what does this do?
        for key in add_enrichment:
            if hasattr(invalid_element, key):
                setattr(invalid_element, key, add_enrichment[key])

    def create_layer_set_from_template(self, resumed, template):
        """create layer set from template"""
        layer_set = LayerSet()
        for layer_template in template['layer'].values():
            layer = Layer()
            layer.thickness = layer_template['thickness']
            material_name = layer_template['material']['name']
            if material_name in self.template_materials:
                material = self.template_materials[material_name]
            else:
                material = self.create_material_from_template(
                    resumed[material_name])
                self.template_materials[material_name] = material
            material.parents.append(layer)
            layer.material = material
            layer.to_layerset.append(layer_set)
            layer_set.layers.append(layer)

        return layer_set

    @staticmethod
    def create_material_from_template(material_template):
        material = Material()
        material.name = material_template['material']
        material.density = material_template['density']
        material.spec_heat_capacity = material_template['heat_capac']
        material.thermal_conduc = material_template['thermal_conduc']
        material.solar_absorp = material_template['solar_absorp']
        return material

    def update_elements(self, elements, enriched_elements):
        # add new created materials to elements
        for mat in self.template_materials.values():
            elements[mat.guid] = mat
        for guid, new_element in enriched_elements.items():
            old_element = elements[guid]
            if type(old_element) is Layer:
                old_material = old_element.material
                if old_material.guid in elements:
                    del elements[old_material.guid]
                new_material = new_element.material
                elements[new_material.guid] = new_material
            if type(old_element) is LayerSet:
                for old_layer in old_element.layers:
                    old_material = old_layer.material
                    if old_material.guid in elements:
                        del elements[old_material.guid]
                    if old_layer.guid in elements:
                        del elements[old_layer.guid]
                for new_layer in new_element.layers:
                    new_material = new_layer.material
                    elements[new_material.guid] = new_material
                    elements[new_layer.guid] = new_layer
            if guid in elements:
                del elements[guid]
            elements[new_element.guid] = new_element
        return elements

    @staticmethod
    def get_resumed_material_templates(attrs: dict = None) -> dict:
        """get dict with the material templates and its respective attributes"""
        material_templates = get_material_templates()
        resumed = {}
        for k in material_templates:
            resumed[material_templates[k]['name']] = {}
            if attrs is not None:
                for attr in attrs:
                    if attr == 'thickness':
                        resumed[material_templates[k]['name']][attr] = \
                            material_templates[k]['thickness_default']
                    else:
                        resumed[material_templates[k]['name']][attr] = \
                            material_templates[k][attr]
            else:
                for attr in material_templates[k]:
                    if attr == 'thickness_default':
                        resumed[material_templates[k]['name']]['thickness'] = \
                            material_templates[k][attr]
                    elif attr == 'name':
                        resumed[material_templates[k]['name']]['material'] = \
                            material_templates[k][attr]
                    elif attr == 'thickness_list':
                        continue
                    else:
                        resumed[material_templates[k]['name']][attr] = \
                            material_templates[k][attr]
        return resumed

    @staticmethod
    def get_matches_list(search_words: str, search_list: list) -> list:
        """get patterns for a material name in both english and original language,
        and get afterwards the related elements from list"""

        material_ref = []

        if type(search_words) is str:
            pattern_material = search_words.split()
            translated = translate_deep(search_words)
            if translated:
                pattern_material.extend(translated.split())
            for i in pattern_material:
                material_ref.append(
                    re.compile('(.*?)%s' % i, flags=re.IGNORECASE))

        material_options = []
        for ref in material_ref:
            for mat in search_list:
                if ref.match(mat):
                    if mat not in material_options:
                        material_options.append(mat)
        if len(material_options) == 0:
            return search_list
        return material_options

    @staticmethod
    def material_search(material_options: list, material_input: str):
        material_selection = ListDecision(
            "Multiple possibilities found for material \"%s\"\n"
            "Enter name from given list" % material_input,
            choices=list(material_options),
            global_key='_Material_%s_search' % material_input,
            allow_skip=True,
            live_search=True)
        yield DecisionBunch([material_selection])
        return material_selection.value

VerifyLayersMaterialsTask

from bim2sim.elements.base_elements import Material
from bim2sim.elements.bps_elements import BPSProductWithLayers, LayerSet, Layer
from bim2sim.elements.mapping.units import ureg
from bim2sim.tasks.base import ITask
from bim2sim.utilities.common_functions import all_subclasses, filter_elements
from bim2sim.utilities.types import LOD


class VerifyLayersMaterials(ITask):
    """Verifies if layers and materials and their properties are meaningful."""

    reads = ('elements',)
    touches = ('invalid',)

    def __init__(self, playground):
        super().__init__(playground)
        self.invalid = []

    def run(self, elements: dict):
        self.logger.info("setting verifications")
        # TODO rework how invalids are assigned and use disaggregations instead
        #  elements if existing
        if self.playground.sim_settings.layers_and_materials is not LOD.low:
            materials = filter_elements(elements, Material)
            self.invalid.extend(self.materials_verification(materials))
            layers = filter_elements(elements, Layer)
            self.invalid.extend(self.layers_verification(layers))
            layer_sets = filter_elements(elements, LayerSet)
            self.invalid.extend(self.layer_sets_verification(layer_sets))
            self.invalid.extend(
                self.elements_with_layers_verification(elements))
            self.logger.warning("Found %d invalid elements", len(self.invalid))
        else:
            self.invalid.extend(
                self.elements_with_layers_verification(elements,
                                                        lod_low=True))
        self.invalid = {inv.guid: inv for inv in self.invalid}
        return self.invalid,

    def materials_verification(self, materials):
        """checks validity of the material property values"""
        invalid_layers = []
        for material in materials:
            invalid = False
            for attr in material.attributes:
                value = getattr(material, attr)
                if not self.value_verification(attr, value):
                    invalid = True
                    break
            if invalid:
                for layer in material.parents:
                    if layer not in invalid_layers:
                        invalid_layers.append(layer)
        sorted_layers = list(sorted(invalid_layers,
                                    key=lambda layer_e: layer_e.material.name))
        return sorted_layers

    def layers_verification(self, layers):
        """checks validity of the layer property values"""
        invalid_layers = []
        for layer in layers:
            if layer.guid not in self.invalid:
                invalid = True
                if layer.material:
                    if layer.thickness is not None:
                        invalid = False
                if invalid:
                    invalid_layers.append(layer)
        sorted_layers = list(sorted(invalid_layers,
                                    key=lambda layer_e: layer_e.material.name))
        return sorted_layers

    @staticmethod
    def layer_sets_verification(layer_sets):
        """checks validity of the layer set property values"""
        invalid_layer_sets = []
        for layer_set in layer_sets:
            invalid = True
            if len(layer_set.layers):
                if layer_set.thickness is not None:
                    invalid = False
            if invalid:
                invalid_layer_sets.append(layer_set)
        sorted_layer_sets = list(sorted(invalid_layer_sets,
                                        key=lambda layer_set_e:
                                        layer_set_e.name))
        return sorted_layer_sets

    @staticmethod
    def elements_with_layers_verification(elements, lod_low=False):
        invalid_elements = []
        layer_classes = list(all_subclasses(BPSProductWithLayers))
        for inst in elements.values():
            if type(inst) in layer_classes:
                if not lod_low:
                    invalid = False
                    if not inst.layerset and not inst.material_set:
                        invalid = True
                    if invalid:
                        invalid_elements.append(inst)
                else:
                    invalid_elements.append(inst)
        return invalid_elements

    @staticmethod
    def value_verification(attr: str, value: ureg.Quantity):
        """checks validity of the properties if they are on the blacklist"""
        blacklist = ['density', 'spec_heat_capacity', 'thermal_conduc']
        if (value is None or value <= 0) and attr in blacklist:
            return False
        return True

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.

1 participant