from math import ceil, floor from helix.calculators.subarray_graph import SubarrayGraph from helix.calculators.subarray_helper import extract_subarray from helix.constants.global_constants import minimum_racking_capacity from helix.constants.panel_type import PanelType from helix.models.subarray import Subarray class SeismicCalculator(object): def __init__(self, values, graph_repository): self.values = values self.system_type = values.system_type() self.system_constants = values.module_system_constants() self.anchor_type = values.anchor_type() self.graph_repository = graph_repository def assign_seismic_anchors(self, subarray, panels): panel_data_for_subarray = extract_subarray(panels, subarray.subarray_number) self.assign_anchors_to_subarray(panel_data_for_subarray, subarray) return panels def assign_anchors_to_subarray(self, panels, subarray): sds = self.values.spectral_response() wind_anchors = sum([panel.wind_anchors for panel in panels]) for panel in panels: if panel.seismic_anchors is None: panel.seismic_anchors = 0 required_anchors = subarray.required_seismic_anchors if required_anchors == 0 and (wind_anchors == 0 or sds < 1): return panels graph = self.graph_repository.subarray_graph(subarray.subarray_number) if len(graph.nodes) == 0: return panels more_anchors_needed = True perimeter_covered = sds < 1.0 anchor_threshold = 0 while more_anchors_needed: rung = graph.pop_rung() interval = int(self.seismic_anchor_interval()) nodes_since_last_anchor = interval if len(rung) == 0: graph.reset() anchor_threshold += 1 continue while more_anchors_needed and interval >= 0: for node in rung: nodes_since_last_anchor += 1 if node.wind_anchor + node.seismic_anchor > anchor_threshold: nodes_since_last_anchor = 0 if nodes_since_last_anchor > interval: node.assign_seismic_anchor() required_anchors -= 1 nodes_since_last_anchor = 0 more_anchors_needed = (not perimeter_covered) or required_anchors > 0 if not more_anchors_needed: break perimeter_covered = True if interval <= 1: interval -= 1 else: interval /= 2 for idx, node in enumerate(graph.nodes): panels[idx].seismic_anchors = node.seismic_anchor return panels def compute_provided_lateral_capacity(self, subarray_number, panels): subarray_panels = extract_subarray(panels, subarray_number) anchors = {PanelType.Corner: 0, PanelType.NorthSouth: 0, PanelType.EastWest: 0, PanelType.Middle: 0} for panel in subarray_panels: if panel.seismic_anchors is not None: anchors[panel.panel_type] += panel.seismic_anchors return self.anchors_shear_capacity(anchors[PanelType.Corner], anchors[PanelType.NorthSouth], anchors[PanelType.EastWest], anchors[PanelType.Middle]) def seismic_anchors_for_subarray(self, F_p, subarray_weight, spectral_response, friction_coefficient, seismic_anchors, corner_anchors, north_south_anchors, east_west_anchors, middle_anchors, shear_capacity): demand = self.seismic_demand_for_subarray(F_p, subarray_weight, spectral_response, friction_coefficient, seismic_anchors, corner_anchors, north_south_anchors, east_west_anchors, middle_anchors) return ceil(demand / shear_capacity) def anchors_shear_capacity(self, corner_anchors, north_south_anchors, east_west_anchors, middle_anchors): anchor_shear_capacity = self.anchor_type.shear_capacity() panel_racking_capacity = self.system_constants.racking_capacity corner_capacity = corner_anchors * min(anchor_shear_capacity, panel_racking_capacity(PanelType.Corner)) north_south_capacity = north_south_anchors * min(anchor_shear_capacity, panel_racking_capacity(PanelType.NorthSouth)) east_west_capacity = east_west_anchors * min(anchor_shear_capacity, panel_racking_capacity(PanelType.EastWest)) middle_capacity = middle_anchors * min(anchor_shear_capacity, panel_racking_capacity(PanelType.Middle)) return corner_capacity + north_south_capacity + east_west_capacity + middle_capacity def seismic_demand_for_subarray(self, F_p, subarray_weight, spectral_response, friction_coefficient, seismic_anchors, corner_anchors, north_south_anchors, east_west_anchors, middle_anchors): if (corner_anchors + north_south_anchors + east_west_anchors + middle_anchors == 0) and seismic_anchors == 0: return 0 existing_shear_resistance = self.anchors_shear_capacity(corner_anchors, north_south_anchors, east_west_anchors, middle_anchors) shear_force = 0.7 * F_p * subarray_weight - ( (0.6 - 0.14 * spectral_response) * 0.7 * friction_coefficient * subarray_weight) - existing_shear_resistance return max(shear_force, 0) def required_force_seismic_anchors(self, subarray_number, panels): demand = self.required_force_seismic_demand(subarray_number, panels) system_shear_capacity = min(self.anchor_type.shear_capacity(), minimum_racking_capacity) return ceil(demand / system_shear_capacity) def required_force_seismic_demand(self, subarray_number, panels): subarray_panels = extract_subarray(panels, subarray_number) importance_factor = self.values.importance_factor() spectral_response = self.values.spectral_response() F_p = 1.2 * spectral_response / (1.5 / importance_factor) # number of wind anchors by panel type anchors = {PanelType.Corner: 0, PanelType.NorthSouth: 0, PanelType.EastWest: 0, PanelType.Middle: 0} # total weight subarray_weight = 0 # total number of seismic anchors seismic_anchors = 0 for panel in subarray_panels: if panel.seismic_anchors is not None: seismic_anchors += panel.seismic_anchors # it could be calculated before the loop to avoid redundant calculations effective_area = self.system_constants.surface_area / self.system_constants.ground_coverage_ratio weight = panel.pressure * effective_area anchors[panel.panel_type] += panel.wind_anchors subarray_weight += weight force_required_demand = self.seismic_demand_for_subarray(F_p, subarray_weight, spectral_response, self.system_constants.friction_coefficient, seismic_anchors, anchors[PanelType.Corner], anchors[PanelType.NorthSouth], anchors[PanelType.EastWest], anchors[PanelType.Middle]) return force_required_demand def required_geometric_seismic_anchors(self, subarray_number, panels): if panels[0].coordinate is None or self.values.spectral_response() < 1: return 0 panel_data_for_subarray = extract_subarray(panels, subarray_number) subarray = Subarray(required_seismic_anchors=0, subarray_number=subarray_number) anchors_for_subarray = self.assign_anchors_to_subarray(panel_data_for_subarray, subarray) return sum(panel.seismic_anchors for panel in anchors_for_subarray if panel.seismic_anchors) def seismic_anchor_interval(self): sds = self.values.spectral_response() importance_factor = self.values.importance_factor() interval_constant, interval_multiplier = self.system_constants.seismic_anchor_interval_constants denom = (22.96 * importance_factor * sds - interval_constant + interval_multiplier * sds) if denom <= 0: return 15 return floor(minimum_racking_capacity / denom)