# -*- coding: utf-8 -*-
########### SVN repository information ###################
# $Date: 2024-02-05 15:49:09 -0600 (Mon, 05 Feb 2024) $
# $Author: toby $
# $Revision: 5722 $
# $URL: https://subversion.xray.aps.anl.gov/pyGSAS/trunk/SUBGROUPS.py $
# $Id: SUBGROUPS.py 5722 2024-02-05 21:49:09Z toby $
########### SVN repository information ###################
from __future__ import division, print_function
import re
import copy
import random as ran
import sys
import os
import numpy as np
import numpy.linalg as nl
import GSASIIspc as G2spc
import GSASIIIO as G2IO
import GSASIIlattice as G2lat
import GSASIIElem as G2elem
import GSASIImath as G2mth
import GSASIIpath
GSASIIpath.SetBinaryPath()
bilbaoSite = 'https://www.cryst.ehu.es/cgi-bin/cryst/programs/'
submagSite = bilbaoSite + 'subgrmag1_general_GSAS.pl?'
pseudosym = 'pseudosym/nph-pseudosym'
timeout=150 # time to wait for www.cryst.ehu.es to respond; 2.5 minutes
[docs]
def GetNonStdSubgroups(SGData, kvec,star=False,landau=False,maximal=False):
'''Run Bilboa's SUBGROUPS for a non-standard space group.
This requires doing a post to the Bilboa site, which returns all
subgroups of the entered space group as the text of a web page
with a table containing the space group symbol, the
transformation matrix and index for each subgroup.
:params list kvec: propogation vector as a list of nine string fractions or blank
:params SGData: space group object (see :ref:`Space Group object<SGData_table>`)
:returns: (error,text) error: if True no error or False; where
text containts a possible web page text
'''
print('''
For use of SUBGROUPS, please cite:
Symmetry-Based Computational Tools for Magnetic Crystallography,
J.M. Perez-Mato, S.V. Gallego, E.S. Tasci, L. Elcoro, G. de la Flor, and M.I. Aroyo
Annu. Rev. Mater. Res. 2015. 45,217-48.
doi: 10.1146/annurev-matsci-070214-021008
''')
def getSpGrp(item):
return item.replace('<i>','').replace('</i>','').replace('<sub>','').replace('</sub>','')
def getMatVec(item):
return item.replace('{','[').replace('}',']')
starmag = 'no'
if star:
starmag = 'yes'
land = 'no'
if landau:
land = 'yes'
celtodas = 'no'
limite = 'spgroup'
if maximal:
limite = 'maximal'
postdict = {'centrosymmetry':'0','crystalsystem':'0','landau':land,
'eleccion':'subgrmag1_k','inicio':'nostandard','celtodas':celtodas,
'limite':limite,'list':'Submit','listado':'lista','starmagnetica':starmag,
'pointgroup':'0','polarity':'0','sub':'1',
'super':'','tipog':'gesp','wyckoffstrain':''}
text,table = G2spc.SGPrint(SGData)
OpList = G2spc.TextOps(text,table,reverse=True)
# GenList = G2spc.TextGen(SGData,reverse=True)
for item in OpList:
item += '\n'
sym = ""
for i in OpList:
if sym: sym += '\n'
#if sym: sym += ' ' # use this for testing to generate an error in place of previous
sym += i.lower()
postdict['generators'] = sym
for j in [1,2,3]:
if kvec[3*j-3] == ' ':
break
for i,k in zip(('x','y','z'),kvec[3*j-3:3*j]):
postdict['knm%d%s'%(j,i)] = k
page = G2IO.postURL(submagSite,postdict)
if not page:
print('connection error - not on internet?')
return None,None
page = page.replace('<font style= "text-decoration: overline;">','<font>-')
result = page.replace('&','\n')
result = result.split('\n')
SPGPs = []
MVs = []
baseList = []
itemList = []
superList = []
altList = []
start = 0
for line in result: #work around bug report from Bilbao
start += 1
if 'yesz' in line:
break
for line in result[start:]:
if 'GGG' in line:
lines = line.split('GGG')
line = lines[0]
alts = []
beg = True
for sline in lines:
items = sline.split('z')
gid = int(items[0])
if beg:
baseList.append(gid)
beg = False
alts.append(gid)
itemList.append(gid)
superList.append(getMatVec(items[7]))
SPGPs.append(getSpGrp(items[4]))
MVs.append([getMatVec(items[5]),getMatVec(items[6])])
altList.append(alts)
for sline in lines[1:]:
altList.append([])
else:
items = line.split('z')
gid = int(items[0])
altList.append([gid,])
baseList.append(gid)
itemList.append(gid)
superList.append(getMatVec(items[7]))
SPGPs.append(getSpGrp(items[4]))
MVs.append([getMatVec(items[5]),getMatVec(items[6])])
result = list(zip(SPGPs,MVs,itemList,altList,superList))
return result,baseList
[docs]
def GetNonStdSubgroupsmag(SGData, kvec,star=False,landau=False,maximal=False):
'''Run Bilboa's k-Subgroupsmag for a non-standard space group.
This requires doing a post to the Bilboa site, which returns all
magnetic subgroups of the entered subgroup as the text of a web page
with a table containing the BNS magnetic space group symbol, the
transformation matrix and index for each subgroup.
:params list kvec: propogation vector as a list of three numbers
:params SGData: space group object (see :ref:`Space Group object<SGData_table>`)
:returns: (error,text) error: if True no error or False; where
text containts a possible web page text
'''
print('''
For use of k-SUBGROUPSMAG, please cite:
Symmetry-Based Computational Tools for Magnetic Crystallography,
J.M. Perez-Mato, S.V. Gallego, E.S. Tasci, L. Elcoro, G. de la Flor, and M.I. Aroyo
Annu. Rev. Mater. Res. 2015. 45,217-48.
doi: 10.1146/annurev-matsci-070214-021008
''')
def getSpGrp(item):
return item.replace('<i>','').replace('</i>','').replace('<sub>','').replace('</sub>','')
def getBNS(item):
spgrp = getSpGrp(item)
bns = ''
sid = item.find('<sub>')
if sid == 8:
bns = spgrp[1]
spgrp = '%s_%s %s'%(spgrp[0],bns,spgrp[2:])
return spgrp,bns
def getMatVec(item):
return item.replace('{','[').replace('}',']')
starmag = 'no'
if star:
starmag = 'yes'
land = 'no'
if landau:
land = 'yes'
celtodas = 'no'
limite = 'spgroup'
if maximal:
limite = 'maximal'
postdict = {'centrosymmetry':'0','crystalsystem':'0','landau':land,
'eleccion':'subgrmag1_k','inicio':'nostandard','celtodas':celtodas,
'limite':limite,'list':'Submit','listado':'lista','starmagnetica':starmag,
'pointgroup':'0','polarity':'0','sub':'1.1',
'super':'','tipog':'gmag','wyckoffstrain':''}
text,table = G2spc.SGPrint(SGData)
OpList = G2spc.TextOps(text,table,reverse=True)
# OpList = G2spc.TextGen(SGData,reverse=True)
for item in OpList:
item += '\n'
sym = ""
for i in OpList:
if sym: sym += '\n'
#if sym: sym += ' ' # use this for testing to generate an error in place of previous
sym += i.lower()
postdict['generators'] = sym
for j in [1,2,3]:
if kvec[3*j-3] == ' ':
break
for i,k in zip(('x','y','z'),kvec[3*j-3:3*j]):
postdict['km%d%s'%(j,i)] = k
page = G2IO.postURL(submagSite,postdict)
if not page:
print('connection error - not on internet?')
return None,None
page = page.replace('<font style= "text-decoration: overline;">','<font>-')
result = page.replace('&','\n')
result = result.split('\n')
start = 0
for line in result: #work around bug report from Bilbao
start += 1
if 'yesz' in line:
break
SPGPs = []
BNSs = []
MVs = []
baseList = []
itemList = []
superList = []
altList = []
for line in result[start:]:
if 'GGG' in line:
lines = line.split('GGG')
alts = []
beg = True
for sline in lines:
items = sline.split('z')
gid = int(items[0])
if beg:
baseList.append(gid)
beg = False
alts.append(gid)
itemList.append(gid)
superList.append(getMatVec(items[7]))
spgrp,bns = getBNS(items[4])
SPGPs.append(spgrp)
BNSs.append(bns)
MVs.append([getMatVec(items[5]),getMatVec(items[6])])
altList.append(alts)
for sline in lines[1:]:
altList.append([])
else:
items = line.split('z')
gid = int(items[0])
altList.append([gid,])
baseList.append(gid)
itemList.append(gid)
superList.append(getMatVec(items[7]))
spgrp,bns = getBNS(items[4])
SPGPs.append(spgrp)
BNSs.append(bns)
MVs.append([getMatVec(items[5]),getMatVec(items[6])])
result = list(zip(SPGPs,BNSs,MVs,itemList,altList,superList))
return result,baseList
[docs]
def subBilbaoCheckLattice(spgNum,cell,tol=5):
'''submit a unit cell to Bilbao PseudoLattice
'''
psSite = bilbaoSite + "pseudosym/nph-pseudolattice"
cellstr = '+'.join(['{:.5f}'.format(i) for i in cell])
datastr = "sgr={:}&cell={:}&tol={:}&submit=Show".format(
str(int(spgNum)),cellstr,str(int(tol)))
page = G2IO.postURL(psSite,datastr,timeout=timeout)
if not page:
print('connection error - not on internet?')
return None
page = page.replace('<font style= "text-decoration: overline;">','<font>-')
return page
[docs]
def parseBilbaoCheckLattice(page):
'''find the cell options from the web page returned by Bilbao PseudoLattice
'''
cellopts = [i for i in page.split('<tr>') if '<td><pre>' in i]
found = []
for c in cellopts:
cells = c.split("pre")[1].split('<')[0].replace('>','').split('\n') # list of cells, 1st is approx
try:
acell = [float(i) for i in cells[0].split()]
xmatA = [c.split('[')[i].split(']')[0].split() for i in (1,2,3)]
xmat = np.array([[eval(i) for i in j] for j in xmatA])
cellmat = nl.inv(xmat).T
except:
print('Error processing cell in',c)
continue
found.append((acell,cellmat))
return found
GetStdSGsetCite = '''Using Bilbao Crystallographic Server utility IDENTIFY GROUP. Please cite:
Symmetry-Based Computational Tools for Magnetic Crystallography,
J.M. Perez-Mato, S.V. Gallego, E.S. Tasci, L. Elcoro, G. de la Flor, and
M.I. Aroyo, Annu. Rev. Mater. Res. 2015. 45,217-48.
doi: 10.1146/annurev-matsci-070214-021008'''
[docs]
def GetStdSGset(SGData=None, oprList=[]):
'''Determine the standard setting for a space group from either
a list of symmetry operators or a space group object using the
Bilbao Crystallographic Server utility IDENTIFY GROUP
:param list oprList: a list of symmetry operations (example:
['x,y,z', '-x,-y,-z']). Supply either this or SGData, not both.
:param SGData: from :func:`GSASIIspc.SpcGroup`
Supply either this or oprList, not both.
:returns: (sgnum, sgnam, xformM, offsetV) where:
* sgnum is the space group number,
* sgnam is the space group short H-M name (as used in GSAS-II)
* xformM is 3x3 numpy matrix relating the old cell & coordinates to the new
* offsetV is the vector of offset to be applied to the coordinates
Note that the new cell is given by G2lat.TransformCell([a,b,...],xformM)
'''
import re
Site = bilbaoSite + 'checkgr.pl'
if not bool(oprList) ^ bool(SGData):
raise ValueError('GetStdSGset: Muat specify oprList or SGData and not both')
elif SGData:
SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(SGData)
oprList = [x.lower() for x in SymOpList]
print('\n'+GetStdSGsetCite+'\n')
postdict = {'tipog':'gesp','generators':'\n'.join(oprList)}
page = G2IO.postURL(Site,postdict,timeout=timeout)
if not page:
print('error:','No response')
return [None,None,None,None]
# scrape the HTML output for the new space group # and the xform info
try:
sgnum = int(re.search('\(No. (\d+)\)',page).group(1))
except Exception as msg:
print('error:',msg)
return [None,None,None,None]
sgnam = G2spc.spgbyNum[sgnum]
xform = re.split(r'parclose\.png',re.split(r'paropen\.png',page)[1])[0] # pull out section w/Xform matrix
mat = re.split(r'</pre>',re.split('<pre>',xform)[1])[0].split('\n')
offsetV = [eval(m.split()[3]) for m in mat]
xformM = np.array([[float(i) for i in m.split()[:3]] for m in mat])
return sgnum, sgnam, xformM.T, offsetV
[docs]
def GetSupergroup(SGnum,dlg=None):
'''Get supergroups for a space group in a standard setting
using the Bilbao Crystallographic Server utility "Minimal
Supergroups of Space Groups" (minsup)
This routine is not fully tested and is not currently implemented.
:param int SGnum: a space group number (1-230)
:returns: a list of supergroups where each entry in the list contains
[sgnum, sgnam, index, xtype, url, xformlist) where:
* sgnum is the space group number,
* sgnam is the space group short H-M name (no spacing, not GSAS-II usage)
* index (int) is the change in asymmetric unit size
* xtype (char) is the transformation type (t,k)
* url is a link to compute the subgroup transformations
* xformlist is a list containing all the subgroup transformations.
xformlist contains [(M,V), (M,V),... ] where:
* M is 3x3 numpy matrix relating the old cell & coordinates to the new
* V is the vector of offset to be applied to the coordinates
Note that the new cell is given by G2lat.TransformCell([a,b,...],M)
'''
import re
Site = bilbaoSite + 'nph-minsup'
if dlg: dlg.Update(0,newmsg='Waiting for initial web response')
out = G2IO.postURL(Site,{'gnum':f'{SGnum:}'},timeout=timeout)
if not out: return None
if dlg: dlg.Update(1,newmsg='Initial table of supergroups returned')
lines = out.split('HM symbol')[1].split('/table')[0].split('<tr')
xforms = []
for l in lines[1:]:
ls = l.split('<td>')
if len(ls) < 8: continue
spg = re.sub(r'</?[a-zA-Z]+>','',ls[3])
try:
spgnum = int(ls[4].split('>')[1].split('<')[0])
except:
spgnum = 0
try:
index = int(re.sub(r'</?[a-zA-Z]+>','',ls[5]))
except:
index = 0
xformtype = re.sub(r'</?[a-zA-Z]+>','',ls[6])
click = l.split('click')[1].split('value="')[1].split('"')[0]
xforms.append([spgnum,spg,index,xformtype,click])
if dlg: dlg.SetRange(2+len(xforms))
for i,line in enumerate(xforms):
click = line[-1]
print(SGnum,click)
out1 = G2IO.postURL(Site,{'gnum':SGnum,'show':'show','click':click}
,timeout=timeout)
if not out1: return None
#open(f'/tmp/{click}.html','w').write(out1)
#open(f'/tmp/last.html','w').write(out1)
if dlg: dlg.Update(2+i,newmsg=f'processing {line[1]}, #{i+1} of {len(xforms)}')
mvlist = []
for s in [i.split('</pre>')[0] for i in out1.split('<pre>')[1::2]]:
mvc = np.array(s.split()).reshape(3,4)
mc,vc = mvc[:,:-1],mvc[:,-1] # separate matrix & vector
# cast as float if possible
try:
m = np.array([float(eval(i)) for i in mc.flatten()]).reshape(3,3)
except:
m = mc
try:
v = np.array([float(eval(i)) for i in vc.flatten()])
except:
v = vc
mvlist.append((m,v))
line.append(mvlist)
return xforms
[docs]
def applySym(xform,cell):
'''Determine a unit cell according to a supergroup transformation
computed with the Bilbao Crystallographic Server utility "Minimal
Supergroups of Space Groups" (minsup) utility.
The required symmetry is applied to the cell and the cell is
scaled so that the unit cell volume is unchanged beyond the
scaling in the transformation matrix.
This is not used in GSAS-II at present. Some additional
thought is likely needed to to drop unit cells that are too
far from the required lattice symmetry.
:param list xform: a list of transformations from :func:`GetSupergroup`
:param list cell: the unit cell to be transformmed.
:returns: a list of two cells for each transformation matrix in xform.
The first cell in each pair is the scaled cell where lattice
symmetry has been applied, while the second cell is the direct
transform of the input cell.
'''
SGData = G2spc.SpcGroup(G2spc.spgbyNum[xform[0]])[1]
v = G2lat.calc_V(G2lat.cell2A(cell[:6])) # current cell volume
gmat = G2lat.cell2Gmat(cell[:6])[0] # reciprocal cell tensor
cellsList = []
for x in reversed(xform[-1]):
m = x[0]
vrat = abs(np.linalg.det(m)) # size ratio for new cell
newg = G2lat.prodMGMT(gmat,m) # xform the inverse cell & get the A vector
origA = G2lat.Gmat2A(newg)
A = copy.copy(origA)
print('raw',[f"{i:.4f}" for i in G2lat.A2cell(A)])
# apply lattice symmetry to A
if SGData['SGLaue'] in ['2/m',]:
if SGData['SGUniq'] == 'a':
newA = [A[0],A[1],A[2],0,0,A[5]]
elif SGData['SGUniq'] == 'b':
newA = [A[0],A[1],A[2],0,A[4],0]
else:
newA = [A[0],A[1],A[2],A[3],0,0]
elif SGData['SGLaue'] in ['mmm',]:
newA = [A[0],A[1],A[2],0,0,0]
elif SGData['SGLaue'] in ['4/m','4/mmm']:
A[0] = (A[0]+A[1])/2.
newA = [A[0],A[0],A[2],0,0,0]
elif SGData['SGLaue'] in ['6/m','6/mmm','3m1', '31m', '3']:
A[0] = (A[0]+A[1]+A[3])/3.
newA = [A[0],A[0],A[2],A[0],0,0]
elif SGData['SGLaue'] in ['3R', '3mR']:
A[0] = (A[0]+A[1]+A[2])/3.
A[3] = (A[3]+A[4]+A[5])/3.
newA = [A[0],A[0],A[0],A[3],A[3],A[3]]
elif SGData['SGLaue'] in ['m3m','m3']:
newA = [A[0],A[0],A[0],0,0,0]
else:
newA = copy.copy(A)
# scale the symmetry-enforced A unit cell to match the expected size
vadj = (G2lat.calc_V(newA)*vrat/v)**(2/3)
scalA = [i*vadj for i in newA]
cellsList.append((
list(G2lat.A2cell(scalA)) + [G2lat.calc_V(scalA)],
list(G2lat.A2cell(origA)) + [G2lat.calc_V(origA)],
))
return cellsList
BilbaoSymSearchCite = '''Using the Bilbao Crystallographic Server Pseudosymmetry search (PSEUDO)
program; Please cite:
C. Capillas, E.S. Tasci, G. de la Flor, D. Orobengoa, J.M. Perez-Mato
and M.I. Aroyo. "A new computer tool at the Bilbao Crystallographic
Server to detect and characterize pseudosymmetry". Z. Krist. (2011),
226(2), 186-196 DOI:10.1524/zkri.2011.1321.'''
[docs]
def BilbaoSymSearch1(sgnum, phase, maxdelta=2, angtol=None,
pagelist=None, keepCell=False):
'''Perform a search for a supergroup consistent with a phase
using the Bilbao Pseudosymmetry search (PSEUDO) program, see
C. Capillas, E.S. Tasci, G. de la Flor, D. Orobengoa, J.M. Perez-Mato
and M.I. Aroyo. "A new computer tool at the Bilbao Crystallographic
Server to detect and characterize pseudosymmetry". Z. Krist. (2011),
226(2), 186-196 DOI:10.1524/zkri.2011.1321.
The phase must be in a standard setting.
:param int sgnum: A space group number (1-230)
:param dict phase: a GSAS-II phase object (see
:ref:`Phase Information<Phase_Information>`). Note that the
phase must be in a standard setting (see :func:`GetStdSGset`).
:param float maxdelta: Allowed distance tolerance in pseudosym search (default 2)
:param float angtol: Allowed tolerance for cell angles, used for finding
possible unit cells in from triclinic or monoclinic cells, ignored
otherwise. Defaults to None, which will cause 5 degrees to be used.
:param list pagelist: a list to contain references to the text of web
pages created by the Bilbao web site. If None (default) the web
pages are not saved.
:param bool keepCell: if False (default) and the cell is monoclinic
or triclinic, a search is made for higher symmetry cells. If True,
the search is made with the current cell.
:returns: valsdict,csdict,rowdict,savedcookies where the contents
will change depending on the space group, but valsdict
will contain values to be used in the next call to Bilbao and
savedcookies will contain a cookie needed for this as well.
* For monoclinic and triclinic unit cells: csdict will be None and
rowdict (rowlist) will be a list containing unit cells of higher
symmetry matching the input unit cell to be used for searching
for supergroups.
* For higher symmetry unit cells, csdict will be used to select
which entries will be used in the next search and rowdict
contain possible supergroup settings.
'''
print('\n'+BilbaoSymSearchCite+'\n')
postdict = {
"formulae":'',
"cifile":'',
"filename":"",
"what":'minsup',
"maxik":'1',
"onlythisik":'1',
"mysuper2":'211',
"x1":'1',
"x2":'0',
"x3":'0',
"y1":'0',
"y2":'1',
"y3":'0',
"z1":'0',
"z2":'0',
"z3":'1',
"x4":'0',
"y4":'0',
"z4":'0',
"angtol":'5',
"submit":'Show',}
postdict["maxdelta"] = f'{maxdelta:.1f}'
if sgnum <= 14 and not keepCell:
postdict["what"] = 'pseudocell'
if angtol: # and monoclinic/triclinic
postdict["angtol"] = f'{angtol:.1f}'
postdict["stru"] = f'# Space Group ITA number\n{sgnum}\n'
postdict["stru"] += '# Lattice parameters\n'
postdict["stru"] += ' '.join([f"{i}" for i in phase['General']['Cell'][1:7]])
postdict["stru"] += f"\n# number of atoms & atoms\n{len(phase['Atoms'])}\n"
cx,ct,cs,cia = phase['General']['AtomPtrs']
for i,atom in enumerate(phase['Atoms'],1):
el = ''.join([i for i in atom[ct] if i.isalpha()]) # strip to element symbol
postdict["stru"] += f"{el:4s} {i} - {atom[cx]:.5f} {atom[cx+1]:.5f} {atom[cx+2]:.5f}\n"
# if GSASIIpath.GetConfigValue('debug'): print(postdict["stru"])
savedcookies = {}
page0 = G2IO.postURL(bilbaoSite+pseudosym,postdict,
getcookie=savedcookies,timeout=timeout)
if not page0: return None
if pagelist is not None:
pagelist[0] = page0
if page0 is None: return None
return scanBilbaoSymSearch1(page0,postdict)+[savedcookies]
def scanBilbaoSymSearch1(page0,postdict):
#open(f'/tmp/pseudosym0.html','w').write(page0)
valsdict = {} # base for all supergroups
csdict = {} # supergroups w/default selection value
rowdict = {} # information in table row for each supergroup
if postdict["what"] == 'pseudocell':
form = page0.split('Lattice Pseudosymmetry')[1].split('<form')[1].split('</form')[0]
valsdict["id"] = form.split('name="id"')[1].split('value="')[1].split('"')[0]
valsdict["tol"] = postdict["angtol"]
valsdict["idcell"] = form.split('name="idcell"')[1].split('value="')[1].split('"')[0]
valsdict["what"] = 'minsup-lattice'
valsdict["formulae"] = postdict["formulae"]
valsdict["maxdelta"] = postdict["maxdelta"]
valsdict["submit"] = 'Show'
rowList = []
for line in form.split('<tr')[2:]:
items = line.split('<td')
# parse table row
row = [items[2].split('value=')[1].split('"')[1]] # "lattice" #
row.append(items[3].split('>')[1].split('<')[0]) # lattice type
row += items[4].split('<pre>')[1].split('<')[0].split('\n') # ideal & as xformed lattice
row.append(np.array([
float(eval(i)) for i in
items[5].split('<pre>')[1].split('<')[0]
.replace('[','').replace(']','').split()
]).reshape(3,3))
row.append(items[6].split('>')[1].split('<')[0]) # strain
row.append(items[7].split('>')[1].split('<')[0]) # tol
rowList.append(row)
return [valsdict,None,rowList]
# scan output for values to be used next
for row in page0.split('<input'):
field = row.split('>')[0]
if 'name=' not in field: continue
if 'value=' not in field: continue
name = value = None
for item in field.split():
if '=' not in item: continue
key,val = item.split('=')
if key.lower() == 'name':
name = val.replace('"','')
if key.lower() == 'value':
value = val.replace('"','')
if name == 'cs' and value is not None:
csdict[value] = "checked" in field
tr = [i.split('>',1)[1].split('</td')[0] for i in row.split('<td')]
tr[6] = tr[6].replace('<br>','\n')
tr = [re.sub(r'\<.*?\>','',i).strip() for i in tr]
rowdict[value] = tr[1:7] + [not 'invalid' in tr[7]]
break
elif name is not None and value is not None:
if name in valsdict and type(valsdict[name]) is str:
valsdict[name] = [valsdict[name],value]
elif name in valsdict:
valsdict[name] += [value]
else:
valsdict[name] = value
break
return [valsdict,csdict,rowdict]
[docs]
def BilbaoLowSymSea1(valsdict,row,savedcookies,pagelist=None):
'''Using a candidate higher symmetry unit cell from
:func:`BilbaoSymSearch1` for monoclinic and triclinic cells,
create a list of possible supergroups.
Those that match the possible lattice types
are marked for potential follow-up to see if coordinates can be
are consistent with that symmetry.
:returns: latticeList,valsdict,tbl where
* latticeList: a list of the possible Bravais lattice types
* valsdict: a dict with values needed for the next web form
* tbl a list of supergroups with four values per entry,
True/False if the lattice type matches,
a label with the space group number and the index (sg@ind),
the space group number and a lattice type (cell & centering)
'''
postdict = {}
postdict.update(valsdict)
num = row[0]
if GSASIIpath.GetConfigValue('debug'): print(f"processing cell #{num}")
postdict['lattice'] = num
page1 = G2IO.postURL(bilbaoSite+pseudosym,postdict,
usecookie=savedcookies,timeout=timeout)
if not page1: return None,None,None,None
lbl = f'cell{num}'
if pagelist is not None:
pagelist[lbl] = page1
if 'Possible lattices:' not in page1: return 3*[None]
if '<form' not in page1: return 3*[None]
latticeList = page1.split('Possible lattices:')[1].split('<')[0].strip().split(',')
form = page1.split('<form')[1].split('</form')[0]
valsdict = {}
for key in ('id','polares','idcell','what','formulae','maxdelta','lattice','subcosets'):
valsdict[key] = form.split(f'name={key}')[1].split('"')[1]
tbl = []
for l in form.split('<tr'):
line = l.split('</tr')[0]
if 'super_numind' not in line: continue
items = line.split('<td')
super_numind = items[2].split('value=')[1].split('>')[0]
super_numind = super_numind.replace('checked','').strip()
sg = items[3].split('center">')[1].split('</td')[0]
sg = sg.replace('<i>','').replace('</i>','')
sg = sg.replace('<sub>','').replace('</sub>','')
lattice = items[5].split('>')[1].split('<')[0]
tbl.append([(lattice in latticeList),super_numind,sg,lattice])
return lbl,latticeList,valsdict,tbl
[docs]
def BilbaoLowSymSea2(num,valsdict,row,savedcookies,pagelist=None):
'''For a selected cell & supergroup from :func:`BilbaoLowSymSea1`,
see if the coordinates are consistent with the supergroup
'''
postdict = {}
postdict.update(valsdict)
postdict['super_numind'] = row[1]
if GSASIIpath.GetConfigValue('debug'): print(f"processing cell #{num} supergroup {row[1]}")
page1 = G2IO.postURL(bilbaoSite+pseudosym,postdict,
usecookie=savedcookies,timeout=timeout)
if page1 is None: return '',None
lbl = f'cell{num}_{row[1]}'
if pagelist is not None:
pagelist[lbl] = page1
superdat = page1.split('Idealized structure (supergroup setting')
if len(superdat) >= 2:
return lbl,superdat[1].split('<pre>')[1].split('</pre')[0].replace('\t',' ').split('\n')
return lbl,None
[docs]
def BilbaoSymSearch2(valsdict,csdict,rowdict,savedcookies,
pagelist=None,dlg=None,ophsnam='?'):
'''Perform a supergroup search from the list of identified supergroups
found in BilbaoSymSearch1 (typically where the cell is higher symmetry
than monoclinic or triclinic; see :func:`BilbaoLowSymSea1` and
:func:`BilbaoLowSymSea2` for the low symmetry case.)
'''
structures = {}
for i,num in enumerate(sorted(csdict)):
if dlg:
GoOn = dlg.Update(i+1,newmsg=
f'Searching for supergroup(s) consistent with phase {ophsnam}'+
f'\nProcessing supergroup #{num}')
if not GoOn: return {}
if csdict[num]:
if GSASIIpath.GetConfigValue('debug'): print(f"processing {num}")
postdict = {}
postdict.update(valsdict)
postdict['cs'] = num
page1 = G2IO.postURL(bilbaoSite+pseudosym,postdict,
usecookie=savedcookies,timeout=timeout)
if pagelist is not None:
pagelist[num] = page1
if page1 is None:
structures[num] = "No response, likely web timeout"
else:
searchRes = page1.split('Case #')[1].split('/table')[0]
superdat = page1.split('Idealized structure (supergroup setting')
if len(superdat) >= 2:
structures[num] = superdat[1].split('<pre>')[1].split('</pre'
)[0].replace('\t',' ').split('\n')
elif '>tol' in searchRes:
structures[num] = f"coordinates inconsistent with symmetry {rowdict[num][0]}"
return structures
[docs]
def find2SearchAgain(pagelist,req='@'):
'''Scan through web pages from supergroup tests and pull out
where coordinates pass the tests to be potentially used to search
entries to be used to search for a higher symmetry setting.
'''
dicts = {}
for key,page in pagelist.items():
if key == 0: continue
if req and req not in key: continue
if page is None: continue
if '<form' not in page: continue
if 'Continue to search for pseudosymmetry' not in page: continue
for f in page.split('<form')[1:]:
if 'Continue to search for pseudosymmetry' not in f: continue
d = {}
for field in f.split('<input')[1:]:
k = field.split('name=')[1].split()[0]
if 'value="' in field:
v = field.split('value="')[1].split('"')[0]
else:
v = field.split('value=')[1].split('>')[0]
d[k] = v
k = key
i = 0
while k in dicts:
i += 1
k = key + '_' + str(i)
dicts[k] = d
return dicts
[docs]
def BilbaoReSymSearch(key,postdict,pagelist=None):
'''Perform a supergroup search on a result from previously
identified supergroup that was found in :func:`find2SearchAgain`
from the returned web pages. Provides results about the same as from
:func:`BilbaoSymSearch1`
:returns: valsdict,csdict,rowdict,savedcookies where valsdict
will contain values to be used in the next call to Bilbao and
savedcookies will contain a cookie needed for this as well.
csdict will be used to select
which entries will be used in the next search and rowdict
contains possible supergroup settings.
'''
savedcookies = {}
page1 = G2IO.postURL(bilbaoSite+pseudosym,postdict
,getcookie=savedcookies,timeout=timeout)
if pagelist is not None:
pagelist[key] = page1
if page1 is None: return {},{},{},savedcookies
valsdict,csdict,rowdict = scanBilbaoSymSearch1(page1,postdict)
return valsdict,csdict,rowdict,savedcookies
[docs]
def saveNewPhase(G2frame,phData,newData,phlbl,msgs,orgFilName):
'''create a .gpx file from a structure from the BilbaoSite pseudosym site
saved in newData
'''
def fmtCell(cell):
s = ''
for i in cell[0:3]: s += f"{i:.3f}, "
for i in cell[3:5]: s += f"{i:.2f}, "
s += f"{cell[5]:.2f}"
return s
if newData is None:
print(phlbl,'empty structure')
return
elif type(newData) is str:
msgs[phlbl] = newData
return
# create a new phase
try:
sgnum = int(newData[0].strip())
sgsym = G2spc.spgbyNum[sgnum]
sgname = sgsym.replace(" ","")
except:
print(f'Problem with processing record:\n{newData}')
return
newPhase = copy.deepcopy(phData)
newPhase['ranId'] = ran.randint(0,sys.maxsize),
if 'magPhases' in phData: del newPhase['magPhases']
generalData = newPhase['General']
generalData['SGData'] = SGData = G2spc.SpcGroup(sgsym)[1]
generalData['Cell'][1:7] = [float(i) for i in newData[1].split()]
generalData['Cell'][7] = G2lat.calc_V(G2lat.cell2A(generalData['Cell'][1:7]))
cx,ct,cs,cia = generalData['AtomPtrs']
Atoms = newPhase['Atoms'] = []
for a in newData[3:]:
if not a.strip(): continue
try:
elem,n,wyc,x,y,z = a.split()
atom = []
atom.append(elem+n)
atom.append(elem)
atom.append('')
for i in x,y,z: atom.append(float(i))
atom.append(1.0)
SytSym,Mult = G2spc.SytSym(np.array(atom[3:6]),SGData)[:2]
atom.append(SytSym)
atom.append(Mult)
atom.append('I')
atom += [0.02,0.,0.,0.,0.,0.,0.,]
atom.append(ran.randint(0,sys.maxsize))
Atoms.append(atom)
except:
print(f'error in atom line {a}')
#finally: pass
phData.update(newPhase)
G2elem.SetupGeneral(phData,phData['General']['Mydir']) # fixup composition info
# save new file
G2frame.GSASprojectfile = os.path.splitext(orgFilName
)[0]+'_super_'+sgname.replace('/','$')+'.gpx'
while os.path.exists(G2frame.GSASprojectfile):
s = re.split(r'_([\d]+)\.gpx',G2frame.GSASprojectfile)
if len(s) == 1:
G2frame.GSASprojectfile = os.path.splitext(G2frame.GSASprojectfile)[0] + '_1.gpx'
else:
num = 10
try:
num = int(s[1]) + 1
except:
pass
G2frame.GSASprojectfile = f'{s[0]}_{num}.gpx'
G2IO.ProjFileSave(G2frame)
# get transformed contents
nacomp,nccomp = G2mth.phaseContents(phData)
msgs[phlbl] = f"With space group {sgsym} and cell={fmtCell(generalData['Cell'][1:7])}"
msgs[phlbl] += f", vol={generalData['Cell'][7]:.2f} A^3"
msgs[phlbl] += f", project file created as {G2frame.GSASprojectfile}"
msgs[phlbl] += f". After transform, unit cell {G2mth.fmtPhaseContents(nccomp)}"
msgs[phlbl] += f", density={G2mth.getDensity(generalData)[0]:.2f} g/cm^3"
msgs[phlbl] += f". Asymmetric unit {G2mth.fmtPhaseContents(nacomp)} ({len(phData['Atoms'])} atoms)."
return G2frame.GSASprojectfile
[docs]
def test():
'''This tests that routines in Bilbao Crystallographic Server
are accessible and produce output that we can parse. The output
is displayed but not checked to see that it agrees with what
has been provided previously.
'''
SGData = G2spc.SpcGroup('f d -3 m')[1]
print('test SUBGROUPSMAG')
results,baseList = GetNonStdSubgroupsmag(SGData,('0','0','0',' ',' ',' ',' ',' ',' ',' '))
print(results)
if results:
for [spgp,bns,mv,gid,altList,supList] in results:
if gid in baseList:
print('Space group: %d %s BNS: %s'%(gid,spgp,bns))
print('MV',mv)
print('altList:',altList)
print('superList: ',supList)
print('\n\ntest SUBGROUPS')
results,baseList = GetNonStdSubgroups(SGData,('1/3','1/3','1/2',' ',' ',' ',' ',' ',' ',' '))
print(results)
if results:
for [spgp,mv,gid,altList,supList] in results:
if gid in baseList:
print('Space group: %d %s'%(gid,spgp))
print('MV',mv)
print('altList:',altList)
print('superList: ',supList)
print('\n\ntest Bilbao IDENTIFY GROUP')
sgnum,sgsym,xmat,xoff = GetStdSGset(G2spc.SpcGroup('R 3 C r')[1])
if sgnum:
print(f'Space group R3c (rhomb) transforms to std setting: {sgsym} (#{sgnum})')
print(' xform matrix',xmat)
print(' coord offset:',xoff)
if __name__ == '__main__':
# run self-tests
selftestquiet = False
test()
print ("OK")