Source code for GSASII.SUBGROUPS

# -*- coding: utf-8 -*-
'''
Module SUBGROUPS
========================

Routines that access the Bilbao Crystallographic Server. 

Note that there is a test for some of these routines in 
file ``tests/run_bilbao.py``.
'''

from __future__ import division, print_function
import re
import copy
import random as ran
import sys
import os
import wx
import numpy as np
import numpy.linalg as nl
from . import GSASIIpath
GSASIIpath.SetBinaryPath()
from . import GSASIIspc as G2spc
from . import GSASIIlattice as G2lat
from . import GSASIIElem as G2elem
from . import GSASIIctrlGUI as G2G

import GSASII.Bilbao.BCS_API as BCS

bilbaoURL = "http://cryst.ehu.es"
#bilbaoSite = f'{bilbaoURL}/cgi-bin/cryst/programs/'
# routines used here:
#timeout=150  # time to wait for Bilbao to respond; 2.5 minutes
#timeout=15  # time to wait for Bilbao to respond; short since probably broken

# new implementation stuff
BCS_SERVER = "https://cryst.ehu.es/apibcs/v1/run"
do_once = None # used to make sure that BCS_init can be called repeatedy

[docs] def BCS_init(threadCallback=None): '''Initialize access to the Bilbao Crystallographic Server. :Returns: True if initialization fails due to the lack of a key ''' if threadCallback == '': # setup default threading for BCS Post commands def BCS_sleep(): try: wx.GetApp().Yield() wx.MilliSleep(100) except: import time time.sleep(0.1) #print('BCS_sleep') BCS.initAPI(threadSleep=BCS_sleep) return elif threadCallback: BCS.initAPI(threadSleep=threadCallback) global do_once if do_once is not None: return False key = os.environ.get("BCS_API_KEY") if key is not None: print('Bilbao key (BCS_API_KEY) obtained from environment variable') else: key = GSASIIpath.GetConfigValue('BCS_API_KEY') if key is None: try: import wx from . import GSASIIctrlGUI as G2G G2frame = wx.GetApp().GetMainTopWindow() dlg = wx.MessageDialog(G2frame, 'The Bilbao key (BCS_API_KEY) has not been defined. Do you want to see a tutorial on how to setup Bilbao access?', 'No Bilbao key', wx.YES_NO | wx.ICON_EXCLAMATION) result = dlg.ShowModal() if result == wx.ID_YES: G2G.ShowWebPage('https://advancedphotonsource.github.io/GSAS-II-tutorials/RegisterBilbao/RegisterBilbao.html',G2frame) # except Exception as err: # print(err) except: pass msg = '''The Bilbao key (BCS_API_KEY) has not been defined. You must register an account and generate an "API Key" with the Bilbao Crystallographic Server before GSAS-II can access the site for you. (See https://advancedphotonsource.github.io/GSAS-II-tutorials/RegisterBilbao/RegisterBilbao.html). ''' print(msg) return True else: BCS.initAPI(BCS_SERVER,key) if GSASIIpath.GetConfigValue('debug'): print('Bilbao initialized') do_once = True return False
[docs] def RegisterProgressDialog(pgbar=None): '''Register a ProgressDialog so it is updated while waiting for the Bilbao server to respond. To reset, when the ProgressDialog is destroyed, call with no argument. :returns: the result from BCS_init, True if initialization fails due to the lack of a key. ''' ProgressDialog = None def pulse_pgbar(): '''Animate the Progress bar, but if an error occurs (likely because the routine is accidentally being called after the ProgressBar has been destroyed, ignore the error and reset so this routine is not called again. ''' try: pgbar.Pulse() except RuntimeError: BCS_init(threadCallback='') def BCS_pulseDlg(): wx.GetApp().Yield() wx.MilliSleep(100) if ProgressDialog: wx.CallAfter(ProgressDialog.Pulse) if pgbar: ProgressDialog = pgbar return BCS_init(threadCallback=BCS_pulseDlg) else: return BCS_init(threadCallback='')
def getSpGrp(item): return item.replace('<i>','').replace('</i>','').replace('<sub>','').replace('</sub>','') def getMatVec(item): return item.replace('{','[').replace('}',']')
[docs] def GetNonStdSubgroups(SGData, kvec,star=False,landau=False,maximal=False): '''Run Bilbao's SUBGROUPS for a non-standard space group. This requires doing a post to the Bilbao 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 ''' if BCS_init(): return None,'Unable to initialize Bilbao access' print(f'\nFor use of k-SUBGROUPSMAG, please cite:\n\n{G2G.GetCite("Bilbao: k-SUBGROUPSMAG",wrap=70,indent=5)}') starmag = 'no' if star: starmag = 'yes' land = 'no' if landau: land = 'yes' #celtodas = 'no' limite = 'spgroup' if maximal: limite = 'maximal' for j in [2,3]: if kvec[3*j-3] != ' ': print('GetNonStdSubgroups warning: Only one k-vector is implemented') break text,table = G2spc.SGPrint(SGData) OpList = G2spc.TextOps(text,table,reverse=True) page = BCS.BCS_SUBGROUPS('',[i.lower() for i in OpList],*kvec[0:3],GSAS=True, landau=land,starmagnetica=starmag,limite=limite) 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 Bilbao's k-Subgroupsmag for a non-standard space group. This requires doing a post to the Bilbao 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 ''' if BCS_init(): return print(f'\nFor use of k-SUBGROUPSMAG, please cite:\n\n{G2G.GetCite("Bilbao: k-SUBGROUPSMAG",wrap=70,indent=5)}') 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 starmag = 'no' if star: starmag = 'yes' land = 'no' if landau: land = 'yes' celtodas = 'no' limite = 'spgroup' if maximal: limite = 'maximal' for j in [2,3]: if kvec[3*j-3] != ' ': print('GetNonStdSubgroups warning: Only one k-vector is implemented') break text,table = G2spc.SGPrint(SGData) OpList = G2spc.TextOps(text,table,reverse=True) page = BCS.BCS_kSUBGROUPSMAG('',[i.lower() for i in OpList], *kvec[0:3],GSAS=True, landau=land,starmagnetica=starmag,limite=limite) 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
# Alas, pseudolattice has been removed from Bilbao #pseudolattice = "pseudosym/nph-pseudolattice" # def subBilbaoCheckLattice(spgNum,cell,tol=5): # '''submit a unit cell to Bilbao PseudoLattice # ''' # psSite = bilbaoSite + 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 = GSASIIpath.postURL(psSite,datastr,timeout=timeout) # if postpostURL(page): return None # if not page: # print('connection error - not on internet?') # return None # page = page.replace('<font style= "text-decoration: overline;">','<font>-') # return page # 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
[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 if not bool(oprList) ^ bool(SGData): raise ValueError('GetStdSGset: Must specify oprList or SGData and not both') elif SGData: SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(SGData) oprList = [x.lower() for x in SymOpList] else: oprList = [x.lower() for x in oprList] if BCS_init(): return print('Using Bilbao Crystallographic Server utility IDENTIFY GROUP. '+ 'Please cite:\n'+ G2G.GetCite('Bilbao: k-SUBGROUPSMAG',wrap=70,indent=5)) page = BCS.BCS_IDENTIFYGROUP(oprList) # scrape the HTML output for the new space group # and the xform info try: sgnum = int(re.search(r'\(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(eval(i)) for i in m.split()[:3]] for m in mat]) return sgnum, sgnam, xformM.T, offsetV
# minsup = 'nph-minsup' # coded but not used # 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 + minsup # if dlg: dlg.Update(0,newmsg='Waiting for initial web response') # out = GSASIIpath.postURL(Site,{'gnum':f'{SGnum:}'},timeout=timeout) # if postpostURL(out): return None # 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 = GSASIIpath.postURL(Site,{'gnum':SGnum,'show':'show','click':click} # ,timeout=timeout) # if postpostURL(out1): return None # if not out1: return None # #with open(f'/tmp/{click}.html','w') as fp: # # fp.write(out1) # #with open(f'/tmp/last.html','w') as fp: # # fp.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 # 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
[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: formDict,csdict,rowdict,stru where the contents will change depending on the space group, but formDict will contain values to be used in the next call to Bilbao * 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. ''' if BCS_init(): return print(f'''\nUsing the Bilbao Crystallographic Server Pseudosymmetry search (PSEUDO) program; Please cite:\n {G2G.GetCite('Bilbao: PSEUDO',wrap=70,indent=5)}\n''') maxdeltaS = f'{maxdelta:.1f}' stru = f'# Space Group ITA number\n{sgnum}\n' stru += '# Lattice parameters\n' stru += ' '.join([f"{i}" for i in phase['General']['Cell'][1:7]]) stru += f"\n# number of 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 stru += f"{el:4s} {i} - {atom[cx]:.5f} {atom[cx+1]:.5f} {atom[cx+2]:.5f}\n" if sgnum <= 16 and not keepCell: if angtol: # and monoclinic/triclinic angtolS = f'{angtol:.1f}' else: angtolS = '5' page0 = BCS.BCS_PSEUDO(stru=stru, # Initial Structure (LS) maxdelta = maxdeltaS, # Maximum allowed distance for pseudosymmetry search what = 'pseudocell', # Look for higher symmemtry cell angtol = angtolS # Angular tolerance for pseudolattice check ) if not page0: return None if "form" not in page0: return None if pagelist is not None: pagelist[0] = page0 return scanBilbaoPseudocell(page0)+(stru,) else: page0 = BCS.BCS_PSEUDO(stru=stru, # Initial Structure (LS) maxdelta = maxdeltaS, # Maximum allowed distance for pseudosymmetry search what = 'minsup', # Minimal supergroups ) if not page0: return None if "form" not in page0: return None if pagelist is not None: pagelist[0] = page0 return scanBilbaoMinsup(page0)+(stru,)
def scanHTMLform(form): _ATTR_RE = re.compile(r""" (?P<key>[A-Za-z_][\w:-]*) # attribute name \s*=\s* (?P<val> "(?:[^"\\]|\\.)*" # double-quoted | '(?:[^'\\]|\\.)*' # single-quoted | [^\s"']+ # unquoted (up to whitespace) ) """,re.VERBOSE) def parse_str(s): '''Parse an HTML-ish attribute string like type=hidden name="SOURCE_TAG" value="2171892_S00E099..." Returns a dict. Outer quotes around values are stripped if present. ''' out = {} for m in _ATTR_RE.finditer(s): key = m.group("key") val = m.group("val").strip() # Strip only ONE layer of matching outer quotes (single or double) if len(val) >= 2 and val[0] == val[-1] and val[0] in ("'", '"'): out[key.lower()] = val[1:-1] else: out[key.lower()] = val return out formDict = {} for i,line in enumerate(form.split("<input")): if i == 0: # save the CGI name from the <FORM action> entry d = parse_str(line) formDict['_cgi_action'] = d.get('action') continue line = line.split('>')[0] d = parse_str(line) if 'name' in d and 'value' in d: name = d['name'] if d.get('type','').lower() == 'checkbox': if 'checked' not in line: continue if name not in formDict: formDict[name] = [] formDict[name] += [d['value']] else: formDict[name] = d['value'] #print([(key+'="'+d[key]+'"') for key in ('name','value')]) return formDict
[docs] def scanBilbaoPseudocell(page0): '''Scan the output from Bilbao minsup search. Obtains a set of cells and symmetry. :returns: formDict,csdict,rowdict where: * formDict: has the information to be submitted on the next form * csdict: is None searching on each row * rowdict: a dict with the information for each cell/sym that was found. ''' #csdict = {} # supergroups w/default selection value #rowdict = {} # information in table row for each supergroup # get the form information from the page if '<form' not in page0: return {},None,[] form = page0.split('<form')[1].split('</form')[0] formDict = scanHTMLform(form) if not 'Lattice Pseudosymmetry' in page0: return [formDict,None,[]] form = page0.split('Lattice Pseudosymmetry')[1].split('<form')[1].split('</form')[0] rowList = [] for line in form.split('<tr')[2:]: # either one or zero cells is expected 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 formDict,None,rowList
[docs] def scanBilbaoMinsup(page0): '''Scan the output from Bilbao minsup search. Obtains a set of cells and symmetry. :returns: formDict,csdict,rowdict where: * formDict: has the information to be submitted on the next form * csdict: a dict of True/False values with the defaults for continued searching on each row * rowdict: a dict with the information for each cell/sym that was found. The contents of each row are list elements: HM symbol, sgnum, index, index i_k, TR mat, Trans Cell, WP valid, latt_valid ''' csdict = {} # supergroups w/default selection value rowdict = {} # information in table row for each supergroup # get the form information from the page form = page0.split('<form')[1].split('</form')[0] formDict = scanHTMLform(form) # 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 # separate rows tr = [i.split('>',1)[1].split('</td')[0] for i in row.split('<td')] wycValid = not 'invalid' in tr[7] latValid = not 'comply' in tr[6] tr[6] = tr[6].split('<br>')[0] # strip out formatting, etc. tr = [re.sub(r'\<.*?\>','',i).strip() for i in tr] rowdict[value] = tr[1:7] + [wycValid, latValid] break elif name is not None and value is not None: break return formDict,csdict,rowdict
[docs] def BilbaoLowSymSea1(formDict,row,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) ''' print("BilbaoLowSymSea1",formDict,row,list(pagelist.keys())) postdict = {i:formDict[i] for i in formDict if not i.startswith('_')} postdict['lattice'] = row[0] if GSASIIpath.GetConfigValue('debug'): print(f"processing row #{row[0]} cell type {row[1]}") page1 = BCS.BCS_submit(postdict) breakpoint() # page1 = GSASIIpath.postURL(bilbaoSite+pseudosym,postdict, # usecookie=savedcookies,timeout=timeout) # if postpostURL(page1) or not page1: return None,None,None,None lbl = f'cell{row[0]}' 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 ''' print("BilbaoLowSymSea2",num,valsdict,row,savedcookies,len(pagelist)) postdict = {} postdict.update(valsdict) postdict['super_numind'] = row[1] if GSASIIpath.GetConfigValue('debug'): print(f"processing cell #{num} supergroup {row[1]}") page1 = GSASIIpath.postURL(bilbaoSite+pseudosym,postdict, usecookie=savedcookies,timeout=timeout) if postpostURL(page1) or 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 BilbaoGetStructure(csdict,rowdict,stru, pagelist=None,dlg=None,dlgNum=None,prefix=''): '''Get the structure for a specified supergroup using the results from BilbaoSymSearch1 ''' structures = {} for num in csdict: if not csdict[num]: continue if dlg: dlgNum = 1 + dlgNum % 10 GoOn = dlg.Update(dlgNum,newmsg= f'Obtaining structure for supergroup {num} ({rowdict[num][0]})') if not GoOn[0]: return None,None dlg.Raise() if GSASIIpath.GetConfigValue('debug'): print(f"getting structure {prefix+num}") page1 = BCS.BCS_PSEUDO(stru,what="mysuper",mysuper2=rowdict[num][1], origin="yes",polares=0.4,trmat_abc=rowdict[num][4]) if page1 is None: structures[prefix+num] = "No response, likely web timeout" else: if pagelist is not None: pagelist[prefix+num] = page1 searchRes = page1.split('Case #')[1].split('/table')[0] superdat = page1.split('Idealized structure (supergroup setting') if len(superdat) >= 2: structures[prefix+num] = superdat[1].split('<pre>')[1].split('</pre' )[0].replace('\t',' ').split('\n') elif '>tol' in searchRes: structures[prefix+num] = f"coordinates inconsistent with symmetry {rowdict[num][0]}" return structures,dlgNum
[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,result,maxdelta=2,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: formDict,csdict,rowdict where formDict will contain values to be used in the next call to Bilbao csdict will be used to select which entries will be used in the next search and rowdict contains possible supergroup settings. ''' maxdeltaS = f'{maxdelta:.1f}' page0 = BCS.BCS_PSEUDO(stru=result['stru'], # structure to search maxdelta = maxdeltaS, # Maximum allowed distance for pseudosymmetry search what = 'minsup', # Minimal supergroups ) if not page0: return None if "form" not in page0: return None if pagelist is not None: pagelist[key+'R'] = page0 return scanBilbaoMinsup(page0)
[docs] def createStdSetting(cifFile,rd): '''Use the Bilbao "CIF to Standard Setting" web service to obtain a structure in a standard setting. Then update the reader object with the space group, cell and atom positions from this. This is called from the CIF importer in :mod:`G2phase_CIF` when a structure is encountered that has different symmetry operators from what GSAS-II generates. ''' try: import requests # delay this until now, since rarely needed except: # this import seems to fail with the Anaconda pythonw on # macs; it should not! print('Warning: failed to import requests. Python config error') return None if BCS_init(): return if not os.path.exists(cifFile): print(f'createStdSetting error: file {cifFile} not found') return False print(f'''Submitting structure to Bilbao "CIF to Standard Setting" (strtidy) web service. Please cite:\n {G2G.GetCite('Bilbao: PSEUDOLATTICE',wrap=70,indent=5)}''') r0 = BCS.BCS_CIF2STANDARD(cifFile,response_format='html') structure = r0[r0.lower().find('<pre>')+5:r0.lower().find('</pre>')].strip() spnum,celllist,natom = structure.split('\n')[:3] #spgNam = G2spc.spgbyNum[int(spnum)] cell = [float(i) for i in celllist.split()] # replace cell, space group and atom info with info from Bilbao # could try to xfer Uiso (Uij needs xform), but that would be too involved rd.Phase['General']['SGData'] = SGData = G2spc.SpcGroup(G2spc.spgbyNum[int(spnum)])[1] rd.Phase['General']['Cell'] = [False] + list(cell) + [G2lat.calc_V(G2lat.cell2A(cell))] rd.Phase['Atoms'] = [] for i,line in enumerate(structure.split('\n')[3:]): atomlist = ['','Xe','',0.,0.,0.,1.0,'',0.,'I',0.01, 0.,0.,0.,0.,0.,0.,] elem,lbl,wyc,x,y,z = line.split() elem = elem.rstrip('0123456789-+') atomlist[0] = elem + lbl if G2elem.CheckElement(elem): atomlist[1] = elem atomlist[3:6] = [float(i) for i in (x,y,z)] atomlist[7],atomlist[8] = G2spc.SytSym(atomlist[3:6],SGData)[:2] atomlist[1] = G2elem.FixValence(atomlist[1]) atomlist.append(ran.randint(0,sys.maxsize)) # add a random Id rd.Phase['Atoms'].append(atomlist) if i == int(natom)-1: break del rd.SymOps['xyz'] # as-read sym ops now obsolete
#if __name__ == '__main__': # Note that self-tests have been moved to file ``tests/run_bilbao.py``.