Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arni/extract henry coeff #100

Merged
merged 19 commits into from Apr 11, 2019
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
362c877
Add extract_henry_coefficient function along with tests for it
Surluson Jan 18, 2019
a006a8f
Added a functionality to fit a isotherm to a basic langmuir model
Surluson Jan 18, 2019
1b72f69
Added a new test module, misc_test.jl
Surluson Jan 18, 2019
ca216a2
Add a RMSE error to and changed the langmuir fit procedure
Surluson Jan 20, 2019
c0f78aa
Update initial guesses for langmuir fit and allow for a linear method…
Surluson Mar 8, 2019
eb2809c
Redid the fit_isotherm functionality. Now takes in model, suchas hen…
Surluson Mar 9, 2019
4749b26
Remove dependancy on MultivariateStats. Fitting procedure is purely d…
Surluson Mar 9, 2019
871b2f3
Reworked tests to apply correctly to the new fitting procedure
Surluson Mar 9, 2019
9767501
Fixed merger
Surluson Mar 9, 2019
6d5295a
Fixed mistake in misc_test.jl
Surluson Mar 9, 2019
4b403e0
Removed old functions and added docstring to
Surluson Mar 10, 2019
cac38ef
Add Optim.jl to REQUIRE
Surluson Mar 10, 2019
b7ca417
Removed old call to MultivariateStats
Surluson Mar 10, 2019
779c310
Changed how the scaled rmse is scaled
Surluson Mar 12, 2019
1d95b9a
Simplified the fitting procedure for Henry. Func now returns a Dict
Surluson Apr 10, 2019
938523b
Fixed error in Langmuir guess. Reused more code.
Surluson Apr 10, 2019
28a3115
Removed old artifacts from code
Surluson Apr 11, 2019
7f68e70
remove unneccesariy isotherm data, change function name to fit_adsorp…
CorySimon Apr 11, 2019
59679f4
update doc string function name
CorySimon Apr 11, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions REQUIRE
Expand Up @@ -10,3 +10,4 @@ ProgressMeter
JLD2
LightGraphs
FileIO
Optim
78 changes: 78 additions & 0 deletions src/Misc.jl
Expand Up @@ -136,3 +136,81 @@ function read_atomic_masses()

return atomic_masses
end


function _guess(df::DataFrame, pressure_col_name::Symbol, loading_col_name::Symbol, model::Symbol)
n = df[loading_col_name]
p = df[pressure_col_name]
if model == :langmuir
M0 = n[end] * 1.1
if isapprox(n[1], 0.0)
K0 = n[2]/p[2]
else
K0 = n[1]/p[1]
end
return Dict("M0" => M0, "K0" => K0)
elseif model == :henry
if isapprox(n[1], 0.0)
K0 = n[2]/p[2]
else
K0 = n[1]/p[1]
end
return Dict("K0" => K0)
else
error("Model not available. Currently only `:langmuir` and `:henry` are available")
end
end



"""
params = fit_isotherm(df, pressure_col_name, loading_col_name, model)

Takes in a DataFrame `df` containing an isotherm. Will try to fit a model to the data.
Available models are `:henry` and `:langmuir`
The Langmuir model is in the following form:
N = (MKP)/(1+KP)
where N is the total adsorption, M is the maximum monolayer coverage, K is the Langmuir constant and P is the partial pressure of the gas.
`params` will contain a fit to M and K
The Henry model takes the following form:
N = KP
and `params` will contain K

# Arguments
- `df::DataFrame`: The DataFrame containing the pressure and adsorption data for the isotherm
- `pressure_col_name::Symbol`: The header of the pressure column. Can be found with `names(df)`
- `loading_col_name::Symbol`: The header of the loading/adsorption column. Can be found with `names(df)`
- `model::Symbol`: The model chosen to fit the isotherm

# Returns
- `params::Dict{AbstractString, Float64}`: A Dictionary with the parameters corresponding to each model along with the MSE of the fit. `:langmuir` contains "M" and "K". `:henry` contains "K".
"""
function fit_isotherm(df::DataFrame, pressure_col_name::Symbol, loading_col_name::Symbol, model::Symbol; henry_tol::Float64 = 0.25)
sort!(df, [pressure_col_name])
n = df[loading_col_name]
p = df[pressure_col_name]
if ! isapprox(p[1], 0.0, atol=0.01)
prepend!(n, 0.0)
prepend!(p, 0.0)
end
θ0 = _guess(df, pressure_col_name, loading_col_name, model)

if model == :langmuir
objective_function_langmuir(θ) = return sum([(n[i] - θ[1] * θ[2] * p[i]/(1 + θ[2] * p[i]))^2 for i = 1:length(n)])
res = optimize(objective_function_langmuir, [θ0["M0"], θ0["K0"]], LBFGS())
M, K = res.minimizer
mse = res.minimum / length(n)
return Dict("M" => M, "K" => K, "MSE" => mse)

elseif model == :henry
min_mse = Inf
objective_function_henry(θ) = return sum([(n[i] - θ[1] * p[i])^2 for i = 1:length(n)])
res = optimize(objective_function_henry, [θ0["K0"]] * 1.1, LBFGS())
K = res.minimizer
mse = res.minimum / length(n)
if mse < min_mse
min_mse = mse
end
return Dict("K" => K[1], "MSE" => mse)
end
end
3 changes: 2 additions & 1 deletion src/PorousMaterials.jl
Expand Up @@ -14,6 +14,7 @@ using Printf
using LinearAlgebra
using LightGraphs
using Distributed
using Optim
import Base.push!


Expand Down Expand Up @@ -106,7 +107,7 @@ export
nearest_image!, nearest_r², nearest_r,

# Misc.jl
read_xyz, read_cpk_colors, read_atomic_radii, write_xyz,
read_xyz, read_cpk_colors, read_atomic_radii, write_xyz, fit_isotherm,

# Crystal.jl
Framework, read_crystal_structure_file, remove_overlapping_atoms_and_charges,
Expand Down
9 changes: 9 additions & 0 deletions test/isotherm.csv
@@ -0,0 +1,9 @@
Pressure(bar),Adsorption(mmol/g)
0.0,0.0
0.560,0.534
1.333,1.295
2.370,2.204
3.551,3.153
4.830,4.080
6.128,4.951
7.244,5.616
29 changes: 29 additions & 0 deletions test/misc_test.jl
@@ -0,0 +1,29 @@
module Misc_Test

using PorousMaterials
using Test
using DataFrames
using CSV
using Optim

@testset "Misc Tests" begin
df = CSV.read("isotherm.csv")
p_col_name, l_col_name = names(df)
henry = fit_isotherm(df[1:3,:], p_col_name, l_col_name, :henry)["K"]
@test isapprox(henry, 0.9688044280548711)

P = range(0, stop=1, length=100)
M = 23.0
K = 11.9
N = (M*K.*P)./(1 .+ K.*P)
N = [N[1], N[11], N[21], N[31], N[41], N[51], N[61], N[71], N[81]]
P = [P[1], P[11], P[21], P[31], P[41], P[51], P[61], P[71], P[81]]
new_df = DataFrame(Pressure = P, Loading = N)
x = fit_isotherm(new_df, Symbol("Pressure"), Symbol("Loading"), :langmuir)
M2, K2 = x["M"], x["K"]
@test isapprox(M, M2)
@test isapprox(K, K2)
end


end
1 change: 1 addition & 0 deletions test/runtests.jl
Expand Up @@ -16,4 +16,5 @@ include("gcmc_checkpoints_test.jl")
include("henry_checkpoint_test.jl")
include("grid_test.jl")
include("path_test.jl")
include("misc_test.jl")
include("generic_rotation_test.jl")