def test_estimate_opex_rated_power(): df = estimate_opex(2, 100, 1e3) year_one = df.loc[df['project_year'] == 0] other_years = df.loc[df['project_year'] != 0] assert (year_one["costs"] == 0.).all() assert (other_years["costs"] == 100 * 1e3).all()
def test_estimate_opex_combined(): df = estimate_opex(2, 100, 1e3, 1e3, 8766) year_one = df.loc[df['project_year'] == 0] other_years = df.loc[df['project_year'] != 0] assert (year_one["costs"] == 0.).all() assert (other_years["costs"] == 100 * 1e3 + 1e3).all()
def test_estimate_opex_rated_power(): df = estimate_opex(2, 100, 1e3) year_one = df.loc[df['project_year'] == 0] other_years = df.loc[df['project_year'] != 0] assert (pd.isnull(df["phase"])).all() assert (df["quantity"] == 1).all() assert (year_one["unitary_cost"] == 0.).all() assert (other_years["unitary_cost"] == 100 * 1e3).all()
def test_estimate_opex_mttf(): df = estimate_opex(2, annual_repair_cost_estimate=1e3, annual_array_mttf_estimate=8766) year_one = df.loc[df['project_year'] == 0] other_years = df.loc[df['project_year'] != 0] assert (year_one["costs"] == 0.).all() assert (other_years["costs"] == 1e3).all()
def test_estimate_opex_combined(): df = estimate_opex(2, 100, 1e3, 1e3, 8766, "Test") year_one = df.loc[df['project_year'] == 0] other_years = df.loc[df['project_year'] != 0] assert (df["phase"] == "Test").all() assert (df["quantity"] == 1).all() assert (year_one["unitary_cost"] == 0.).all() assert (other_years["unitary_cost"] == 100 * 1e3 + 1e3).all()
def test_estimate_opex_mttf(): df = estimate_opex(2, annual_repair_cost_estimate=1e3, annual_array_mttf_estimate=8766) year_one = df.loc[df['project_year'] == 0] other_years = df.loc[df['project_year'] != 0] assert (pd.isnull(df["phase"])).all() assert (df["quantity"] == 1).all() assert (year_one["unitary_cost"] == 0.).all() assert (other_years["unitary_cost"] == 1e3).all()
def connect(self, debug_entry=False): '''The connect method is used to execute the external program and populate the interface data store with values. Note: Collecting data from the interface for use in the external program can be accessed using self.data.my_input_variable. To put new values into the interface once the program has run we set self.data.my_output_variable = value ''' bom_cols = ['phase', 'quantity', 'unitary_cost', 'project_year'] # CAPEX Dataframes device_bom = pd.DataFrame(columns=bom_cols) electrical_bom = pd.DataFrame(columns=bom_cols) moorings_bom = pd.DataFrame(columns=bom_cols) installation_bom = pd.DataFrame(columns=bom_cols) capex_oandm_bom = pd.DataFrame(columns=bom_cols) externalities_bom = pd.DataFrame(columns=bom_cols) opex_bom = pd.DataFrame() energy_record = pd.DataFrame() # Prepare costs if (self.data.n_devices is not None and self.data.device_cost is not None): quantities = [self.data.n_devices] costs = [self.data.device_cost] years = [0] device_bom = make_phase_bom(quantities, costs, years, "Devices") # Patch double counting of umbilical if (self.data.electrical_bom is not None and self.data.moorings_bom is not None): # Remove matching identifiers from electrical bom unique = list(set(self.data.moorings_bom["Key Identifier"])) matching = self.data.electrical_bom["Key Identifier"].isin(unique) self.data.electrical_bom = self.data.electrical_bom[~matching] if self.data.electrical_bom is not None: electrical_bom = self.data.electrical_bom.drop("Key Identifier", axis=1) name_map = { "Quantity": 'quantity', "Cost": 'unitary_cost', "Year": 'project_year' } electrical_bom = electrical_bom.rename(columns=name_map) electrical_bom["phase"] = "Electrical Sub-Systems" elif self.data.electrical_estimate is not None: electrical_bom = estimate_cost_per_power( 1, self.data.electrical_estimate, "Electrical Sub-Systems") if self.data.moorings_bom is not None: moorings_bom = self.data.moorings_bom.drop("Key Identifier", axis=1) name_map = { "Quantity": 'quantity', "Cost": 'unitary_cost', "Year": 'project_year' } moorings_bom = moorings_bom.rename(columns=name_map) moorings_bom["phase"] = "Mooring and Foundations" elif self.data.moorings_estimate is not None: moorings_bom = estimate_cost_per_power(1, self.data.moorings_estimate, "Mooring and Foundations") if self.data.installation_bom is not None: installation_bom = self.data.installation_bom.drop( "Key Identifier", axis=1) name_map = { "Quantity": 'quantity', "Cost": 'unitary_cost', "Year": 'project_year' } installation_bom = installation_bom.rename(columns=name_map) installation_bom["phase"] = "Installation" elif self.data.install_estimate is not None: installation_bom = estimate_cost_per_power( 1, self.data.install_estimate, "Installation") if self.data.capex_oandm is not None: quantities = [1] costs = [self.data.capex_oandm] years = [0] capex_oandm_bom = make_phase_bom(quantities, costs, years, "Condition Monitoring") if self.data.externalities_capex is not None: quantities = [1] costs = [self.data.externalities_capex] years = [0] capex_oandm_bom = make_phase_bom(quantities, costs, years, "Externalities") # Combine the capex dataframes capex_bom = pd.concat([ device_bom, electrical_bom, moorings_bom, installation_bom, capex_oandm_bom, externalities_bom ], ignore_index=True, sort=False) if self.data.opex_per_year is not None: opex_bom = self.data.opex_per_year.copy() opex_bom.index.name = 'project_year' opex_bom = opex_bom.reset_index() elif (self.data.lifetime is not None and (self.data.opex_estimate is not None or (self.data.annual_repair_cost_estimate is not None and self.data.annual_array_mttf_estimate is not None))): opex_bom = estimate_opex(self.data.lifetime, 1, self.data.opex_estimate, self.data.annual_repair_cost_estimate, self.data.annual_array_mttf_estimate) # Add OPEX externalities if not opex_bom.empty and self.data.externalities_opex is not None: opex_bom = opex_bom.set_index('project_year') opex_bom += self.data.externalities_opex opex_bom = opex_bom.reset_index() # Prepare energy if self.data.network_efficiency is not None: net_coeff = self.data.network_efficiency * 1e3 else: net_coeff = 1e3 if self.data.energy_per_year is not None: energy_record = self.data.energy_per_year.copy() energy_record = energy_record * net_coeff energy_record.index.name = 'project_year' energy_record = energy_record.reset_index() elif (self.data.estimate_energy_record and self.data.lifetime is not None and self.data.annual_energy is not None): energy_record = estimate_energy(self.data.lifetime, self.data.annual_energy, net_coeff) if debug_entry: return result = main(capex_bom, opex_bom, energy_record, self.data.discount_rate) self.data.capex_total = result["CAPEX"] self.data.discounted_capex = result["Discounted CAPEX"] self.data.capex_breakdown = result["CAPEX breakdown"] if self.data.externalities_capex is not None: self.data.capex_no_externalities = \ self.data.capex_total - self.data.externalities_capex # Build metrics table if possible n_rows = None if not opex_bom.empty: n_rows = len(opex_bom.columns) - 1 elif not energy_record.empty: n_rows = len(energy_record.columns) - 1 else: return table_cols = [ "LCOE", "LCOE CAPEX", "LCOE OPEX", "OPEX", "Energy", "Discounted OPEX", "Discounted Energy" ] metrics_dict = {} for col_name in table_cols: if result[col_name] is not None: values = result[col_name].values if "Energy" in col_name: values /= 1e3 else: values = [None] * n_rows metrics_dict[col_name] = values metrics_table = pd.DataFrame(metrics_dict) self.data.economics_metrics = metrics_table # Do univariate stats on discounted metrics args_table = { "Discounted OPEX": "discounted_opex", "Discounted Energy": "discounted_energy" } for key, arg_root in args_table.iteritems(): if metrics_table[key].isnull().any(): continue data = metrics_table[key].values mean = None mode = None lower = None upper = None # Catch one or two data points if len(data) == 1: mean = data[0] mode = data[0] elif len(data) == 2: mean = data.mean() else: try: distribution = UniVariateKDE(data) mean = distribution.mean() mode = distribution.mode() intervals = distribution.confidence_interval(95) lower = intervals[0] upper = intervals[1] except np.linalg.LinAlgError: mean = data.mean() arg_mean = "{}_mean".format(arg_root) arg_mode = "{}_mode".format(arg_root) arg_lower = "{}_lower".format(arg_root) arg_upper = "{}_upper".format(arg_root) self.data[arg_mean] = mean self.data[arg_mode] = mode self.data[arg_lower] = lower self.data[arg_upper] = upper # Bivariate stats on LCOE and related variables if (metrics_table["Discounted Energy"].isnull().any() or len(metrics_table["Discounted Energy"])) < 3: return opex = metrics_table["Discounted OPEX"] / 1000. energy = metrics_table["Discounted Energy"] try: distribution = BiVariateKDE(opex, energy) except np.linalg.LinAlgError: return mean_coords = distribution.mean() self.data.lcoe_mean = (result["Discounted CAPEX"] / 1000. + mean_coords[0]) / mean_coords[1] mode_coords = distribution.mode() lcoe_mode = (result["Discounted CAPEX"] / 1000. + mode_coords[0]) / mode_coords[1] self.data.lcoe_mode_opex = mode_coords[0] * 1000 self.data.lcoe_mode_energy = mode_coords[1] self.data.lcoe_mode = lcoe_mode xx, yy, pdf = distribution.pdf() clevels = pdf_confidence_densities(pdf) cx, cy = pdf_contour_coords(xx, yy, pdf, clevels[0]) lcoes = [] for discounted_opex, discounted_energy in zip(cx, cy): lcoe = (result["Discounted CAPEX"] / 1000. + discounted_opex) / discounted_energy lcoes.append(lcoe) self.data.confidence_density = clevels[0] self.data.lcoe_lower = min(lcoes) self.data.lcoe_upper = max(lcoes) # Calculate values using most likely OPEX / Energy combination # CAPEX vs OPEX Breakdown and OPEX Breakdown if externalities discounted_opex_mode = mode_coords[0] * 1000 breakdown = { "Discounted CAPEX": result["Discounted CAPEX"], "Discounted OPEX": discounted_opex_mode } self.data.cost_breakdown = breakdown if self.data.externalities_opex is None: discounted_maintenance = discounted_opex_mode else: years = range(1, len(opex_bom) + 1) discounted_externals = [ self.data.externalities_opex / (1 + self.data.discount_rate)**i for i in years ] discounted_external = np.array(discounted_externals).sum() discounted_maintenance = discounted_opex_mode - \ discounted_external self.data.opex_breakdown = { "Maintenance": discounted_external, "Externalities": discounted_maintenance } # LCOE Breakdowns in cent/kWh discounted_energy_mode = mode_coords[1] * 10. if self.data.capex_breakdown is not None: capex_lcoe_breakdown = {} for k, v in self.data.capex_breakdown.iteritems(): capex_lcoe_breakdown[k] = round(v / discounted_energy_mode, 2) self.data.capex_lcoe_breakdown = capex_lcoe_breakdown lcoe_maintenance = round( discounted_maintenance / discounted_energy_mode, 2) if self.data.externalities_opex is None: lcoe_external = 0 else: lcoe_external = round(discounted_external / discounted_energy_mode, 2) self.data.opex_lcoe_breakdown = { "Maintenance": lcoe_maintenance, "Externalities": lcoe_external } total_capex = sum(capex_lcoe_breakdown.values()) total_opex = lcoe_maintenance + lcoe_external self.data.lcoe_breakdown = {"CAPEX": total_capex, "OPEX": total_opex} # LCOE distribution raw = {"values": pdf, "coords": [xx, yy]} self.data.lcoe_pdf = raw return