servalcat 0.4.99__cp312-cp312-macosx_10_14_x86_64.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 (45) hide show
  1. servalcat/__init__.py +10 -0
  2. servalcat/__main__.py +120 -0
  3. servalcat/ext.cpython-312-darwin.so +0 -0
  4. servalcat/refine/__init__.py +0 -0
  5. servalcat/refine/cgsolve.py +100 -0
  6. servalcat/refine/refine.py +906 -0
  7. servalcat/refine/refine_geom.py +233 -0
  8. servalcat/refine/refine_spa.py +366 -0
  9. servalcat/refine/refine_xtal.py +281 -0
  10. servalcat/refine/spa.py +144 -0
  11. servalcat/refine/xtal.py +276 -0
  12. servalcat/refmac/__init__.py +0 -0
  13. servalcat/refmac/exte.py +182 -0
  14. servalcat/refmac/refmac_keywords.py +639 -0
  15. servalcat/refmac/refmac_wrapper.py +395 -0
  16. servalcat/spa/__init__.py +0 -0
  17. servalcat/spa/fofc.py +479 -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 +977 -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 +1547 -0
  27. servalcat/utils/fileio.py +744 -0
  28. servalcat/utils/generate_operators.py +296 -0
  29. servalcat/utils/hkl.py +714 -0
  30. servalcat/utils/logger.py +140 -0
  31. servalcat/utils/maps.py +345 -0
  32. servalcat/utils/model.py +782 -0
  33. servalcat/utils/refmac.py +760 -0
  34. servalcat/utils/restraints.py +781 -0
  35. servalcat/utils/symmetry.py +295 -0
  36. servalcat/xtal/__init__.py +0 -0
  37. servalcat/xtal/french_wilson.py +258 -0
  38. servalcat/xtal/run_refmac_small.py +240 -0
  39. servalcat/xtal/sigmaa.py +1644 -0
  40. servalcat/xtal/twin.py +121 -0
  41. servalcat-0.4.99.dist-info/METADATA +55 -0
  42. servalcat-0.4.99.dist-info/RECORD +45 -0
  43. servalcat-0.4.99.dist-info/WHEEL +5 -0
  44. servalcat-0.4.99.dist-info/entry_points.txt +4 -0
  45. servalcat-0.4.99.dist-info/licenses/LICENSE +373 -0
@@ -0,0 +1,395 @@
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 json
12
+ import os
13
+ import sys
14
+ import tempfile
15
+ import subprocess
16
+ import argparse
17
+ from collections import OrderedDict
18
+ import servalcat # for version
19
+ from servalcat.utils import logger
20
+ from servalcat.refmac import refmac_keywords
21
+ from servalcat import utils
22
+
23
+ def add_arguments(parser):
24
+ parser.description = 'Run REFMAC5 with gemmi-prepared restraints'
25
+ parser.add_argument('--exe', default="refmac5", help='refmac5 binary')
26
+ parser.add_argument("--monlib",
27
+ help="Monomer library path. Default: $CLIBD_MON")
28
+ parser.add_argument('--ligand', nargs="*", action="append")
29
+ parser.add_argument("opts", nargs="+",
30
+ help="HKLIN hklin XYZIN xyzin...")
31
+ parser.add_argument('--auto_box_with_padding', type=float, help="Determine box size from model with specified padding")
32
+ parser.add_argument('--no_adjust_hydrogen_distances', action='store_true', help="By default it adjusts hydrogen distances using ideal values. This option is to disable it.")
33
+ parser.add_argument('--keep_original_output', action='store_true', help="with .org extension")
34
+ parser.add_argument("--keep_entities", action='store_true',
35
+ help="Do not override entities")
36
+ parser.add_argument('--prefix', help="output prefix")
37
+ parser.add_argument("-v", "--version", action="version",
38
+ version=logger.versions_str())
39
+ # TODO --cell to override unit cell?
40
+
41
+ # add_arguments()
42
+
43
+ def parse_args(arg_list):
44
+ parser = argparse.ArgumentParser()
45
+ add_arguments(parser)
46
+ return parser.parse_args(arg_list)
47
+ # parse_args()
48
+
49
+ def read_stdin(stdin):
50
+ print("Waiting for input..")
51
+ # these make keywords will be ignored (just passed to refmac): ribo,valu,spec,form,sdmi,segi
52
+ ret = {"make":{}, "ridge":{}, "refi":{}}
53
+ inputs = []
54
+ for l in refmac_keywords.get_lines(stdin):
55
+ refmac_keywords.parse_line(l, ret)
56
+ inputs.append(l + "\n")
57
+
58
+ def sorry(s): raise SystemExit("Sorry, '{}' is not supported".format(s))
59
+ if ret["make"].get("hydr") == "f":
60
+ sorry("make hydr full")
61
+ if ret["make"].get("buil") == "y":
62
+ sorry("make build yes")
63
+ return inputs, ret
64
+ # read_stdin()
65
+
66
+ def prepare_crd(st, crdout, ligand, make, monlib_path=None, h_pos="elec",
67
+ no_adjust_hydrogen_distances=False, fix_long_resnames=True,
68
+ keep_entities=False, unre=False):
69
+ assert h_pos in ("elec", "nucl")
70
+ h_change = dict(a=gemmi.HydrogenChange.ReAddButWater,
71
+ y=gemmi.HydrogenChange.NoChange,
72
+ n=gemmi.HydrogenChange.Remove)[make.get("hydr", "a")]
73
+ utils.model.fix_deuterium_residues(st)
74
+ for chain in st[0]:
75
+ if not chain.name:
76
+ chain.name = "X" # Refmac behavior. Empty chain name will cause a problem
77
+ for res in chain:
78
+ if res.is_water():
79
+ res.name = "HOH"
80
+ for at in res:
81
+ if at.occ > 1: # XXX should I check special positions?
82
+ at.occ = 1.
83
+
84
+ refmac_fixes = utils.refmac.FixForRefmac()
85
+ if keep_entities:
86
+ refmac_fixes.store_res_labels(st)
87
+ else:
88
+ utils.model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
89
+
90
+ if unre:
91
+ logger.writeln("Monomer library will not be loaded due to unrestrained refinement request")
92
+ monlib = gemmi.MonLib()
93
+ else:
94
+ # TODO read dictionary from xyzin (priority: user cif -> monlib -> xyzin
95
+ try:
96
+ monlib = utils.restraints.load_monomer_library(st,
97
+ monomer_dir=monlib_path,
98
+ cif_files=ligand,
99
+ stop_for_unknowns=not make.get("newligand"))
100
+ except RuntimeError as e:
101
+ raise SystemExit("Error: {}".format(e))
102
+
103
+ use_cispeps = make.get("cispept", "y") != "y"
104
+ make_link = make.get("link", "n")
105
+ make_ss = make.get("ss", "y")
106
+ only_from = set()
107
+ if make_link == "y":
108
+ # add all links
109
+ add_found = True
110
+ elif make_ss == "y":
111
+ add_found = True
112
+ only_from.add("disulf")
113
+ else:
114
+ add_found = False
115
+
116
+ utils.restraints.fix_elements_in_model(monlib, st)
117
+ utils.restraints.find_and_fix_links(st, monlib, add_found=add_found,
118
+ find_metal_links=(make_link == "y"),
119
+ find_symmetry_related=False, add_only_from=only_from)
120
+ for con in st.connections:
121
+ if con.link_id not in ("?", "", "gap") and con.link_id not in monlib.links:
122
+ logger.writeln(" removing unknown link id ({}). Ad-hoc link will be generated.".format(con.link_id))
123
+ con.link_id = ""
124
+
125
+ max_seq_num = max([max(res.seqid.num for res in chain) for model in st for chain in model])
126
+ if max_seq_num > 9999:
127
+ logger.writeln("Max residue number ({}) exceeds 9999. Needs workaround.".format(max_seq_num))
128
+ topo = gemmi.prepare_topology(st, monlib, warnings=logger.silent(), ignore_unknown_links=True)
129
+ refmac_fixes.fix_before_topology(st, topo,
130
+ fix_microheterogeneity=False,
131
+ fix_resimax=True,
132
+ fix_nonpolymer=False)
133
+
134
+ if unre:
135
+ # Refmac5 does not seem to do anything to hydrogen when unre regardless of "make hydr"
136
+ topo = gemmi.prepare_topology(st, monlib, warnings=logger.silent(), ignore_unknown_links=True)
137
+ metal_kws = []
138
+ else:
139
+ if make.get("hydr") == "a": logger.writeln("(re)generating hydrogen atoms")
140
+ try:
141
+ topo, metal_kws = utils.restraints.prepare_topology(st, monlib, h_change=h_change, ignore_unknown_links=False,
142
+ check_hydrogen=(h_change==gemmi.HydrogenChange.NoChange),
143
+ use_cispeps=use_cispeps)
144
+ except RuntimeError as e:
145
+ raise SystemExit("Error: {}".format(e))
146
+
147
+ if make.get("hydr") != "n" and st[0].has_hydrogen():
148
+ if h_pos == "nucl" and (make.get("hydr") == "a" or not no_adjust_hydrogen_distances):
149
+ resnames = st[0].get_all_residue_names()
150
+ utils.restraints.check_monlib_support_nucleus_distances(monlib, resnames)
151
+ logger.writeln("adjusting hydrogen position to nucleus")
152
+ topo.adjust_hydrogen_distances(gemmi.Restraints.DistanceOf.Nucleus, default_scale=1.1)
153
+ elif h_pos == "elec" and make.get("hydr") == "y" and not no_adjust_hydrogen_distances:
154
+ logger.writeln("adjusting hydrogen position to electron cloud")
155
+ topo.adjust_hydrogen_distances(gemmi.Restraints.DistanceOf.ElectronCloud)
156
+
157
+ if fix_long_resnames: refmac_fixes.fix_long_resnames(st)
158
+
159
+ # remove "given" ncs matrices
160
+ # TODO write them back to the output files
161
+ st.ncs = gemmi.NcsOpList(x for x in st.ncs if not x.given)
162
+
163
+ # for safety
164
+ if "_entry.id" in st.info:
165
+ st.info["_entry.id"] = st.info["_entry.id"].replace(" ", "")
166
+ date_key = "_pdbx_database_status.recvd_initial_deposition_date"
167
+ if date_key in st.info:
168
+ tmp = st.info[date_key]
169
+ if len(tmp) > 5 and tmp[4] == "-":
170
+ if len(tmp) > 8 and tmp[8] != "" and not tmp[5:7].isdigit():
171
+ tmp = "XX"
172
+ elif len(tmp) > 6 and tmp[5] == "-":
173
+ if not tmp[3:5].isdigit():
174
+ tmp = "XX"
175
+ st.info[date_key] = tmp
176
+ # internal chain ID
177
+ for chain in st[0]:
178
+ for res in chain:
179
+ if len(chain.name) < 3:
180
+ # Change Axp (or AAxp) to A_p (or AA_p)
181
+ res.subchain = chain.name + "_" + res.subchain[len(chain.name)+1:]
182
+ else:
183
+ # Refmac only expects '_' at 2nd or 3rd position, and can accept up to 4 letters.
184
+ # Using raw chain ID may change alignment result for local NCS restraints,
185
+ # but to avoid this we would need chain ID translation, which is too complicated.
186
+ # This also invalidates _struct_asym, which Refmac does not seem to care
187
+ res.subchain = chain.name
188
+ # change st.name if needed
189
+ block_names = utils.restraints.dictionary_block_names(monlib, topo)
190
+ for i in range(1000):
191
+ if st.name.lower() in block_names:
192
+ st.name = st.name + str(i)
193
+ doc = gemmi.prepare_refmac_crd(st, topo, monlib, h_change)
194
+ doc.write_file(crdout, options=gemmi.cif.Style.NoBlankLines)
195
+ logger.writeln("crd file written: {}".format(crdout))
196
+ return refmac_fixes, [x+"\n" for x in metal_kws]
197
+ # prepare_crd()
198
+
199
+ def get_output_model_names(xyzout):
200
+ # ref: WRITE_ATOMS_REFMAC in oppro_allocate.f
201
+ if xyzout is None: xyzout = "XYZOUT"
202
+ pdb, mmcif = "", ""
203
+ if len(xyzout) > 3:
204
+ if xyzout.lower().endswith("pdb"):
205
+ mmcif = xyzout[:-4] + ".mmcif"
206
+ pdb = xyzout
207
+ else:
208
+ if xyzout.lower().endswith("cif") and len(xyzout) > 5:
209
+ if xyzout.lower().endswith("mmcif"):
210
+ mmcif = xyzout
211
+ pdb = xyzout[:-6] + ".pdb"
212
+ else:
213
+ mmcif = xyzout
214
+ pdb = xyzout[:-4] + ".pdb"
215
+ else:
216
+ mmcif = xyzout + ".mmcif"
217
+ pdb = xyzout
218
+ else:
219
+ mmcif = xyzout + ".mmcif"
220
+ pdb = xyzout
221
+
222
+ return pdb, mmcif
223
+ # get_output_model_names()
224
+
225
+ def modify_output(pdbout, cifout, fixes, hout, cispeps, keep_original_output=False):
226
+ st = utils.fileio.read_structure(cifout)
227
+ st.cispeps = cispeps
228
+ if os.path.exists(pdbout):
229
+ st_pdb = gemmi.read_pdb(pdbout)
230
+ st.raw_remarks = st_pdb.raw_remarks
231
+ # Refmac only writes NCS matrices to pdb
232
+ st.ncs = st_pdb.ncs
233
+ if "_struct_ncs_oper.id" in st_pdb.info: # to write identity operator
234
+ st.info["_struct_ncs_oper.id"] = st_pdb.info["_struct_ncs_oper.id"]
235
+ if fixes is not None:
236
+ fixes.modify_back(st)
237
+ for con in st.connections:
238
+ if con.link_id == "disulf":
239
+ con.type = gemmi.ConnectionType.Disulf
240
+ # should we check metals and put MetalC?
241
+
242
+ # fix entity (Refmac seems to make DNA non-polymer; as seen in 1fix)
243
+ if not fixes or not fixes.res_labels:
244
+ utils.model.setup_entities(st, clear=True, overwrite_entity_type=True, force_subchain_names=True)
245
+ for e in st.entities:
246
+ if not e.full_sequence and e.entity_type == gemmi.EntityType.Polymer and e.subchains:
247
+ rspan = st[0].get_subchain(e.subchains[0])
248
+ e.full_sequence = [r.name for r in rspan]
249
+
250
+ # fix label_seq_id
251
+ for chain in st[0]:
252
+ for res in chain:
253
+ res.label_seq = None
254
+ st.assign_label_seq_id()
255
+
256
+ # add servalcat version
257
+ if len(st.meta.software) > 0 and st.meta.software[-1].name == "refmac":
258
+ st.meta.software[-1].version += f" (refmacat {servalcat.__version__})"
259
+
260
+ suffix = ".org"
261
+ os.rename(cifout, cifout + suffix)
262
+ utils.fileio.write_mmcif(st, cifout, cifout + suffix)
263
+
264
+ if st.has_d_fraction:
265
+ st.store_deuterium_as_fraction(False) # also useful for pdb
266
+ logger.writeln("will write a H/D expanded mmcif file")
267
+ cifout2 = cifout[:cifout.rindex(".")] + "_hd_expand" + cifout[cifout.rindex("."):]
268
+ utils.fileio.write_mmcif(st, cifout2, cifout + suffix)
269
+
270
+ chain_id_len_max = max([len(x) for x in utils.model.all_chain_ids(st)])
271
+ seqnums = [res.seqid.num for chain in st[0] for res in chain]
272
+ if chain_id_len_max > 1 or min(seqnums) <= -1000 or max(seqnums) >= 10000:
273
+ logger.writeln("This structure cannot be saved as an official PDB format. Using hybrid-36. Header part may be inaccurate.")
274
+ if not hout:
275
+ st.remove_hydrogens() # remove hydrogen from pdb, while kept in mmcif
276
+ os.rename(pdbout, pdbout + suffix)
277
+ utils.fileio.write_pdb(st, pdbout)
278
+ if not keep_original_output:
279
+ os.remove(pdbout + suffix)
280
+ os.remove(cifout + suffix)
281
+ # modify_output()
282
+
283
+ def main(args):
284
+ if len(args.opts) % 2 != 0: raise SystemExit("Invalid number of args")
285
+ args.ligand = sum(args.ligand, []) if args.ligand else []
286
+
287
+ inputs, keywords = read_stdin(sys.stdin) # TODO read psrestin also?
288
+ if not keywords["make"].get("exit"):
289
+ refmac_ver = utils.refmac.check_version(args.exe)
290
+ if not refmac_ver:
291
+ raise SystemExit("Error: Check Refmac installation or use --exe to give the location.")
292
+ if refmac_ver < (5, 8, 404):
293
+ raise SystemExit("Error: this version of Refmac is not supported. Update to 5.8.404 or newer")
294
+
295
+ opts = OrderedDict((args.opts[2*i].lower(), args.opts[2*i+1]) for i in range(len(args.opts)//2))
296
+ xyzin = opts.get("xyzin")
297
+ xyzout = opts.get("xyzout")
298
+ libin = opts.pop("libin", None)
299
+ if libin: args.ligand.append(libin)
300
+ if not args.monlib:
301
+ # if --monlib is given, it has priority.
302
+ args.monlib = opts.pop("clibd_mon", None)
303
+ for k in ("temp1", "scrref"): # scrref has priority
304
+ if k in opts:
305
+ logger.writeln("updating CCP4_SCR from {}={}".format(k, opts[k]))
306
+ os.environ["CCP4_SCR"] = os.path.dirname(opts[k]) # XXX "." may be given, which causes problem (os.path.isdir("") is False)
307
+ utils.refmac.ensure_ccp4scr()
308
+ if args.prefix:
309
+ if "xyzin" in opts and "xyzout" not in opts: opts["xyzout"] = args.prefix + ".pdb"
310
+ if "hklin" in opts and "hklout" not in opts: opts["hklout"] = args.prefix + ".mtz"
311
+ if "tlsin" in opts and "tlsout" not in opts: opts["tlsout"] = args.prefix + ".tls"
312
+
313
+ # TODO what if restin is given or make cr prepared is given?
314
+ # TODO check make pept/link/suga/ss/conn/symm/chain
315
+
316
+ # Process model
317
+ crdout = None
318
+ refmac_fixes = None
319
+ cispeps = []
320
+ unre = keywords["refi"].get("type") == "unre"
321
+ if xyzin is not None:
322
+ #tmpfd, crdout = tempfile.mkstemp(prefix="gemmi_", suffix=".crd") # TODO use dir=CCP4_SCR
323
+ #os.close(tmpfd)
324
+ st = utils.fileio.read_structure(xyzin)
325
+ if not st.cell.is_crystal() and not unre:
326
+ if args.auto_box_with_padding is not None:
327
+ st.cell = utils.model.box_from_model(st[0], args.auto_box_with_padding)
328
+ st.spacegroup_hm = "P 1"
329
+ logger.writeln("Box size from the model with padding of {}: {}".format(args.auto_box_with_padding, st.cell.parameters))
330
+ else:
331
+ raise SystemExit("Error: unit cell is not defined in the model.")
332
+ xyzout_dir = os.path.dirname(get_output_model_names(opts.get("xyzout"))[0])
333
+ crdout = os.path.join(xyzout_dir,
334
+ "gemmi_{}_{}.crd".format(utils.fileio.splitext(os.path.basename(xyzin))[0], os.getpid()))
335
+ refmac_fixes, metal_kws = prepare_crd(st, crdout, args.ligand, make=keywords["make"], monlib_path=args.monlib,
336
+ h_pos="nucl" if keywords.get("source")=="ne" else "elec",
337
+ no_adjust_hydrogen_distances=args.no_adjust_hydrogen_distances,
338
+ keep_entities=args.keep_entities,
339
+ unre=unre)
340
+ inputs = metal_kws + inputs # add metal exte first; otherwise it may be affected by user-defined inputs
341
+ opts["xyzin"] = crdout
342
+ cispeps = st.cispeps
343
+
344
+ if keywords["make"].get("exit"):
345
+ return
346
+
347
+ # Run Refmac
348
+ cmd = [args.exe] + list(sum(tuple(opts.items()), ()))
349
+ env = os.environ
350
+ logger.writeln("Running REFMAC5..")
351
+ if args.monlib:
352
+ logger.writeln("CLIBD_MON={}".format(args.monlib))
353
+ env["CLIBD_MON"] = os.path.join(args.monlib, "") # should end with /
354
+ logger.writeln(" ".join(cmd))
355
+ p = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE,
356
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
357
+ universal_newlines=True, env=env)
358
+ if crdout: p.stdin.write("make cr prepared\n")
359
+ p.stdin.write("".join(inputs))
360
+ p.stdin.close()
361
+ # prepare conversion for long residue names
362
+ resn_conv = {}
363
+ if refmac_fixes:
364
+ for old, new in refmac_fixes.resn_old_new:
365
+ n = "{:4s}".format(old)
366
+ if len(n) > 4: n += " "
367
+ resn_conv[new] = n
368
+ # print raw output
369
+ for l in iter(p.stdout.readline, ""):
370
+ for tn in resn_conv:
371
+ l = l.replace(tn, resn_conv[tn])
372
+ logger.write(l)
373
+ retcode = p.wait()
374
+ logger.writeln("\nRefmac finished with exit code= {}".format(retcode))
375
+
376
+ if not args.keep_original_output and crdout and os.path.exists(crdout):
377
+ os.remove(crdout)
378
+
379
+ # Modify output
380
+ if xyzin is not None:
381
+ pdbout, cifout = get_output_model_names(opts.get("xyzout"))
382
+ if os.path.exists(cifout):
383
+ modify_output(pdbout, cifout, refmac_fixes, keywords["make"].get("hout"), cispeps, args.keep_original_output)
384
+ # main()
385
+
386
+ def command_line():
387
+ import sys
388
+ args = parse_args(sys.argv[1:])
389
+ if args.prefix:
390
+ logger.set_file(args.prefix + ".log")
391
+ logger.write_header(command="refmacat")
392
+ main(args)
393
+
394
+ if __name__ == "__main__":
395
+ command_line()
File without changes