servalcat 0.4.60__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of servalcat might be problematic. Click here for more details.

Files changed (44) hide show
  1. servalcat/__init__.py +10 -0
  2. servalcat/__main__.py +120 -0
  3. servalcat/ext.cp312-win_amd64.pyd +0 -0
  4. servalcat/refine/__init__.py +0 -0
  5. servalcat/refine/cgsolve.py +100 -0
  6. servalcat/refine/refine.py +733 -0
  7. servalcat/refine/refine_geom.py +207 -0
  8. servalcat/refine/refine_spa.py +327 -0
  9. servalcat/refine/refine_xtal.py +242 -0
  10. servalcat/refine/spa.py +132 -0
  11. servalcat/refine/xtal.py +227 -0
  12. servalcat/refmac/__init__.py +0 -0
  13. servalcat/refmac/exte.py +182 -0
  14. servalcat/refmac/refmac_keywords.py +536 -0
  15. servalcat/refmac/refmac_wrapper.py +360 -0
  16. servalcat/spa/__init__.py +0 -0
  17. servalcat/spa/fofc.py +462 -0
  18. servalcat/spa/fsc.py +385 -0
  19. servalcat/spa/localcc.py +188 -0
  20. servalcat/spa/realspcc_from_var.py +128 -0
  21. servalcat/spa/run_refmac.py +961 -0
  22. servalcat/spa/shift_maps.py +293 -0
  23. servalcat/spa/shiftback.py +137 -0
  24. servalcat/spa/translate.py +129 -0
  25. servalcat/utils/__init__.py +35 -0
  26. servalcat/utils/commands.py +1277 -0
  27. servalcat/utils/fileio.py +745 -0
  28. servalcat/utils/generate_operators.py +296 -0
  29. servalcat/utils/hkl.py +699 -0
  30. servalcat/utils/logger.py +116 -0
  31. servalcat/utils/maps.py +340 -0
  32. servalcat/utils/model.py +774 -0
  33. servalcat/utils/refmac.py +747 -0
  34. servalcat/utils/restraints.py +605 -0
  35. servalcat/utils/symmetry.py +295 -0
  36. servalcat/xtal/__init__.py +0 -0
  37. servalcat/xtal/french_wilson.py +250 -0
  38. servalcat/xtal/run_refmac_small.py +240 -0
  39. servalcat/xtal/sigmaa.py +1403 -0
  40. servalcat-0.4.60.dist-info/METADATA +56 -0
  41. servalcat-0.4.60.dist-info/RECORD +44 -0
  42. servalcat-0.4.60.dist-info/WHEEL +5 -0
  43. servalcat-0.4.60.dist-info/entry_points.txt +4 -0
  44. servalcat-0.4.60.dist-info/licenses/LICENSE +373 -0
@@ -0,0 +1,747 @@
1
+ """
2
+ Author: "Keitaro Yamashita, Garib N. Murshudov"
3
+ MRC Laboratory of Molecular Biology
4
+
5
+ This software is released under the
6
+ Mozilla Public License, version 2.0; see LICENSE.
7
+ """
8
+ from __future__ import absolute_import, division, print_function, generators
9
+ import gemmi
10
+ import numpy
11
+ import subprocess
12
+ import shlex
13
+ import json
14
+ import copy
15
+ import re
16
+ import os
17
+ import string
18
+ import itertools
19
+ import tempfile
20
+ from servalcat.utils import logger
21
+ from servalcat.utils import fileio
22
+
23
+ re_version = re.compile("#.* Refmac *version ([^ ]+) ")
24
+ re_error = re.compile('(warn|error *[:]|error *==|^error)', re.IGNORECASE)
25
+ re_outlier_start = re.compile("\*\*\*\*.*outliers")
26
+
27
+ def check_version(exe="refmac5"):
28
+ ver = ()
29
+ try:
30
+ output = ""
31
+ p = subprocess.Popen([exe], shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
32
+ universal_newlines=True)
33
+ p.stdin.write("end\n")
34
+ p.stdin.close()
35
+ for l in iter(p.stdout.readline, ""):
36
+ output += l
37
+ r_ver = re_version.search(l)
38
+ if r_ver:
39
+ logger.writeln("Refmac version: {}".format(r_ver.group(1)))
40
+ ver = tuple(map(int, r_ver.group(1).split(".")))
41
+ p.wait()
42
+ if not ver and output:
43
+ logger.writeln("\nError: failed to check the Refmac version. The raw output:\n")
44
+ logger.writeln(output)
45
+ except OSError as e:
46
+ logger.writeln("Error: Cannot execute {}".format(exe))
47
+ return ver
48
+ # check_version()
49
+
50
+ def ensure_ccp4scr():
51
+ tmpdir = os.environ.get("CCP4_SCR")
52
+ if tmpdir:
53
+ if os.path.isdir(tmpdir): # TODO check writability
54
+ try:
55
+ t = tempfile.TemporaryFile(dir=tmpdir)
56
+ t.close()
57
+ return
58
+ except OSError:
59
+ logger.writeln("Warning: cannot write files in CCP4_SCR= {}".format(tmpdir))
60
+ else:
61
+ try:
62
+ os.makedirs(tmpdir)
63
+ return
64
+ except:
65
+ logger.writeln("Warning: cannot create CCP4_SCR= {}".format(tmpdir))
66
+
67
+ os.environ["CCP4_SCR"] = tempfile.mkdtemp(prefix="ccp4tmp")
68
+ logger.writeln("Updated CCP4_SCR= {}".format(os.environ["CCP4_SCR"]))
69
+ # ensure_ccp4scr()
70
+
71
+ def external_restraints_json_to_keywords(json_in):
72
+ ret = []
73
+ with open(json_in) as f: exte_list = json.load(f)
74
+ for e in exte_list:
75
+ if "use" in e:
76
+ ret.append("EXTERNAL USE {}".format(e["use"]))
77
+ if "dmax" in e:
78
+ ret.append("EXTERNAL DMAX {0}".format(e["dmax"]))
79
+ if "weight_scale" in e:
80
+ ret.append("EXTERNAL WEIGHT SCALE {0}".format(e["weight_scale"]))
81
+ if "weight_gmwt" in e:
82
+ ret.append("EXTERNAL WEIGHT GMWT {0}".format(e["weight_gmwt"]))
83
+ if "file" in e:
84
+ ret.append("@"+e["file"])
85
+
86
+ return "\n".join(ret) + "\n"
87
+ # external_restraints_json_to_keywords()
88
+
89
+ def read_tls_file(tlsin):
90
+ # TODO sort out L/S units - currently use Refmac tlsin/out as is
91
+ # TODO change to gemmi::TlsGroup?
92
+
93
+ groups = []
94
+ with open(tlsin) as ifs:
95
+ for l in ifs:
96
+ l = l.strip()
97
+ if l.startswith("TLS"):
98
+ title = l[4:]
99
+ groups.append(dict(title=title, ranges=[], origin=None, T=None, L=None, S=None))
100
+ elif l.startswith("RANG"):
101
+ r = l[l.index(" "):].strip()
102
+ groups[-1]["ranges"].append(r)
103
+ elif l.startswith("ORIG"):
104
+ try:
105
+ groups[-1]["origin"] = gemmi.Position(*(float(x) for x in l.split()[1:]))
106
+ except:
107
+ raise ValueError("Problem with TLS file: {}".format(l))
108
+ elif l.startswith("T "):
109
+ try:
110
+ groups[-1]["T"] = [float(x) for x in l.split()[1:7]]
111
+ except:
112
+ raise ValueError("Problem with TLS file: {}".format(l))
113
+ elif l.startswith("L "):
114
+ try:
115
+ groups[-1]["L"] = [float(x) for x in l.split()[1:7]]
116
+ except:
117
+ raise ValueError("Problem with TLS file: {}".format(l))
118
+ elif l.startswith("S "):
119
+ try:
120
+ groups[-1]["S"] = [float(x) for x in l.split()[1:10]]
121
+ except:
122
+ raise ValueError("Problem with TLS file: {}".format(l))
123
+
124
+ return groups
125
+ # read_tls_file()
126
+
127
+ def write_tls_file(groups, tlsout):
128
+ with open(tlsout, "w") as f:
129
+ for g in groups:
130
+ f.write("TLS {}\n".format(g["title"]))
131
+ for r in g["ranges"]:
132
+ f.write("RANGE {}\n".format(r))
133
+ if g["origin"] is not None:
134
+ f.write("ORIGIN ")
135
+ f.write(" ".join("{:8.4f}".format(x) for x in g["origin"].tolist()))
136
+ f.write("\n")
137
+ for k in "TLS":
138
+ if g[k] is not None:
139
+ f.write("{:4s}".format(k))
140
+ f.write(" ".join("{:8.4f}".format(x) for x in g[k]))
141
+ f.write("\n")
142
+ # write_tls_file()
143
+
144
+ class FixForRefmac:
145
+ """
146
+ Workaround for Refmac limitations
147
+ - microheterogeneity
148
+ - residue number > 9999
149
+
150
+ XXX fix external restraints accordingly
151
+ TODO fix _struct_conf, _struct_sheet_range, _pdbx_struct_sheet_hbond
152
+ """
153
+ def __init__(self):
154
+ self.MAXNUM = 9999
155
+ self.fixes = []
156
+ self.resn_old_new = []
157
+
158
+ def fix_before_topology(self, st, topo, fix_microheterogeneity=True, fix_resimax=True, fix_nonpolymer=True, add_gaps=False):
159
+ self.chainids = set(chain.name for chain in st[0])
160
+ if fix_microheterogeneity:
161
+ self.fix_microheterogeneity(st, topo)
162
+ if add_gaps:
163
+ self.add_gaps(st, topo)
164
+ if fix_resimax: # This modifies chains, so topo will be broken
165
+ self.fix_too_large_seqnum(st, topo)
166
+ if fix_nonpolymer: # This modifies chains, so topo will be broken
167
+ self.fix_nonpolymer(st)
168
+
169
+ def new_chain_id(self, original_chain_id):
170
+ # decide new chain ID
171
+ for i in itertools.count(start=1):
172
+ new_id = "{}{}".format(original_chain_id, i)
173
+ if new_id not in self.chainids:
174
+ self.chainids.add(new_id)
175
+ return new_id
176
+
177
+ def fix_metadata(self, st, changedict):
178
+ # fix connections
179
+ # changedict = dict(changes)
180
+ aa2tuple = lambda aa: (aa.chain_name, aa.res_id.seqid.num, chr(ord(aa.res_id.seqid.icode)|0x20))
181
+ for con in st.connections:
182
+ for aa in (con.partner1, con.partner2):
183
+ changeto = changedict.get(aa2tuple(aa))
184
+ if changeto is not None:
185
+ aa.chain_name = changeto[0]
186
+ aa.res_id.seqid.num = changeto[1]
187
+ aa.res_id.seqid.icode = changeto[2]
188
+
189
+ def add_gaps(self, st, topo):
190
+ # Refmac (as of 5.8.0352) has a bug that makes two links for IAS (IAS-pept and usual TRANS/CIS)
191
+ # However this implementation is even more harmful.. if gap is inserted to real gaps then necessary p link is also gone!
192
+ for chain in st[0]:
193
+ rs = chain.get_polymer()
194
+ for i in range(1, len(rs)):
195
+ res0 = rs[i-1]
196
+ res = rs[i]
197
+ links = topo.links_to_previous(res)
198
+ if len(links) == 0 or links[0].link_id in ("gap", "?"):
199
+ con = gemmi.Connection()
200
+ con.asu = gemmi.Asu.Same
201
+ con.type = gemmi.ConnectionType.Unknown
202
+ con.link_id = "gap"
203
+ con.partner1 = gemmi.AtomAddress(chain.name, res0.seqid, res0.name, "", "\0")
204
+ con.partner2 = gemmi.AtomAddress(chain.name, res.seqid, res.name, "", "\0")
205
+ logger.writeln("Refmac workaround (gap link): {}".format(con))
206
+ st.connections.append(con)
207
+
208
+ def fix_microheterogeneity(self, st, topo):
209
+ mh_res = []
210
+ chains = []
211
+ icodes = {} # to avoid overlaps
212
+ modifications = [] # return value
213
+
214
+ # Check if microheterogeneity exists
215
+ for chain in st[0]:
216
+ for rg in chain.get_polymer().residue_groups():
217
+ if len(rg) > 1:
218
+ ress = [r for r in rg]
219
+ chains.append(chain.name)
220
+ mh_res.append(ress)
221
+ ress_str = "/".join([str(r) for r in ress])
222
+ logger.writeln("Microheterogeneity detected in chain {}: {}".format(chain.name, ress_str))
223
+
224
+ if not mh_res: return
225
+
226
+ for chain in st[0]:
227
+ for res in chain:
228
+ if res.seqid.icode != " ":
229
+ icodes.setdefault(chain.name, {}).setdefault(res.seqid.num, []).append(res.seqid.icode)
230
+
231
+ def append_links(bond, prr, toappend):
232
+ atoms = bond.atoms
233
+ assert len(atoms) == 2
234
+ found = None
235
+ for i in range(2):
236
+ if any(filter(lambda ra: atoms[i]==ra, prr)): found = i
237
+ if found is not None:
238
+ toappend.append([atoms[i], atoms[1-i]]) # prev atom, current atom
239
+ # append_links()
240
+
241
+ mh_res_all = sum(mh_res, [])
242
+ mh_link = {}
243
+
244
+ # Check links
245
+ for chain in st[0]:
246
+ for res in chain:
247
+ # If this residue is microheterogeneous
248
+ if res in mh_res_all:
249
+ for link in topo.links_to_previous(res):
250
+ mh_link.setdefault(id(res), []).append([link.res1, "prev", link.link_id, []])
251
+ append_links(topo.first_bond_in_link(link), link.res1, mh_link[id(res)][-1][-1])
252
+
253
+ # Check if previous residue(s) is microheterogeneous
254
+ for link in topo.links_to_previous(res):
255
+ prr = link.res1
256
+ if prr in mh_res_all:
257
+ mh_link.setdefault(id(prr), []).append([res, "next", link.link_id, []])
258
+ append_links(topo.first_bond_in_link(link), prr, mh_link[id(prr)][-1][-1])
259
+
260
+ # Change IDs
261
+ for chain_name, rr in zip(chains, mh_res):
262
+ chars = string.ascii_uppercase
263
+ # avoid already used inscodes
264
+ if chain_name in icodes and rr[0].seqid.num in icodes[chain_name]:
265
+ used_codes = set(icodes[chain_name][rr[0].seqid.num])
266
+ chars = list(filter(lambda x: x not in used_codes, chars))
267
+ for ir, r in enumerate(rr[1:]):
268
+ modifications.append([(chain_name, r.seqid.num, r.seqid.icode),
269
+ (chain_name, r.seqid.num, chars[ir])])
270
+ r.seqid.icode = chars[ir]
271
+
272
+ logger.writeln("DEBUG: mh_link= {}".format(mh_link))
273
+ # Update connections (LINKR)
274
+ for chain_name, rr in zip(chains, mh_res):
275
+ for r in rr:
276
+ for p in mh_link.get(id(r), []):
277
+ for atoms in p[-1]:
278
+ con = gemmi.Connection()
279
+ con.asu = gemmi.Asu.Same
280
+ con.type = gemmi.ConnectionType.Covale
281
+ con.link_id = p[2]
282
+ if p[1] == "prev":
283
+ p1 = gemmi.AtomAddress(chain_name, p[0].seqid, p[0].name, atoms[1].name, atoms[1].altloc)
284
+ p2 = gemmi.AtomAddress(chain_name, r.seqid, r.name, atoms[0].name, atoms[0].altloc)
285
+ else:
286
+ p1 = gemmi.AtomAddress(chain_name, r.seqid, r.name, atoms[1].name, atoms[1].altloc)
287
+ p2 = gemmi.AtomAddress(chain_name, p[0].seqid, p[0].name, atoms[0].name, atoms[0].altloc)
288
+
289
+ con.partner1 = p1
290
+ con.partner2 = p2
291
+ logger.writeln(" Adding link: {}".format(con))
292
+ st.connections.append(con)
293
+ for r1, r2 in itertools.combinations(rr, 2):
294
+ for a1 in set([a.altloc for a in r1]):
295
+ for a2 in set([a.altloc for a in r2]):
296
+ con = gemmi.Connection()
297
+ con.asu = gemmi.Asu.Same
298
+ con.link_id = "gap"
299
+ # XXX altloc will be ignored when atom does not match.. grrr
300
+ con.partner1 = gemmi.AtomAddress(chain_name, r1.seqid, r1.name, "", a1)
301
+ con.partner2 = gemmi.AtomAddress(chain_name, r2.seqid, r2.name, "", a2)
302
+ st.connections.append(con)
303
+
304
+ self.fixes.append(modifications)
305
+ # fix_microheterogeneity()
306
+
307
+ def fix_nonpolymer(self, st):
308
+ # Refmac (as of 5.8.0352) has a bug that links non-neighbouring nucleotides
309
+ # It only happens with mmCIF file
310
+ newchains = []
311
+ changes = []
312
+ for chain in st[0]:
313
+ polymer = chain.get_polymer()
314
+ if len(polymer) == len(chain): continue
315
+ if len(polymer) == 0: continue
316
+ del_idxes = []
317
+ newchains.append(gemmi.Chain(self.new_chain_id(chain.name)))
318
+ logger.writeln("Refmac workaround (nonpolymer-fix) {} => {} ({} residues)".format(chain.name, newchains[-1].name,
319
+ len(chain) - len(polymer)))
320
+ for i, res in enumerate(chain):
321
+ if res in polymer: continue
322
+ newchains[-1].add_residue(res)
323
+ del_idxes.append(i)
324
+ changes.append([(chain.name, res.seqid.num, res.seqid.icode),
325
+ (newchains[-1].name, newchains[-1][-1].seqid.num, newchains[-1][-1].seqid.icode)])
326
+ for i in reversed(del_idxes):
327
+ del chain[i]
328
+
329
+ for c in newchains:
330
+ st[0].add_chain(c)
331
+ if changes:
332
+ st.remove_empty_chains()
333
+ self.fix_metadata(st, dict(changes))
334
+ self.fixes.append(changes)
335
+
336
+ def fix_too_large_seqnum(self, st, topo):
337
+ # Refmac cannot handle residue id > 9999
338
+ # What to do:
339
+ # - move to new chains
340
+ # - modify link records (and others?)
341
+ # - add link record if needed
342
+ newchains = []
343
+ changes = []
344
+
345
+ for chain in st[0]:
346
+ maxseqnum = max([r.seqid.num for r in chain])
347
+ if maxseqnum > self.MAXNUM:
348
+ offset = 0
349
+ #target = [res for res in chain if res.seqid.num > 9999]
350
+ del_idxes = []
351
+ for ires, res in enumerate(chain):
352
+ if res.seqid.num <= self.MAXNUM: continue
353
+ if res.seqid.num - offset > self.MAXNUM:
354
+ newchains.append(gemmi.Chain(self.new_chain_id(chain.name)))
355
+ offset = res.seqid.num - 1
356
+ # need to keep link to previous residue if exists
357
+ for link in topo.links_to_previous(res):
358
+ logger.writeln("Link: {} {} {} alt= {} {}".format(link.link_id, link.res1, link.res2,
359
+ link.alt1, link.alt2))
360
+
361
+ con = gemmi.Connection()
362
+ con.type = gemmi.ConnectionType.Covale
363
+ con.link_id = link.link_id
364
+ #return link
365
+ bond = topo.first_bond_in_link(link)
366
+ if bond is not None:
367
+ con.partner1 = gemmi.AtomAddress(chain.name, link.res1.seqid, link.res1.name, bond.atoms[0].name, bond.atoms[0].altloc)
368
+ con.partner2 = gemmi.AtomAddress(chain.name, link.res2.seqid, link.res2.name, bond.atoms[1].name, bond.atoms[1].altloc)
369
+ st.connections.append(con)
370
+
371
+ newchains[-1].add_residue(res)
372
+ newchains[-1][-1].seqid.num -= offset
373
+ del_idxes.append(ires)
374
+ prev = chain[ires-1].seqid if ires > 0 else None
375
+ changes.append([(chain.name, res.seqid.num, res.seqid.icode),
376
+ (newchains[-1].name, newchains[-1][-1].seqid.num, newchains[-1][-1].seqid.icode)])
377
+ logger.writeln("Refmac workaround (too large seq) {} => {} {}".format(changes[-1][0], changes[-1][1], res.name))
378
+
379
+ for i in reversed(del_idxes):
380
+ del chain[i]
381
+
382
+ for c in newchains:
383
+ st[0].add_chain(c)
384
+ if changes:
385
+ st.remove_empty_chains()
386
+ self.fix_metadata(st, dict(changes))
387
+ self.fixes.append(changes)
388
+
389
+ def fix_long_resnames(self, st):
390
+ # this function should be called separately (after preparing topology)
391
+ st.shorten_ccd_codes()
392
+ self.resn_old_new = [x for x in st.shortened_ccd_codes]
393
+
394
+ def fix_model(self, st, changedict):
395
+ chain_newid = set()
396
+ for chain in st[0]:
397
+ for res in chain:
398
+ changeto = changedict.get((chain.name, res.seqid.num, res.seqid.icode))
399
+ if changeto is not None:
400
+ logger.writeln("back: {} {} to {}".format(chain.name, res.seqid, changeto))
401
+ #chain.name = changeto[0] # this is ok when modify back
402
+ chain_newid.add((chain, changeto[0]))
403
+ res.seqid.num = changeto[1]
404
+ res.seqid.icode = changeto[2]
405
+
406
+ for chain, newid in chain_newid:
407
+ chain.name = newid
408
+ st.merge_chain_parts()
409
+ self.fix_metadata(st, changedict)
410
+
411
+ def modify_back(self, st):
412
+ for fix in reversed(self.fixes):
413
+ reschanges = dict([x[::-1] for x in fix])
414
+ self.fix_model(st, reschanges)
415
+
416
+ if self.resn_old_new:
417
+ st.shortened_ccd_codes = self.resn_old_new
418
+ st.restore_full_ccd_codes()
419
+
420
+
421
+ class Refmac:
422
+ def __init__(self, **kwargs):
423
+ self.prefix = "refmac"
424
+ self.hklin = self.xyzin = ""
425
+ self.source = "electron"
426
+ self.lab_f = None
427
+ self.lab_sigf = None
428
+ self.lab_phi = None
429
+ self.libin = None
430
+ self.tlsin = None
431
+ self.hydrogen = "all"
432
+ self.hout = False
433
+ self.ncycle = 10
434
+ self.tlscycle = 0
435
+ self.resolution = None
436
+ self.weight_matrix = None
437
+ self.weight_auto_scale = None
438
+ self.bfactor = None
439
+ self.jellybody = None
440
+ self.jellybody_sigma, self.jellybody_dmax = 0.01, 4.2
441
+ self.ncsr = None
442
+ self.shake = None
443
+ self.keyword_files = []
444
+ self.keywords = []
445
+ self.external_restraints_json = None
446
+ self.exe = "refmac5"
447
+ self.monlib_path = None
448
+ self.keep_chain_ids = False
449
+ self.show_log = False # summary only if false
450
+ self.global_mode = kwargs.get("global_mode")
451
+
452
+ for k in kwargs:
453
+ if k == "args":
454
+ self.init_from_args(kwargs["args"])
455
+ else:
456
+ setattr(self, k, kwargs[k])
457
+
458
+ ensure_ccp4scr()
459
+ # __init__()
460
+
461
+ def init_from_args(self, args):
462
+ self.hklin = args.mtz
463
+ self.xyzin = args.model
464
+ self.libin = args.ligand
465
+ self.tlsin = args.tlsin
466
+ self.ncycle = args.ncycle
467
+ self.tlscycle = args.tlscycle
468
+ self.lab_f = args.lab_f
469
+ self.lab_phi = args.lab_phi
470
+ self.lab_sigf = args.lab_sigf
471
+ self.hydrogen = args.hydrogen
472
+ self.hout = args.hout
473
+ self.ncsr = args.ncsr
474
+ self.bfactor = args.bfactor
475
+ self.jellybody = args.jellybody
476
+ self.jellybody_sigma, self.jellybody_dmax = args.jellybody_params
477
+ self.resolution = args.resolution
478
+ self.weight_auto_scale = args.weight_auto_scale
479
+ self.keyword_files = args.keyword_file
480
+ self.keywords = args.keywords
481
+ self.external_restraints_json = args.external_restraints_json
482
+ self.exe = args.exe
483
+ self.show_log = args.show_refmac_log
484
+ self.monlib_path = args.monlib
485
+ # init_from_args()
486
+
487
+ def copy(self, **kwargs):
488
+ ret = copy.deepcopy(self)
489
+ for k in kwargs:
490
+ setattr(ret, k, kwargs[k])
491
+
492
+ return ret
493
+ # copy()
494
+
495
+ def set_libin(self, ligands):
496
+ if not ligands: return
497
+ if len(ligands) > 1:
498
+ mcif = "merged_ligands.cif" # XXX directory!
499
+ logger.writeln("Merging ligand cif files: {}".format(ligands))
500
+ fileio.merge_ligand_cif(ligands, mcif)
501
+ self.libin = mcif
502
+ else:
503
+ self.libin = ligands[0]
504
+ # set_libin()
505
+
506
+
507
+ def make_keywords(self):
508
+ ret = ""
509
+ labin = []
510
+ if self.lab_f: labin.append("FP={}".format(self.lab_f))
511
+ if self.lab_sigf: labin.append("SIGFP={}".format(self.lab_sigf))
512
+ if self.lab_phi: labin.append("PHIB={}".format(self.lab_phi))
513
+ if labin:
514
+ ret += "labin {}\n".format(" ".join(labin))
515
+
516
+
517
+ ret += "make hydr {}\n".format(self.hydrogen)
518
+ ret += "make hout {}\n".format("yes" if self.hout else "no")
519
+
520
+ if self.global_mode == "spa":
521
+ ret += "solvent no\n"
522
+ ret += "scale lssc isot\n"
523
+ ret += "source em mb\n"
524
+ elif self.source == "electron":
525
+ ret += "source ec mb\n"
526
+ elif self.source == "neutron":
527
+ ret += "source n\n"
528
+
529
+ ret += "ncycle {}\n".format(self.ncycle)
530
+ if self.resolution is not None:
531
+ ret += "reso {}\n".format(self.resolution)
532
+ if self.weight_matrix is not None:
533
+ ret += "weight matrix {}\n".format(self.weight_matrix)
534
+ elif self.weight_auto_scale is not None:
535
+ ret += "weight auto {:.2e}\n".format(self.weight_auto_scale)
536
+ else:
537
+ ret += "weight auto\n"
538
+
539
+ if self.bfactor is not None:
540
+ ret += "bfactor set {}\n".format(self.bfactor)
541
+ if self.jellybody:
542
+ ret += "ridge dist sigma {:.3e}\n".format(self.jellybody_sigma)
543
+ ret += "ridge dist dmax {:.2e}\n".format(self.jellybody_dmax)
544
+ if self.ncsr:
545
+ ret += "ncsr {}\n".format(self.ncsr)
546
+ if self.shake:
547
+ ret += "rand {}\n".format(self.shake)
548
+ if self.tlscycle > 0:
549
+ ret += "refi tlsc {}\n".format(self.tlscycle)
550
+ ret += "tlsout addu\n"
551
+ if self.keep_chain_ids:
552
+ ret += "pdbo keep auth\n"
553
+
554
+ if self.external_restraints_json:
555
+ ret += external_restraints_json_to_keywords(self.external_restraints_json)
556
+
557
+ if self.keyword_files:
558
+ for f in self.keyword_files:
559
+ ret += "@{}\n".format(f)
560
+
561
+ if self.keywords:
562
+ ret += "\n".join(self.keywords).strip() + "\n"
563
+
564
+ return ret
565
+ # make_keywords()
566
+
567
+ def xyzout(self): return self.prefix + ".pdb"
568
+ def hklout(self): return self.prefix + ".mtz"
569
+ def tlsout(self): return self.prefix + ".tls"
570
+
571
+ def make_cmd(self):
572
+ cmd = [self.exe]
573
+ cmd.extend(["hklin", self.hklin])
574
+ cmd.extend(["hklout", self.hklout()])
575
+ cmd.extend(["xyzin", self.xyzin])
576
+ cmd.extend(["xyzout", self.xyzout()])
577
+ if self.libin:
578
+ cmd.extend(["libin", self.libin])
579
+ if self.tlsin:
580
+ cmd.extend(["tlsin", self.tlsin])
581
+ if self.tlscycle > 0:
582
+ cmd.extend(["tlsout", self.tlsout()])
583
+ if self.source == "neutron":
584
+ cmd.extend(["atomsf", os.path.join(os.environ["CLIBD"], "atomsf_neutron.lib")])
585
+
586
+ return cmd
587
+ # make_cmd()
588
+
589
+ def run_refmac(self, write_summary_json=True):
590
+ cmd = self.make_cmd()
591
+ stdin = self.make_keywords()
592
+ with open(self.prefix+".inp", "w") as ofs: ofs.write(stdin)
593
+
594
+ logger.writeln("Running REFMAC5..")
595
+ logger.writeln("{} <<__eof__ > {}".format(" ".join(shlex.quote(x) for x in cmd), self.prefix+".log"))
596
+ logger.write(stdin)
597
+ logger.writeln("__eof__")
598
+
599
+ env = os.environ
600
+ if self.monlib_path: env["CLIBD_MON"] = os.path.join(self.monlib_path, "") # should end with /
601
+
602
+ p = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
603
+ universal_newlines=True, env=env)
604
+ p.stdin.write(stdin)
605
+ p.stdin.close()
606
+
607
+ log = open(self.prefix+".log", "w")
608
+ cycle = 0
609
+ re_cycle_table = re.compile("Cycle *([0-9]+). Rfactor analysis")
610
+ re_actual_weight = re.compile("Actual weight *([^ ]+) *is applied to the X-ray term")
611
+ rmsbond = ""
612
+ rmsangle = ""
613
+ log_delay = []
614
+ summary_write = (lambda x: log_delay.append(x)) if self.show_log else logger.writeln
615
+ outlier_flag = False
616
+ last_table_flag = False
617
+ last_table_keys = []
618
+ occ_flag = False
619
+ occ_cycles = 0
620
+ ret = {"version":None,
621
+ "cycles": [{"cycle":i} for i in range(self.ncycle+self.tlscycle+1)],
622
+ } # metadata
623
+
624
+ for l in iter(p.stdout.readline, ""):
625
+ log.write(l)
626
+
627
+ if self.show_log:
628
+ print(l, end="")
629
+
630
+ r_ver = re_version.search(l)
631
+ if r_ver:
632
+ ret["version"] = r_ver.group(1)
633
+ summary_write("Starting Refmac {} (PID: {})".format(r_ver.group(1), p.pid))
634
+
635
+ # print error/warning
636
+ r_err = re_error.search(l)
637
+ if r_err:
638
+ if self.global_mode == "spa":
639
+ if "Figure of merit of phases has not been assigned" in l:
640
+ continue
641
+ elif "They will be assumed to be equal to 1.0" in l:
642
+ continue
643
+ summary_write(l.rstrip())
644
+
645
+ # print outliers
646
+ r_outl = re_outlier_start.search(l)
647
+ if r_outl:
648
+ outlier_flag = True
649
+ summary_write(l.rstrip())
650
+ elif outlier_flag:
651
+ if l.strip() == "" or "monitored" in l or "dev=" in l or "sigma=" in l.lower() or "sigma.=" in l:
652
+ summary_write(l.rstrip())
653
+ else:
654
+ outlier_flag = False
655
+
656
+ if "TLS refinement cycle" in l:
657
+ cycle = int(l.split()[-1])
658
+ elif "----Group occupancy refinement----" in l:
659
+ occ_flag = True
660
+ occ_cycles += 1
661
+ cycle += 1
662
+ elif "CGMAT cycle number =" in l:
663
+ cycle = int(l[l.index("=")+1:]) + self.tlscycle + occ_cycles
664
+ occ_flag = False
665
+
666
+ r_cycle = re_cycle_table.search(l)
667
+ if r_cycle: cycle = int(r_cycle.group(1))
668
+
669
+ for i in range(len(ret["cycles"]), cycle):
670
+ ret["cycles"].append({"cycle":i})
671
+
672
+ if "Overall R factor =" in l and cycle > 0:
673
+ rfac = l[l.index("=")+1:].strip()
674
+ if self.global_mode != "spa":
675
+ ret["cycles"][cycle-1]["r_factor"] = rfac
676
+ summary_write(" cycle= {:3d} R= {}".format(cycle-1, rfac))
677
+ elif "Average Fourier shell correlation =" in l and cycle > 0:
678
+ fsc = l[l.index("=")+1:].strip()
679
+ if occ_flag:
680
+ note = "(occupancy)"
681
+ elif cycle == 1:
682
+ note = "(initial)"
683
+ elif cycle <= self.tlscycle+1:
684
+ note = "(TLS)"
685
+ elif cycle > self.ncycle + occ_cycles + self.tlscycle:
686
+ note = "(final)"
687
+ else:
688
+ note = ""
689
+
690
+ if self.global_mode == "spa":
691
+ ret["cycles"][cycle-1]["fsc_average"] = fsc
692
+ summary_write(" cycle= {:3d} FSCaverage= {} {}".format(cycle-1, fsc, note))
693
+ elif "Rms BondLength" in l:
694
+ rmsbond = l
695
+ elif "Rms BondAngle" in l:
696
+ rmsangle = l
697
+
698
+ r_actual_weight = re_actual_weight.search(l)
699
+ if r_actual_weight:
700
+ ret["cycles"][cycle-1]["actual_weight"] = r_actual_weight.group(1)
701
+
702
+ # Final table
703
+ if " Ncyc Rfact Rfree FOM" in l:
704
+ last_table_flag = True
705
+ last_table_keys = l.split()
706
+ if last_table_keys[-1] == "$$": del last_table_keys[-1]
707
+ elif last_table_flag:
708
+ if "$$ Final results $$" in l:
709
+ last_table_flag = False
710
+ continue
711
+ sp = l.split()
712
+ if len(sp) == len(last_table_keys) and sp[0] != "$$":
713
+ cyc = int(sp[last_table_keys.index("Ncyc")])
714
+ key_name = dict(rmsBOND="rms_bond", zBOND="rmsz_bond",
715
+ rmsANGL="rms_angle", zANGL="rmsz_angle",
716
+ rmsCHIRAL="rms_chiral")
717
+ for k in key_name:
718
+ if k in last_table_keys:
719
+ ret["cycles"][cyc][key_name[k]] = sp[last_table_keys.index(k)]
720
+ else:
721
+ logger.error("table does not have key {}?".format(k))
722
+
723
+ retcode = p.wait()
724
+ log.close()
725
+ if log_delay:
726
+ logger.writeln("== Summary of Refmac ==")
727
+ logger.writeln("\n".join(log_delay))
728
+
729
+ if rmsbond:
730
+ logger.writeln(" Initial Final")
731
+ logger.writeln(rmsbond.rstrip())
732
+ logger.writeln(rmsangle.rstrip())
733
+
734
+ logger.writeln("REFMAC5 finished with exit code= {}".format(retcode))
735
+
736
+ if write_summary_json:
737
+ json.dump(ret,
738
+ open("{}_summary.json".format(self.prefix), "w"),
739
+ indent=True)
740
+
741
+ # TODO check timestamp
742
+ if not os.path.isfile(self.xyzout()) or not os.path.isfile(self.hklout()):
743
+ raise RuntimeError("REFMAC5 did not produce output files. Check {}".format(self.prefix+".log"))
744
+
745
+ return ret
746
+ # run_refmac()
747
+ # class Refmac