@@ -20,9 +20,11 @@ def decorator(fn):
2020 return decorator
2121
2222
23+ import numpy as np
2324from . import debye
2425from . import equation_of_state as eos
2526from ..utils .math import bracket
27+ from ..utils .misc import copy_documentation
2628from . import bukowinski_electronic as el
2729from .anharmonic_debye import AnharmonicDebye as Anharmonic
2830
@@ -351,3 +353,296 @@ def validate_parameters(self, params):
351353 if "bel_0" not in params :
352354 params ["bel_0" ] = None
353355 params ["gel" ] = None
356+
357+
358+ class ModularMGDWithAnharmonicity (ModularMGD ):
359+ """
360+ This class extends the ModularMGD class to include anharmonicity effects
361+ according to a simplification of the model proposed by
362+ Wu and Wentzcovitch, 2009.
363+
364+ The basis of the anharmonic model is the definition of a scaled volume, :math:`V'`:
365+ :math:`\\ ln (V'/V) = = - c \\ ln (V/V_0)`.
366+ In this expression, :math:`V` is the target volume,
367+ :math:`V_0` is a first order approximation to the volume at the same pressure
368+ and reference temperature :math:`T_0`, and :math:`c` is an anharmonicity parameter
369+ provided in the params dictionary as `c_anh`.
370+
371+ The anharmonic Helmholtz energy :math:`F` is related to the scaled volume by the equation:
372+
373+ .. math::
374+
375+ F(V,T) = F_h(V',T) + F_h(V,T_0) - F_h(V',T_0)
376+
377+ where :math:`F_h` is the harmonic Helmholtz energy,
378+ potentially with electronic contributions.
379+
380+ Note: This model is not the same as that published in
381+ Wu and Wentzcovitch (2009). The results are expected to be similar,
382+ but the :math:`c` parameter will in general need to be tweaked.
383+ This is because only a local approximation to the volume change
384+ between 0 K and the target temperature is used. This does not mean that
385+ the model is less able to capture the essential physics of the problem;
386+ indeed, the model of Wu and Wentzcovitch (2009) is only intended to be
387+ an effective ansatz.
388+ """
389+
390+ def lnVoverV0_approx (self , temperature , volume , params ):
391+ a_h = ModularMGD .thermal_expansivity (
392+ super (), np .nan , temperature , volume , params
393+ )
394+ KT_h = ModularMGD .isothermal_bulk_modulus_reuss (
395+ super (), np .nan , temperature , volume , params
396+ )
397+ KT0_h = ModularMGD .isothermal_bulk_modulus_reuss (
398+ super (), np .nan , params ["T_0" ], volume , params
399+ )
400+ P_th = a_h * KT_h * (temperature - params ["T_0" ])
401+ return P_th / KT0_h # assume a constant bulk modulus
402+
403+ @copy_documentation (ModularMGD .pressure )
404+ def pressure (self , temperature , volume , params ):
405+ dV = 1e-5 * params ["V_0" ]
406+
407+ lnVoverV0 = self .lnVoverV0_approx (temperature , volume , params )
408+ dlnVoverV0_dV = (
409+ self .lnVoverV0_approx (temperature , volume + dV , params )
410+ - self .lnVoverV0_approx (temperature , volume - dV , params )
411+ ) / (2 * dV )
412+
413+ lnVprimeoverV = - params ["c_anh" ] * lnVoverV0
414+ Vprime = volume * np .exp (lnVprimeoverV )
415+
416+ dlnVprimeoverV_dV = - params ["c_anh" ] * dlnVoverV0_dV
417+ dVprime_dV = Vprime * (1.0 / volume + dlnVprimeoverV_dV )
418+
419+ T0 = params ["T_0" ]
420+ P_V_T0 = ModularMGD .pressure (super (), T0 , volume , params )
421+ P_Vp_T = ModularMGD .pressure (super (), temperature , Vprime , params )
422+ P_Vp_T0 = ModularMGD .pressure (super (), T0 , Vprime , params )
423+
424+ P = P_V_T0 + dVprime_dV * (P_Vp_T - P_Vp_T0 )
425+
426+ return P
427+
428+ @copy_documentation (ModularMGD .isothermal_bulk_modulus_reuss )
429+ def isothermal_bulk_modulus_reuss (self , pressure , temperature , volume , params ):
430+ dV = 1e-5 * params ["V_0" ]
431+
432+ lnVoverV0 = self .lnVoverV0_approx (temperature , volume , params )
433+ lnVoverV0_plus_dV = self .lnVoverV0_approx (temperature , volume + dV , params )
434+ lnVoverV0_minus_dV = self .lnVoverV0_approx (temperature , volume - dV , params )
435+
436+ dlnVoverV0_dV = (lnVoverV0_plus_dV - lnVoverV0_minus_dV ) / (2 * dV )
437+ d2lnVoverV0_dV2 = (lnVoverV0_plus_dV - 2 * lnVoverV0 + lnVoverV0_minus_dV ) / (
438+ dV ** 2
439+ )
440+
441+ lnVprimeoverV = - params ["c_anh" ] * lnVoverV0
442+ Vprime = volume * np .exp (lnVprimeoverV )
443+
444+ dlnVprimeoverV_dV = - params ["c_anh" ] * dlnVoverV0_dV
445+ dVprime_dV = Vprime * (1.0 / volume + dlnVprimeoverV_dV )
446+ d2Vprime_dV2 = Vprime * (
447+ (1.0 / volume + dlnVprimeoverV_dV ) ** 2
448+ - 1.0 / volume ** 2
449+ - params ["c_anh" ] * d2lnVoverV0_dV2
450+ )
451+
452+ T0 = params ["T_0" ]
453+ P_Vp_T = ModularMGD .pressure (super (), temperature , Vprime , params )
454+ P_Vp_T0 = ModularMGD .pressure (super (), T0 , Vprime , params )
455+
456+ KT_V_T0 = ModularMGD .isothermal_bulk_modulus_reuss (
457+ super (), np .nan , T0 , volume , params
458+ )
459+ KT_Vp_T = ModularMGD .isothermal_bulk_modulus_reuss (
460+ super (), np .nan , temperature , Vprime , params
461+ )
462+ KT_Vp_T0 = ModularMGD .isothermal_bulk_modulus_reuss (
463+ super (), np .nan , T0 , Vprime , params
464+ )
465+
466+ KT = (
467+ KT_V_T0
468+ - volume * d2Vprime_dV2 * (P_Vp_T - P_Vp_T0 )
469+ + volume / Vprime * (dVprime_dV ) ** 2 * (KT_Vp_T - KT_Vp_T0 )
470+ )
471+ return KT
472+
473+ @copy_documentation (ModularMGD ._molar_heat_capacity_v )
474+ def _molar_heat_capacity_v (self , pressure , temperature , volume , params ):
475+ dT = 1e-2
476+
477+ lnVoverV0 = self .lnVoverV0_approx (temperature , volume , params )
478+ lnVoverV0_plus_dT = self .lnVoverV0_approx (temperature + dT , volume , params )
479+ lnVoverV0_minus_dT = self .lnVoverV0_approx (temperature - dT , volume , params )
480+
481+ dlnVoverV0_dT = (lnVoverV0_plus_dT - lnVoverV0_minus_dT ) / (2 * dT )
482+ d2lnVoverV0_dT2 = (lnVoverV0_plus_dT - 2 * lnVoverV0 + lnVoverV0_minus_dT ) / (
483+ dT ** 2
484+ )
485+
486+ c = params ["c_anh" ]
487+ lnVprimeoverV = - c * lnVoverV0
488+ Vprime = volume * np .exp (lnVprimeoverV )
489+
490+ dlnVprimeoverV_dT = - c * dlnVoverV0_dT
491+ d2lnVprimeoverV_dT2 = - c * d2lnVoverV0_dT2
492+
493+ dVprime_dT = Vprime * dlnVprimeoverV_dT
494+ d2Vprime_dT2 = Vprime * (dlnVprimeoverV_dT ** 2 + d2lnVprimeoverV_dT2 )
495+
496+ T0 = params ["T_0" ]
497+
498+ Cvh_Vprime_T = ModularMGD ._molar_heat_capacity_v (
499+ super (), np .nan , temperature , Vprime , params
500+ )
501+ P_Vprime_T = ModularMGD .pressure (super (), temperature , Vprime , params )
502+ P_Vprime_T0 = ModularMGD .pressure (super (), T0 , Vprime , params )
503+ KT_Vprime_T = ModularMGD .isothermal_bulk_modulus_reuss (
504+ super (), np .nan , temperature , Vprime , params
505+ )
506+ KT_Vprime_T0 = ModularMGD .isothermal_bulk_modulus_reuss (
507+ super (), np .nan , T0 , Vprime , params
508+ )
509+ dPdT_Vprime_T = (
510+ ModularMGD .thermal_expansivity (super (), np .nan , temperature , Vprime , params )
511+ * KT_Vprime_T
512+ )
513+
514+ C_v = Cvh_Vprime_T + temperature * (
515+ 2.0 * dPdT_Vprime_T * dVprime_dT
516+ - (P_Vprime_T0 - P_Vprime_T ) * d2Vprime_dT2
517+ - (KT_Vprime_T - KT_Vprime_T0 ) / Vprime * dVprime_dT ** 2
518+ )
519+ return C_v
520+
521+ def thermal_expansivity (self , pressure , temperature , volume , params ):
522+ dV = 1e-5 * params ["V_0" ]
523+ dT = 1e-2
524+
525+ # lnVoverV0 derivatives
526+ lnVoverV0 = self .lnVoverV0_approx (temperature , volume , params )
527+ lnVoverV0_plusV = self .lnVoverV0_approx (temperature , volume + dV , params )
528+ lnVoverV0_minusV = self .lnVoverV0_approx (temperature , volume - dV , params )
529+ lnVoverV0_plusT = self .lnVoverV0_approx (temperature + dT , volume , params )
530+ lnVoverV0_minusT = self .lnVoverV0_approx (temperature - dT , volume , params )
531+ lnVoverV0_plusV_plusT = self .lnVoverV0_approx (
532+ temperature + dT , volume + dV , params
533+ )
534+ lnVoverV0_plusV_minusT = self .lnVoverV0_approx (
535+ temperature - dT , volume + dV , params
536+ )
537+ lnVoverV0_minusV_plusT = self .lnVoverV0_approx (
538+ temperature + dT , volume - dV , params
539+ )
540+ lnVoverV0_minusV_minusT = self .lnVoverV0_approx (
541+ temperature - dT , volume - dV , params
542+ )
543+
544+ dlnVoverV0_dV = (lnVoverV0_plusV - lnVoverV0_minusV ) / (2 * dV )
545+ dlnVoverV0_dT = (lnVoverV0_plusT - lnVoverV0_minusT ) / (2 * dT )
546+ d2lnVoverV0_dTdV = (
547+ lnVoverV0_plusV_plusT
548+ - lnVoverV0_plusV_minusT
549+ - lnVoverV0_minusV_plusT
550+ + lnVoverV0_minusV_minusT
551+ ) / (4 * dV * dT )
552+
553+ c = params ["c_anh" ]
554+ lnVprimeoverV = - c * lnVoverV0
555+ Vprime = volume * np .exp (lnVprimeoverV )
556+ dlnVprime_dV = - c * dlnVoverV0_dV
557+ dlnVprime_dT = - c * dlnVoverV0_dT
558+ d2lnVprime_dTdV = - c * d2lnVoverV0_dTdV
559+
560+ dVprime_dV = Vprime * (1.0 / volume + dlnVprime_dV )
561+ dVprime_dT = Vprime * dlnVprime_dT
562+ d2Vprime_dTdV = Vprime * (
563+ dlnVprime_dT * (1.0 / volume + dlnVprime_dV ) + d2lnVprime_dTdV
564+ )
565+
566+ T0 = params ["T_0" ]
567+ P_Vprime_T = ModularMGD .pressure (super (), temperature , Vprime , params )
568+ P_Vprime_T0 = ModularMGD .pressure (super (), T0 , Vprime , params )
569+ KT_Vprime_T = ModularMGD .isothermal_bulk_modulus_reuss (
570+ super (), np .nan , temperature , Vprime , params
571+ )
572+ KT_Vprime_T0 = ModularMGD .isothermal_bulk_modulus_reuss (
573+ super (), np .nan , T0 , Vprime , params
574+ )
575+ alphaKT_Vprime_T = (
576+ ModularMGD .thermal_expansivity (super (), np .nan , temperature , Vprime , params )
577+ * KT_Vprime_T
578+ )
579+
580+ aKT = (
581+ alphaKT_Vprime_T * dVprime_dV
582+ - (KT_Vprime_T - KT_Vprime_T0 ) * dVprime_dV * dVprime_dT / Vprime
583+ + (P_Vprime_T - P_Vprime_T0 ) * d2Vprime_dTdV
584+ )
585+ KT = self .isothermal_bulk_modulus_reuss (pressure , temperature , volume , params )
586+
587+ return aKT / KT
588+
589+ def entropy (self , pressure , temperature , volume , params ):
590+ dT = 1e-2
591+
592+ lnVoverV0 = self .lnVoverV0_approx (temperature , volume , params )
593+ dlnVoverV0_dT = (
594+ self .lnVoverV0_approx (temperature + dT , volume , params )
595+ - self .lnVoverV0_approx (temperature - dT , volume , params )
596+ ) / (2 * dT )
597+
598+ c = params ["c_anh" ]
599+ lnVprimeoverV = - c * lnVoverV0
600+ Vprime = volume * np .exp (lnVprimeoverV )
601+
602+ dlnVprimeoverV_dT = - c * dlnVoverV0_dT
603+ dVprime_dT = Vprime * dlnVprimeoverV_dT
604+
605+ Sh_Vprime_T = ModularMGD .entropy (super (), np .nan , temperature , Vprime , params )
606+ P_Vprime_T = ModularMGD .pressure (super (), temperature , Vprime , params )
607+ P_Vprime_T0 = ModularMGD .pressure (super (), params ["T_0" ], Vprime , params )
608+
609+ S = Sh_Vprime_T + (P_Vprime_T - P_Vprime_T0 ) * dVprime_dT
610+ return S
611+
612+ def _helmholtz_energy (self , pressure , temperature , volume , params ):
613+ lnVoverV0 = self .lnVoverV0_approx (temperature , volume , params )
614+ lnVprimeoverV = - params ["c_anh" ] * lnVoverV0
615+ Vprime = volume * np .exp (lnVprimeoverV )
616+ Fh_Vprime_T = ModularMGD ._helmholtz_energy (
617+ super (), np .nan , temperature , Vprime , params
618+ )
619+ F_V_T0 = ModularMGD ._helmholtz_energy (
620+ super (), np .nan , params ["T_0" ], volume , params
621+ )
622+ F_Vprime_T0 = ModularMGD ._helmholtz_energy (
623+ super (), np .nan , params ["T_0" ], Vprime , params
624+ )
625+
626+ F = Fh_Vprime_T + F_V_T0 - F_Vprime_T0
627+ return F
628+
629+ def validate_parameters (self , params ):
630+ """
631+ Check for existence and validity of the parameters
632+ """
633+
634+ ModularMGD .validate_parameters (self , params )
635+
636+ if "anharmonic_thermal_model" in params :
637+ raise ValueError (
638+ "The ModularMGDWithAnharmonicity class "
639+ "does not allow for a separate anharmonic "
640+ "contribution. Anharmonicity is incorporated "
641+ "through a single 'c_anh' parameter."
642+ )
643+
644+ if "c_anh" not in params :
645+ raise ValueError (
646+ "The ModularMGDWithAnharmonicity class "
647+ "requires a 'c_anh' parameter to be set."
648+ )
0 commit comments