@@ -670,8 +670,9 @@ def fmtCellConstraints(cellConstr):
670670# cellXformRelations = fmtCellConstraints(GenerateCellConstraints())
671671
672672def GenAtomConstraints (oldPhase ,newPhase ,atCodes ,Trans ):
673- '''Generate the constraints on generated atoms between oldPhase and newPhase
674- where atCodes links new atoms to the original atoms
673+ '''Generate the constraints on generated atoms between oldPhase
674+ and newPhase where atCodes links new atoms to the original atoms.
675+ uUsed for a magnetic phase transformed generated from oldPhase.
675676
676677 :param oldPhase: the phase object for the original phase
677678 :param newPhase: the phase object for the newly created phase
@@ -731,9 +732,104 @@ def GenAtomConstraints(oldPhase,newPhase,atCodes,Trans):
731732 constraints .append ([DepCons ,IndpCon ,None ,None ,'e' ])
732733 return constraints ,message
733734
735+ def MatchGenAtomConstraints (oldPhase ,newPhase ,Trans ,U ,V ):
736+ '''Generate the constraints on generated atoms in newPhase similar to
737+ :func:`GenAtomConstraints` but without pointers to relate the atoms.
738+ The atom positions are regenerated from the original phase and then
739+ are matched to the magnetic positions in newPhase. The positions
740+ generated here are what are then used for the new atom positions.
741+
742+ :param oldPhase: the phase object for the original phase
743+ :param newPhase: the phase object for the newly created phase
744+ :param np.array Trans: the transformation matrix
745+ :param np.array U: the offset vector, subtracted before transformation matrix
746+ :param np.array V: the offset vector, added after transformation matrix
747+ :returns: (constraints, message) where
748+ * constraints is a list of constraints that should be added to the
749+ Phase section of the constraints dict.
750+ * message is a message to display or None
751+ '''
752+ from . import GSASIIobj as G2obj
753+ constraints = []
754+ message = ''
755+ opRanId = oldPhase ['ranId' ]
756+ oAtoms = oldPhase ['Atoms' ]
757+ npRanId = newPhase ['ranId' ]
758+ cx ,ct ,cs ,cia = newPhase ['General' ]['AtomPtrs' ]
759+ nAtoms = newPhase ['Atoms' ]
760+ nSGData = newPhase ['General' ]['SGData' ]
761+ xnames = ['dAx' ,'dAy' ,'dAz' ]
762+ atCodes = None
763+ # constraints on matching atom params between phases
764+ fmt4 = lambda x : ', ' .join ([f'{ i :.6f} ' for i in x ])
765+ # regenerate and potentially relocate transformed atoms
766+ tmpPhase = copy .deepcopy (newPhase )
767+ tmpPhase ,tmpCodes = TransformPhase (oldPhase ,tmpPhase ,Trans ,
768+ np .array (U ),np .array (V ),True )
769+ Amat = cell2AB (newPhase ['General' ]['Cell' ][1 :7 ])[0 ]
770+ atCodes = len (nAtoms )* [None ]
771+ for iN ,natom in enumerate (nAtoms ):
772+ nxyz = np .array (natom [cx :cx + 3 ])
773+ #print('searching to match new atom',fmt4(nxyz))
774+ for iT ,tatom in enumerate (tmpPhase ['Atoms' ]):
775+ if natom [ct ] != tatom [ct ]: continue
776+ txyz = np .array (tatom [cx :cx + 3 ])
777+ #print('generated atom pos',fmt4(txyz))
778+ for oXYZ ,osym ,cell ,spn in G2spc .GenAtom (txyz ,
779+ tmpPhase ['General' ]['SGData' ],False ,[],True ):
780+ dist = np .sqrt (np .sum (np .inner (Amat ,(oXYZ - nxyz ))** 2 ))
781+ #print(oXYZ, oXYZ-nxyz, np.allclose(oXYZ,nxyz),dist)
782+ if dist < 0.1 : # allow a bit of distance (0.1A) between generated and CIF positions
783+ # found a matching atom, replace coordinates & save code
784+ atCodes [iN ] = tmpCodes [iT ]
785+ natom [cx :cx + 3 ] = tatom [cx :cx + 3 ]
786+ #print(' matched at',fmt4(tatom[cx:cx+3]))
787+ break
788+ if atCodes [iN ]: break
789+ else :
790+ message += f'No matching atom found for { natom [ct ]} at { fmt4 (natom [cx :cx + 3 ])} \n '
791+
792+ for ia ,code in enumerate (atCodes ): # iterate of matching pairs of atoms
793+ if not ia and nAtoms [ia ][cia ] == 'A' and 'ADP' not in message :
794+ message += 'Anisotropic ADP constraints not yet developed\n '
795+ if code is None : continue
796+ siteSym = G2spc .SytSym (nAtoms [ia ][cx :cx + 3 ],nSGData )[0 ]
797+ CSX = G2spc .GetCSxinel (siteSym )
798+ item = code .split ('+' )[0 ]
799+ iAtOrg ,opr = item .split (':' )
800+ iAtOrg = int (iAtOrg )
801+ Nop = abs (int (opr ))% 100 - 1
802+ if '-' in opr :
803+ Nop *= - 1
804+ Opr = oldPhase ['General' ]['SGData' ]['SGOps' ][abs (Nop )][0 ]
805+ if Nop < 0 : #inversion
806+ Opr *= - 1
807+ XOpr = np .inner (Opr ,Trans )
808+ invOpr = nl .inv (XOpr )
809+ for i ,ix in enumerate (list (CSX [0 ])):
810+ if not ix :
811+ continue
812+ name = xnames [i ]
813+ IndpCon = [1.0 ,G2obj .G2VarObj ([npRanId ,None ,name ,nAtoms [ia ][- 1 ]])]
814+ DepCons = []
815+ for iop ,opval in enumerate (invOpr [i ]):
816+ oldVar = G2obj .G2VarObj ([opRanId ,None ,xnames [iop ],oAtoms [iAtOrg ][- 1 ]])
817+ if abs (opval ) > 1e-6 :
818+ DepCons .append ([float (opval ),oldVar ])
819+ if len (DepCons ) == 1 : # use equivalence if possible
820+ constraints .append ([DepCons [0 ],IndpCon ,None ,None ,'e' ])
821+ elif len (DepCons ) > 1 :
822+ IndpCon [0 ] = - 1.
823+ constraints .append ([IndpCon ]+ DepCons + [0.0 ,None ,'c' ])
824+ for name in ['Afrac' ,'AUiso' ]:
825+ IndpCon = [1.0 ,G2obj .G2VarObj ([npRanId ,None ,name ,nAtoms [ia ][- 1 ]])]
826+ DepCons = [1.0 ,G2obj .G2VarObj ([opRanId ,None ,xnames [iop ],oAtoms [iAtOrg ][- 1 ]])]
827+ constraints .append ([DepCons ,IndpCon ,None ,None ,'e' ])
828+ return constraints ,message
829+
734830def GenCellConstraints (Trans ,oRanId ,nRanId ,origA ,oSGLaue ,nSGLaue ,debug = False ):
735- '''Generate the constraints between two unit cells constants for a phase transformed
736- by matrix Trans.
831+ '''Generate the constraints between two unit cells constants for
832+ a magnetic phase transformed by matrix Trans.
737833
738834 :param np.array Trans: a 3x3 direct cell transformation matrix where,
739835 Trans = np.array([ [2/3, 4/3, 1/3], [-1, 0, 0], [-1/3, -2/3, 1/3] ])
@@ -746,6 +842,7 @@ def GenCellConstraints(Trans,oRanId,nRanId,origA,oSGLaue,nSGLaue,debug=False):
746842 :param dict nSGLaue: space group info for transformed phase
747843 :param bool debug: If true, the constraint input is used to compute and print A*
748844 and from that the direct cell for the transformed phase.
845+ :returns: a list of generated constraints
749846 '''
750847 from . import GSASIIobj as G2obj
751848 Anew = []
@@ -781,6 +878,33 @@ def GenCellConstraints(Trans,oRanId,nRanId,origA,oSGLaue,nSGLaue,debug=False):
781878 print ('xfm cell' ,fmt4 (A2cell (Anew )))
782879 return constrList
783880
881+ def GenHAPConstraints (Trans ,oRanId ,nRanId ,hRanId ):
882+ '''Generate the HAP constraints between two phase constants
883+ for a phase transformed by matrix Trans.
884+
885+ :param np.array Trans: a 3x3 direct cell transformation matrix where,
886+ Trans = np.array([ [2/3, 4/3, 1/3], [-1, 0, 0], [-1/3, -2/3, 1/3] ])
887+ (for a' = 2/3a + 4/3b + 1/3c; b' = -a; c' = -1/3, -2/3, 1/3)
888+ :param int oRanId: Random id for the original phase
889+ :param int nRanId: Random id for the transformed phase to be constrained from
890+ original phase
891+ :param str hRanId: histogram Random Id
892+ :returns: a list of generated constraints
893+ '''
894+ from . import GSASIIobj as G2obj
895+ constrList = []
896+ detTrans = np .abs (nl .det (Trans )) # volume ratio
897+ # Phase fractions (note volume term)
898+ IndpCon = [1.0 ,G2obj .G2VarObj ([oRanId ,hRanId ,'Scale' ,None ])]
899+ DepCons = [detTrans ,G2obj .G2VarObj ([nRanId ,hRanId ,'Scale' ,None ])]
900+ constrList .append ([IndpCon ,DepCons ,None ,None ,'e' ])
901+ # Isotropic size and mustrain (anisotropic constraints are messy)
902+ for name in ['Size;i' ,'Mustrain;i' ]:
903+ IndpCon = [1.0 ,G2obj .G2VarObj ([oRanId ,hRanId ,name ,None ])]
904+ DepCons = [1.0 ,G2obj .G2VarObj ([nRanId ,hRanId ,name ,None ])]
905+ constrList .append ([IndpCon ,DepCons ,None ,None ,'e' ])
906+ return constrList
907+
784908def cellUnique (SGData ):
785909 '''Returns the indices for the unique A tensor terms
786910 based on the Laue class.
@@ -844,7 +968,17 @@ def TransformU6(U6,Trans):
844968 return UijtoU6 (Uij )
845969
846970def ExpandCell (Atoms ,atCodes ,cx ,Trans ):
847- Unit = [int (max (abs (np .array (unit )))- 1 ) for unit in Trans .T ]
971+ '''Expand a list of atoms to an new unit cell.
972+
973+ :param list Atoms: atoms array as found in a Phase object
974+ :param list atCodes:list of codes that describe symmetry applied to
975+ generate atom from original position
976+ :param int cx: pointer to location of x coord in Atoms
977+ :param np.array Trans: 3x3 transformation matrix
978+ :returns: newAtoms,Codes where newAtoms is the expanded list of
979+ atoms and Codes is an expanded and updated version of atCodes
980+ '''
981+ Unit = [int (max (abs (np .array (unit )))- 1 ) for unit in np .array (Trans ).T ]
848982 nUnit = (Unit [0 ]+ 1 )* (Unit [1 ]+ 1 )* (Unit [2 ]+ 1 )
849983 Ugrid = np .mgrid [0 :Unit [0 ]+ 1 ,0 :Unit [1 ]+ 1 ,0 :Unit [2 ]+ 1 ]
850984 Ugrid = np .reshape (Ugrid ,(3 ,nUnit )).T
@@ -869,7 +1003,7 @@ def TransformPhase(oldPhase,newPhase,Trans,Uvec,Vvec,ifMag,Force=True):
8691003 :param oldPhase: dict G2 phase info for old phase
8701004 :param newPhase: dict G2 phase info for new phase; with new cell & space group
8711005 atoms are from oldPhase & will be transformed
872- :param Trans: lattice transformation matrix M
1006+ :param np.array Trans: lattice transformation matrix M
8731007 :param Uvec: array parent coordinates transformation vector U
8741008 :param Vvec: array child coordinate transformation vector V
8751009 :param ifMag: bool True if convert to magnetic phase;
0 commit comments