Question-and-Answer Resource for the Building Energy Modeling Community
Get started with the Help page

# Clarifications regarding BEopt modified payback period

Hello,

I am seeking clarification on how BEopt's modified payback period is calculated. The help in v2.8 and v3.0 include a general description, but there are some ambiguities, and unlike the other financial metrics there is no equation to parse. Below is some pseudo-code indicating my understanding of the description (for a thirty year analysis period), but we are not able to faithfully recreate BEopt's values.

IF SUM(PACKAGE UTILITIES[1-30]) <= SUM(REFERENCE UTILITIES[1-30])
EXPENSE=PACKAGE INITIAL COST - REFERENCE INITIAL COST
SAVED=0

FOR YR IN 1 TO 30
EXPENSE+=PACKAGE REPLACEMENT[YR] - REFERENCE PACKAGE REPLACEMENT[YR]
SAVED+=PACKAGE UTILITY[YR] - REFERENCE UTILITY[YR]

IF SAVED>=EXPENSE
PAYBACK=YR-1 + (SAVED-EXPENSE)/PACKAGE UTILITY[YR]
BREAK
END
END
ELSE
PAYBACK = NaN
END


P.S. We need to calculate our own modified payback periods, because we want to produce payback periods for heat pump packages that use a furnace home as the point of comparison, since they are still the most prevalent equipment in our market.

edit retag close merge delete

Sort by ยป oldest newest most voted

Edit: Sorry, you wanted modified payback and not modified internal rate of return.

Here is the modified payback calculation:

public double CalculateModifiedPayback(OutputPoint outputPt, OutputPoint refOutputPt)
{
// Calculate total of cash outflows
float TotalCashOutflow = 0;
for (int year = 0; year <= AnalysisPd; year++)
{
TotalCashOutflow += (float)GetPresentValueGivenFutureCost(CalculateNetHomeownerCashFlowForYear(outputPt, refOutputPt, year, false), year, GetNominalDiscountRate());
}

// Calculate cumulative positive cash flows
float[] CumulativePositiveCashFlows = new float[AnalysisPd + 1];
for (int year = 0; year <= AnalysisPd; year++)
{
CumulativePositiveCashFlows[year] = -1 * (float)GetPresentValueGivenFutureCost(CalculateNetHomeownerCashFlowForYear(outputPt, refOutputPt, year, true), year, GetNominalDiscountRate());
if (year > 0)
CumulativePositiveCashFlows[year] += CumulativePositiveCashFlows[year - 1];
}

// Find year where cumulative positive cash flow exceeds the total cash outflow
for (int year = 0; year <= AnalysisPd; year++)
{
if (CumulativePositiveCashFlows[year] >= TotalCashOutflow)
{
if (year == 0)
return 0;

// Interpolate between previous year and this year to obtain
// a non-integer payback value
return year - 1 + (TotalCashOutflow - CumulativePositiveCashFlows[year - 1]) / (CumulativePositiveCashFlows[year] - CumulativePositiveCashFlows[year - 1]);
}
}

return double.NaN;
}


CalculateNetHomeownerCashFlowForYear method:

private double CalculateNetHomeownerCashFlowForYear(OutputPoint outputPt, OutputPoint refOutputPt, int year, bool PositiveOnly)
{
double retval = 0;
retval += CalculateNetCashFlowForYear(outputPt.NominalCashFlow(year).Incentives, refOutputPt.NominalCashFlow(year).Incentives, PositiveOnly);
retval += CalculateNetCashFlowForYear(outputPt.NominalCashFlow(year).LoanInterest, refOutputPt.NominalCashFlow(year).LoanInterest, PositiveOnly);
retval += CalculateNetCashFlowForYear(outputPt.NominalCashFlow(year).LoanPrincipal, refOutputPt.NominalCashFlow(year).LoanPrincipal, PositiveOnly);
retval += CalculateNetCashFlowForYear(outputPt.NominalCashFlow(year).LoanTaxDeduction, refOutputPt.NominalCashFlow(year).LoanTaxDeduction, PositiveOnly);
retval += CalculateNetCashFlowForYear(outputPt.NominalCashFlow(year).ReplacementCost, refOutputPt.NominalCashFlow(year).ReplacementCost, PositiveOnly);
retval += CalculateNetCashFlowForYear(outputPt.NominalCashFlow(year).ResidualValue, refOutputPt.NominalCashFlow(year).ResidualValue, PositiveOnly);
retval += CalculateNetCashFlowForYear(outputPt.NominalCashFlow(year).FederalTaxCredits, refOutputPt.NominalCashFlow(year).FederalTaxCredits, PositiveOnly);
retval += CalculateNetCashFlowForYear(outputPt.NominalCashFlow(year).NonFederalTaxCredits, refOutputPt.NominalCashFlow(year).NonFederalTaxCredits, PositiveOnly);
retval += CalculateNetCashFlowForYear(outputPt.NominalCashFlow(year).UtilityBill, refOutputPt.NominalCashFlow(year).UtilityBill, PositiveOnly);
retval += CalculateNetCashFlowForYear(outputPt.NominalCashFlow(year).CashPayment, refOutputPt.NominalCashFlow(year).CashPayment, PositiveOnly);
return retval;
}


CalculateNetCashFlowForYear method:

private double CalculateNetCashFlowForYear(float outputPtVal, float refOutputPtVal, bool PositiveOnly)
{
double diff = outputPtVal - refOutputPtVal;
if ((diff < 0 && PositiveOnly) || (diff > 0 && !PositiveOnly))
return diff;
return 0;
}


GetPresentValueGivenFutureCost method:

public static double GetPresentValueGivenFutureCost(double Cost, double YearOfCost, double DiscountRate)
{
return (Cost / (Math.Pow((1 + DiscountRate), YearOfCost)));
}

more

Thanks, but that's modified internal rate of return, not modified payback. The general flow looks similar to what I hypothesized, but since some of the trappings are different due to it having a different purpose it's a little difficult to be certain how to adapt this. In particular, there's nothing that relates to the least clear portion of the help file's description of modified payback, "The modified payback is the moment in time where the cumulative positive cash flow exceeds the total cash outflow". For example, is residual value really part of payback period?

( 2023-03-08 09:56:16 -0500 )edit

Sorry, I updated it to show the modified payback calculation.

( 2023-03-08 10:00:43 -0500 )edit

Thanks. CalculateNetHomeownerCashFlowForYeart got wiped in the update. What's the difference between true and false for the fourth argument? And I assume GetPresentValueGivenFutureCost is standard NPV?

( 2023-03-08 10:12:45 -0500 )edit
1

( 2023-03-08 10:18:32 -0500 )edit

Please start posting anonymously - your entry will be published after you log in or create a new account.