Ejemplo n.º 1
0
def test_make_phase_bom():

    df = make_phase_bom([0, 1, 2], [10, 20, 30], [0, 1, 2])

    assert (pd.isnull(df["phase"])).all()
    assert np.isclose(df["quantity"], [0, 1, 2]).all()
    assert np.isclose(df["unitary_cost"], [10, 20, 30]).all()
    assert np.isclose(df["project_year"], [0, 1, 2]).all()
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
def test_make_phase_bom_phase_fail():

    with pytest.raises(ValueError):
        make_phase_bom([0, 1, 2], [10, 20, 30], [0, 1])
Ejemplo n.º 4
0
def test_make_phase_bom_phase():

    df = make_phase_bom([0, 1, 2], [10, 20, 30], [0, 1, 2], "Test")

    assert (df["phase"] == "Test").all()