from math import ceil, floor import copy from helix.Repositories.graph_repository import GraphRepository from helix.calculators.ballast_calculator import BallastCalculator from helix.calculators.bom_calculator import BomCalculator from helix.calculators.coordinates_calculator import CoordinatesCalculator from helix.calculators.pressure_coefficient_calculator import PressureCoefficientCalculator from helix.calculators.seismic_calculator import SeismicCalculator from helix.calculators.subarray_helper import get_subarray_sizes_and_rows, extract_subarray from helix.calculators.summary_values_calculator import SummaryValuesCalculator from helix.calculators.wind_pressure_calculator import WindPressureCalculator class Calculator(object): def __init__(self, user_values, calculate_panel_data=True): self.values = user_values self._q_z = None self._c_p_matrix = None self._L_B = None self._K_z = None self.subarrays = None self.buildings = self.values.buildings_polygons() self.buildings_for_drawing = [] self.panels = self.values.csv() if calculate_panel_data and self.panels is not None: for idx, panel in enumerate(self.panels): panel.id = idx + 1 self.panels.sort(key=lambda x: x.subarray) self.subarrays = get_subarray_sizes_and_rows(self.panels) self.__compute_ballast() _,_,self.buildings_for_drawing = self.__transform_coordinates() self.graph_repository = GraphRepository(self.panels, self.subarrays, self.values.system_type()) if self.values.user_override_seismic_anchors(): user_provided_panels = self.values.get_user_provided_seismic_anchors() for user_panel in user_provided_panels: panel = [panel for panel in self.panels if panel.id == user_panel.id][0] panel.seismic_anchors = user_panel.seismic_anchors self.__compute_seismic_anchors(self.panels) # Update subarrays to include required seismic anchors self.__update_ballast(self.panels) else: # Update subarrays *and panels* to include required seismic anchors self.panels = self.__compute_seismic_anchors(self.panels) def k_z(self): if self._K_z is None: self._K_z = WindPressureCalculator(self.values).K_z() return self._K_z def L_B(self): if self._L_B is None: self._L_B = PressureCoefficientCalculator(self.values).L_B() return self._L_B def summary_table(self): return BallastCalculator(self.values).summary_table(self.__c_p_matrix(), self.q_z()) def minimum_array_sizes(self): return PressureCoefficientCalculator(self.values).minimum_array_size(self.L_B()) # Used in the array summary page - is the table of weight, psf, anchors, ballast, etc. for the entire system def summary_values(self): seismic_anchors = self.subarray_summary() ballast_calculator = BallastCalculator(self.values) seismic_interval = SeismicCalculator(self.values, self.graph_repository).seismic_anchor_interval() return SummaryValuesCalculator(self.values).summary_values(self.panels, seismic_anchors, self.__c_p_matrix(), self.q_z(), seismic_interval, ballast_calculator) def documentation_summary_values(self): seismic_anchors = self.subarray_summary() ballast_calculator = BallastCalculator(self.values) seismic_interval = SeismicCalculator(self.values, self.graph_repository).seismic_anchor_interval() return SummaryValuesCalculator(self.values).documentation_summary_values(self.panels, seismic_anchors, self.__c_p_matrix(), self.q_z(), seismic_interval, ballast_calculator) # used in the array visualization - is parsed into json and displayed using the fancy canvas def get_computed_csv_columns(self): return BallastCalculator(self.values).show_presented_link_trays(self.panels) def compute_bom(self): required_seismic_anchors = self.subarray_summary() return BomCalculator(self.values, self.panels, required_seismic_anchors, self.graph_repository).compute_bom() def documentation_bom(self): required_seismic_anchors = self.subarray_summary() return BomCalculator(self.values, self.panels, required_seismic_anchors, self.graph_repository).documentation_bom() # used in the array summary page - is part of the fancy scrolling table of summing up each subarray def subarray_summary(self): summary_values_calculator = SummaryValuesCalculator(self.values) for subarray in self.subarrays: panels_for_subarray = extract_subarray(self.panels, subarray.subarray_number) weight, _ = summary_values_calculator.system_weight_and_pressure(panels_for_subarray) subarray.weight = weight return self.subarrays def q_z(self): if self._q_z is None: self._q_z = WindPressureCalculator(self.values).q_z(self.k_z()) return self._q_z def __c_p_matrix(self): if self._c_p_matrix is None: self._c_p_matrix = PressureCoefficientCalculator(self.values).c_p_matrix(self.L_B()) return self._c_p_matrix def __compute_seismic_anchors(self, panels): panels = copy.deepcopy(panels) seismic_calculator = SeismicCalculator(self.values, self.graph_repository) for subarray in self.subarrays: if subarray.required_seismic_anchors is None: subarray.required_seismic_anchors = 0 panels = self.__seismic_anchors_for_subarray(panels, subarray, seismic_calculator) return panels def __seismic_anchors_for_subarray(self, panels, subarray, seismic_calculator): # do first estimation to obtain upper bound required_seismic = seismic_calculator.required_force_seismic_anchors(subarray.subarray_number, panels) test_value = required_seismic tried_acceptable_values = [] def assign_seismic_anchors(count): subarray.required_seismic_anchors = count seismic_calculator.assign_seismic_anchors(subarray, panels) self.__update_ballast(panels) assigned = sum([panel.seismic_anchors for panel in panels if panel.seismic_anchors is not None]) return assigned step = max(1, test_value // 2) while True: seismic_anchors_assigned = assign_seismic_anchors(test_value) provided_force = seismic_calculator.compute_provided_lateral_capacity(subarray.subarray_number, panels) required_seismic_force = seismic_calculator.required_force_seismic_demand(subarray.subarray_number, panels) if seismic_anchors_assigned == 0: # anchors were not assigned propably because self.graph_repository.subarray_graph(subarray.subarray_number) is empty # which may be because of test construction return panels if provided_force < required_seismic_force: test_value += step else: if (test_value in tried_acceptable_values) or (required_seismic_force == 0): return panels tried_acceptable_values.append(test_value) test_value = max(0, test_value - step) step = max(1, step // 2) def __transform_coordinates(self): return CoordinatesCalculator(self.values).transform_coordinates(self.panels, self.subarrays, self.buildings) def __compute_ballast(self): ballast_calculator = BallastCalculator(self.values) changed_panels = ballast_calculator.ballast_and_trays_matrix(self.__c_p_matrix(), self.q_z(), self.panels) self.panels = [panel.merge(changed_panels[idx]) for idx, panel in enumerate(self.panels)] def __update_ballast(self, panels): BallastCalculator(self.values).update_ballast(self.__c_p_matrix(), self.q_z(), panels)