Analyzing the geothermal results

I have reduced all the simulation results to a single data file of the recovery factors and CO2 emission. In this notebook, I'm going to analyze the data.


In [4]:
using Dierckx, DataFrames, PyPlot, JLD, CoolProp, CSV
include("SetPyPlot.jl")
SetPyPlot.setrcparam()
@load "input_param.jld"


Out[4]:
16-element Array{Symbol,1}:
 :MW_CO2
 :MW_coal
 :MW_gas
 :e_igcc
 :e_ngcc
 :e_pc
 :em_coal
 :em_gas
 :eta_driver
 :eta_pump
 :ex_ccs
 :ex_coal
 :ex_gas
 :η_igcc
 :η_ngcc
 :η_pc

Read the Dutch subsurface porosity data

This data is obtained from the following webpage: Source: http://nlog.nl/reservoireigenschappen


In [29]:
phi_data = CSV.read("TNO_data.csv")
perm_data = dropmissing(phi_data, :Perm_arith)


Out[29]:

16 rows × 10 columns (omitted printing of 4 columns)

Top_tvd_mBasis_tvd_mBruto_dikte_mNetto_dikte_mKlei_Volume_percentPorositeitsmodel
Float64Float64Float64Float64Float64String
13299.993410.48110.4981.4528.7Density-Neutron
23455.043548.5793.5315.536.1Density-Neutron
31443.461561.82118.36118.3610.8Density-Neutron
43463.183573.14109.9692.2733.2Sonic
53619.633710.1290.496.942.2Sonic
63214.263309.4195.1488.8227.2Density-Neutron
73323.453415.9892.5388.7414.7Density-Neutron
83995.184108.39113.2190.1920.4Density-Neutron
94132.594207.1574.5632.2229.1Density-Neutron
104032.114139.03106.9192.8412.7Sonic
114171.164236.5165.3630.1522.7Sonic
123671.533783.1111.5797.0719.9Density-Neutron
133819.593909.8690.2723.8135.4Density-Neutron
141999.142074.0874.9474.9421.5Density-Neutron
153615.053715.38100.3393.0628.9Density-Neutron
163748.793821.3972.653.0923.4Density-Neutron

In [35]:
figure(figsize=(4,3))
plot(phi_data.Basis_tvd_m, phi_data.Porositeit, "o")
xlabel("Depth [m]")
ylabel("Porosity [vol%]")


Out[35]:
PyObject Text(66.0, 0.5, 'Porosity [vol%]')

In [34]:
# plotyy(x1, y1, x2, y2; fig_size, x_label, y1_label, y2_label, y1_style, y2_style)
SetPyPlot.plotyy(phi_data.Basis_tvd_m, phi_data.Porositeit, 
    perm_data.Basis_tvd_m, perm_data.Perm_arith, y1_style = "ob", y2_style="vr", 
    x_label="Depth [m]", y1_label="Porosity [vol%]", y2_label="mili Darcy")
# plot(phi_data.Basis_tvd_m, phi_data.Perm_arith)


Out[34]:
(Figure(PyObject <Figure size 1050x750 with 2 Axes>), PyObject <matplotlib.axes._subplots.AxesSubplot object at 0x7fe476a83dd0>, PyObject <matplotlib.axes._subplots.AxesSubplot object at 0x7fe476af9b90>)

In [13]:
em_gas*1e3


Out[13]:
0.05499999999999999

In [3]:
df_geo = CSV.read("geothermal_final_results.csv")


Out[3]:

150 rows × 11 columns

q_m3_sk_m2L_mt_life_sR_thR_prR_zec_CO2COPc_CO2_heatheat_per_m3
Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰
10.01388892.0e-14500.01.05763e9-0.310919-3.78832-5.077610.2865081.645990.03341451.5436e5
20.01388892.0e-14650.01.4292e9-0.417142-4.2143-5.620730.312541.506480.03650891.54967e5
30.01388892.0e-14800.01.77659e9-0.523523-4.63601-6.158410.3383121.389510.03958241.55609e5
40.01388892.0e-14900.02.02833e9-0.60341-4.95111-6.560160.3575681.314880.0418291.55504e5
50.01388892.0e-141000.02.2559e9-0.676724-5.23971-6.928130.3752051.25230.04391911.55751e5
60.01388895.0e-14500.01.05763e90.388476-1.05631-1.594290.1195523.944650.01394291.5436e5
70.01388895.0e-14650.01.4292e90.352124-1.20935-1.789430.1289053.652590.01505781.54967e5
80.01388895.0e-14800.01.77659e90.313042-1.36818-1.991930.1386113.391410.01621751.55609e5
90.01388895.0e-14900.02.02833e90.282732-1.48961-2.146760.1460323.219550.01708311.55504e5
100.01388895.0e-141000.02.2559e90.254659-1.6015-2.289410.1528693.073670.01789391.55751e5
110.01388891.0e-13500.01.05763e90.621608-0.145634-0.4331840.06389997.380150.007452431.5436e5
120.01388891.0e-13650.01.4292e90.608546-0.207706-0.5123250.06769316.955450.007907461.54967e5
130.01388891.0e-13800.01.77659e90.591896-0.278904-0.6031020.07204416.524980.008429141.55609e5
140.01388891.0e-13900.02.02833e90.578113-0.335783-0.6756240.07552016.225590.00883451.55504e5
150.01388891.0e-131000.02.2559e90.56512-0.388756-0.7431640.07875735.966040.009218851.55751e5
160.01388892.0e-13500.01.05763e90.7381740.3097010.1473690.036073813.07290.004207171.5436e5
170.01388892.0e-13650.01.4292e90.7367560.2931180.1262250.037087212.69540.004332291.54967e5
180.01388892.0e-13800.01.77659e90.7313240.2657350.09131210.038760612.12790.004534981.55609e5
190.01388892.0e-13900.02.02833e90.7258030.2411320.05994340.040264111.67680.004710181.55504e5
200.01388892.0e-131000.02.2559e90.7203510.2176130.02995690.041701411.26750.004881311.55751e5
210.01388895.0e-13500.01.05763e90.8081130.5829020.4957010.019378224.33610.002260011.5436e5
220.01388895.0e-13650.01.4292e90.8136830.5936120.5093560.018723725.14660.002187181.54967e5
230.01388895.0e-13800.01.77659e90.814980.5925180.5079610.018790625.01720.002198491.55609e5
240.01388895.0e-13900.02.02833e90.8144180.5872810.5012840.019110624.60190.00223561.55504e5
250.01388895.0e-131000.02.2559e90.8134890.5814350.493830.019467924.13560.002278791.55751e5
260.01388891.0e-12500.01.05763e90.8314270.6739690.6118110.01381334.14110.001610961.5436e5
270.01388891.0e-12650.01.4292e90.8393250.6937770.6370660.012602537.36050.001472141.54967e5
280.01388891.0e-12800.01.77659e90.8428660.7014460.6468430.012133938.74170.001419661.55609e5
290.01388891.0e-12900.02.02833e90.8439560.7026650.6483970.012059438.98680.001410731.55504e5
300.01388891.0e-121000.02.2559e90.8445350.7027090.6484540.012056738.97170.001411281.55751e5
&vellip&vellip&vellip&vellip&vellip&vellip&vellip&vellip&vellip&vellip&vellip&vellip

Sensitivity analysis

Step 1

Find a list of permeability, well-spacing, and flow rates


In [4]:
flow_rates = df_geo[:q_m3_s] # [m^3/s]
k          = df_geo[:k_m2]   # [m^2]
L          = df_geo[:L_m]    # [m]
flow_range = union(flow_rates)
k_range    = union(k)
L_range    = union(L)


Out[4]:
5-element Array{Union{Missing, Float64},1}:
  500.0
  650.0
  800.0
  900.0
 1000.0

In [5]:
k_range


Out[5]:
6-element Array{Union{Missing, Float64},1}:
 2.0e-14
 5.0e-14
 1.0e-13
 2.0e-13
 5.0e-13
 1.0e-12

Step 2

For a fixed value of well spacing, plot the effect of perm for different flow rates, and see if the results look good:

Effect of permeability


In [6]:
line_style = ["-", "--", "-.", "-", ":"]
for L_well in L_range
    figure(figsize=(5,3))
    xlabel("Permeability [m\$^2\$]")
    ylabel("Practical recovery factor [-]")
    i=0
    for q in flow_range
        i+=1
        df_temp = df_geo[(df_geo[:q_m3_s].==q) .& (df_geo[:L_m].==L_well), :]
        plot(df_temp[:k_m2], df_temp[:R_pr], line_style[i], label = "q = $(q*3600) m\$^3\$/h")
    end
    legend()
    plot([0, 1e-12], [0,0], "--k")
    axis([0, 1e-12, -1,1])
#     title("L = $L_well [m]")
    savefig("figs/practical_recovery_perm.png")
end



In [7]:
for L_well in L_range
    figure(figsize=(4,3))
    xlabel("Permeability [m\$^2\$]")
    ylabel("Zero-emission recovery factor [-]")
    for q in flow_range
        df_temp = df_geo[(df_geo[:q_m3_s].==q) .& (df_geo[:L_m].==L_well), :]
        plot(df_temp[:k_m2], df_temp[:R_ze], line_style[i], label = "q = $(q*3600) m\$^3\$/h")
#         plot(df_temp[:k_m2], df_temp[:R_pr], label = "q = $(q*3600) m\$^3\$/h")
    end
    legend()
    plot([0, 1e-12], [0,0], "--k")
    axis([0, 1e-12, -1,1])
    title("L = $L_well [m]")
end


Effect of flow rate

Change the flow rate in different well spacing in different reservoirs


In [8]:
for k_res in k_range
    figure(figsize=(5,3))
    xlabel("Flow rate \L[m^3/h]")
    ylabel("Practical recovery factor [-]")
    for L_well in L_range
        df_temp = df_geo[(df_geo[:k_m2].==k_res) & (df_geo[:L_m].==L_well), :]
        plot(df_temp[:q_m3_s]*3600, df_temp[:R_pr], line_style[i], alpha = 0.5, label = "L = $L_well [m]")
    end
    legend()
    #plot([0, 1e-12], [0,0], "--k")
    #axis([0, 1e-12, -1,1])
    title("k = $(k_res*1e12) [D]")
end


syntax: invalid escape sequence

Effect of flow rate on the project life time

Conclusion: project life time does not depend on the permeability of the reservoir. It is important for the project to have an acceptable lifetime to justify the capital investments.


In [9]:
for k_res in k_range
    figure(figsize=(4,3))
    xlabel("Flow rate [m\$^3\$/h]")
    ylabel("Project life time [year]")
    for L_well in L_range
        df_temp = df_geo[(df_geo[:k_m2].==k_res) .& (df_geo[:L_m].==L_well), :]
        plot(df_temp[:q_m3_s]*3600, df_temp[:t_life_s]/(3600*24*365), line_style[i], alpha = 0.8, label = "L = $L_well [m]")
    end
    legend()
    #plot([0, 1e-12], [0,0], "--k")
    #axis([0, 1e-12, -1,1])
#     plot(df_geo[:q_m3_s]*3600, df_geo[:t_life_s]/(3600*24*365), "o")
    savefig("figs/project_life_time.png")
#     title("k = $(k_res*1e12) [D]")
end


CO2 emission:


In [10]:
figure(figsize=(4,3))
semilogy(df_geo[:q_m3_s]*3600, df_geo[:c_CO2], alpha = 0.5, "o")
semilogy([0, 260], [em_gas, em_gas]*1e3, "--k")
xlabel("Flow rate [m\$^3\$/h]")
ylabel("CO\$_2\$ emission [kg CO\$_2\$/MJ]")
axis([0, 260, 1e-2, 30])
savefig("figs/CO2_emission_flow.png")



In [11]:
figure(figsize=(4,3))
semilogy(df_geo[:k_m2], df_geo[:c_CO2], alpha = 0.5, "o")
semilogy([0, 1.1e-12], [em_gas, em_gas]*1e3, "--k")
xlabel("Permeability [m\$^2\$]")
ylabel("CO\$_2\$ emission [kg CO\$_2\$/MJ]")
axis([0, 1.1e-12, 1e-2, 30])
savefig("figs/CO2_emission_perm.png")



In [26]:
figure(figsize=(4,3))
semilogy(df_geo[:k_m2], df_geo[:c_CO2]*0.15, alpha = 0.5, "o")
semilogy([0, 1.1e-12], [em_gas, em_gas]*1e3, "--k")
xlabel("Permeability [m\$^2\$]")
ylabel("CO\$_2\$ emission [kg CO\$_2\$/MJ]")
savefig("figs/CO2_emission_perm.png")



In [12]:
df_geo.colindex


ArgumentError: column name :colindex not found in the data frame

Stacktrace:
 [1] getindex(::DataFrame, ::Symbol) at /home/ali/.julia/packages/DataFrames/IKMvt/src/other/index.jl:164
 [2] getproperty(::DataFrame, ::Symbol) at /home/ali/.julia/packages/DataFrames/IKMvt/src/abstractdataframe/abstractdataframe.jl:227
 [3] top-level scope at In[12]:1

Find the optimum range

Apply the following criteria:

  • life time longer than 30 years
  • positive zero-emission recovery factors
  • lower that two times methane emission

In [13]:
T_c = 2.0+273.15 # [K]
T_h = 22+273.15 # [K]
COP_th = T_h/(T_h-T_c)
COP_air = 2.0 # average value (per unit electricity)
COP_geo = 5.0 # shallow heat (comes from the sun)
COP_methane = 1.0 # ignoring heat loss

t_life_crit = 30       # years
c_CO2_crit  = em_gas*1e3*2.0 # kg/MJ

u_ht = 1.0 # W/m2/K
A_ht = 4*10.0*3.0 # only surrounding walls
Q_heat = u_ht*A_ht*(T_h-T_c)/1000 # kJ/s
n_houses = 400 # number of houses to be covered by the geothermal project
m_water = Q_heat*1000*n_houses/(PropsSI("HMASS", "T", 80+273.15, "P", 1e5, "H2O")-PropsSI("HMASS", "T", 40+273.15, "P", 1e5, "H2O"))
q_water = m_water/PropsSI("D", "T", 40+273.15, "P", 1e5, "H2O") # [m3/s]

df_practical = df_geo[(df_geo[:t_life_s].>(30.0*24*3600)) & (df_geo[:R_ze].>0.0) & (df_geo[:c_CO2].<c_CO2_crit), :]


MethodError: no method matching &(::BitArray{1}, ::BitArray{1})
Closest candidates are:
  &(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:502
  &(!Matched::PyCall.PyObject, ::Any) at /home/ali/.julia/packages/PyCall/a5Jd3/src/pyoperators.jl:13
  &(::Any, !Matched::PyCall.PyObject) at /home/ali/.julia/packages/PyCall/a5Jd3/src/pyoperators.jl:14

Stacktrace:
 [1] top-level scope at In[13]:17

In [14]:
flow_rates_pr = df_practical[:q_m3_s] # [m^3/s]
k_pr          = df_practical[:k_m2]   # [m^2]
L_pr          = df_practical[:L_m]    # [m]
flow_range_pr = union(flow_rates_pr)
k_range_pr    = union(k_pr)
L_range_pr    = union(L_pr)


UndefVarError: df_practical not defined

Stacktrace:
 [1] top-level scope at In[14]:1

In [15]:
k_range_pr


UndefVarError: k_range_pr not defined

Stacktrace:
 [1] top-level scope at In[15]:1

In [16]:
figure(figsize=(5,3))
plot(df_practical[:q_m3_s]*3600, df_practical[:c_CO2], alpha = 0.5, "o")
plot([0, 260], [em_gas, em_gas]*1e3, "--k")
xlabel("Flow rate [m^3/h]")
ylabel("CO2 emission [kg CO2/MJ]")
tight_layout()
savefig("carbon_emission_geothermal.png")


UndefVarError: df_practical not defined

Stacktrace:
 [1] top-level scope at In[16]:2

Compare with the current state of technology

The theoretical COP can be calculated based on the temperature of the outside (temperature outside of the house, where heat is extracted) and the desirable temperature inside (where heat is delivered). It is calculated by $$COP_{th} = \frac{T_h}{T_h-T_c}$$

compare with a methane burner

Find cases with a COP less than 1.0


In [17]:
df_current = df_geo[df_geo[:COP].<1.0, :]


Out[17]:

29 rows × 11 columns

q_m3_sk_m2L_mt_life_sR_thR_prR_zec_CO2COPc_CO2_heatheat_per_m3
Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰Float64⍰
10.02777782.0e-14500.05.28817e8-1.4896-8.39255-10.9480.5678780.8303990.06623321.54382e5
20.02777782.0e-14650.07.14598e8-1.71221-9.27316-12.07080.6216930.7573090.07262551.54987e5
30.02777782.0e-14800.08.88297e8-1.9307-10.1328-13.16680.6742270.6971930.07888781.55628e5
40.02777782.0e-14900.01.01416e9-2.09324-10.7707-13.98020.7132120.6591840.0834364155522.0
50.02777782.0e-141000.01.12795e9-2.24194-11.3538-14.72360.7488460.6274350.08765851.55767e5
60.04166672.0e-14500.03.52545e8-2.67527-13.0241-16.85320.8509150.5541760.0992464154389.0
70.04166672.0e-14650.04.76399e8-3.01422-14.3591-18.55540.9325030.5048840.1089361.54994e5
80.04166672.0e-14800.05.92198e8-3.34479-15.6566-20.20961.011790.4645820.1183861.55634e5
90.04166672.0e-14900.06.7611e8-3.58998-16.6174-21.43471.070510.4391680.125237155528.0
100.04166672.0e-141000.07.51967e8-3.81405-17.4949-22.55351.124130.4179630.131591155773.0
110.05555562.0e-14500.02.64408e8-3.86746-17.6811-22.79091.135510.4152790.1324411.54393e5
120.05555562.0e-14650.03.57299e8-4.32272-19.4705-25.07241.244860.3781970.1454271.54997e5
130.05555562.0e-14800.04.44149e8-4.76532-21.2056-27.28461.350890.3479590.158064155637.0
140.05555562.0e-14900.05.07082e8-5.09317-22.4892-28.92131.429340.3289130.1672181.55531e5
150.05555562.0e-141000.05.63975e8-5.3926-23.6611-30.41541.500960.3130290.1757031.55776e5
160.05555565.0e-14650.03.57299e8-1.24637-7.45347-9.750670.510490.9222570.05963631.54997e5
170.05555565.0e-14800.04.44149e8-1.41973-8.13684-10.6220.5522510.8511640.0646174155637.0
180.05555565.0e-14900.05.07082e8-1.54926-8.64583-11.27090.5833560.8059040.06824631.55531e5
190.05555565.0e-141000.05.63975e8-1.6677-9.11071-11.86370.6117660.7680110.07161361.55776e5
200.06944442.0e-14500.02.11527e8-5.06588-22.3624-28.75961.421590.3317060.1658091.54395e5
210.06944442.0e-14650.02.85839e8-5.63742-24.606-31.62021.55870.3020470.1820911.54999e5
220.06944442.0e-14800.03.55319e8-6.19202-26.7786-34.39021.691470.2778970.1979151.55639e5
230.06944442.0e-14900.04.05666e8-6.60253-28.3852-36.43861.789650.2626920.209371.55533e5
240.06944442.0e-141000.04.5118e8-6.9773-29.8514-38.3081.879250.2500150.2199871.55778e5
250.06944445.0e-14500.02.11527e8-1.56986-8.70607-11.34770.5870380.8032710.06847011.54395e5
260.06944445.0e-14650.02.85839e8-1.79203-9.58497-12.46830.6407480.7347660.07485371.54999e5
270.06944445.0e-14800.03.55319e8-2.01009-10.4429-13.56220.6931770.6781160.08110711.55639e5
280.06944445.0e-14900.04.05666e8-2.17269-11.0811-14.37590.7321790.6420930.08565741.55533e5
290.06944445.0e-141000.04.5118e8-2.32123-11.6636-15.11850.7677730.6119530.08987621.55778e5

:MW_CO2
:MW_coal
:MW_gas
:e_igcc
:e_ngcc
:e_pc
:em_coal
:em_gas
:eta_driver :eta_pump
:ex_ccs
:ex_coal
:ex_gas
:η_igcc
:η_ngcc
:η_pc


In [18]:
figure(figsize=(5,3))
plot(df_geo[:k_m2]*1e12, df_geo[:COP], "o", alpha = 0.5)
plot([0,1.1], [1.0, 1.0], "--k", label = "methane")
plot([0,1.1], [2.0, 2.0], "-r", label = "heat pump")
plot([0,1.1], [5.0, 5.0], ":g", label = "geo heat pump")
axis([0,1.05,0,40])
legend()
ylabel("COP")
xlabel("Permeability [D]")


Out[18]:
PyObject Text(0.5, 66.0, 'Permeability [D]')

In [21]:
figure(figsize=(4,3))
semilogy(df_geo[:k_m2]*1e12, df_geo[:COP], "o", alpha = 0.5)
semilogy([0,1.1], [0.8, 0.8], "--k", label = "methane")
semilogy([0,1.1], [1.0, 1.0], "-r", label = "heat pump")
semilogy([0,1.1], [2.0, 2.0], ":g", label = "geo heat pump")
axis([0,1.05,0,40])
legend()
ylabel("COP")
xlabel("Permeability [D]")
savefig("figs/COP_alternatives.png")