Files
old-krovovi-kalkulator/helix/calculators/ballast_calculator.py
2017-11-07 09:23:57 +01:00

249 lines
12 KiB
Python

from collections import namedtuple, OrderedDict
from math import ceil, floor
from helix.constants.panel_type import PanelType
from helix.constants.system_type import SystemType
from helix.models.panel import Panel, PanelWarnings
Result = namedtuple('Result', ['ballast_count', 'link_tray_count', 'cross_tray_count', 'system_weight', 'needs_anchor'])
class BallastCalculator(object):
def __init__(self, user_values):
self.values = user_values
self.system_type = user_values.system_type()
self.anchor_type = user_values.anchor_type()
self.system_constants = self.system_type.system_constants()
self.module_constants = user_values.module_system_constants()
def ballast_and_trays_matrix(self, c_p_matrix, q_z, panels, ballast_block_weight=None):
if not ballast_block_weight:
ballast_block_weight = self.values.ballast_block_weight()
ballast_store = self.calculate_ballast_store(c_p_matrix, q_z, ballast_block_weight)
for idx, panel in enumerate(panels):
stored_panel = ballast_store[panel.panel_type][panel.wind_zone][panel.fuzzy_wind_zone]
panels[idx] = stored_panel.merge(panel)
return panels
def update_ballast(self, c_p_matrix, q_z, panels):
ballast_block_weight = self.values.ballast_block_weight()
ballast_store = self.calculate_ballast_store(c_p_matrix, q_z, ballast_block_weight)
seismic_ballast_store = {}
for panel in panels:
seismic_anchors = panel.seismic_anchors if panel.seismic_anchors else 0
if seismic_anchors != 0:
key = hash(panel.wind_zone) + hash(panel.panel_type) + seismic_anchors + hash(panel.fuzzy_wind_zone) # hack
stored_panel = seismic_ballast_store.get(key)
if stored_panel:
panel.ballast = stored_panel.ballast
panel.link_tray = stored_panel.link_tray
panel.cross_tray = stored_panel.cross_tray
panel.pressure = stored_panel.pressure
else:
anchors = panel.wind_anchors + seismic_anchors
c_p = c_p_matrix[panel.wind_zone, panel.panel_type.index()] * (1.15 if panel.fuzzy_wind_zone else 1)
force = self.uplift(c_p, q_z) - anchors * self.anchor_type.uplift_capacity()
ballast_and_tray_count = self.ballast_and_tray_count(force, panel.panel_type, ballast_block_weight, anchors)
pressure = self.calculate_pressure_on_roof(ballast_and_tray_count.ballast_count, ballast_block_weight, ballast_and_tray_count.system_weight)
panel.ballast = ballast_and_tray_count.ballast_count
panel.link_tray = ballast_and_tray_count.link_tray_count
panel.cross_tray = ballast_and_tray_count.cross_tray_count
panel.pressure = pressure
seismic_ballast_store[key] = panel
else:
stored_panel = ballast_store[panel.panel_type][panel.wind_zone][panel.fuzzy_wind_zone]
panel.ballast = stored_panel.ballast
panel.link_tray = stored_panel.link_tray
panel.cross_tray = stored_panel.cross_tray
panel.pressure = stored_panel.pressure
return panels
def calculate_ballast_store(self, cp_matrix, qz, ballast_block_weight):
max_psf = self.values.max_system_pressure()
store = {}
for panel_type in PanelType.all():
sub_store = {}
for wind_zone, _ in enumerate(self.values.system_type().system_constants().wind_zones):
sub_store[wind_zone] = {}
for use_fuzzy in (True, False):
sub_store[wind_zone][use_fuzzy] = self.ballast_tray_and_anchor_count(wind_zone=wind_zone,
panel_type=panel_type,
ballast_block_weight=ballast_block_weight,
max_system_pressure=max_psf,
c_p_matrix=cp_matrix,
q_z=qz,
use_fuzzy=use_fuzzy)
store[panel_type] = sub_store
return store
def summary_table(self, c_p_matrix, q_z):
wind_zones = self.system_constants.wind_zones
ballast_block_weight = self.values.ballast_block_weight()
max_system_pressure = self.values.max_system_pressure()
table = OrderedDict()
for panel_type in PanelType.all():
ballast_counts = []
anchor_counts = []
pressures = []
warnings = []
for wind_zone_index, _ in enumerate(wind_zones):
ballast_tray_anchor_panels = self.ballast_tray_and_anchor_count(wind_zone_index, panel_type,
ballast_block_weight, max_system_pressure,
c_p_matrix, q_z)
anchor_count = ballast_tray_anchor_panels.wind_anchors
ballast_count = ballast_tray_anchor_panels.ballast
pressure = ballast_tray_anchor_panels.pressure
warning = ballast_tray_anchor_panels.warnings
pressure_as_string = "{0:.2f}".format(pressure)
# Because pressure is stored as a floating point number, it is possible, because floats
# for pressure to be something like 5.02999999999999. Which is clearly meant to be 5.03.
# This represents that as a string, which doesn't have that issue.
anchor_counts.append(anchor_count)
ballast_counts.append(ballast_count)
pressures.append(pressure_as_string)
warnings.append(warning)
table[panel_type] = {
'ballast blocks': ballast_counts,
'anchors': anchor_counts,
'pressure': pressures,
'warnings': warnings
}
return table
def ballast_tray_and_anchor_count(self, wind_zone, panel_type, ballast_block_weight, max_system_pressure,
c_p_matrix, q_z, use_fuzzy=False):
fuzzy_factor = 1.15 if use_fuzzy else 1
c_p = c_p_matrix[wind_zone, panel_type.index()] * fuzzy_factor
uplift_force = self.uplift(c_p, q_z)
warnings = []
keep_trying = True
anchor_count = 0
pressure = 0.
tries = 0
ballast_and_tray_count = None
while keep_trying:
remainder_force = uplift_force - anchor_count * self.anchor_type.uplift_capacity()
ballast_and_tray_count = self.ballast_and_tray_count(remainder_force, panel_type, ballast_block_weight, anchor_count)
pressure = self.calculate_pressure_on_roof(ballast_and_tray_count.ballast_count, ballast_block_weight, ballast_and_tray_count.system_weight)
keep_trying = (ballast_and_tray_count.needs_anchor or pressure > max_system_pressure) and ballast_and_tray_count.ballast_count > 0
if keep_trying:
anchor_count = self.calculate_anchors(panel_type, uplift_force) + tries
tries += 1
keep_trying &= tries < 100
if uplift_force / self.module_constants.surface_area >= self.module_constants.max_psf:
warnings.append(PanelWarnings.MaxPsf)
return Panel(wind_zone=wind_zone,
panel_type=panel_type,
ballast=ballast_and_tray_count.ballast_count,
link_tray=self.interpret_tray_count(ballast_and_tray_count.link_tray_count, panel_type),
cross_tray=ballast_and_tray_count.cross_tray_count,
wind_anchors=anchor_count,
pressure=pressure,
fuzzy_wind_zone=use_fuzzy,
warnings=warnings)
def ballast_and_tray_count(self, force_to_resist, panel_type, ballast_block_weight, anchor_count):
system_weight = self.module_constants.base_weight(panel_type, 0)
ballast_count = self.calculate_ballast(force_to_resist, system_weight, ballast_block_weight)
link_tray_count = 0
cross_tray_count = 0
needs_anchor = False
keep_trying = True
tries = 0
while keep_trying and tries < 3:
tries += 1
if ballast_count:
new_link_tray_count, _ = self.calculate_trays(ballast_count + 2 * anchor_count,
self.module_constants.link_tray_thresholds(panel_type))
# Recalculate weight given new link trays; recalculate ballast given new weight
system_weight = self.module_constants.base_weight(panel_type, new_link_tray_count + cross_tray_count)
ballast_count = self.calculate_ballast(force_to_resist, system_weight, ballast_block_weight)
new_cross_tray_count, needs_anchor = self.calculate_trays(ballast_count + 2 * anchor_count,
self.module_constants.cross_tray_thresholds(
panel_type))
system_weight = self.module_constants.base_weight(panel_type, new_cross_tray_count + new_link_tray_count)
ballast_count = self.calculate_ballast(force_to_resist, system_weight, ballast_block_weight)
if link_tray_count == new_link_tray_count and cross_tray_count == new_cross_tray_count:
keep_trying = False
link_tray_count = new_link_tray_count
cross_tray_count = new_cross_tray_count
else:
keep_trying = False
return Result(ballast_count, link_tray_count=link_tray_count, cross_tray_count=cross_tray_count,
system_weight=system_weight, needs_anchor=needs_anchor)
def uplift(self, c_p, q_z):
return q_z * self.module_constants.surface_area * c_p
def calculate_ballast(self, uplift, non_ballast_weight, ballast_block_weight):
if non_ballast_weight > uplift:
return 0
return ceil((uplift - non_ballast_weight) / ballast_block_weight)
def calculate_trays(self, ballast_count, thresholds):
for idx, threshold in enumerate(thresholds):
if ballast_count <= threshold:
return idx, False
return len(thresholds) - 1, True
def calculate_pressure_on_roof(self, ballast_count, ballast_block_weight, non_ballast_weight):
effective_area = self.module_constants.surface_area / self.module_constants.ground_coverage_ratio
return (ballast_count * ballast_block_weight + non_ballast_weight) / effective_area
def interpret_tray_count(self, link_tray_count, panel_type):
if self.system_type == SystemType.singleTilt:
if panel_type == PanelType.EastWest:
return 2
elif panel_type == PanelType.Corner or panel_type == PanelType.NorthSouth:
return 0
return link_tray_count or 0
def calculate_anchors(self, panel_type, uplift):
base_system_weight = self.module_constants.base_weight(panel_type, 0)
anchor_capacity = self.anchor_type.uplift_capacity()
return max(floor((uplift - base_system_weight) / anchor_capacity), 1)
def show_presented_link_trays(self, panels):
for panel in panels:
panel.presented_link_tray = self.present_link_tray(panel.link_tray, panel.panel_type)
return panels
def present_link_tray(self, link_tray_count, panel_type):
if self.system_type == SystemType.singleTilt:
link_tray_representation = {
PanelType.Corner: 0,
PanelType.NorthSouth: 0,
PanelType.EastWest: 2,
PanelType.Middle: min(1, int(link_tray_count)),
}[panel_type]
else:
link_tray_representation = {
PanelType.Corner: 2,
PanelType.NorthSouth: 2,
PanelType.EastWest: 1,
PanelType.Middle: min(1, int(link_tray_count)),
}[panel_type]
return link_tray_representation