From 33e8010b2fb01dc6d3bb6eec69a9de02eefdfc41 Mon Sep 17 00:00:00 2001 From: Mark van Koningsveld Date: Sat, 25 Jan 2020 10:07:09 +0100 Subject: [PATCH] Moved general functions to cora and plot and cleaned up other code --- opentisim/agribulk_mixins.py | 10 +- opentisim/agribulk_system.py | 156 ++------------------------ opentisim/core.py | 148 ++++++++++++++++++++++++- opentisim/hydrogen_mixins.py | 34 ++++-- opentisim/hydrogen_system.py | 209 +++-------------------------------- opentisim/plot.py | 4 +- 6 files changed, 207 insertions(+), 354 deletions(-) diff --git a/opentisim/agribulk_mixins.py b/opentisim/agribulk_mixins.py index 8fb5559..507a777 100644 --- a/opentisim/agribulk_mixins.py +++ b/opentisim/agribulk_mixins.py @@ -329,7 +329,7 @@ def scenario_random(self, startyear=2019, lifecycle=20, rate=1.02, mu=0.01, sigm self.scenario_data = pd.DataFrame(data=scenario_data) - def plot_demand(self, width=0.1, alpha=0.6, fontsize=15): + def plot_demand(self, width=0.1, alpha=0.6, fontsize=20): """generate a histogram of the demand data""" # generate plot fig, ax = plt.subplots(figsize=(20, 10)) @@ -352,6 +352,10 @@ def plot_demand(self, width=0.1, alpha=0.6, fontsize=15): ax.set_ylabel('Demand [tons]', fontsize=fontsize) ax.set_title('Demand: {}'.format(self.name), fontsize=fontsize) ax.set_xticks([x for x in years]) - ax.set_xticklabels([int(x) for x in years], fontsize=fontsize) + ax.set_xticklabels([int(x) for x in years], rotation='vertical', fontsize=fontsize) ax.yaxis.set_tick_params(labelsize=fontsize) - ax.legend(fontsize=fontsize) + + # print legend + fig.legend(loc='lower center', bbox_to_anchor=(0, -.01, .9, 0.7), + fancybox=True, shadow=True, ncol=5, fontsize=fontsize) + fig.subplots_adjust(bottom=0.15) diff --git a/opentisim/agribulk_system.py b/opentisim/agribulk_system.py index 4f0a272..f64c36f 100644 --- a/opentisim/agribulk_system.py +++ b/opentisim/agribulk_system.py @@ -133,10 +133,10 @@ def simulate(self): self.calculate_revenue(year) # 6. collect all cash flows (capex, opex, revenues) - cash_flows, cash_flows_WACC_real = self.add_cashflow_elements() + # cash_flows, cash_flows_WACC_real = core.add_cashflow_elements(self) # 7. calculate PV's and aggregate to NPV - self.NPV() + core.NPV(self, Labour(**agribulk_defaults.labour_data)) # *** Individual investment methods for terminal elements def berth_invest(self, year, handysize, handymax, panamax): @@ -298,7 +298,7 @@ def quay_invest(self, year, length, depth): quay_wall.year_online = year + quay_wall.delivery_time # add cash flow information to quay_wall object in a dataframe - quay_wall = self.add_cashflow_data_to_element(quay_wall) + quay_wall = core.add_cashflow_data_to_element(self, quay_wall) self.elements.append(quay_wall) @@ -348,7 +348,7 @@ def crane_invest(self, year): crane.year_online = max([year + crane.delivery_time, max(years_online)]) # add cash flow information to quay_wall object in a dataframe - crane = self.add_cashflow_data_to_element(crane) + crane = core.add_cashflow_data_to_element(self, crane) # add object to elements self.elements.append(crane) @@ -438,7 +438,7 @@ def conveyor_quay_invest(self, year, agribulk_defaults_quay_conveyor_data): conveyor_quay.year_online = max(new_crane_years) # add cash flow information to quay_wall object in a dataframe - conveyor_quay = self.add_cashflow_data_to_element(conveyor_quay) + conveyor_quay = core.add_cashflow_data_to_element(self, conveyor_quay) self.elements.append(conveyor_quay) @@ -535,7 +535,7 @@ def storage_invest(self, year, agribulk_defaults_storage_data): storage.year_online = year + storage.delivery_time # add cash flow information to quay_wall object in a dataframe - storage = self.add_cashflow_data_to_element(storage) + storage = core.add_cashflow_data_to_element(self, storage) self.elements.append(storage) @@ -592,7 +592,7 @@ def unloading_station_invest(self, year): station.year_online = year + station.delivery_time # add cash flow information to quay_wall object in a dataframe - station = self.add_cashflow_data_to_element(station) + station = core.add_cashflow_data_to_element(self, station) self.elements.append(station) @@ -666,7 +666,7 @@ def conveyor_hinter_invest(self, year, agribulk_defaults_hinterland_conveyor_dat conveyor_hinter.year_online = max(years_online) # add cash flow information to quay_wall object in a dataframe - conveyor_hinter = self.add_cashflow_data_to_element(conveyor_hinter) + conveyor_hinter = core.add_cashflow_data_to_element(self, conveyor_hinter) self.elements.append(conveyor_hinter) @@ -881,146 +881,6 @@ def calculate_revenue(self, year): except: pass - # *** Financial analyses - def add_cashflow_data_to_element(self, element): - """Place cashflow data in element dataframe - Elements that take two years to build are assign 60% to year one and 40% to year two.""" - - # years - years = list(range(self.startyear, self.startyear + self.lifecycle)) - - # capex - capex = element.capex - - # opex - maintenance = element.maintenance - insurance = element.insurance - labour = element.labour - - # year online - year_online = element.year_online - year_delivery = element.delivery_time - - df = pd.DataFrame() - - # years - df["year"] = years - - # capex - if year_delivery > 1: - df.loc[df["year"] == year_online - 2, "capex"] = 0.6 * capex - df.loc[df["year"] == year_online - 1, "capex"] = 0.4 * capex - else: - df.loc[df["year"] == year_online - 1, "capex"] = capex - - # opex - if maintenance: - df.loc[df["year"] >= year_online, "maintenance"] = maintenance - if insurance: - df.loc[df["year"] >= year_online, "insurance"] = insurance - if labour: - df.loc[df["year"] >= year_online, "labour"] = labour - - df.fillna(0, inplace=True) - - element.df = df - - return element - - def add_cashflow_elements(self): - """Cycle through each element and collect all cash flows into a pandas dataframe.""" - - cash_flows = pd.DataFrame() - labour = Labour(**agribulk_defaults.labour_data) - - # initialise cash_flows - cash_flows['year'] = list(range(self.startyear, self.startyear + self.lifecycle)) - cash_flows['capex'] = 0 - cash_flows['maintenance'] = 0 - cash_flows['insurance'] = 0 - cash_flows['energy'] = 0 - cash_flows['labour'] = 0 - cash_flows['demurrage'] = self.demurrage - cash_flows['revenues'] = self.revenues - - # add labour component for years were revenues are not zero - cash_flows.loc[cash_flows[ - 'revenues'] != 0, 'labour'] = labour.international_staff * labour.international_salary + labour.local_staff * labour.local_salary - - for element in self.elements: - if hasattr(element, 'df'): - for column in cash_flows.columns: - if column in element.df.columns and column != "year": - cash_flows[column] += element.df[column] - - cash_flows.fillna(0) - - # calculate WACC real cashflows - cash_flows_WACC_real = pd.DataFrame() - cash_flows_WACC_real['year'] = cash_flows['year'] - for year in range(self.startyear, self.startyear + self.lifecycle): - for column in cash_flows.columns: - if column != "year": - cash_flows_WACC_real.loc[cash_flows_WACC_real['year'] == year, column] = \ - cash_flows.loc[ - cash_flows[ - 'year'] == year, column] / ( - (1 + self.WACC_real()) ** ( - year - self.startyear)) - - return cash_flows, cash_flows_WACC_real - - def WACC_nominal(self, Gearing=60, Re=.10, Rd=.30, Tc=.28): - """Nominal cash flow is the true dollar amount of future revenues the company expects - to receive and expenses it expects to pay out, including inflation. - When all cashflows within the model are denoted in real terms and including inflation.""" - - Gearing = Gearing - Re = Re # return on equity - Rd = Rd # return on debt - Tc = Tc # income tax - E = 100 - Gearing - D = Gearing - - WACC_nominal = ((E / (E + D)) * Re + (D / (E + D)) * Rd) * (1 - Tc) - - return WACC_nominal - - def WACC_real(self, inflation=0.02): # old: interest=0.0604 - """Real cash flow expresses a company's cash flow with adjustments for inflation. - When all cashflows within the model are denoted in real terms and have been - adjusted for inflation (no inlfation has been taken into account), - WACC_real should be used. WACC_real is computed by as follows:""" - - WACC_real = (self.WACC_nominal() + 1) / (inflation + 1) - 1 - - return WACC_real - - def NPV(self): - """Gather data from Terminal elements and combine into a cash flow overview""" - - # add cash flow information for each of the Terminal elements - cash_flows, cash_flows_WACC_real = self.add_cashflow_elements() - - # prepare years, revenue, capex and opex for plotting - years = cash_flows_WACC_real['year'].values - revenue = self.revenues - capex = cash_flows_WACC_real['capex'].values - opex = cash_flows_WACC_real['insurance'].values + \ - cash_flows_WACC_real['maintenance'].values + \ - cash_flows_WACC_real['energy'].values + \ - cash_flows_WACC_real['demurrage'].values + \ - cash_flows_WACC_real['labour'].values - - # collect all results in a pandas dataframe - df = pd.DataFrame(index=years, data=-capex, columns=['CAPEX']) - df['OPEX'] = -opex - df['REVENUE'] = revenue - df['PV'] = - capex - opex + revenue - df['cum-PV'] = np.cumsum(- capex - opex + revenue) - - return df - # *** General functions def calculate_vessel_calls(self, year=2019): """Calculate volumes to be transported and the number of vessel calls (both per vessel type and in total) """ diff --git a/opentisim/core.py b/opentisim/core.py index a93d346..9ef6d42 100644 --- a/opentisim/core.py +++ b/opentisim/core.py @@ -2,6 +2,7 @@ import pandas as pd import numpy as np + # *** General functions def report_element(Terminal, Element, year): elements = 0 @@ -21,8 +22,9 @@ def report_element(Terminal, Element, year): return elements_online, elements + def find_elements(Terminal, obj): - """return elements of type obj part of self.elements""" + """return elements of type obj part of Terminal.elements""" list_of_elements = [] if Terminal.elements != []: @@ -32,6 +34,7 @@ def find_elements(Terminal, obj): return list_of_elements + def occupancy_to_waitingfactor(occupancy=.3, nr_of_servers_chk=4, poly_order=6): """Waiting time factor (E2/E2/n Erlang queueing theory using 6th order polynomial regression)""" @@ -61,6 +64,7 @@ def occupancy_to_waitingfactor(occupancy=.3, nr_of_servers_chk=4, poly_order=6): # Return waiting factor return waiting_factor + def waitingfactor_to_occupancy(factor=.3, nr_of_servers_chk=4, poly_order=6): """Waiting time factor (E2/E2/n Erlang queueing theory using 6th order polynomial regression)""" @@ -90,3 +94,145 @@ def waitingfactor_to_occupancy(factor=.3, nr_of_servers_chk=4, poly_order=6): # Return occupancy return occupancy + +def add_cashflow_data_to_element(Terminal, element): + """Place cashflow data in element dataframe + Elements that take two years to build are assign 60% to year one and 40% to year two.""" + + # years + years = list(range(Terminal.startyear, Terminal.startyear + Terminal.lifecycle)) + + # capex + capex = element.capex + + # opex + maintenance = element.maintenance + insurance = element.insurance + labour = element.labour + + # year online + year_online = element.year_online + year_delivery = element.delivery_time + + df = pd.DataFrame() + + # years + df["year"] = years + + # capex + if year_delivery > 1: + df.loc[df["year"] == year_online - 2, "capex"] = 0.6 * capex + df.loc[df["year"] == year_online - 1, "capex"] = 0.4 * capex + else: + df.loc[df["year"] == year_online - 1, "capex"] = capex + + # opex + if maintenance: + df.loc[df["year"] >= year_online, "maintenance"] = maintenance + if insurance: + df.loc[df["year"] >= year_online, "insurance"] = insurance + if labour: + df.loc[df["year"] >= year_online, "labour"] = labour + + df.fillna(0, inplace=True) + + element.df = df + + return element + + +def add_cashflow_elements(Terminal, labour): + """Cycle through each element and collect all cash flows into a pandas dataframe.""" + + cash_flows = pd.DataFrame() + + # initialise cash_flows + cash_flows['year'] = list(range(Terminal.startyear, Terminal.startyear + Terminal.lifecycle)) + cash_flows['capex'] = 0 + cash_flows['maintenance'] = 0 + cash_flows['insurance'] = 0 + cash_flows['energy'] = 0 + cash_flows['labour'] = 0 + cash_flows['demurrage'] = Terminal.demurrage + cash_flows['revenues'] = Terminal.revenues + + # add labour component for years were revenues are not zero + cash_flows.loc[cash_flows[ + 'revenues'] != 0, 'labour'] = labour.international_staff * labour.international_salary + labour.local_staff * labour.local_salary + + for element in Terminal.elements: + if hasattr(element, 'df'): + for column in cash_flows.columns: + if column in element.df.columns and column != "year": + cash_flows[column] += element.df[column] + + cash_flows.fillna(0) + + # calculate WACC real cashflows + cash_flows_WACC_real = pd.DataFrame() + cash_flows_WACC_real['year'] = cash_flows['year'] + for year in range(Terminal.startyear, Terminal.startyear + Terminal.lifecycle): + for column in cash_flows.columns: + if column != "year": + cash_flows_WACC_real.loc[cash_flows_WACC_real['year'] == year, column] = \ + cash_flows.loc[ + cash_flows[ + 'year'] == year, column] / ( + (1 + WACC_real()) ** ( + year - Terminal.startyear)) + + return cash_flows, cash_flows_WACC_real + + +def WACC_nominal(Gearing=60, Re=.10, Rd=.30, Tc=.28): + """Nominal cash flow is the true dollar amount of future revenues the company expects + to receive and expenses it expects to pay out, including inflation. + When all cashflows within the model are denoted in real terms and including inflation.""" + + Gearing = Gearing + Re = Re # return on equity + Rd = Rd # return on debt + Tc = Tc # income tax + E = 100 - Gearing + D = Gearing + + WACC_nominal = ((E / (E + D)) * Re + (D / (E + D)) * Rd) * (1 - Tc) + + return WACC_nominal + + +def WACC_real(inflation=0.02): # old: interest=0.0604 + """Real cash flow expresses a company's cash flow with adjustments for inflation. + When all cashflows within the model are denoted in real terms and have been + adjusted for inflation (no inlfation has been taken into account), + WACC_real should be used. WACC_real is computed by as follows:""" + + WACC_real = (WACC_nominal() + 1) / (inflation + 1) - 1 + + return WACC_real + + +def NPV(Terminal, labour): + """Gather data from Terminal elements and combine into a cash flow overview""" + + # add cash flow information for each of the Terminal elements + cash_flows, cash_flows_WACC_real = add_cashflow_elements(Terminal, labour) + + # prepare years, revenue, capex and opex for plotting + years = cash_flows_WACC_real['year'].values + revenue = Terminal.revenues + capex = cash_flows_WACC_real['capex'].values + opex = cash_flows_WACC_real['insurance'].values + \ + cash_flows_WACC_real['maintenance'].values + \ + cash_flows_WACC_real['energy'].values + \ + cash_flows_WACC_real['demurrage'].values + \ + cash_flows_WACC_real['labour'].values + + # collect all results in a pandas dataframe + df = pd.DataFrame(index=years, data=-capex, columns=['CAPEX']) + df['OPEX'] = -opex + df['REVENUE'] = revenue + df['PV'] = - capex - opex + revenue + df['cum-PV'] = np.cumsum(- capex - opex + revenue) + + return df diff --git a/opentisim/hydrogen_mixins.py b/opentisim/hydrogen_mixins.py index 1d1d649..fa3994a 100644 --- a/opentisim/hydrogen_mixins.py +++ b/opentisim/hydrogen_mixins.py @@ -284,14 +284,34 @@ def scenario_random(self, startyear=2019, lifecycle=20, rate=1.02, mu=0.01, sigm self.scenario_data = pd.DataFrame(data=scenario_data) - def plot_demand(self): - plt.figure(figsize=(10, 7.5)) + def plot_demand(self, width=0.1, alpha=0.6, fontsize=20): + """generate a histogram of the demand data""" + # generate plot + fig, ax = plt.subplots(figsize=(20, 10)) + + years = np.array([]) try: - plt.plot(self.historic_data['year'], self.historic_data['volume'], 'o:r') + ax.bar([x + 0 * width for x in self.historic_data['year'].values], self.historic_data['volume'].values, + width=width, alpha=alpha, label="historic data", color='blue', edgecolor='blue') + years = self.historic_data['year'].values except: pass - plt.plot(self.scenario_data['year'], self.scenario_data['volume'], 'o:b') - plt.xlabel('Time [years]') - plt.ylabel('Demand ' + self.name + ' [tons]') - plt.title('Demand ' + self.name) + + ax.bar([x + 0 * width for x in self.scenario_data['year'].values], self.scenario_data['volume'].values, + width=width, alpha=alpha, label="scenario data", color='red', edgecolor='red') + + years = np.concatenate((years, self.scenario_data['year'].values)) + + ax.set_xlabel('Years', fontsize=fontsize) + ax.set_ylabel('Demand [tons]', fontsize=fontsize) + ax.set_title('Demand: {}'.format(self.name), fontsize=fontsize) + ax.set_xticks([x for x in years]) + ax.set_xticklabels([int(x) for x in years], rotation='vertical', fontsize=fontsize) + ax.yaxis.set_tick_params(labelsize=fontsize) + + # print legend + fig.legend(loc='lower center', bbox_to_anchor=(0, -.01, .9, 0.7), + fancybox=True, shadow=True, ncol=5, fontsize=fontsize) + fig.subplots_adjust(bottom=0.18) + diff --git a/opentisim/hydrogen_system.py b/opentisim/hydrogen_system.py index 15149d2..ebdea40 100644 --- a/opentisim/hydrogen_system.py +++ b/opentisim/hydrogen_system.py @@ -145,11 +145,11 @@ def simulate(self): for year in range(self.startyear, self.startyear + self.lifecycle): self.throughput_elements(year) - # 7. collect all cash flows (capex, opex, revenues) - cash_flows, cash_flows_WACC_nominal = self.add_cashflow_elements() + # # 7. collect all cash flows (capex, opex, revenues) + # cash_flows, cash_flows_WACC_nominal = self.add_cashflow_elements() # 8. calculate PV's and aggregate to NPV - self.NPV() + core.NPV(self, Labour(**hydrogen_defaults.labour_data)) # *** Individual investment methods for terminal elements def berth_invest(self, year): @@ -194,14 +194,14 @@ def berth_invest(self, year): throughput_online, throughput_planned, throughput_planned_jetty, throughput_planned_pipej, throughput_planned_storage, throughput_planned_h2retrieval, throughput_planned_pipeh = self.throughput_elements(year) if self.debug: - print(' Berth occupancy online (@ start of year): {}'.format(berth_occupancy_online)) - print(' Berth occupancy planned (@ start of year): {}'.format(berth_occupancy_planned)) - print(' Unloading occupancy online (@ start of year): {}'.format(unloading_occupancy_online)) - print(' Unloading occupancy planned (@ start of year): {}'.format(unloading_occupancy_planned)) - print(' waiting time occupancy (@ start of year): {}'.format(waiting_time_occupancy)) - print(' waiting time factor (@ start of year): {}'.format(factor)) - print(' throughput online {}'.format(throughput_online)) - print(' throughput planned {}'.format(throughput_planned)) + print(' Berth occupancy online (@ start of year): {:.2f} (trigger level: {:.2f})'.format(berth_occupancy_online, self.allowable_berth_occupancy)) + print(' Berth occupancy planned (@ start of year): {:.2f} (trigger level: {:.2f})'.format(berth_occupancy_planned, self.allowable_berth_occupancy)) + print(' Unloading occupancy online (@ start of year): {:.2f}'.format(unloading_occupancy_online)) + print(' Unloading occupancy planned (@ start of year): {:.2f}'.format(unloading_occupancy_planned)) + print(' waiting time occupancy (@ start of year): {:.2f}'.format(waiting_time_occupancy)) + print(' waiting time factor (@ start of year): {:.2f}'.format(factor)) + print(' throughput online {:.2f}'.format(throughput_online)) + print(' throughput planned {:.2f}'.format(throughput_planned)) print('') print('--- Start investment analysis ----------------------') @@ -292,7 +292,7 @@ def jetty_invest(self, year, nrofdolphins): jetty.residual = max(jetty.assetvalue, 0) # add cash flow information to jetty object in a dataframe - jetty = self.add_cashflow_data_to_element(jetty) + jetty = core.add_cashflow_data_to_element(self, jetty) self.elements.append(jetty) @@ -373,7 +373,7 @@ def pipeline_jetty_invest(self, year): pipeline_jetty.residual = max(pipeline_jetty.assetvalue, 0) # add cash flow information to pipeline_jetty object in a dataframe - pipeline_jetty = self.add_cashflow_data_to_element(pipeline_jetty) + pipeline_jetty = core.add_cashflow_data_to_element(self, pipeline_jetty) self.elements.append(pipeline_jetty) @@ -450,7 +450,7 @@ def storage_invest(self, year, hydrogen_defaults_storage_data): storage.residual = max(storage.assetvalue, 0) # add cash flow information to storage object in a dataframe - storage = self.add_cashflow_data_to_element(storage) + storage = core.add_cashflow_data_to_element(self, storage) self.elements.append(storage) @@ -508,7 +508,7 @@ def h2retrieval_invest(self, year, hydrogen_defaults_h2retrieval_data): h2retrieval.residual = max(h2retrieval.assetvalue, 0) # add cash flow information to h2retrieval object in a dataframe - h2retrieval = self.add_cashflow_data_to_element(h2retrieval) + h2retrieval = core.add_cashflow_data_to_element(self, h2retrieval) self.elements.append(h2retrieval) @@ -579,7 +579,7 @@ def pipeline_hinter_invest(self, year): # add cash flow information to pipeline_hinter object in a dataframe - pipeline_hinter = self.add_cashflow_data_to_element(pipeline_hinter) + pipeline_hinter = core.add_cashflow_data_to_element(self, pipeline_hinter) self.elements.append(pipeline_hinter) @@ -759,183 +759,6 @@ def calculate_revenue(self, year, hydrogen_defaults_commodity_data): except: pass - # *** Financial analyses - def add_cashflow_elements(self): - - cash_flows = pd.DataFrame() - labour = Labour(**hydrogen_defaults.labour_data) - - - # initialise cash_flows - cash_flows['year'] = list(range(self.startyear, self.startyear + self.lifecycle)) - cash_flows['capex'] = 0 - cash_flows['maintenance'] = 0 - cash_flows['insurance'] = 0 - cash_flows['energy'] = 0 - cash_flows['labour'] = 0 - cash_flows['demurrage'] = self.demurrage - cash_flows['revenues'] = self.revenues - cash_flows['residual'] = 0 - - # add labour component for years were revenues are not zero - cash_flows.loc[cash_flows[ - 'revenues'] != 0, 'labour'] = labour.international_staff * labour.international_salary + labour.local_staff * labour.local_salary - - for element in self.elements: - if hasattr(element, 'df'): - for column in cash_flows.columns: - if column in element.df.columns and column != "year": - cash_flows[column] += element.df[column] - - cash_flows.fillna(0) - - # calculate WACC nominal cashflows - cash_flows_WACC_nom = pd.DataFrame() - cash_flows_WACC_nom['year'] = cash_flows['year'] - for year in range(self.startyear, self.startyear + self.lifecycle): - for column in cash_flows.columns: - if column != "year": - cash_flows_WACC_nom.loc[cash_flows_WACC_nom['year'] == year, column] = \ - cash_flows.loc[ - cash_flows[ - 'year'] == year, column] / ( - (1 + self.WACC_nominal()) ** ( - year - self.startyear)) - - return cash_flows, cash_flows_WACC_nom - - def add_cashflow_data_to_element(self, element): - - """Place cashflow data in element dataframe""" - - # years - years = list(range(self.startyear, self.startyear + self.lifecycle)) - - # capex - capex = element.capex - - # opex - maintenance = element.maintenance - insurance = element.insurance - labour = element.labour - - # revenue - residual = element.residual - - # year online - year_online = element.year_online - year_delivery = element.delivery_time - lifespan = element.lifespan - - df = pd.DataFrame() - - # years - df["year"] = years - - # capex - if year_delivery == 2: - df.loc[df["year"] == year_online - 2, "capex"] = 0.6 * capex - df.loc[df["year"] == year_online - 1, "capex"] = 0.4 * capex - df.loc[df["year"] == year_online + lifespan - 2, "capex"] = 0.6 * capex - df.loc[df["year"] == year_online + lifespan - 1, "capex"] = 0.4 * capex - if year_delivery == 3: - df.loc[df["year"] == year_online - 3, "capex"] = 0.5 * capex - df.loc[df["year"] == year_online - 2, "capex"] = 0.35 * capex - df.loc[df["year"] == year_online - 1, "capex"] = 0.15 * capex - df.loc[df["year"] == year_online + lifespan - 3, "capex"] = 0.5 * capex - df.loc[df["year"] == year_online + lifespan - 2, "capex"] = 0.35 * capex - df.loc[df["year"] == year_online + lifespan - 1, "capex"] = 0.150 * capex - if year_delivery == 4: - df.loc[df["year"] == year_online - 4, "capex"] = 0.4 * capex - df.loc[df["year"] == year_online - 3, "capex"] = 0.3 * capex - df.loc[df["year"] == year_online - 2, "capex"] = 0.2 * capex - df.loc[df["year"] == year_online - 1, "capex"] = 0.1 * capex - df.loc[df["year"] == year_online + lifespan - 4, "capex"] = 0.4 * capex - df.loc[df["year"] == year_online + lifespan - 3, "capex"] = 0.3 * capex - df.loc[df["year"] == year_online + lifespan - 2, "capex"] = 0.2 * capex - df.loc[df["year"] == year_online + lifespan - 1, "capex"] = 0.1 * capex - if year_delivery == 5: - df.loc[df["year"] == year_online - 5, "capex"] = 0.30 * capex - df.loc[df["year"] == year_online - 4, "capex"] = 0.25 * capex - df.loc[df["year"] == year_online - 3, "capex"] = 0.20 * capex - df.loc[df["year"] == year_online - 2, "capex"] = 0.15 * capex - df.loc[df["year"] == year_online - 1, "capex"] = 0.1 * capex - df.loc[df["year"] == year_online + lifespan - 5, "capex"] = 0.3 * capex - df.loc[df["year"] == year_online + lifespan - 4, "capex"] = 0.25 * capex - df.loc[df["year"] == year_online + lifespan - 3, "capex"] = 0.20 * capex - df.loc[df["year"] == year_online + lifespan - 2, "capex"] = 0.15 * capex - df.loc[df["year"] == year_online + lifespan - 1, "capex"] = 0.1 * capex - if year_delivery == 1: - df.loc[df["year"] == year_online - 1, "capex"] = capex - df.loc[df["year"] == year_online + lifespan- 1, "capex"] = capex - - # opex - if maintenance: - df.loc[df["year"] >= year_online, "maintenance"] = maintenance - if insurance: - df.loc[df["year"] >= year_online, "insurance"] = insurance - if labour: - df.loc[df["year"] >= year_online, "labour"] = labour - - # revenue - if residual: - df.loc[df["year"] == self.startyear + self.lifecycle - 1, "residual"] = residual - - df.fillna(0, inplace=True) - - element.df = df - - return element - - def WACC_nominal(self, Gearing=60, Re=.10, Rd=.15, Tc=.20): - """Nominal cash flow is the true dollar amount of future revenues the company expects - to receive and expenses it expects to pay out, including inflation. - When all cashflows within the model are denoted in real terms and including inflation.""" - - Gearing = Gearing - Re = Re # return on equity - Rd = Rd # return on debt - Tc = Tc # income tax - E = 100 - Gearing - D = Gearing - - WACC_nominal = ((E / (E + D)) * Re + (D / (E + D)) * Rd) * (1 - Tc) - - return WACC_nominal - - def WACC_real(self, inflation=0.0321): # old: interest=0.0604 - """Real cash flow expresses a company's cash flow with adjustments for inflation. - When all cashflows within the model are denoted in real terms and have been - adjusted for inflation (no inlfation has been taken into account), - WACC_real should be used. WACC_real is computed by as follows:""" - - WACC_real = (self.WACC_nominal() + 1) / (inflation + 1) - 1 - - return WACC_real - - def NPV(self): - """Gather data from Terminal elements and combine into a cash flow plot""" - - # add cash flow information for each of the Terminal elements - cash_flows, cash_flows_WACC_nom = self.add_cashflow_elements() - - # prepare years, revenue, capex and opex for plotting - years = cash_flows_WACC_nom['year'].values - revenue = cash_flows_WACC_nom['revenues'].values + cash_flows_WACC_nom['residual'].values - capex = cash_flows_WACC_nom['capex'].values - opex = cash_flows_WACC_nom['insurance'].values + \ - cash_flows_WACC_nom['maintenance'].values + \ - cash_flows_WACC_nom['energy'].values + \ - cash_flows_WACC_nom['demurrage'].values + \ - cash_flows_WACC_nom['labour'].values - # throughput = cash_flows_WACC_nom['throughput'].values - PV = - capex - opex + revenue - print('PV: {}'.format(PV)) - - print('NPV: {}'.format(np.sum(PV))) - - # print('cost price: {}'.format(np.sum(PV)/throughput)) - # *** General functions def calculate_vessel_calls(self, year=2019): """Calculate volumes to be transported and the number of vessel calls (both per vessel type and in total) """ diff --git a/opentisim/plot.py b/opentisim/plot.py index 7aa643b..5c3e8f0 100644 --- a/opentisim/plot.py +++ b/opentisim/plot.py @@ -2,7 +2,7 @@ import matplotlib.pyplot as plt # *** General functions -def cashflow_plot(Terminal, cash_flows, title='Cash flow plot', width=0.3, alpha=0.6, fontsize=20): +def cashflow_plot(Terminal, cash_flows, title='Cash flow plot', width=0.2, alpha=0.6, fontsize=20): """Gather data from Terminal elements and combine into a cash flow plot""" # prepare years, revenue, capex and opex for plotting @@ -63,6 +63,6 @@ def cashflow_plot(Terminal, cash_flows, title='Cash flow plot', width=0.3, alpha # print legend fig.legend(loc='lower center', bbox_to_anchor=(0, -.01, .9, 0.7), - fancybox=True, shadow=True, ncol=5) + fancybox=True, shadow=True, ncol=5, fontsize=fontsize) fig.subplots_adjust(bottom=0.15)