/* $RCSfile$ * $Author: egonw $ * $Date: 2005-11-10 09:52:44 -0600 (Thu, 10 Nov 2005) $ * $Revision: 4255 $ * * Copyright (C) 2003-2005 Miguel, Jmol Development, www.jmol.org * * Contact: jmol-developers@lists.sf.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jmol.symmetry; import java.util.Arrays; import java.util.Map; import org.jmol.api.SymmetryInterface; import org.jmol.script.SV; import org.jmol.viewer.JC; import org.jmol.viewer.Viewer; import javajs.util.AU; import javajs.util.BS; import javajs.util.Lst; import javajs.util.M4; import javajs.util.P3; import javajs.util.PT; import javajs.util.SB; import javajs.util.T3; /** * A holding class for ClegData and ClegNode, which are called by * org.jmol.modelkit.Modelkit to process CLEG strings. * * This code access multiple methods in Jmol, so it is not independently * implemented outside that context. But it encapsulates the essentals of * */ final public class CLEG { /** * allows 300, 400, 500, 600 instead of p/ l/ r/ f/ * could be turned on; this is for Jmol testing * */ public static boolean allow300 = false; public static final String HEX_TO_RHOMB = "2/3a+1/3b+1/3c,-1/3a+1/3b+1/3c,-1/3a-2/3b+1/3c"; public static final String RHOMB_TO_HEX = "a-b,b-c,a+b+c"; /** * running data items for assignSpaceGroup iteration */ public static class ClegData { final public String[] tokens; public Symmetry sym; public M4 trMat; public String errString; M4 trLink; private M4 trTemp = new M4(); ClegNode prevNode; Map retMap; Lst retLst; boolean asM4; public ClegData(SymmetryInterface sym, String[] tokens) { this.tokens = tokens; this.sym = (Symmetry) sym; } public void setSymmetry(SymmetryInterface sym) { this.sym = (Symmetry) sym; } public M4 addSGTransform(String tr, String what) { if (trMat == null) { System.out.println("ClegData reset"); trMat = new M4(); trMat.setIdentity(); } if (tr != null) { trMat.mul(matFor(tr)); } if (what != null) System.out.println("ClegData adding " + what + " " + tr + " now " + abcFor(trMat)); return trMat; } public String abcFor(M4 trm) { return sym.staticGetTransformABC(trm, false); } public M4 matFor(String trm) { return (M4) sym.convertTransform(trm, (trm.indexOf(">") > 0 ? null : trTemp)); } public void removePrevNodeTrm() { if (prevNode != null && prevNode.myTrm != null && !prevNode.disabled) { addSGTransform("!" + prevNode.myTrm, "!prevNode.myTrm"); } } public String calculate(M4 trm0) { trm0.invert(); M4 trm1 = M4.newM4(trMat); trm1.mul(trm0); return (String) sym.convertTransform(null, trm1); } /** * fill in the blanks to create a CLEG string when joined. * * @param node */ public void updateTokens(ClegNode node) { int index = node.index; String s = node.name; if (s.startsWith("ITA/")) s = s.substring(4); else // 133.1 s = node.myIta + ":" + node.myTrm; setToken(index, s); if (node.calculated != null && index > 0) setToken(index - 1, node.calculated); } private void setToken(int index, String s) { tokens[index] = s; } public void addTransformLink() { if (trLink == null) { trLink = new M4(); trLink.setIdentity(); } trLink.mul(trTemp); } public void setNodeTransform(ClegNode node) { node.myTrm = abcFor(trMat); node.setITAName(); } public void addTransform(int index, String transform) { addSGTransform(transform, ">t>"); // if the link is null, this will be the flag that we have x >> y addTransformLink(); System.out.println("CLEG.addTransform index=" + index + " trm=" + trMat); } public ClegNode getPrevNode() { return prevNode; } public ClegNode setPrevNode(ClegNode node) { return prevNode = node; } public String addPrimitiveTransform(String myIta, String myTrm) { String hmName = (String) sym.getSpaceGroupInfoObj("hmName", myIta + ":" + myTrm, false, false); if (hmName == null) return myTrm; char c = hmName.charAt(0); if ("ABCFI".indexOf(c) < 0) return myTrm; M4 t = M4.newMV(UnitCell.getPrimitiveTransform(c), P3.new3(0,0,0)); t.mul(matFor(myTrm)); return abcFor(t); } public void setReturnMap(Map ret) { if (ret != null) { asM4 = (ret.get("ASM4") == Boolean.TRUE); ret.clear(); } retMap = ret; } public void setReturnLst(Lst ret) { if (ret != null) ret.clear(); retLst = ret; } } public static class ClegNode { public final static String TYPE_REFERENCE = "ref"; public final static String CALC_SUB = "sub"; public final static String CALC_SUBP = "sub("; public final static String CALC_SUPER = "super"; public final static String CALC_SUPERP = "super("; public final static String CALC_SET = "set"; String name; /** * the number 1 to 230 */ String myIta; /** * a Transformation(P,p) expression */ String myTrm; /** * 0-based index of this node in a CLEG expression A > tt > B > tt >> C ... */ int index; /** * "sub" or "super" for Jmol >sub> >super> */ private String calcNext; private int calcI1; private int calcI2; private int calcDepthMin; private int calcDepthMax; private int calcIndexMin; private int calcIndexMax; String calculated; // transform car boolean disabled; //private boolean applySetting = true; /** * Cleg ends with >, implying we want a calculation of the final transform * and use that to assign the final space group setting * */ private boolean isThisModelCalc; private String hallSymbol; private int specialType = SpaceGroup.TYPE_SPACE; String specialPrefix = ""; public ClegNode(ClegData data, int index, String name) { if (name == null) return; this.index = index; // sets this.name init(data, name); } public void disable() { disabled = true; } /** * Set specialType and specialPrefix and normalize name * * @param name * @return name stripped of prefix and adjusted for 300+ if allowed */ private String checkSpecial(String name) { switch (specialType = SpaceGroup.getExplicitSpecialGroupType(name)) { case SpaceGroup.TYPE_INVALID: return null; case SpaceGroup.TYPE_SPACE: if (!allow300) return name; int ptDot = name.indexOf("."); String sname = (ptDot > 0 ? name.substring(0, ptDot) : name); int itno = SpaceGroup.getITNo(sname, 0); if (itno < 300) return name; if (itno > SpaceGroup.TYPE_FRIEZE) return null; specialType = (itno / 100) * 100; specialPrefix = SpaceGroup.getGroupTypePrefix(itno); return "" + (itno - specialType) + name.substring(sname.length()); default: // special specialPrefix = name.substring(0, 2); return name.substring(2); } } private void init(ClegData data, String name) { int pt = isProbableClegSetting(name); if (pt > 0) { myIta = name.substring(0, pt); myTrm = name.substring(pt + 1); } if (name.equals(TYPE_REFERENCE)) { isThisModelCalc = true; } // register and remove p/ l/ r/ f/ prefix if there is one name = checkSpecial(name); boolean isPrimitive = name.endsWith(":p"); if (isPrimitive) name = name.substring(0, name.length() - 2); boolean isITAnDotm = name.startsWith("ITA/"); if (isITAnDotm) { // ITA/140 or ITA/140.2 or ITA/p/23 or ITA/r/34.2 name = checkSpecial(name.substring(4)); } boolean isHM = false; hallSymbol = null; String hallTrm = null; if (specialType == SpaceGroup.TYPE_SPACE && name.charAt(0) == '[') { // [P 2y] is a Hall symbol pt = name.indexOf(']'); if (pt < 0) { data.errString = "invalid Hall symbol: " + name + "!"; return; } hallSymbol = name.substring(1, pt); pt = name.indexOf(":", pt); if (pt > 0) hallTrm = name.substring(pt + 1); name = "Hall:" + hallSymbol; } else if (name.startsWith("HM:")) { // not sure this is still useful; ok, leave this this way isHM = true; } else if (name.length() <= 3) { // quick check for nnn isITAnDotm = (SpaceGroup.getITNo(name, 0) > 0); if (isITAnDotm) { name = checkSpecial(name) + ".1"; } } // Hall:xxxx, or JmolID, or other if (!isITAnDotm && hallSymbol == null && !isHM) { pt = (PT.isDigit(name.charAt(0)) ? name.indexOf(" ") : -1); if (pt > 0) name = name.substring(0, pt); if (name.indexOf('.') > 0 && !Float.isNaN(PT.parseFloat(name))) { // "6.1" isITAnDotm = true; } } if (isITAnDotm) { // We need the tranformation matrix for "10.2" // so that we can set the unit cell, as findSpacegroup does NOT // set the unit cell in SG_IS_ASSIGN mode and there is a // unit cell already. This call will build the space group if necessary // if it is from Wyckoff myTrm = (name.endsWith(".1") ? "a,b,c" : (String) data.sym.getSpaceGroupInfoObj("itaTransform", specialPrefix + name, false, false)); if (myTrm == null) { data.errString = "Unknown ITA setting: " + specialPrefix + name + "!"; return; } String[] parts = PT.split(name, "."); myIta = parts[0]; } else { // look-up for Hermann-Mauguin name. if (myIta == null) myIta = (String) data.sym.getSpaceGroupInfoObj("itaNumber", specialPrefix + name, false, false); if (myTrm == null) myTrm = (String) data.sym.getSpaceGroupInfoObj("itaTransform", specialPrefix + name, false, false); if (hallSymbol != null && hallTrm != null) { if (myTrm.equals("a,b,c")) { myTrm = hallTrm; } else { data.errString = "Non-reference Hall symbol cannot also contain a setting: " + name + "!"; return; } } } if ("0".equals(myIta)) { data.errString = "Could not get ITA space group for " + name + "!"; return; } if (isPrimitive) { myTrm = data.addPrimitiveTransform(myIta, myTrm); } setITAName(); } public String setITAName() { return name = "ITA/" + specialPrefix + myIta + ":" + myTrm; } public boolean update(ClegData data) { if (data.errString != null) return false; if (name == null) return true; ClegNode prev = data.prevNode; if (prev.isThisModelCalc) prev.myIta = myIta; /** * haveReferenceCell is set true if and only if we can apply the setting * if it is present or implied; this is the case when the current unit * cell is the "symmetry" cell -- that is, that cell represents the proper * unit cell for the current space group; this is checked for a matching * ITA number. */ boolean haveReferenceCell = (data.trLink == null && (myIta != null && (myIta.equals(prev.myIta) || prev.calcNext != null))); // System.out.println("ClegNode update i=" + index + " n=" + name // + "\n data.trLink=" + data.trLink + "\n " + prev + " " + prev.myIta // + " " + prev.calcNext + " " // //+ applySetting // + " haveref=" + haveReferenceCell); if (!haveReferenceCell) return true; M4 trm0 = M4.newM4(data.trMat); data.removePrevNodeTrm(); String trCalc = null; if (prev.calcNext != null) { boolean isSub = true; boolean isImplicit = false; boolean isCalcFunction = false; switch (prev.calcNext) { case CALC_SUPERP: case CALC_SUBP: isCalcFunction = true; break; case CALC_SUPER: isSub = false; break; case CALC_SUB: break; case "": case CALC_SET: // from >>; acts for sub or set prev.calcNext = CALC_SET; isImplicit = true; break; } int ita1 = PT.parseInt(prev.myIta); int ita2 = PT.parseInt(myIta); boolean unspecifiedSettingChangeOnly = !isCalcFunction && (data.retLst == null && (data.retMap == null || data.asM4) && isImplicit && ita1 == ita2); if (!unspecifiedSettingChangeOnly) { int flags = (prev.calcIndexMax << 24) | (prev.calcIndexMin << 16) | (prev.calcDepthMax << 8) | prev.calcDepthMin; trCalc = (String) data.sym.getSubgroupJSON((isSub ? prev.name : name), (isSub ? name : prev.name), prev.calcI1, prev.calcI2, flags, data.retMap, data.retLst); boolean haveCalc = (trCalc != null); if (haveCalc) { if (trCalc.endsWith("!")) { data.errString = trCalc; return false; } if (!isSub) trCalc = "!" + trCalc; } String calc = prev.myIta + ">" + (haveCalc ? trCalc : "?") + ">" + myIta; if (!haveCalc) { data.errString = calc + "!"; return false; } //System.out.println("CLEG sub := " + calc); data.addSGTransform(trCalc, CALC_SUB); } } if (!disabled) data.addSGTransform(myTrm, "myTrm"); calculated = data.calculate(trm0); System.out.println("calculated is " + calculated); return true; } public String getName() { return name; } public String getCleanITAName() { String s = (name.startsWith("ITA/") ? name.substring(4) : name); if (specialType != SpaceGroup.TYPE_SPACE && !s.startsWith(specialPrefix)) s = specialPrefix + s; return s; } public boolean isDefaultSetting() { return (myTrm == null || cleanCleg000(myTrm).equals("a,b,c")); } /** * sup(i0, i1, indexMax, depthMax) * * @param data * @param token * @return true if sytax is correct */ public boolean setCalcNext(ClegData data, String token) { int pt = token.length(); switch (pt == 0 ? token : CLEG.getCalcType(token)) { case CALC_SUB: if (data.retLst != null || data.retMap != null) { pt = 0; break; } //$FALL-THROUGH$ case CALC_SUPER: calcI1 = 1; calcI2 = 1; pt = 3; break; case CALC_SUBP: pt = -3; break; case CALC_SUPERP: pt = -5; break; } boolean isErr = true; while (true) { if (pt == 0) { //G>>H calcIndexMin = 2; calcIndexMax = 0xFF; calcDepthMin = 1; calcDepthMax = 0xFF; isErr = false; } else if (pt > 0) { //G>SUB>H calcIndexMin = 2; calcIndexMax = 0xFF; calcDepthMin = 1; calcDepthMax = 1; isErr = false; } else { if (token.indexOf(")") != token.length() - 1) break; String[] params = PT.split(PT.trim(token.toLowerCase().substring(-pt + 1), ")"), ","); try { if (token.length() == 5 || token.indexOf('=') >= 0) { calcIndexMin = 2; calcIndexMax = 0xFF; calcDepthMin = 1; calcDepthMax = 0xFF; token = token.substring(0, -pt); for (int i = params.length; --i >= 0;) { String p = params[i]; int val = Math.min(0xFF, Integer.parseInt(p.substring(p.indexOf('=') + 1))); if (p.startsWith("indexmax=")) calcIndexMax = Math.max(2, val); else if (p.startsWith("indexmin=")) calcIndexMin = Math.max(2, val); else if (p.startsWith("index=")) calcIndexMin = calcIndexMax = Math.max(2, val); if (p.startsWith("depthmax=")) calcDepthMax = Math.max(1, val); else if (p.startsWith("depthmin=")) calcDepthMin = Math.max(1, val); else if (p.startsWith("depth=")) calcDepthMin = calcDepthMax = Math.max(1, val); } } else { switch (params.length) { case 2: calcI2 = Math.max(0, Integer.parseInt(params[1])) & 0xFF; //$FALL-THROUGH$ case 1: // 0 will be treated as 1 calcI1 = Math.max(1, Integer.parseInt(params[0])) & 0xFF; break; } } token = token.substring(0, -pt); isErr = false; } catch (Exception e) { } } break; } if (isErr) { data.errString = "Error parsing CLEG " + token + "!"; return false; } calcNext = token; return true; } @Override public String toString() { return "[ClegNode #" + index + " " + name + " " + myIta + ":" + myTrm + (disabled ? " disabled" : "") + "]"; } } public static void standardizeTokens(String[] tokens, boolean isEnd) { for (int i = tokens.length; --i >= 0;) { String t = tokens[i]; if (t.length() == 0) { continue; } t = cleanCleg000(t); if (t.endsWith(":h")) { if (!t.startsWith("R")) t = t.substring(0, t.length() - 2); } else if (!isEnd && t.endsWith(":" + HEX_TO_RHOMB)) { //don't do this t = t.substring(0, t.lastIndexOf(":")) + ":r"; } else if (t.endsWith(":r")) { if (!t.startsWith("R")) t = t.substring(0, t.length() - 1) + HEX_TO_RHOMB; } else if (t.equals("r")) { t = HEX_TO_RHOMB; } else if (t.equals("h")) { t = RHOMB_TO_HEX; } // if (isEnd) // t = SpaceGroup.canonicalizeCleg(t); tokens[i] = t; } System.out.println("MK StandardizeTokens " + Arrays.toString(tokens)); } public static String cleanCleg000(String t) { return (t.endsWith(";0,0,0") ? t.substring(t.length() - 6) : t); } /** * Determine if the string is of the general form * [/]:...,.... but not checking for details of the it number or the setting * allows optional / prefix where is one of 'p','l','r','f' * @param name * @return position of ":" or -1; */ public static int isProbableClegSetting(String name) { int p = name.indexOf(":"); int type = SpaceGroup.getExplicitSpecialGroupType(name); return (type >= 0 && p > 0 && SpaceGroup.getITNo(type == SpaceGroup.TYPE_SPACE ? name : name.substring(2), p) > 0 && name.indexOf(",") > p ? p : 0); } /** * Test nodes and transforms for valid syntax. 133:b1 is OK, perhaps, at * this point, but 142:0,0,0 is not and P2/m:a,b,c is not * and IT numbers must be in the appropriate range * * @param tokens * @param sym * @param allow300 allow CLEG extension to 301-317 for plane, 401-480 for layer, 501-575 for rod, 601-607 for frieze * @return true for OK syntax */ public static boolean checkFullSyntax(String[] tokens, SymmetryInterface sym, boolean allow300) { for (int i = 0; i < tokens.length; i++) { String s = tokens[i].trim(); if (s.length() == 0 || s.startsWith("sub") || s.startsWith("super")) continue; int groupType = SpaceGroup.getExplicitSpecialGroupType(s); if (groupType > SpaceGroup.TYPE_SPACE) s = s.substring(2); int ptColon = s.indexOf(":"); int ptComma = s.indexOf(",", ptColon + 1); int ptDot = s.indexOf("."); boolean isClegSetting = (ptColon > 0 && ptComma > ptColon); int ptHall = s.indexOf("]"); boolean isHall = (ptHall > 0 && s.charAt(0) == '[' && (ptColon < 0 || ptColon == ptHall + 1)); float itno = (isHall ? 1 : PT.parseFloatStrict(ptColon > 0 ? s.substring(0, ptColon) : s)); if (Float.isNaN(itno)) { // a transform or an H-M name without a cleg setting is ok, // but anything with a dot or a colon and an expression containing "," should not be here if (ptDot > 0 || isClegSetting) return false; } else if (!SpaceGroup.isInRange(itno, groupType, !isClegSetting, allow300 && groupType == SpaceGroup.TYPE_SPACE)) { return false; } if (ptComma < 0) continue; String transform = (ptColon > 0 ? s.substring(ptColon + 1) : s); if (((M4) sym.convertTransform(transform, null)).determinant3() == 0) return false; } return true; } public static String getCalcType(String token) { return (token.length() == 0 || token.equals(ClegNode.CALC_SUB) ? ClegNode.CALC_SUB : token.charAt(0) != 's' ? null : token.startsWith(ClegNode.CALC_SUBP) ? ClegNode.CALC_SUBP : token.equals(ClegNode.CALC_SUPER) ? ClegNode.CALC_SUPER : token.startsWith(ClegNode.CALC_SUPERP) ? ClegNode.CALC_SUPERP : null); } public static boolean isTransformOnly(String token) { return (isTransform(token, false) && token.indexOf(":") < 0); } public static boolean isTransform(String token, boolean checkColonRH) { return (token.length() == 0 || token.indexOf(',') > 0 || "!r!h".indexOf(token) >= 0) || checkColonRH && (token.endsWith(":r")|| token.endsWith(":h")); } /** * The main entry point for calculation of an overall transfrom. Allows a * single space group to mean "current going to..." * * @param vwr * @param cleg * @param retLstOrMap * a Map or Lst that are to return solutions * @return matrix or null */ @SuppressWarnings("unchecked") M4 getMatrixTransform(Viewer vwr, String cleg, Object retLstOrMap) { if (cleg.length() == 0) cleg = "."; if (cleg.indexOf(">") < 0 && !cleg.equals(".")) cleg = ">>" + cleg; String[] tokens = PT.split(cleg, ">"); if (tokens[0].length() == 0) tokens[0] = ClegNode.TYPE_REFERENCE; ClegData data = new ClegData(vwr.getSymTemp(), tokens); Map retMap = (retLstOrMap instanceof Map ? (Map) retLstOrMap : null); Lst retLst = (retMap == null && retLstOrMap instanceof Lst ? (Lst) retLstOrMap : null); data.setReturnMap(retMap); data.setReturnLst(retLst); String err = assignSpaceGroup(data, new AssignedSGParams(vwr, cleg.equals("."))); if (err.indexOf("!") > 0) { System.err.println(err); if (retMap != null) retMap.put("error", err); return null; } if (retLst == null && retMap == null) { System.out.println("CLEG transform: " + PT.join(tokens, '>', 0)); cleg = data.abcFor(data.trMat); System.out.println("CLEG transform: " + tokens[0] + ">" + cleg + ">" + tokens[tokens.length - 1]); } return data.trMat; } private static class AssignedSGParams { final Viewer vwr; final boolean mkCalcOnly; /** * special case where the input is just ".", which means "of the current * space group" */ final boolean mkIsAssign; final SB mkSb; boolean mkIgnoreAllSettings; SymmetryInterface mkSym00; BS mkBitset; Object mkParamsOrUC; boolean mkWasNode; int mkIndex; AssignedSGParams(Viewer vwr, boolean isCurrentSG) { this.vwr = vwr; mkCalcOnly = true; mkIsAssign = false; mkSb = null; if (isCurrentSG) mkSym00 = vwr.getOperativeSymmetry(); } AssignedSGParams(Viewer vwr, SymmetryInterface sym00, BS bs, Object paramsOrUC, int index, boolean ignoreAllSettings, SB sb, boolean isAssign) { this.vwr = vwr; this.mkCalcOnly = false; this.mkIndex = index; this.mkSym00 = sym00; this.mkBitset = bs; this.mkParamsOrUC = paramsOrUC; this.mkIgnoreAllSettings = ignoreAllSettings; this.mkSb = sb; this.mkIsAssign = isAssign; } } public CLEG() { // for instantiation only } /** * * @param vwr * @param bs * @param cleg a CLEG expression or "." * @param paramsOrUC * @param sb * @return log message, which indicates an error if ending with "!" */ String transformSpaceGroup(Viewer vwr, BS bs, String cleg, Object paramsOrUC, SB sb) { SymmetryInterface sym0 = vwr.getCurrentUnitCell(); SymmetryInterface sym = vwr.getOperativeSymmetry(); if (sym0 != null && sym != sym0) sym.getUnitCell(sym0.getV0abc(null, null), false, "modelkit"); // change << to >super> if (cleg.indexOf("<") >= 0) { cleg = PT.rep(cleg, "<<", ">super>").replace('<', '>'); } // allow unterminated to mean no change in space group if (cleg.length() == 0 || cleg.endsWith(">")) cleg += "."; ClegData data = new ClegData(vwr.getSymTemp(), PT.split(cleg, ">")); AssignedSGParams asgParams = new AssignedSGParams(vwr, sym, bs, paramsOrUC, 0, false, sb, cleg.equals(".")); String ret = assignSpaceGroup(data, asgParams); if (ret.endsWith("!")) return ret; if (asgParams.mkIsAssign) sb.append(ret); return ret; } /** * Assign the space group and associated unit cell from the MODELKIT * SPACEGROUP command. * * All aspects depend upon having one of: * * - a space group number: n (1-230); will be converted to n.1 * * - a valid n.m Jmol-recognized ITA setting: "13.3" * * - an ITA space group number with a transform: "10:c,a,b" * * - a valid jmolId such as "3:b", which will be converted to an ITA n.m * setting * * - (hopefully unambiguous) Hermann-Mauguin symbol, such as "p 4/n b m :2" * * jmolId and H-M symbols will be converted to ITA n.m format. * * Options include two basic forms: * * 1. non-CLEG format. Just the space group identifier: "13.3", "3:b", etc. * with a UNITCELL option * * MODELKIT SPACEGROUP "P 1 2/1 1" UNITCELL * * where is one of: * * - [a b c alpha beta gamma] * * - [ origin va vb vc ] * * - a transform such as "a,b,c;0,0,1/2" * * The unit cell is absolute or, if a transform, applied to the current unit * cell. * * It is assumed in this case that the given unit cell is the cell for the * setting descibed. The standard-to-desired setting of the name is not * applied to the unit cell. * * 3. CLEG format * * identifier * * identifier [> transform ]n > identifier * * identifier > [ [transform >]n [> identifier ]m ]p > identifier * * for example: * * * * MODELKIT SPACEGROUP "13" * * MODELKIT SPACEGROUP "13:-a-c,b,a" * * MODELKIT SPACEGROUP "P 1 2/n 1" * * MODELKIT SPACEGROUP "P 2/n" * * MODELKIT SPACEGROUP "13.2" // second in Jmol's list of ITA settings for * space group 13 * * MODELKIT SPACEGROUP "13:b2" // Jmol code * * MODELKIT SPACEGROUP "10:b,c,a > a-c,b,2c;0,0,1/2 > 13:a,-a-c,b" // fix - * this was to c2 * * Note that there are two possibilities, regarding current model to start of * chain, and start of chain to end of chain: * * 1) The space group number does not change (this a SETTING change) * * 2) The space group number changes (this is a group >> subgroup or a group * >> supergroup change. * * * * Identifiers with settings are treated as follows: * * - If the space group number is UNCHANGED, for example * * 10 >> 10:c,a,b * * then the final setting is applied to the unit cell as P * UC, and the space * group operations { R } are adjusted using (!P) * R * P, where P is the * transform of the setting and !P is its inverse. * * Note that there is an implicit transform from the CURRENT unit cell and * space group when the two have the same space group number: * * current == 10:b,c,a * * MODELKIT SPACEGROUP 10:c,b,a ... * * Here the current setting will be processed with a transform that brings it * from 10:b,c,a to 10:c,b,a as: * * MODELKIT SPACEGROUP "10:b,c,a > !b,c,a > 10:a,b,c > c,a,b > 10:c,a,b" * * Note that this allows for the direct use of Hermann-Mauguin notation. In * the case of H-M settings WITHIN THE SAME SPACE GROUP, this can be condensed * to: * * MODELKIT P 1 1 2/m >> P 2/m 1 1 * * where ">>" means "you figure it out, Jmol." Basically, this method will * interpret ">>" to mean "switch settings" and will implement that as * * P 1 1 2/m > !b,c,a > c,a,b > P 2/m 1 1 * * * Subgroups and Supergroups * * - If the space group number is CHANGED, for example, * * "10 > ... transforms ... > 13:a,-a-c,b" * * or the current space group of the model is 10, then the final setting is * only applied to the operators of the final space group. Any change in unit * cell setting is assumed to already be accounted for by the indicated * transforms. * * For example, in the case from from P 2/m to P 2/c, no unambiguous implicit * transformation is possible, and an error message will be returned * indicating that a transform is required if none is presented. Such * group-group transforms an be found at the BCS, specifically for ITA default * settings. Thus, we might write: * * P 1 1 2/m >> 10 > a-c,b,2c;0,0,1/2 > 13 >> P 2/c 1 1 * * where we are EXPLICITY indicating that the standard-to-standard ITA * transform: * * 10 > a-c,b,2c;0,0,1/2 > 13 * * as part of the CLEG sequence. In this case, the indicated transforms * between settings will be added to the sequence. * * This ensures that the desired specific conversion is used. * * Additional aspects of the notation include: * * ! prefix - "not" or "inverse of" * * :r setting - abbreviation for * :2/3a+1/3b+1/3c,-1/3a+1/3b+1/3c,-1/3a-2/3b+1/3c * * :h setting - abbreviation for "!r": * :!2/3a+1/3b+1/3c,-1/3a+1/3b+1/3c,-1/3a-2/3b+1/3c * * [xx xx xx] - Hall notation, such as [P 2/1], can also be used. See * http://cci.lbl.gov/sginfo/itvb_2001_table_a1427_hall_symbols.html * * Note that this allows also for fully transformed Hall notation as a * setting. For example: * * 85.4 same as [-p 4a"]:a-b,a+b,c;0,1/4,-1/8 * * Were we are using the Hall symbol for the standard setting of space group * 85 in this case, and applying the appropriate tranform to 85.4 (P 42/m). * * In all cases, the two actions performed include: * * - replacing the current unit cell * * - replacing the space group * * Note that in cases where there is a series of transforms, the initial * action of this method is to convert that sequence to a single overall * transform first. If any identifiers are in the chain other than the first * or last position, they are not checked and are simply ignored. * * @param data * @param asgParams * @return message or error (message ending in "!") */ private static String assignSpaceGroup(ClegData data, AssignedSGParams asgParams) { // case 1: modelkit zap SPACEGROUP sg+setting // display in standard cell orientation, just like loading a file // create a compatible unit cell, then fill it with the space group. // all setting details are handled already. // // case 2: modelkit SPACEGROUP sg+setting // determine the current spacegroup transform and remove it // // modelkit spacegroup 10 // modelkit spacegroup 10 unitcell [a b c alpha beta gamma] // modelkit spacegroup 10:b,c,a // modelkit zap spacegroup 10:b,c,a unitcell [a b c alpha beta gamma] // in this case, we set the unitcell on the SECOND round. // modelkit spacegroup 10>a,b,c>13 // modelkit spacegroup >a,b,c>13 // modelkit spacegroup 10:b,c,a > -b+c,-b-c,a:0,1/2,0 > 13:a,-a-c,b // general initialization Viewer vwr = asgParams.vwr; int index = asgParams.mkIndex; String[] tokens = data.tokens; // that is, we are adding a reference token before a setting boolean initializing = (index < 0); if (initializing) { index = 0; } if (index >= tokens.length) return "invalid CLEG expression!"; boolean haveUCParams = (asgParams.mkParamsOrUC != null); if (tokens.length > 1 && haveUCParams) { return "invalid syntax - can't mix transformations and UNITCELL option!"; } if (index == 0 && !initializing) standardizeTokens(tokens, false); boolean isFinal = (index == tokens.length - 1); String token = tokens[index].trim(); boolean isDot = token.equals("."); boolean nextTransformExplicit = (isFinal ? isDot : isTransformOnly(tokens[index + 1]) && tokens[index + 1].length() > 0); if (index == 0) { if (token.length() == 0 || isDot) { // >> or "." // but not >h>... if (asgParams.mkSym00 == null) { return "no starting space group for CLEG!"; } if (!asgParams.mkIsAssign) { tokens[0] = asgParams.mkSym00.getSpaceGroupClegId(); asgParams.mkIndex = (isDot && isFinal ? 0 : 1); asgParams.mkWasNode = (asgParams.mkIndex == 1); asgParams.mkIgnoreAllSettings = nextTransformExplicit; return assignSpaceGroup(data, asgParams); } } if (asgParams.mkIsAssign) { if (asgParams.mkSym00 == null) return "no starting space group for calculation!"; } } boolean isCalc = (getCalcType(token) != null); //System.out.println("token " + token + " wasNode " + data.wasNode); if ((isCalc || isTransformOnly(token)) != asgParams.mkWasNode) { return "invalid CLEG expression, not node>transform>node>transform>....!"; } asgParams.mkWasNode = !asgParams.mkWasNode; String calcNext = (isCalc ? token : null); if (isCalc) { token = tokens[++index].trim(); isFinal = (index == tokens.length - 1); } // check for "...>..>.", which picks up the previous node name. (Not sure how that works) if (isFinal && isDot && data.getPrevNode() != null) { token = data.getPrevNode().getCleanITAName(); } int pt = token.lastIndexOf(":"); // could be "154:_2" or "R 3 2 :" or 5:a,b,c boolean zapped = (asgParams.mkSym00 == null); boolean isUnknown = false; boolean haveTransform = isTransform(token, false); // r !r h !h but not :r or :h boolean isSetting = (haveTransform && pt >= 0); boolean isTransformOnly = (haveTransform && !isSetting); boolean restarted = false; boolean ignoreNodeTransform = false; boolean ignoreFirstSetting = false; SymmetryInterface sym = data.sym; // initiaize a node if this is not just a transformation ClegNode node = null; if (!isTransformOnly) { data.setSymmetry(sym); node = new ClegNode(data, index, token); if (data.errString != null) return data.errString; } // first time only... if (data.getPrevNode() == null) { // first time through if (!checkFullSyntax(tokens, sym, allow300)) return "invalid CLEG expression!"; // check for a zap if (!asgParams.mkCalcOnly && !isTransformOnly && zapped && !node.isDefaultSetting()) { // modelkit zap spacegroup nnn.m or nnn:ttttt // What about non-reference H-M?? String ita = node.myIta; // easiest is to restart with the default configuration and unit cell // modelkit zap spacegroup ita .... String[] cleg = new String[] { node.specialPrefix + ita }; AssignedSGParams paramsInit = (asgParams.mkCalcOnly ? new AssignedSGParams(vwr, false) : new AssignedSGParams(vwr, null, null, asgParams.mkParamsOrUC, -1, true, asgParams.mkSb, false)); ClegData cdInit = new ClegData(vwr.getSymTemp(), cleg); String err = assignSpaceGroup(cdInit, paramsInit); if (err.endsWith("!")) return err; if (asgParams.mkCalcOnly) { sym = asgParams.mkSym00 = cdInit.sym; data.trMat = cdInit.trMat; } else { asgParams.mkSym00 = vwr.getOperativeSymmetry(); } if (asgParams.mkSym00 == null) return "CLEG spacegroup initialization error!"; zapped = false; restarted = true; } // check for ignoring setting transform due to explicit conversion // modelkit spacegroup 10:c,a,b>a,b,2c>.... // modelkit spacegroup 10>a,b,2c>.... // moswlkir spacegroup >h>... // The first setting in the chain may have to be reversed, but . // we ignore the first setting if we don't need to move the unit cell to it. // : ignoreFirstSetting = (index == 0 // it's the first token and either... && (!asgParams.mkCalcOnly // this isn't just a calculation && !zapped // and the model has a unit cell && nextTransformExplicit // and there is an explicit transformation coming up next // ...or ... || asgParams.mkIsAssign // we are specifically checking the current space group )); String ita0 = (zapped ? null : asgParams.mkSym00.getSpaceGroupClegId()); String trm0 = null; if (!zapped) { if (ita0 == null || ita0.equals("0")) { } else { int pt1 = ita0.indexOf(":"); if (pt1 > 0) { trm0 = ita0.substring(pt1 + 1); ita0 = ita0.substring(0, pt1); pt1 = -1; } if (ignoreFirstSetting) { trm0 = (String) asgParams.mkSym00 .getSpaceGroupInfoObj("itaTransform", null, false, false); } } } // all times, not just the first if (data.trMat == null) { data.trMat = new M4(); data.trMat.setIdentity(); } if (!asgParams.mkIsAssign) { data.setSymmetry(sym); data.setPrevNode(new ClegNode(data, -1, ita0 == null ? null : ita0 + ":" + trm0)); if (asgParams.mkIgnoreAllSettings) data.getPrevNode().disable(); if (!data.getPrevNode().update(data)) return data.errString; } } if (isCalc) { if (!data.getPrevNode().setCalcNext(data, calcNext)) { return data.errString; } } if (isTransformOnly) { if (isFinal) { isUnknown = true;// now what? return "CLEG pathway is incomplete!"; } // not a setting, not a node; could be >> if (token.length() > 0) data.addTransform(index, token); // ITERATE ... ++asgParams.mkIndex; return assignSpaceGroup(data, asgParams); } // thus ends consideration of chain of transforms // first or last. //boolean applySetting = (!asgParams.mkCalcOnly || index > 0); if (!ignoreFirstSetting) { data.setSymmetry(sym); if (ignoreNodeTransform) node.disable(); // if (!applySetting) // node.disableSetting(); if (!node.update(data)) return data.errString; if (isFinal && isDot) { data.setNodeTransform(node); } data.updateTokens(node); } if (!isFinal) { asgParams.mkIndex++; data.setPrevNode(node); return assignSpaceGroup(data, asgParams); } // handle parameters float[] params = null; T3[] oabc = null; T3 origin = null; params = (!haveUCParams || !AU.isAD(asgParams.mkParamsOrUC) ? null : (float[]) asgParams.mkParamsOrUC); if (zapped) { sym.setUnitCellFromParams( params == null ? new float[] { 10, 10, 10, 90, 90, 90 } : params, false, Float.NaN); asgParams.mkParamsOrUC = null; haveUCParams = false; } if (haveUCParams) { // have UNITCELL params, either [a b c..] or [o a b c] or 'a,b,c:...' if (AU.isAD(asgParams.mkParamsOrUC)) { params = (float[]) asgParams.mkParamsOrUC; } else { oabc = (T3[]) asgParams.mkParamsOrUC; origin = oabc[0]; } } else if (!zapped) { data.setSymmetry(sym = asgParams.mkSym00); if (data.trMat == null) { data.trMat = new M4(); data.trMat.setIdentity(); } oabc = sym.getV0abc(new Object[] { data.trMat }, null); origin = oabc[0]; } if (oabc != null) { params = sym.getUnitCell(oabc, false, "assign").getUnitCellParams(); if (origin == null) origin = oabc[0]; } // ready to roll.... boolean isP1 = (token.equalsIgnoreCase("P1") || token.equals("ITA/1.1")); // TODO: P1 is only for space groups need isP1(token) or node.isP1 try { BS bsAtoms; boolean noAtoms; int modelIndex = -1; if (asgParams.mkCalcOnly) { asgParams.mkBitset = bsAtoms = new BS(); noAtoms = true; } else { if (asgParams.mkBitset != null && asgParams.mkBitset.isEmpty()) return "no atoms specified!"; // limit the atoms to this model if bs is null bsAtoms = vwr.getThisModelAtoms(); BS bsCell = (isP1 ? bsAtoms : SV.getBitSet( vwr.evaluateExpressionAsVariable("{within(unitcell)}"), true)); if (asgParams.mkBitset == null) { asgParams.mkBitset = bsAtoms; } if (asgParams.mkBitset != null) { bsAtoms.and(asgParams.mkBitset); if (!isP1) bsAtoms.and(bsCell); } noAtoms = bsAtoms.isEmpty(); modelIndex = (noAtoms && vwr.am.cmi < 0 ? 0 : noAtoms ? vwr.am.cmi : vwr.ms.at[bsAtoms.nextSetBit(0)].getModelIndex()); if (!asgParams.mkIsAssign) vwr.ms.getModelAuxiliaryInfo(modelIndex) .remove(JC.INFO_SPACE_GROUP_INFO); } if (!zapped && !asgParams.mkIsAssign) { // store trm0 for SpaceGroupFinder sym.saveOrRetrieveTransformMatrix(data.trMat); } if (params == null) params = sym.getUnitCellMultiplied().getUnitCellParams(); isUnknown |= asgParams.mkIsAssign; @SuppressWarnings("unchecked") Map sgInfo = (noAtoms && isUnknown ? null : (Map) vwr.findSpaceGroup(sym, isUnknown ? bsAtoms : null, isUnknown ? null : node.getName(), params, origin, oabc, JC.SG_IS_ASSIGN | (asgParams.mkCalcOnly ? JC.SG_CALC_ONLY : 0) | (!zapped || asgParams.mkIsAssign ? 0 : JC.SG_FROM_SCRATCH))); if (sgInfo == null) { return "Space group " + node.getName() + " is unknown or not compatible!"; } if (oabc == null || zapped) oabc = (P3[]) sgInfo.get("unitcell"); token = (String) sgInfo.get("name"); String jmolId = (String) sgInfo.get("jmolId"); BS basis = (BS) sgInfo.get("basis"); SpaceGroup sg = (SpaceGroup) sgInfo.remove("sg"); sym.getUnitCell(oabc, false, null); sym.setSpaceGroupTo(sg == null ? jmolId : sg); sym.setSpaceGroupName(token); if (asgParams.mkCalcOnly) { data.setSymmetry(sym); return "OK"; } // not a calculation if (basis == null) { basis = sym.removeDuplicates(vwr.ms, bsAtoms, true); } vwr.ms.setSpaceGroup(modelIndex, sym, basis); if (asgParams.mkIsAssign) { return token; } if (zapped || restarted) { vwr.runScript( "unitcell on; center unitcell;axes unitcell; axes 0.1; axes on;" + "set perspectivedepth false;moveto 0 axis c1;draw delete;show spacegroup"); } // don't change the first line of this message -- it will be used in packing. String finalTransform = data.abcFor(data.trMat); if (!initializing) { standardizeTokens(tokens, true); String msg = PT.join(tokens, '>', 0) + (basis.isEmpty() ? "" : "\n basis=" + basis); System.out.println("CLEG=" + msg); asgParams.mkSb.append(msg).append("\n"); } return finalTransform; } catch (Exception e) { if (!Viewer.isJS) e.printStackTrace(); return e.getMessage() + "!"; } } }