CIFS_Storage_Demand_Model/Scripts/2_Compiled_Results_Data.r
2026-02-19 17:06:34 -07:00

135 lines
9.9 KiB
R

#A script which attempts to pull in all data, and create a data frame with the maximum revenue values for each facility, year and discount rate. The output can then be used to make figures and graphs
library(tidyverse)
library(parallel)
NCORES <- detectCores()-1
library(lpSolve) #For solving discrete value maximization for the power plants
####Manual inputs
#Range of discount rates to calculate in the model. Each facility will have each rate calculated, so more values slows the results but allows for more discount rates to be reported in the findings.
#DISCOUNT_RATE_LIST <- c(0.03,0.05,0.07)
#DISCOUNT_RATE_LIST <- c(0.03,0.0325,0.035,0.0375,0.04,0.045,0.0475,0.05,0.07,0.1)
DISCOUNT_RATE_LIST <- seq(0.01,0.15,by=0.0025)
#The cost per ton of shipping uranium, used to see what can be shipped on day one of the project.
SHIPPING_COST_PER_TON <- 1.2874*26000 #Inflation adjusted from New Mexico Report
#The savings per year of having a CIFS at a served reactor (cost to house the CIFS).
CV <- 1.2874*(6984013) #Data from New Mexico Report, Converted from 2019 to Dec 2025
#Locations to save results
RES_DIR <- "./Data/Results/"
INTERMEDIATE_DIR <- "./Data/Results/Separate_Costs_and_Benefits_Data/"
#Directory where the individual CIFS project plan cost data can be found.
#This has data has already been combined with the original files (low,high) and inflation adjusted.
CIFS_INDIVIDUAL_COST_DATA_DIR <- 'Data/Cleaned_Data/'
#Create any need save locations
dir.create(RES_DIR,recursive=TRUE,showWarnings=FALSE)
dir.create(INTERMEDIATE_DIR,recursive=TRUE,showWarnings=FALSE)
###################################Cost results
CIFS <- rbind(readRDS(paste0(CIFS_INDIVIDUAL_COST_DATA_DIR,"Texas_CIFS_Costs.Rds")),readRDS(paste0(CIFS_INDIVIDUAL_COST_DATA_DIR,"New_Mexico_CIFS_Costs.Rds")))
#Adjust for inflation
#CIFS[,-1:-5] <- 1.2874*CIFS[,-1:-5]
COSTS <- do.call(rbind,lapply(DISCOUNT_RATE_LIST ,function(DISCOUNT){CIFS %>% group_by(Location,Capacity,Cost_Assumption) %>% mutate(NPC=Total/(1+DISCOUNT)^Year) %>% summarize(Discount=DISCOUNT,Costs=sum(NPC))})) %>% ungroup
TEMP <- COSTS%>% group_by(Location,Cost_Assumption,Discount) %>% summarize(ST_COST=min(Costs),ST_CAPACITY=min(Capacity),M_COST=(max(Costs)-min(Costs))/(max(Capacity)-min(Capacity))) %>% ungroup
CAPACITY_INCREMENT <- 5000
#rm(COST_DATA)
for(i in 1:nrow(TEMP)){
LOC <- as.character(TEMP[i,"Location"])
COST_LEVEL <- as.character(TEMP[i,"Cost_Assumption"])
DISCOUNT <- as.numeric(TEMP[i,"Discount"])
ST_CAP <- as.numeric(TEMP[i,"ST_CAPACITY"])
ST_COST <- as.numeric(TEMP[i,"ST_COST"])
COST_SLOPE <- as.numeric(TEMP[i,]$M_COST)
CAPACITY <- seq(ST_CAP,160000,by=5000)
COST <- ST_COST+COST_SLOPE*CAPACITY_INCREMENT*(0:(length(CAPACITY)-1))
C_RES <- cbind(CAPACITY,COST) %>% as_tibble %>% mutate(Location=LOC,Cost_Assumption=COST_LEVEL,Discount=DISCOUNT) %>% select(Location,Cost_Assumption,Discount,Capacity=CAPACITY,Cost=COST)
if(!exists("COST_DATA")){COST_DATA <- C_RES}else{COST_DATA<- rbind(COST_DATA,C_RES)}
}
saveRDS(COST_DATA ,paste0(INTERMEDIATE_DIR,"All_CIFS_Discounted_Costs.Rds"))
#COST_DATA <- COST_DATA %>% filter(Cost_Assumption=='Average') %>% select(-Cost_Assumption) %>% unique
COST_DATA <- COST_DATA %>% filter(Cost_Assumption=='High') %>% select(-Cost_Assumption) %>% unique
##All unique capacity levels that the revenues need to be calculated for
CAPACITY_LIST <- COST_DATA %>% pull(Capacity) %>% unique
###
TOTAL <- read_csv("Data/Raw_Data/Curie_Spent_Fuel_Site_Totals.csv") %>% mutate(OP_YEAR=year(Op_Date_Min),CLOSE_YEAR=year(Close_Date_Max))%>% select(Facility,Total_Assemblies,Total_Tons,OP_YEAR,CLOSE_YEAR)
FACILITY_LIST <- TOTAL %>% pull(Facility)
#https://www.nrc.gov/reactors/operating/licensing/renewal/subsequent-license-renewal
SUBMITTED <-rbind(c(FACILITY_LIST[str_detect(FACILITY_LIST,"Duane*" )],2025),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Nine Mile*" )],2026),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Ginna*" )],2026),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Cooper*" )],2026),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Farley*" )],2027),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Prairie*" )],2027),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Brunswick*" )],2027),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Cook" )],2027),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Hope" )],2027),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Salem" )],2027),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Perry" )],2027),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Millstone" )],2028),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Palisades" )],2028),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Beaver" )],2028),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Callaway" )],2029),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Three Mile Island" )],2029),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Davis-Besse" )],2029),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Wolf" )],2030),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Lucie" )],2021),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Robinson" )],2025),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Hatch" )],2025))
SUBMITTED <- SUBMITTED %>% as_tibble
colnames(SUBMITTED ) <- c("Facility","App_Date","Status")
SUBMITTED <- SUBMITTED%>% mutate(Status="Applied",App_Date=as.numeric(App_Date)) %>% select(Facility,Status,App_Date)
#Issued
RENEWED <- rbind(c(FACILITY_LIST[str_detect(FACILITY_LIST,"Turkey" )],"Granted",2018,2033),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Peach" )],"Granted",2019,2034),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Surry" )],"Granted",2020,2033),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"North" )],"Granted",2021,2040),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Monticello" )],"Granted",2024,2030),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Oconee" )],"Granted",2025,2034),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Summer" )],"Granted",2025,2042),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Beach" )],"Granted",2025,2033),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Browns" )],"Granted",2025,2036),
c(FACILITY_LIST[str_detect(FACILITY_LIST,"Dresden" )],"Granted",2025,2031))
RENEWED <- RENEWED %>% as_tibble
colnames(RENEWED) <- c("Facility","Status","App_Date","Op_Date")
RENEWED <- RENEWED %>% mutate(Op_Date=as.numeric(Op_Date),App_Date=as.numeric(App_Date))
AVG_LENGTH <- RENEWED %>% mutate(DIFF=Op_Date-App_Date) %>% pull(DIFF) %>% mean %>% round
SUBMITTED <- SUBMITTED %>% mutate(Op_Date=App_Date+AVG_LENGTH)
UPDATE <- rbind(RENEWED,SUBMITTED )
TOTAL_ORIG <- TOTAL
TOTAL <- TOTAL %>% left_join(UPDATE) %>% mutate(CLOSE_YEAR=ifelse(Op_Date>CLOSE_YEAR & !is.na(Status),Op_Date,CLOSE_YEAR)) %>% select(-Status,-App_Date,-Op_Date)
source("./Scripts/Functions/NPV_Functions.r")
#Calculate and individual facilities rate, for all discount rates, and all years
TOTAL_VALUE_METRICS <- MULTI_DISCOUNT_RATE_NPV(DISCOUNT_RATE_LIST,TOTAL ,DOLLARS_SAVED_PER_YEAR=CV)%>% left_join(read_csv("Data/Raw_Data/Curie_Spent_Fuel_Site_Totals.csv")) %>% select(Year,Facility,Discount,Total_Tons,Revenue)
TOTAL_VALUE_METRICS_ORIG <- MULTI_DISCOUNT_RATE_NPV(DISCOUNT_RATE_LIST,TOTAL_ORIG ,DOLLARS_SAVED_PER_YEAR=CV) %>% left_join(read_csv("Data/Raw_Data/Curie_Spent_Fuel_Site_Totals.csv")) %>% select(Year,Facility,Discount,Total_Tons,Revenue)
saveRDS(TOTAL_VALUE_METRICS ,"Data/Cleaned_Data/Reactor_Values.Rds")
####Find the results for each facility size
#Main Results
CREATE_FULL_RESULTS <- function(REVENUE_RESULTS,COST_RESULTS){return(REVENUE_RESULTS %>% left_join(COST_RESULTS) %>% mutate(Profit=Revenue-Cost) %>% group_by(Discount,Capacity,Location) %>% mutate(Time_Benefit=(1-Discount)*(lead(Profit)-Profit),Op_Cost=Discount*Profit,Marginal=Time_Benefit-Op_Cost) %>% unique)}
Rev_No_Shipping <- do.call(rbind,mclapply(CAPACITY_LIST,function(CAPACITY){do.call(rbind,lapply(DISCOUNT_RATE_LIST,function(x){YEARLY_RESULTS(TOTAL_VALUE_METRICS,x,CAPACITY,0)}))},mc.cores=min(length(CAPACITY_LIST),NCORES)))
saveRDS(Rev_No_Shipping,paste0(INTERMEDIATE_DIR ,"Revenue_Estimates.Rds"))
Rev_No_Shipping <- CREATE_FULL_RESULTS(Rev_No_Shipping,COST_DATA)
saveRDS(Rev_No_Shipping,paste0(RES_DIR,"Model_Estimates.Rds"))
#Same as main results but the optimization is based on information known in 2018, using unadjusted CURIE map data
Rev_No_Shipping_2018 <- do.call(rbind,mclapply(CAPACITY_LIST,function(CAPACITY){do.call(rbind,lapply(DISCOUNT_RATE_LIST,function(x){YEARLY_RESULTS(TOTAL_VALUE_METRICS_ORIG,x,CAPACITY,0)}))},mc.cores=min(length(CAPACITY_LIST),NCORES)))
saveRDS(Rev_No_Shipping_2018,paste0(INTERMEDIATE_DIR ,"Revenue_Estimates_2018.Rds"))
Rev_No_Shipping_2018 <- CREATE_FULL_RESULTS(Rev_No_Shipping_2018,COST_DATA)
saveRDS(Rev_No_Shipping_2018,paste0(RES_DIR,"Model_Estimates_2018.Rds"))
#Same as main results but only the reactors with a value ready to ship are included. That is to say other reactors may be willing to pay for future rights to hold the spent fuel but only those reactors with current values ready to ship are included, which set the bounds of the starting CIFS size. This is used for demonstration.
Rev_Shipping <- do.call(rbind,mclapply(CAPACITY_LIST,function(CAPACITY){do.call(rbind,lapply(DISCOUNT_RATE_LIST,function(x){YEARLY_RESULTS(TOTAL_VALUE_METRICS,x,CAPACITY,SHIPPING_COST_PER_TON )}))},mc.cores=min(length(CAPACITY_LIST),NCORES)))
saveRDS(Rev_Shipping,paste0(INTERMEDIATE_DIR ,"Revenue_Estimates_With_Shipping_Costs.Rds"))
Rev_Shipping <- CREATE_FULL_RESULTS(Rev_Shipping,COST_DATA)
saveRDS(Rev_Shipping,paste0(RES_DIR,"Model_Estimates_With_Shipping_Costs.Rds"))
#Same as Rev_Shipping, but only information available in 2018 from the CURIE map is used (no updated operation dates).
Rev_Shipping_2018 <- do.call(rbind,mclapply(CAPACITY_LIST,function(CAPACITY){do.call(rbind,lapply(DISCOUNT_RATE_LIST,function(x){YEARLY_RESULTS(TOTAL_VALUE_METRICS_ORIG,x,CAPACITY,SHIPPING_COST_PER_TON )}))},mc.cores=min(length(CAPACITY_LIST),NCORES)))
saveRDS(Rev_Shipping_2018,paste0(INTERMEDIATE_DIR ,"Revenue_Estimates_With_Shipping_Costs_2018.Rds"))
Rev_Shipping_2018 <- CREATE_FULL_RESULTS(Rev_Shipping_2018,COST_DATA)
saveRDS(Rev_Shipping_2018,paste0(RES_DIR,"Model_Estimates_With_Shipping_Costs_2018.Rds"))