185 lines
8.7 KiB
Python
185 lines
8.7 KiB
Python
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)
|