@@ -4,23 +4,28 @@ use crate::ideal_chain_contribution::IdealChainContribution;
44use crate :: weight_functions:: { WeightFunction , WeightFunctionInfo , WeightFunctionShape } ;
55use feos_core:: {
66 Contributions , EosResult , EosUnit , EquationOfState , HelmholtzEnergy , HelmholtzEnergyDual ,
7- MolarWeight , StateHD ,
7+ IdealGasContribution , IdealGasContributionDual , MolarWeight , StateHD ,
88} ;
99use ndarray:: * ;
1010use num_dual:: * ;
1111use petgraph:: graph:: { Graph , UnGraph } ;
1212use petgraph:: visit:: EdgeRef ;
1313use petgraph:: Directed ;
1414use quantity:: { QuantityArray , QuantityArray1 , QuantityScalar } ;
15+ use std:: fmt;
1516use std:: ops:: { AddAssign , MulAssign } ;
1617use std:: rc:: Rc ;
1718
1819/// Wrapper struct for the [HelmholtzEnergyFunctional] trait.
1920#[ derive( Clone ) ]
2021pub struct DFT < T > {
22+ /// Helmholtz energy functional
2123 pub functional : T ,
24+ /// map segment -> component
2225 pub component_index : Array1 < usize > ,
26+ /// chain lengths of individual components
2327 pub m : Array1 < f64 > ,
28+ /// ideal chain contribution
2429 pub ideal_chain_contribution : IdealChainContribution ,
2530}
2631
@@ -54,6 +59,19 @@ impl<T: MolarWeight<U>, U: EosUnit> MolarWeight<U> for DFT<T> {
5459 }
5560}
5661
62+ struct DefaultIdealGasContribution ( ) ;
63+ impl < D : DualNum < f64 > > IdealGasContributionDual < D > for DefaultIdealGasContribution {
64+ fn de_broglie_wavelength ( & self , _: D , components : usize ) -> Array1 < D > {
65+ Array1 :: zeros ( components)
66+ }
67+ }
68+
69+ impl fmt:: Display for DefaultIdealGasContribution {
70+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
71+ write ! ( f, "Ideal gas (default)" )
72+ }
73+ }
74+
5775impl < T : HelmholtzEnergyFunctional > EquationOfState for DFT < T > {
5876 fn components ( & self ) -> usize {
5977 self . component_index [ self . component_index . len ( ) - 1 ] + 1
@@ -107,6 +125,10 @@ impl<T: HelmholtzEnergyFunctional> EquationOfState for DFT<T> {
107125 ) ) ;
108126 res
109127 }
128+
129+ fn ideal_gas ( & self ) -> & dyn IdealGasContribution {
130+ self . functional . ideal_gas ( )
131+ }
110132}
111133
112134/// A general Helmholtz energy functional.
@@ -125,6 +147,18 @@ pub trait HelmholtzEnergyFunctional: Sized {
125147 /// equation of state anyways).
126148 fn compute_max_density ( & self , moles : & Array1 < f64 > ) -> f64 ;
127149
150+ /// Return the ideal gas contribution.
151+ ///
152+ /// Per default this function returns an ideal gas contribution
153+ /// in which the de Broglie wavelength is 1 for every component.
154+ /// Therefore, the correct ideal gas pressure is obtained even
155+ /// with no explicit ideal gas term. If a more detailed model is
156+ /// required (e.g. for the calculation of internal energies) this
157+ /// function has to be overwritten.
158+ fn ideal_gas ( & self ) -> & dyn IdealGasContribution {
159+ & DefaultIdealGasContribution ( )
160+ }
161+
128162 /// Overwrite this, if the functional consists of heterosegmented chains.
129163 fn bond_lengths ( & self , _temperature : f64 ) -> UnGraph < ( ) , f64 > {
130164 Graph :: with_capacity ( 0 , 0 )
@@ -139,6 +173,7 @@ pub trait HelmholtzEnergyFunctional: Sized {
139173}
140174
141175impl < T : HelmholtzEnergyFunctional > DFT < T > {
176+ /// Calculate the grand potential density $\omega$.
142177 pub fn grand_potential_density < U , D > (
143178 & self ,
144179 temperature : QuantityScalar < U > ,
@@ -169,12 +204,49 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
169204 Ok ( f * t * U :: reference_pressure ( ) )
170205 }
171206
207+ pub ( crate ) fn ideal_gas_contribution < D > (
208+ & self ,
209+ temperature : f64 ,
210+ density : & Array < f64 , D :: Larger > ,
211+ ) -> Array < f64 , D >
212+ where
213+ D : Dimension ,
214+ D :: Larger : Dimension < Smaller = D > ,
215+ {
216+ let n = self . components ( ) ;
217+ let ig = self . functional . ideal_gas ( ) ;
218+ let lambda = ig. de_broglie_wavelength ( temperature, n) ;
219+ let mut phi = Array :: zeros ( density. raw_dim ( ) . remove_axis ( Axis ( 0 ) ) ) ;
220+ for ( i, rhoi) in density. outer_iter ( ) . enumerate ( ) {
221+ phi += & rhoi. mapv ( |rhoi| ( rhoi. ln ( ) + lambda[ i] - 1.0 ) * rhoi) ;
222+ }
223+ phi * temperature
224+ }
225+
226+ fn ideal_gas_contribution_dual < D > (
227+ & self ,
228+ temperature : Dual64 ,
229+ density : & Array < f64 , D :: Larger > ,
230+ ) -> Array < Dual64 , D >
231+ where
232+ D : Dimension ,
233+ D :: Larger : Dimension < Smaller = D > ,
234+ {
235+ let n = self . components ( ) ;
236+ let ig = self . functional . ideal_gas ( ) ;
237+ let lambda = ig. de_broglie_wavelength ( temperature, n) ;
238+ let mut phi = Array :: zeros ( density. raw_dim ( ) . remove_axis ( Axis ( 0 ) ) ) ;
239+ for ( i, rhoi) in density. outer_iter ( ) . enumerate ( ) {
240+ phi += & rhoi. mapv ( |rhoi| ( lambda[ i] + rhoi. ln ( ) - 1.0 ) * rhoi) ;
241+ }
242+ phi * temperature
243+ }
244+
172245 fn intrinsic_helmholtz_energy_density < D , N > (
173246 & self ,
174247 temperature : N ,
175248 density : & Array < f64 , D :: Larger > ,
176249 convolver : & Rc < dyn Convolver < N , D > > ,
177- contributions : Contributions ,
178250 ) -> EosResult < Array < N , D > >
179251 where
180252 N : DualNum < f64 > + ScalarOperand ,
@@ -187,7 +259,7 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
187259 let functional_contributions = self . functional . contributions ( ) ;
188260 let mut helmholtz_energy_density: Array < N , D > = self
189261 . ideal_chain_contribution
190- . calculate_helmholtz_energy_density ( & density. mapv ( N :: from) , contributions ) ?;
262+ . calculate_helmholtz_energy_density ( & density. mapv ( N :: from) ) ?;
191263 for ( c, wd) in functional_contributions. iter ( ) . zip ( weighted_densities) {
192264 let nwd = wd. shape ( ) [ 0 ] ;
193265 let ngrid = wd. len ( ) / nwd;
@@ -203,6 +275,9 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
203275 Ok ( helmholtz_energy_density * temperature)
204276 }
205277
278+ /// Calculate the entropy density $s$.
279+ ///
280+ /// Untested with heterosegmented functionals.
206281 pub fn entropy_density < D > (
207282 & self ,
208283 temperature : f64 ,
@@ -215,21 +290,26 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
215290 D :: Larger : Dimension < Smaller = D > ,
216291 {
217292 let temperature_dual = Dual64 :: from ( temperature) . derive ( ) ;
218- let helmholtz_energy_density = self . intrinsic_helmholtz_energy_density (
219- temperature_dual,
220- density,
221- convolver,
222- contributions,
223- ) ?;
293+ let mut helmholtz_energy_density =
294+ self . intrinsic_helmholtz_energy_density ( temperature_dual, density, convolver) ?;
295+ match contributions {
296+ Contributions :: Total => {
297+ helmholtz_energy_density += & self . ideal_gas_contribution_dual :: < D > ( temperature_dual, density) ;
298+ } ,
299+ Contributions :: ResidualP |Contributions :: IdealGas => panic ! ( "Entropy density can only be calculated for Contributions::Residual or Contributions::Total" ) ,
300+ Contributions :: Residual => ( ) ,
301+ }
224302 Ok ( helmholtz_energy_density. mapv ( |f| -f. eps [ 0 ] ) )
225303 }
226304
305+ /// Calculate the individual contributions to the entropy density.
306+ ///
307+ /// Untested with heterosegmented functionals.
227308 pub fn entropy_density_contributions < D > (
228309 & self ,
229310 temperature : f64 ,
230311 density : & Array < f64 , D :: Larger > ,
231312 convolver : & Rc < dyn Convolver < Dual64 , D > > ,
232- contributions : Contributions ,
233313 ) -> EosResult < Vec < Array < f64 , D > > >
234314 where
235315 D : Dimension ,
@@ -244,7 +324,7 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
244324 Vec :: with_capacity ( functional_contributions. len ( ) + 1 ) ;
245325 helmholtz_energy_density. push (
246326 self . ideal_chain_contribution
247- . calculate_helmholtz_energy_density ( & density. mapv ( Dual64 :: from) , contributions ) ?,
327+ . calculate_helmholtz_energy_density ( & density. mapv ( Dual64 :: from) ) ?,
248328 ) ;
249329
250330 for ( c, wd) in functional_contributions. iter ( ) . zip ( weighted_densities) {
@@ -265,6 +345,9 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
265345 . collect ( ) )
266346 }
267347
348+ /// Calculate the internal energy density $u$.
349+ ///
350+ /// Untested with heterosegmented functionals.
268351 pub fn internal_energy_density < D > (
269352 & self ,
270353 temperature : f64 ,
@@ -278,18 +361,22 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
278361 D :: Larger : Dimension < Smaller = D > ,
279362 {
280363 let temperature_dual = Dual64 :: from ( temperature) . derive ( ) ;
281- let helmholtz_energy_density_dual = self . intrinsic_helmholtz_energy_density (
282- temperature_dual,
283- density,
284- convolver,
285- contributions,
286- ) ?;
364+ let mut helmholtz_energy_density_dual =
365+ self . intrinsic_helmholtz_energy_density ( temperature_dual, density, convolver) ?;
366+ match contributions {
367+ Contributions :: Total => {
368+ helmholtz_energy_density_dual += & self . ideal_gas_contribution_dual :: < D > ( temperature_dual, density) ;
369+ } ,
370+ Contributions :: ResidualP |Contributions :: IdealGas => panic ! ( "Internal energy density can only be calculated for Contributions::Residual or Contributions::Total" ) ,
371+ Contributions :: Residual => ( ) ,
372+ }
287373 let helmholtz_energy_density = helmholtz_energy_density_dual
288374 . mapv ( |f| f. re - f. eps [ 0 ] * temperature)
289375 + ( external_potential * density) . sum_axis ( Axis ( 0 ) ) * temperature;
290376 Ok ( helmholtz_energy_density)
291377 }
292378
379+ /// Calculate the (residual) functional derivative $\frac{\delta\mathcal{F}}{\delta\rho_i(\mathbf{r})}$.
293380 #[ allow( clippy:: type_complexity) ]
294381 pub fn functional_derivative < D > (
295382 & self ,
@@ -325,8 +412,8 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
325412 ) )
326413 }
327414
328- // iSAFT correction to the functional derivative
329- pub fn isaft_integrals < D > (
415+ /// Calculate the bond integrals $I_{\alpha\alpha'}(\mathbf{r})$
416+ pub fn bond_integrals < D > (
330417 & self ,
331418 temperature : f64 ,
332419 functional_derivative : & Array < f64 , D :: Larger > ,
@@ -338,13 +425,13 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
338425 {
339426 // calculate weight functions
340427 let bond_lengths = self . functional . bond_lengths ( temperature) . into_edge_type ( ) ;
341- let mut isaft_weight_functions = bond_lengths. map (
428+ let mut bond_weight_functions = bond_lengths. map (
342429 |_, _| ( ) ,
343430 |_, & l| WeightFunction :: new_scaled ( arr1 ( & [ l] ) , WeightFunctionShape :: Delta ) ,
344431 ) ;
345432 for n in bond_lengths. node_indices ( ) {
346433 for e in bond_lengths. edges ( n) {
347- isaft_weight_functions . add_edge (
434+ bond_weight_functions . add_edge (
348435 e. target ( ) ,
349436 e. source ( ) ,
350437 WeightFunction :: new_scaled ( arr1 ( & [ * e. weight ( ) ] ) , WeightFunctionShape :: Delta ) ,
@@ -354,7 +441,7 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
354441
355442 let expdfdrho = functional_derivative. mapv ( |x| ( -x) . exp ( ) ) ;
356443 let mut i_graph: Graph < _ , Option < Array < f64 , D > > , Directed > =
357- isaft_weight_functions . map ( |_, _| ( ) , |_, _| None ) ;
444+ bond_weight_functions . map ( |_, _| ( ) , |_, _| None ) ;
358445
359446 let bonds = i_graph. edge_count ( ) ;
360447 let mut calc = 0 ;
@@ -384,9 +471,8 @@ impl<T: HelmholtzEnergyFunctional> DFT<T> {
384471 . to_owned ( ) ,
385472 |acc : Array < f64 , D > , e| acc * e. weight ( ) . as_ref ( ) . unwrap ( ) ,
386473 ) ;
387- i1 = Some (
388- convolver. convolve ( i0. clone ( ) , & isaft_weight_functions[ edge. id ( ) ] ) ,
389- ) ;
474+ i1 =
475+ Some ( convolver. convolve ( i0. clone ( ) , & bond_weight_functions[ edge. id ( ) ] ) ) ;
390476 break ' nodes;
391477 }
392478 }
0 commit comments