-
Notifications
You must be signed in to change notification settings - Fork 874
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] magnetic spacegroup analyzer #2049
base: master
Are you sure you want to change the base?
Conversation
…zer object implemented. Ready to be pushed to master.
…up added, see mspg2mpg_map.txt. Used in pymatgen.symmetry.analyzer.MagneticSpaceGroupAnalyzer object.
…ion of mCifWriter failed, because refined_structure could not yet be made to respect magnetic space group symmetry.
…agSymmOpTransformation. Ready to be pushed to master.
…cStructure. In MagneticSpacegroupAnalyzer implemented get_paramagnetic_symmetry_operations, because get_symmetry_operations of SpacegroupAnalyzer only works for the conventional cell and not for the primitive cell. Bug?
…ion had a bug for frac_coord = 1 it did not go to 0. Fix: structure.translate_site by 1e-8. This is not a clean fix. Better change structure.translate_site() in core/structure to do fcoords = np.mod( fcoords + 10 + 1e-9, 1) - 1e-9 instead of fcoords = np.mod( fcoords, 1 ).
…able to take a conventional or primitive structure. origin_centering now returns MagSymmOp instead of xyzt_string. Create a dictionary of all magnetic domains MagneticSpacegroupAnalyzer.
…roup, which was an interface to use the webbased tool by the Bilbao Cryst. Server. Also implemented _same_MagSymm.
…y spacegroup symmetry in __init__ of MagneticSpacegroupAnalyzer. After jumping around in different versions to find a working version, suddenly all spacegroup symmetries could be identified. When returning to this version, it passed the test with Ca3MnCoO6 without making any changes. Some flag seems to go out of its scope and makes the application of the spacegroup operation unstable. Before the test could be passed (without apparent reason), I switched on and off primitive and Cartesian flags in various routines. Previous version (that was thought to be messed up) has documentation, deleted _same_struc and brows4MagneticSpacegroup, become slightly faster.
…cegroups is stored in a dictionary as a local variable. Deleted mspg_order.txt, which became obsolete. Tested entry 0.13: all good.
…ticSpacegroupAnalyzer.get_domains().
…ry check from __init__ to _check_mop(), removed print() and time() cmds.
Thank you @mt-huebsch! This looks like an incredibly useful contribution, I would be happy to see this merged. I will take some time to review the PR carefully to provide comments and a response to your question on testing.
By default, only |
Dear @mkhorton, thank you for your quick response! Regarding the .txt file, I will change the format. No problem. Thank you for showing interest in my contribution. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made a partial code review with individual comments above. Most of my comments so far are not major, they're mainly regarding code conventions, rather than correctness. Please let me know if there are any questions!
self.symmetry_ops = mspg_list | ||
self._MagneticSpacegroup = self._search4MagneticSpacegroup() | ||
|
||
def _check_mop(self, mop): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even for private methods, a quick docstring is helpful.
crystallographic spacegroup operations with time reversal symmetry even and odd. | ||
By default returns symmetry operations in fractional coordinates for a given structure, | ||
but can also return symmetry operations in Cartesian coordinates. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cartesian
option is not documented in docstring
self._magmom_tol = magmom_tolerance | ||
self._domains = None | ||
|
||
self._spacegroupAnalyzer = SpacegroupAnalyzer(self._structure, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capitalization should be avoided inside variable names, and should be spaced by underscore, e.g. this should be self._spacegroup_analyzer
. This follows the official "PEP8" recommendations.
og_number = ".".join([str(x) for x in self._MagneticSpacegroup._data["og_number"]]) | ||
return (og_label, og_number) | ||
|
||
def _same_MagneticStructure(self, struc1, struc2, pos_tolerance=0.0015, magmom_tolerance=0.3): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps worth discussing -- I think this functionality might already be partially present in StructureMatcher
or perhaps CollinearMagneticStructureAnalyzer.match()
, or maybe this method has a more narrow application than I understand? Where can struc1
and struc2
come from here?
set2 = set(tuple([tuple([species2[i],sites2_prim[i],magms2_cart[i]]) for i in range(nsite)])) | ||
res = self._difference(set1, set2, pos_tolerance=pos_tolerance, magmom_tolerance=magmom_tolerance) | ||
if res == set(): | ||
same = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An empty set evaluates as False
in Python, so this statement could be if not res
-- this is no more or less correct, but this latter notation is more commonly used. The variable res
could be called diff
to make its purpose more clear.
res.append(t1) | ||
return set(res) | ||
|
||
def _search4MagneticSpacegroup(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following PEP8, and to be more explicit, this could be called _search_for_magnetic_spacegroup
) | ||
|
||
if np.any(np.abs(self._structure._lattice.matrix - self._spacegroupAnalyzer._cell[0]) > 1e-12): | ||
exit("Error in the definitions of the unit cell.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exit()
might make some users sad since it would quit their Python session, instead an Exception should be raised. For example, raise VallueError("Error in the definitions of the unit cell.")
.
For approximation comparisons, np.all_close()
is a nice short-hand for the np.any(np.abs(...) > ...)
construction.
Returns: | ||
tuple(str, list(int)): Magnetic point group for structure. | ||
""" | ||
MAGSPACEGROUP2MAGPOINTGROUP = os.path.join(os.path.dirname(__file__), "mspg2mpg_map.txt") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can make this a module-level variable so that the file doesn't have to be re-loaded and parsed every time. I can make a further practical suggestion here for how to structure the file, so that it's not necessary to iterate over lines individually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've attached a possible alternative as a yaml file (will have to be unzipped, since GitHub doesn't allow me to upload .yaml directly).
Then, at the top of this file, we could write:
from monty.serialization import loadfn
MAGSPACEGROUP_TO_MAGPOINTGROUP = loadfn(os.path.join(os.path.dirname(__file__), "magspacegroup_to_magpointgroup.yaml"))
This will be loaded as a dictionary keyed by the OG no., so in this get_magnetic_pointgroup
function it can just return MAGSPACEGROUP_TO_MAGPOINTGROUP[glob_og_no]["pointgroup_label"], ...
for example.
(monty.serialization.loadfn and monty.serialization.dumpfn are convenience functions for loading and dumping Python objects, pymatgen objects etc. to JSON or YAML)
This is fantastic. Thank you @mt-huebsch! I have a couple of questions. The major roadblock that I encountered while trying to develop a magnetic symmetry analyzer was for cases in which the "setting" of the msg operations is non-standard. From doing a search on the MAGNDATA server, you can see that the majority of magnetic structures (822 out of the 1217 structures), the msg operations are defined in a non-standard setting (defined by a similarity transform from the standard lattice or vector space). While I get the same very nice result for Ca3Co2-xMnxO6, I get an error: I'm curious if I'm doing something wrong? Here is how I got the error:
It wasn't trivial to me how to find the similarity transform for a msg. I'm curious if that is what's happening here. I'll dive deeper into your code to see if you account for this. Did you encounter a similar issue? It's possible to check this setting transformation with the symmetry finder in the ISOTROPY suite: https://stokes.byu.edu/iso/findsym.php. I believe that there are ways of identifying if two sg representations are "similar" using matrix normal forms, which appears to be the basis for many of the symmetry finding algorithms developed by Stokes et. al. and in If you found a work-around for finding the transformation to another setting, I am very curious to learn what your approach was. I don't see where you account for this in the code but I'll look more closely. If this is an issue, maybe this feature can be implemented in the future. Thank you again! |
Dear @guymoore13, Thank you for running tests on the code!! Indeed, you have discovered a bug. I need to transform to the standardized conventional cell. As far as I understand the same ambiguity is already present in the spacegroup operations, so spglib can handle it. I am already working on the solution and can perform all necessary transformations, but at the moment I haven't figured out how "standardized" is defined exactly. Maybe I am wrong but spglib and Stokes et al seem to have a different definition of standardized. Please let me know in case you have any thoughts on that. |
Dear @mt-huebsch, thank you for your response! You are correct that spglib accounts for this, although I'm also still trying to identify how they go about addressing the setting/basis issue. Due to the ambiguity in the "standard setting" in which to compare operations, I was trying a different approach - because I encountered this question as well. For this reason, I was trying to find a way of comparing "reduced representations" of the msg operations - which should be independent from the settings of each representation (and therefore the similarity transform to the standard setting). I haven't made much progress on this front, but I will let you know if I do - it may be nice to compare notes at some point. I was having issues with the "uniqueness" of the diagonalization scheme that I was using. Your approach to find the standard setting may be more robust. I look forward to hearing how it goes! |
Great work and a very important contribution! I want to report just a couple of minor things I have noticed so far
Your rotations and translations come directly from spglib. However, it often returns small translation vectors that are duplicated i,e. [0, 0, 0], [1e-17,1e-17,1e-17] etc You can reproduce it on 1.0.1_Ag2CrO2.mcif from MAGNDATA. As a result, the number of symmetry operations is larger than it should be (40 symmetries instead of 4 in case of Ag2CrO2). A similar issue is described in _get_symmetry of SpacegroupAnalyzer. One can avoid it by using
First, in |
@maxmarkov thanks for the testing. Could you clarify how:
avoids the duplicated |
Dear @maxmarkov! Thank you for testing. Honestly, I haven't had a look at q>0 magnetic structures, but that is just a side remark. I will add it to my to do list. Regarding _spacegroupAnalyzer._get_symmetry(), it is always good to make the code more robust. But rather than hardcoding a rounding, maybe it should be implemented using pos_tolerance with a good fall back default value. What is pymatgen's policy on that, @mkhorton? Bdw, previously I used _spacegroupAnalyzer._get_symmetry_operations() at that point, but this function always returns the conventional symmetries. This is unexpected behavior to me, if the input structure is primitive. That is why I wrote _paramag_symmetry_operations() in the first place. Although it is probably not good if the SpacegroupAnalyzer and the MagneticSpacegroupAnalyzer behave differently. Harmonizing the behavior should maybe be added to the to do list, too. @guymoore13, regarding the setting issue, I have a small update: Every MSPG symbol is derived from a non-magnetic SPG symbol. That is why fortunately the same alternative settings apply. But the standard setting for MSPGs is not the one closest to the standard setting of the parent SPG [1]. [1] https://www.annualreviews.org/doi/10.1146/annurev-matsci-070214-021008 I will try to carve out more time to get this functionality ready for distribution. Thank you all for your feedback and patience. Best regards! |
Thank you for the detailed response @mt-huebsch. Yes, I think we are saying more or less the same thing. You mention the Hall symbol, and my approach has been to store the matrix normal forms required to identify the Hall symbols (on page 7 of the spglib paper that you shared). They use the Smith canonical form. I saw this a bit later - I was using a different matrix diagonalization. However, I recently discovered that the Smith canonical form has some very nice properties, especially for integer matrices. They use Take care, Guy |
3c23114
to
36e289c
Compare
d725325
to
dca98be
Compare
e3fbc67
to
41e6d99
Compare
Summary
Added MagneticSpacegroupAnalyzer that given a magnetic structure
Added ApplyMagSymmOpTransformation to standard transformations
Additional dependencies introduced
None
To Do
I was not sure how/where to add a test. This snipped shows how to run the main functionality of the code, i.e. finding the magnetic spacegroup:
path_to_s = "0.13_Ca3Co2-xMnxO6.mcif"
s = mg.Structure.from_file( path_to_s)
ma = MagneticSpacegroupAnalyzer( s )
print( "OG info: ", ma.get_og() )
0.13_Ca3Co2-xMnxO6.mcif can be downloaded from MAGNDATA
http://webbdcrista1.ehu.es/magndata/index.php?index=0.13
The OG info for this material is ('R3c', '161.1.1300')
To obtain the magnetic domains, run
domain_dict = ma.get_domains()
print( "Magnetic domains: ", len(domain_dict), [mop.as_xyzt_string() for mop in domain_dict.keys()] )
This retruns
Magnetic domains: 4 ['x, y, z, +1', 'x, y, z, -1', '-x, -y, -z, +1', '-x, -y, -z, -1']
I'd be happy to provide more extensive tests, if you point me in the right direction.
I noticed that this file does not get installed when installing pymatgen from my repository in a new enviroment. Maybe I should have chosen a different extension? Or does this need to be configured?
This is my first contribution, so I would appreciate any comment.
Comment
In the past I have submitted a contribution, where the search for the magnetic spacegroup was dependent on tools of the Bilbao Crystallographic Server. This has now been replaced by a search independent of any external sources. The code is robust enough to handle uncertainties of a typical ab initio run and can return all magnetic domains. Thus, if two ab initio results correspond to different magnetic domains, this feature will help identify the correspondence.
Looking forwards to your feedback!
Cheers,
Marie-Therese