@@ -404,8 +404,12 @@ class LipidLeafletGuest(LipidLeaflet):
404404 phi_guest_h: float or refnx.analysis.Parameter
405405 Guest lying in the head layer. This is a fractional
406406 value representing how much of the space **not** taken up by the lipid
407- is occupied by the guest molecule. The absolute volume fraction is
408- available from the `LipidLeafletGuest.volfrac_guest` property.
407+ is occupied by the guest molecule.
408+ If, however, `absolute_phi` is set to True, then this parameter
409+ represents the absolute value of the guest molecule in the head region.
410+ The absolute volume fraction is always available from the
411+ `LipidLeafletGuest.volfrac_guest_h` property.
412+ Please see the notes section for further details.
409413
410414 .. warning::
411415 This parameter may not be determinable with low uncertainty if the lipid
@@ -415,8 +419,12 @@ class LipidLeafletGuest(LipidLeaflet):
415419 phi_guest_t: float or refnx.analysis.Parameter
416420 Guest lying in the tail layer. This is a fractional
417421 value representing how much of the space **not** taken up by the lipid
418- is occupied by the guest molecule. The absolute volume fraction is
419- available from the `LipidLeafletGuest.volfrac_guest` property.
422+ is occupied by the guest molecule.
423+ If, however, `absolute_phi` is set to True, then this parameter
424+ represents the absolute value of the guest molecule in the tail region.
425+ The absolute volume fraction is always
426+ available from the `LipidLeafletGuest.volfrac_guest_t` property.
427+ Please see the notes section for further details.
420428
421429 .. warning::
422430 This parameter may not be determinable with low uncertainty if the lipid
@@ -444,13 +452,45 @@ class LipidLeafletGuest(LipidLeaflet):
444452 closer to the backing medium.
445453 name: str, optional
446454 The name for the component
455+ absolute_phi: bool, optional
456+ Specifies whether `phi_guest_h` and `phi_guest_t` represent fractional
457+ or absolute volume fractions.
458+ Please see the notes section for further details.
447459
448460 Notes
449461 -----
450462 The sum of coherent scattering lengths must be in Angstroms, the volume
451463 must be in cubic Angstroms. This is because the SLD of a tail group is
452- calculated as `b_tails / vm_tails * 1e6` to achieve the units
464+ calculated as `` b_tails / vm_tails * 1e6` ` to achieve the units
453465 10**6 Angstrom**-2.
466+
467+ `phi_guest_t` and `phi_guest_h` represent either a fractional (what
468+ fraction not occupied by lipid is occupied by guest) or absolute
469+ volume fraction of guest in a layer. If ``absolute_phi is False`` then
470+ the absolute volume fraction of guest in a layer is calculated as::
471+
472+ volfrac_guest_x = (1 - volfrac_x) * phi_guest_x
473+
474+ where ``volfrac_x`` is the absolute volume fraction of lipid in the head
475+ group region (x representing either the head (h) or tail region (t)).
476+ The amount of solvent in that layer is then calculated as::
477+
478+ vfsolv = 1 - volfrac_guest_x - volfrac_x
479+
480+ If ``absolute_phi is `True`` then `phi_guest_x` is regarded as an
481+ absolute volume fraction (not relative) and
482+ ``volfrac_guest_x == phi_guest_x``. ``absolute_phi`` is useful when
483+ one wants to constrain the volume fraction of guest in the head and
484+ tail layers to be the same. Otherwise, setting `absolute_phi` to be
485+ False is preferred, as each layer is less likely to be overfilled.
486+ Appropriate constraints and bounds must be used during the modelling
487+ process to ensure ``volfrac_guest_x + volfrac_x <=1``.
488+ For optimisation using `differential_evolution` this entails
489+ using the :meth:`LipidLeafletGuest.make_constraints` method to create
490+ those constraints. Please see `using constraints`_ for an example.
491+ With MCMC the log-prior term becomes `-np.inf` (i.e. impossible).
492+
493+ .. _using constraints: https://refnx.readthedocs.io/en/latest/inequality_constraints.html#inequality-constraints-with-differential-evolution
454494 """
455495
456496 def __init__ (
@@ -471,6 +511,7 @@ def __init__(
471511 tail_solvent = None ,
472512 reverse_monolayer = False ,
473513 name = "" ,
514+ absolute_phi = False ,
474515 ):
475516 super ().__init__ (
476517 apm ,
@@ -501,6 +542,7 @@ def __init__(
501542 self .phi_guest_t .bounds .lb = 0
502543 self .phi_guest_t .bounds .ub = 1
503544 self .sld_guest = possibly_create_scatterer (sld_guest )
545+ self .absolute_phi = absolute_phi
504546
505547 def __repr__ (self ):
506548 sld_bh = SLD ([self .b_heads_real , self .b_heads_imag ])
@@ -522,7 +564,8 @@ def __repr__(self):
522564 f"head_solvent={ self .head_solvent !r} , "
523565 f"tail_solvent={ self .tail_solvent !r} , "
524566 f"reverse_monolayer={ self .reverse_monolayer } , "
525- f"name={ self .name !r} )"
567+ f"name={ self .name !r} , "
568+ f"absolute_phi={ self .absolute_phi !r} )"
526569 )
527570 return s
528571
@@ -628,23 +671,73 @@ def parameters(self):
628671 def logp (self ):
629672 # penalise unphysical volume fractions.
630673 if (
631- self .volfrac_h > 1
632- or self .volfrac_t > 1
633- or self .phi_guest_h .value > 1
674+ self .phi_guest_h .value > 1
634675 or self .phi_guest_t .value > 1
676+ or self .volfrac_t + self .volfrac_guest_t > 1
677+ or self .volfrac_h + self .volfrac_guest_h > 1
635678 ):
636679 return - np .inf
637680
638681 return 0
639682
683+ def make_constraint (self , objective ):
684+ """
685+ Creates a NonlinearConstraint for a LipidLeafletGuest, ensuring that
686+ volume fraction of material in the head+tail regions lies in [0, 1].
687+ Suitable for use by differential_evolution.
688+
689+ Parameters
690+ ----------
691+ objective: refnx.analysis.Objective
692+ Objective containing the LipidLeafletGuest. Must be the Objective
693+ that is being minimised by differential_evolution.
694+
695+ Returns
696+ -------
697+ nlc: NonlinearConstraint
698+
699+ Notes
700+ -----
701+ You must create separate constraints for each LipidLeafletGuest object
702+ in your system.
703+ The Objective you supply must be for the overall curve fitting system.
704+ i.e. possibly a GlobalObjective.
705+
706+ Examples
707+ --------
708+ >>> # leaflet is a LipidLeafletGuest, used in an Objective, obj
709+ >>> con = leaflet.make_constraint(obj)
710+ >>> fitter = CurveFitter(obj)
711+ >>> fitter.fit("differential_evolution", constraints=(con,))
712+ """
713+
714+ def con (x ):
715+ objective .setp (x )
716+ return (
717+ self .volfrac_h + self .volfrac_guest_h ,
718+ self .volfrac_t + self .volfrac_guest_t ,
719+ )
720+
721+ return NonlinearConstraint (con , 0 , 1 )
722+
640723 @property
641724 def volfrac_guest_h (self ):
642- # Absolute volume fraction of guest in the head group region.
643- vfh = self .volfrac_h
644- return (1.0 - vfh ) * self .phi_guest_h .value
725+ """
726+ Absolute volume fraction of guest in the head group region.
727+ """
728+ if self .absolute_phi :
729+ return self .phi_guest_h .value
730+ else :
731+ vfh = self .volfrac_h
732+ return (1.0 - vfh ) * self .phi_guest_h .value
645733
646734 @property
647735 def volfrac_guest_t (self ):
648- # Absolute volume fraction of guest in the tail group region.
649- vft = self .volfrac_t
650- return (1.0 - vft ) * self .phi_guest_t .value
736+ """
737+ Absolute volume fraction of guest in the tail group region.
738+ """
739+ if self .absolute_phi :
740+ return self .phi_guest_t .value
741+ else :
742+ vft = self .volfrac_t
743+ return (1.0 - vft ) * self .phi_guest_t .value
0 commit comments