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