diff --git a/.github/workflows/CI_ClapeyronHANNA.yml b/.github/workflows/CI_ClapeyronHANNA.yml new file mode 100644 index 000000000..1aee0aca9 --- /dev/null +++ b/.github/workflows/CI_ClapeyronHANNA.yml @@ -0,0 +1,60 @@ +name: CI (ClapeyronHANNA) + +on: + pull_request: + branches: + - master + paths: + - "lib/ClapeyronHANNA/**" + - ".github/workflows/CI_ClapeyronHANNA.yml" + push: + branches: + - master + +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - "1" + os: + - ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + - uses: actions/cache@v4 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + - name: "Install Dependencies and Run Tests" + run: | + import Pkg + Pkg.Registry.update() + Pkg.instantiate() + Pkg.test(; coverage=true) + shell: julia --color=yes --code-coverage=user --depwarn=yes --project=lib/ClapeyronHANNA {0} + - uses: julia-actions/julia-processcoverage@v1 + with: + directories: lib/ClapeyronHANNA/src + - uses: codecov/codecov-action@v4 + with: + file: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + fail_ci_if_error: true \ No newline at end of file diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml new file mode 100644 index 000000000..f4a75f286 --- /dev/null +++ b/.github/workflows/Documentation.yml @@ -0,0 +1,23 @@ +name: Documentation +on: + - push +# needed to allow julia-actions/cache to delete old caches that it has created +permissions: + actions: write + contents: read +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: always. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + docs: + name: Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/julia-buildpkg@latest + - uses: julia-actions/julia-docdeploy@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95a02f593..2bd68a610 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,15 @@ name: CI on: - - push - - pull_request + pull_request: + branches: + - master + paths-ignore: + - 'docs/**' + - 'examples/**' + push: + paths-ignore: + - 'docs/**' + - 'examples/**' # needed to allow julia-actions/cache to delete old caches that it has created permissions: actions: write @@ -61,13 +69,3 @@ jobs: with: file: lcov.info token: ${{ secrets.CODECOV_TOKEN }} - docs: - name: Documentation - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: julia-actions/julia-buildpkg@latest - - uses: julia-actions/julia-docdeploy@latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} diff --git a/docs/Project.toml b/docs/Project.toml index 496d2904e..57312c7fb 100755 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,6 +1,7 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Clapeyron = "7c7805af-46cc-48c9-995b-ed0ed2dc909a" +ClapeyronHANNA = "3f3167c7-7596-4199-a8f8-b0e6cfdc2746" [compat] Documenter = "1" diff --git a/docs/make.jl b/docs/make.jl index 18591cc64..7fbd6bbb4 100755 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,6 +1,5 @@ push!(LOAD_PATH,"../src/") -using Documenter,Clapeyron - +using Documenter,Clapeyron,ClapeyronHANNA makedocs(sitename = "Clapeyron.jl", format = Documenter.HTML( # Use clean URLs, unless built as a "local" build diff --git a/docs/src/eos/activity.md b/docs/src/eos/activity.md index a59dc114c..fe6e1c210 100755 --- a/docs/src/eos/activity.md +++ b/docs/src/eos/activity.md @@ -43,7 +43,7 @@ Clapeyron.tcPRWilsonRes Clapeyron.COSMOSAC02 Clapeyron.COSMOSAC10 Clapeyron.COSMOSACdsp -Clapeyron.HANNA +ClapeyronHANNA.HANNA ``` diff --git a/lib/ClapeyronHANNA/Project.toml b/lib/ClapeyronHANNA/Project.toml index 1a92ec5cf..cae66110f 100644 --- a/lib/ClapeyronHANNA/Project.toml +++ b/lib/ClapeyronHANNA/Project.toml @@ -1,13 +1,14 @@ name = "ClapeyronHANNA" uuid = "3f3167c7-7596-4199-a8f8-b0e6cfdc2746" authors = ["Sebastian Schmitt "] -version = "0.1.0" +version = "0.1.1" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" Clapeyron = "7c7805af-46cc-48c9-995b-ed0ed2dc909a" Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c" JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Transformers = "21ca0261-441d-5938-ace7-c90938fde4d4" [compat] @@ -15,6 +16,7 @@ CSV = "0.10" Clapeyron = "0.6" Flux = "0.14" JLD2 = "0.5" +LinearAlgebra = "1" Transformers = "0.3" julia = "1.6" diff --git a/lib/ClapeyronHANNA/src/ClapeyronHANNA.jl b/lib/ClapeyronHANNA/src/ClapeyronHANNA.jl index 22997041f..5d4e55e3f 100644 --- a/lib/ClapeyronHANNA/src/ClapeyronHANNA.jl +++ b/lib/ClapeyronHANNA/src/ClapeyronHANNA.jl @@ -8,6 +8,7 @@ using Clapeyron: getparams, init_puremodel, Rgas import Clapeyron: default_locations using Flux, Transformers.HuggingFace, Transformers.TextEncoders using CSV, JLD2 +using LinearAlgebra include("HANNA.jl") @@ -47,7 +48,9 @@ silu(x) = @. x/(1+exp(-x)) # Cosine similarity function cosine_similarity(x1,x2;eps=1e-8) - res = sum(x1.*x2)./(max.(sqrt.(sum(x1.^2)),eps).*max.(sqrt.(sum(x2.^2)),eps)) + ∑x1 = sqrt(dot(x1,x1)) + ∑x2 = sqrt(dot(x2,x2)) + res = dot(x1,x2)/(max(∑x1,eps*one(∑x1))*max(∑x2,eps*one(∑x2))) end -end +end #module diff --git a/lib/ClapeyronHANNA/src/HANNA.jl b/lib/ClapeyronHANNA/src/HANNA.jl index 5ba31fa5e..2a68ae460 100644 --- a/lib/ClapeyronHANNA/src/HANNA.jl +++ b/lib/ClapeyronHANNA/src/HANNA.jl @@ -2,13 +2,24 @@ struct HANNAParam <: EoSParam smiles::SingleParam{String} emb_scaled::Vector{Vector{Float64}} - T_scaler::Function + T_scaler::Function #TODO:maybe parametrize this? theta::Dense alpha::Chain phi::Chain Mw::SingleParam{Float64} end +function Clapeyron.split_model(param::HANNAParam,splitter) + return [Clapeyron.each_split_model(param,i) for i ∈ splitter] +end + +function Clapeyron.each_split_model(param::HANNAParam,i) + Mw = Clapeyron.each_split_model(param.Mw,i) + smiles = Clapeyron.each_split_model(param.smiles,i) + emb_scaled = Clapeyron.each_split_model(param.emb_scaled,i) + return HANNAParam(smiles,emb_scaled,param.T_scaler,param.theta,param.alpha,param.phi,Mw) +end + abstract type HANNAModel <: ActivityModel end struct HANNA{c<:EoSModel} <: HANNAModel @@ -59,7 +70,7 @@ HANNA default_locations(::Type{HANNA}) = ["properties/identifiers.csv", "properties/molarmass.csv"] function HANNA(components; - puremodel = nothing, + puremodel = BasicIdeal, userlocations = String[], pure_userlocations = String[], verbose = false) @@ -107,13 +118,7 @@ function HANNA(components; ) phi[1].bias .+= b1_ϕ phi[2].bias .+= b2_ϕ - - if isnothing(puremodel) - _puremodel = init_puremodel(BasicIdeal(),components,pure_userlocations,false) - else - _puremodel = init_puremodel(puremodel,components,pure_userlocations,verbose) - end - + _puremodel = init_puremodel(puremodel,components,pure_userlocations,verbose) params = HANNAParam(params["canonicalsmiles"],emb_scaled,T_scaler,theta,alpha,phi,params["Mw"]) references = String["10.48550/arXiv.2407.18011"] @@ -125,20 +130,25 @@ function C.excess_gibbs_free_energy(model::HANNAModel,p,T,z) # Scale input (T and embs) T_s = model.params.T_scaler(T) - # Fine tuning of the component embeddings θ_i = model.params.theta.(model.params.emb_scaled) - - # Calculate cosine similarity and distance between the two components - cosine_sim = cosine_similarity(θ_i[1],θ_i[2]) - cosine_dist = 1.0-cosine_sim - - # Concatenate embeddings with T and x - c_i = vcat.(T_s,x,θ_i) - α_i = model.params.alpha.(c_i) - c_mix = sum(α_i) - gE_NN = model.params.phi(c_mix)[1] - - # Apply cosine similarity adjustment - return gE_NN * prod(x) * cosine_dist * Rgas(model) * T * sum(z) + gE = zero(Base.promote_eltype(T_s,x)) + n = length(model) + for i in 1:n + for j in (i+1):n + # Calculate cosine similarity and distance between the two components + cosine_sim_ij = cosine_similarity(θ_i[i],θ_i[j]) + cosine_dist_ij = 1.0 - cosine_sim_ij + + # Concatenate embeddings with T and x + c_i = vcat.(T_s,[x[i],x[j]],[θ_i[i],θ_i[j]]) + α_i = model.params.alpha.(c_i) + c_mix = sum(α_i) + gE_NN = model.params.phi(c_mix)[1] + + # Apply cosine similarity adjustment + gE += x[i]*x[j]*gE_NN*cosine_dist_ij + end + end + return gE * Rgas(model) * T * sum(z) end