servalcat 0.4.131__cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_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.
Files changed (45) hide show
  1. servalcat/__init__.py +10 -0
  2. servalcat/__main__.py +120 -0
  3. servalcat/ext.cpython-314t-x86_64-linux-gnu.so +0 -0
  4. servalcat/refine/__init__.py +0 -0
  5. servalcat/refine/cgsolve.py +100 -0
  6. servalcat/refine/refine.py +1162 -0
  7. servalcat/refine/refine_geom.py +245 -0
  8. servalcat/refine/refine_spa.py +400 -0
  9. servalcat/refine/refine_xtal.py +339 -0
  10. servalcat/refine/spa.py +151 -0
  11. servalcat/refine/xtal.py +312 -0
  12. servalcat/refmac/__init__.py +0 -0
  13. servalcat/refmac/exte.py +191 -0
  14. servalcat/refmac/refmac_keywords.py +660 -0
  15. servalcat/refmac/refmac_wrapper.py +423 -0
  16. servalcat/spa/__init__.py +0 -0
  17. servalcat/spa/fofc.py +488 -0
  18. servalcat/spa/fsc.py +391 -0
  19. servalcat/spa/localcc.py +197 -0
  20. servalcat/spa/realspcc_from_var.py +128 -0
  21. servalcat/spa/run_refmac.py +979 -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 +1629 -0
  27. servalcat/utils/fileio.py +836 -0
  28. servalcat/utils/generate_operators.py +296 -0
  29. servalcat/utils/hkl.py +811 -0
  30. servalcat/utils/logger.py +140 -0
  31. servalcat/utils/maps.py +345 -0
  32. servalcat/utils/model.py +933 -0
  33. servalcat/utils/refmac.py +759 -0
  34. servalcat/utils/restraints.py +888 -0
  35. servalcat/utils/symmetry.py +298 -0
  36. servalcat/xtal/__init__.py +0 -0
  37. servalcat/xtal/french_wilson.py +262 -0
  38. servalcat/xtal/run_refmac_small.py +240 -0
  39. servalcat/xtal/sigmaa.py +1954 -0
  40. servalcat/xtal/twin.py +316 -0
  41. servalcat-0.4.131.dist-info/METADATA +60 -0
  42. servalcat-0.4.131.dist-info/RECORD +45 -0
  43. servalcat-0.4.131.dist-info/WHEEL +6 -0
  44. servalcat-0.4.131.dist-info/entry_points.txt +4 -0
  45. servalcat-0.4.131.dist-info/licenses/LICENSE +373 -0
@@ -0,0 +1,660 @@
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
+ from servalcat.utils import logger
10
+ from servalcat.utils import model as model_util
11
+ import gemmi
12
+ b_to_u = model_util.b_to_u
13
+
14
+ def parse_atom_spec(s, itk):
15
+ # s: list of keywords
16
+ ret = {}
17
+ while itk < len(s):
18
+ if s[itk].lower().startswith(("chai", "segm")):
19
+ ret["chain"] = s[itk+1]
20
+ itk += 2
21
+ elif s[itk].lower().startswith("resi"):
22
+ ret["resi"] = int(s[itk+1])
23
+ itk += 2
24
+ elif s[itk].lower().startswith("ins"):
25
+ ret["icode"] = s[itk+1] if s[itk+1] != "." else " "
26
+ itk += 2
27
+ elif s[itk].lower().startswith(("atom", "atna", "name")):
28
+ if s[itk+1] == "{":
29
+ idx_close = s[itk+1:].index("}") + itk + 1
30
+ ret["names"] = s[itk+2:idx_close]
31
+ itk = idx_close + 1
32
+ else:
33
+ ret["names"] = [s[itk+1]]
34
+ itk += 2
35
+ elif s[itk].lower().startswith("alt"):
36
+ ret["altloc"] = s[itk+1]
37
+ itk += 2
38
+ elif s[itk].lower().startswith("symm"):
39
+ ret["symm"] = s[itk+1][0].lower() == "y"
40
+ itk += 2
41
+ else:
42
+ break
43
+
44
+ return ret, itk
45
+ # parse_atom_spec()
46
+
47
+ def parse_from_to(s, itk):
48
+ # s: list of keywords
49
+ ret = {}
50
+ if not s[itk].lower().startswith("from"):
51
+ raise RuntimeError("invalid from_to instruction: {}".format(s))
52
+
53
+ if s[itk+1] == "*":
54
+ ret["ifirst"] = None # Refmac sets -9999
55
+ else:
56
+ ret["ifirst"] = int(s[itk+1])
57
+
58
+ if s[itk+2].lower() != "to":
59
+ ret["chain"] = s[itk+2]
60
+ assert s[itk+3].lower() == "to"
61
+ itk += 4
62
+ else:
63
+ itk += 3
64
+
65
+ if s[itk] == "*":
66
+ ret["ilast"] = None # Refmac sets 9999
67
+ else:
68
+ ret["ilast"] = int(s[itk])
69
+
70
+ if "chain" not in ret:
71
+ ret["chain"] = s[itk+1]
72
+ itk += 2
73
+ else:
74
+ itk += 2
75
+
76
+ return ret, itk
77
+ # parse_from_to()
78
+
79
+ def read_exte(s, raise_unknown=True):
80
+ # using the same variable names as used in read_extra_restraints.f
81
+ ret = dict(defaults={})
82
+ if not s: return ret
83
+ defs = ret["defaults"]
84
+ rest_flag_old = rest_flag = False
85
+ if s[0].lower().startswith("exte"):
86
+ if s[1].lower().startswith("gene"): return ret
87
+ elif s[1].lower().startswith("file"): # XXX not supported
88
+ file_ext_now = s[2]
89
+ elif s[1].lower().startswith("syma"): # symall
90
+ # refmac sets "n" by default if "syma" given - but it is not good!
91
+ defs["symall_block"] = "y" if s[2][0].lower() == "y" else "n"
92
+ if len(s) > 3 and s[3].lower().startswith("excl"): # exclude
93
+ defs["exclude_self_block"] = s[4].lower().startswith("self")
94
+ elif s[1].lower().startswith("typa"): # typeall
95
+ #type_default = 2
96
+ defs["type_default"] = max(0, min(2, int(s[2])))
97
+ elif s[1].lower().startswith("alph"): # alphall
98
+ defs["alpha_default"] = float(s[2])
99
+ elif s[1].lower().startswith("verb"): # verbose
100
+ defs["ext_verbose"] = s[2][0].lower() != "n" and not s[2].lower().startswith("off")
101
+ # print("External verbose is on, i.e.") ...
102
+ elif s[1].lower().startswith("weig"): # weight
103
+ itk = 2
104
+ while itk < len(s): # FIXME check out-of-bounds in s[]
105
+ if s[itk].lower().startswith("scal"):
106
+ itk += 1
107
+ if itk >= len(s): break
108
+ try:
109
+ defs["scale_sigma_dist"] = float(s[itk]) # scale_sigma_loc
110
+ itk += 1
111
+ continue
112
+ except ValueError:
113
+ pass
114
+ for k in ("angl", "tors", "chir", "plan", "dist", "inte"):
115
+ if s[itk].lower().startswith(k):
116
+ itk += 1
117
+ if itk >= len(s): break
118
+ try:
119
+ defs["scale_sigma_{}".format(k)] = float(s[itk])
120
+ itk += 1
121
+ break
122
+ except ValueError:
123
+ pass
124
+ elif s[itk].lower().startswith("sgmn"):
125
+ defs["sigma_min_loc"] = float(s[itk+1])
126
+ itk += 2
127
+ elif s[itk].lower().startswith("sgmx"):
128
+ defs["sigma_max_loc"] = float(s[itk+1])
129
+ itk += 2
130
+ else:
131
+ raise RuntimeError("Error==> EXTE keyword interpretation: {}".format(" ".join(s)))
132
+ elif s[1].lower().startswith(("miss", "unde")): # undefined
133
+ defs["ignore_undefined"] = s[2].lower().startswith("igno")
134
+ elif s[1].lower().startswith("hydr"): # hydrogen
135
+ defs["ignore_hydrogens"] = s[2].lower().startswith("igno")
136
+ elif s[1].lower().startswith("cut"): # TODO I think this should be parsed outside
137
+ ret["sd_ext_cut"] = float(s[2]) # as this affects everything (not only following blocks)
138
+ elif s[1].lower().startswith("dmax"):
139
+ defs["dist_max_external"] = float(s[2])
140
+ elif s[1].lower().startswith("dmin"):
141
+ defs["dist_min_external"] = float(s[2])
142
+ elif s[1].lower().startswith("use"):
143
+ defs["use_atoms"] = s[2][0].lower()
144
+ if defs["use_atoms"] not in ("a", "m", "h"):
145
+ logger.writeln("invalid exte use keyword: {}".format(s[2]))
146
+ defs["use_atoms"] = "a"
147
+ elif s[1].lower().startswith("conv"):
148
+ if s[2].lower().startswith("pref"):
149
+ defs["prefix_ch"] = s[3] # ???
150
+ elif s[1].lower().startswith(("dist", "plan", "chir", "angl", "inte", "tors")):
151
+ ret["rest_type"] = s[1][:4].lower()
152
+ itk = 2
153
+ iat = 0
154
+ ret["restr"] = {}
155
+ n_expect = dict(plan=0, dist=2, inte=2, angl=3).get(ret["rest_type"], 4)
156
+ ret["restr"]["specs"] = [None for _ in range(n_expect)]
157
+ while itk < len(s):
158
+ if s[itk].lower().startswith(("firs", "seco", "thir", "four", "next", "atre", "atin")):
159
+ iat = dict(firs=0, seco=1, thir=2, four=3).get(s[itk][:4].lower(), iat+1)
160
+ atoms, itk = parse_atom_spec(s, itk+1)
161
+ if ret["rest_type"] == "plan":
162
+ ret["restr"]["specs"].append(atoms)
163
+ else:
164
+ ret["restr"]["specs"][iat] = atoms
165
+ elif s[itk].lower().startswith("type"):
166
+ try:
167
+ ret["restr"]["itype_in"] = int(s[itk+1])
168
+ except ValueError:
169
+ ret["restr"]["itype_in"] = dict(o=0, f=2).get(s[itk+1][0].lower(), 1)
170
+ if not (0 <= ret["restr"]["itype_in"] <= 2):
171
+ logger.writeln("WARNING: wrong type is given. setting to 2.\n=> {}".format(" ".join(s)))
172
+ ret["restr"]["itype_in"] = 2
173
+ itk += 2
174
+ elif s[itk].lower().startswith("symm"): # only for distance and angle
175
+ ret["restr"]["symm_in"] = s[itk+1][0].lower() == "y"
176
+ itk += 2
177
+ else:
178
+ d = dict(valu="value", dmin="dmin", dmax="dmax", smin="smin_value", smax="smax_value",
179
+ sigm="sigma_value", alph="alpha_in", prob="prob_in")
180
+ k = s[itk][:4].lower()
181
+ if k in d:
182
+ ret["restr"][d[k]] = float(s[itk+1])
183
+ itk += 2
184
+ else:
185
+ logger.writeln("unrecognised key: {}\n=> {}".format(s[itk], " ".join(s)))
186
+ break
187
+ elif s[1].lower().startswith("stac"):
188
+ ret["rest_type"] = "stac"
189
+ ret["restr"] = {}
190
+ ret["restr"]["specs"] = [[] for _ in range(2)]
191
+ itk = 2
192
+ #if s[itk].lower().startswith("dist"):
193
+ ip = 0
194
+ while itk < len(s):
195
+ if s[itk].lower().startswith("plan"):
196
+ ip = int(s[itk+1])
197
+ itk += 2
198
+ if ip not in (1, 2):
199
+ raise RuntimeError("Problem with stacking instructions. Plane number can be 1 or 2.\n=> {}".format(" ".join(s)))
200
+ elif s[itk].lower().startswith(("firs", "next")):
201
+ atoms, itk = parse_atom_spec(s, itk+1)
202
+ ret["restr"]["specs"][ip-1] = atoms
203
+ elif s[itk].lower().startswith(("dist", "sddi", "angl", "sdan", "type")):
204
+ k = dict(dist="dist_id", sddi="dist_sd", angl="angle_id", sdan="angle_sd", type="type_r")[s[itk][:4].lower()]
205
+ ret["restr"][k] = float(s[itk+1]) if k != "type_r" else int(s[itk+1])
206
+ itk += 2
207
+ else:
208
+ logger.writeln("WARNING: unrecognised keyword: {}\n=> {}".format(s[itk], " ".join(s)))
209
+ itk += 1
210
+ elif s[1].lower().startswith(("harm", "spec")):
211
+ ret["rest_type"] = s[1][:4].lower() # in Refmac, irest_type = 1 if harm else 2
212
+ ret["restr"] = dict(rectype="", toler=0.5, sigma_t=0.5, sigma_u=2.0 * b_to_u, u_val_incl=False)
213
+ itk = 2
214
+ while itk < len(s):
215
+ if s[itk].lower().startswith("auto"):
216
+ ret["restr"]["rectype"] = "auto"
217
+ itk += 1
218
+ elif s[itk].lower().startswith("atin"):
219
+ ret["restr"]["rectype"] = "atom"
220
+ atoms, itk = parse_atom_spec(s, itk+1)
221
+ ret["restr"]["specs"] = [atoms]
222
+ elif s[itk].lower().startswith("resi"):
223
+ ret["restr"]["rectype"] = "resi"
224
+ fromto, itk = parse_from_to(s, itk+1)
225
+ ret["restr"]["specs"] = [fromto]
226
+ if s[itk].lower().startswith("atom"):
227
+ ret["restr"]["specs"][0]["atom"] = s[itk+1] # called atom_resi in Refmac
228
+ itk += 2
229
+ elif s[itk].lower().startswith("sigm"):
230
+ ret["restr"]["sigma_t"] = float(s[itk+1])
231
+ itk += 2
232
+ elif s[itk].lower().startswith("tole"):
233
+ ret["restr"]["toler"] = float(s[itk+1])
234
+ itk += 2
235
+ elif s[itk].lower().startswith(("uval", "bval")):
236
+ if len(s) > itk+1 and s[itk+1].lower().startswith("incl"):
237
+ ret["restr"]["u_val_incl"] = True
238
+ itk += 2
239
+ else:
240
+ ret["restr"]["u_val_incl"] = False
241
+ itk += 1
242
+ elif s[itk].lower().startswith(("sigb", "sigu")):
243
+ ret["restr"]["sigma_u"] = float(s[itk+1]) * b_to_u
244
+ itk += 2
245
+ else:
246
+ logger.writeln("WARNING: unrecognised keyword: {}\n=> {}".format(s[itk], " ".join(s)))
247
+ itk += 1
248
+
249
+ else:
250
+ msg = "unrecognized exte keyword: " + " ".join(s)
251
+ if raise_unknown:
252
+ raise RuntimeError(msg)
253
+ else:
254
+ logger.writeln(f"WARNING: {msg}")
255
+ return ret
256
+ # read_exte()
257
+
258
+ def read_ridge_params(l, r, raise_unknown=True):
259
+ s = l.split()
260
+ assert s[0].lower().startswith("ridg")
261
+ ntok = len(s)
262
+ if s[1].lower().startswith("dist") and ntok > 2:
263
+ if s[2].lower().startswith("with"):
264
+ r.setdefault("groups", []).append({})
265
+ #r["groups"][-1]["sigma"] = sigma_dist_r
266
+ #r["groups"][-1]["dmax"] = dmax_dist_r
267
+ itk = 3
268
+ while itk < ntok:
269
+ if s[itk].lower().startswith("chai"):
270
+ r["groups"][-1]["chain"] = s[itk+1]
271
+ itk += 2
272
+ elif s[itk].lower().startswith("resi"):
273
+ r["groups"][-1]["resi"] = (int(s[itk+1]), int(s[itk+2]))
274
+ itk += 3
275
+ elif s[itk].lower().startswith("sigm"):
276
+ v = float(s[itk+1])
277
+ if v < 0: v = 0.01
278
+ r["groups"][-1]["sigma"] = v
279
+ itk += 2
280
+ elif s[itk].lower().startswith("dmax"):
281
+ v = float(s[itk+1])
282
+ if v < 0: v = 4.2
283
+ r["groups"][-1]["dmax"] = v
284
+ itk += 2
285
+ elif s[2].lower().startswith("incl") and ntok > 3:
286
+ # a: ridge_dist_include_all
287
+ # h: ridge_dist_include_hbond
288
+ # m: ridge_dist_include_main
289
+ v = s[3][0].lower()
290
+ if v in ("a", "h", "m"): r["include"] = v
291
+ elif s[2].lower().startswith("sigm"):
292
+ v = float(s[3])
293
+ r["sigma"] = v if v > 0 else 0.01
294
+ elif s[2].lower().startswith("dmax"):
295
+ v = float(s[3])
296
+ r["dmax"] = v if v > 0 else 4.2
297
+ elif s[2].lower().startswith("inte") and ntok > 3:
298
+ r["interchain"] = s[3][0].lower() == "y"
299
+ elif s[2].lower().startswith("symm") and ntok > 3:
300
+ r["intersym"] = s[3][0].lower() == "y"
301
+ elif s[2].lower().startswith("long") and ntok > 3:
302
+ r["long_range"] = max(0, int(s[3])) # long_range_residue_gap
303
+ elif s[2].lower().startswith("shor") and ntok > 3:
304
+ r["short_range"] = max(0, int(s[3])) # short_range_residue_gap
305
+ elif s[2].lower().startswith("hydr"):
306
+ r["hydrogen"] = ntok < 4 or s[3][0].lower() == "i" # hydrogens_include
307
+ elif s[2].lower().startswith("side") and ntok > 3:
308
+ r["sidechain"] = s[3][0].lower() == "i" # rb_side_chain_include
309
+ elif s[2].lower().startswith("filt"):
310
+ r["bvalue_filter"] = True
311
+ if s[3].lower().startswith("bran"):
312
+ v = float(s[4])
313
+ r["bvalue_filter_range"] = v if v > 0 else 2.0
314
+ else:
315
+ msg = "unrecognised keyword: {}\n=> {}".format(s[2], l)
316
+ if raise_unknown:
317
+ raise RuntimeError(msg)
318
+ else:
319
+ logger.writeln(f"WARNING: {msg}")
320
+ elif s[1].lower().startswith(("atom", "posi")): # not used
321
+ r["sigma_pos"] = float(s[2]) if ntok > 2 else 0.1
322
+ elif s[1].lower().startswith(("bval", "uval")) and ntok > 2:
323
+ if s[2].lower().startswith("diff"):
324
+ itk = 3
325
+ while itk < ntok:
326
+ if s[itk].lower().startswith("sigm"):
327
+ if ntok > itk + 1:
328
+ r["sigma_uval_diff"] = float(s[itk+1])
329
+ itk += 2
330
+ else:
331
+ r["sigma_uval_diff"] = 0.025
332
+ itk += 1
333
+ elif s[itk].lower().startswith("dmax"):
334
+ if ntok > itk + 1:
335
+ r["dmax_uval_diff"] = float(s[itk+1])
336
+ itk += 2
337
+ else:
338
+ r["dmax_uval_diff"] = 4.2
339
+ itk += 1
340
+ elif s[itk].lower().startswith("dmwe"): # not used
341
+ if ntok > itk + 1:
342
+ r["dmax_uval_weight"] = float(s[itk+1]) * b_to_u
343
+ itk += 2
344
+ else:
345
+ r["dmax_uval_weight"] = 3.0
346
+ itk += 1
347
+ else:
348
+ itk += 1
349
+ else:
350
+ r["sigma_b"] = float(s[2])
351
+ r["sigma_u"] = float(s[2])
352
+ else:
353
+ msg = "unrecognised keyword: {}\n=> {}".format(s[1], l)
354
+ if raise_unknown:
355
+ raise RuntimeError(msg)
356
+ else:
357
+ logger.writeln(f"WARNING: {msg}")
358
+
359
+ return r
360
+ # read_ridge_params()
361
+
362
+ def read_occupancy_params(l, r, raise_unknown=True):
363
+ s = l.split()
364
+ if not s[0].lower().startswith("occu"):
365
+ return r
366
+ ntok = len(s)
367
+ r.setdefault("groups", {}) # {igr: [{selection}, {selection}, ..]}
368
+ r.setdefault("const", []) # [[is_comp, group_ids]]
369
+ r.setdefault("ncycle", 0) # 0 means no refine
370
+ if (ntok > 4 and
371
+ s[1].lower().startswith("grou") and
372
+ s[2].lower().startswith("id")):
373
+ igr = s[3]
374
+ gr = r["groups"].setdefault(igr, [])
375
+ gr.append({})
376
+ itk = 4
377
+ while itk < ntok:
378
+ if s[itk].lower().startswith(("chai", "segm")):
379
+ gr[-1]["chains"] = []
380
+ itk += 1
381
+ while itk < ntok and not s[itk].lower().startswith(("resi","atom","alt")):
382
+ gr[-1]["chains"].append(s[itk])
383
+ itk += 1
384
+ elif s[itk].lower().startswith("resi"):
385
+ if s[itk+1].lower().startswith("from"):
386
+ gr[-1]["resi_from"] = gemmi.SeqId(s[itk+2])
387
+ if s[itk+3].lower().startswith("to"):
388
+ gr[-1]["resi_to"] = gemmi.SeqId(s[itk+4])
389
+ itk += 5
390
+ else:
391
+ gr[-1]["resi"] = gemmi.SeqId(s[itk+1])
392
+ itk += 2
393
+ elif s[itk].lower().startswith("atom"):
394
+ gr[-1]["atom"] = s[itk+1]
395
+ itk += 2
396
+ elif s[itk].lower().startswith("alt"):
397
+ gr[-1]["alt"] = s[itk+1]
398
+ itk += 2
399
+ elif (ntok > 4 and
400
+ s[1].lower().startswith("grou") and
401
+ s[2].lower().startswith("alts")):
402
+ r["const"].append((s[3].lower().startswith("comp"), s[4:]))
403
+ elif ntok > 1 and s[1].lower().startswith("refi"):
404
+ if ntok > 3 and s[2].lower().startswith("ncyc"):
405
+ r["ncycle"] = max(int(s[3]), r["ncycle"])
406
+ elif r["ncycle"] == 0:
407
+ r["ncycle"] = 1 # default
408
+ elif raise_unknown:
409
+ raise RuntimeError("unrecognized keyword: " + l)
410
+
411
+ return r
412
+ # read_occupancy_params()
413
+
414
+ def read_restr_params(l, r, raise_unknown=True):
415
+ s = l.split()
416
+
417
+ def read_tors_params(itk):
418
+ ret = {"flag": True}
419
+ if s[itk].lower().startswith("none"): # remove all
420
+ ret["flag"] = False
421
+ itk += 1
422
+ elif s[itk].lower().startswith("resi"):
423
+ ret["residue"] = s[itk+1]
424
+ itk += 2
425
+ elif s[itk].lower().startswith("grou"):
426
+ ret["group"] = s[itk+1] # group_name_tors_restr_o
427
+ itk += 2
428
+ elif s[itk].lower().startswith("link"):
429
+ ret["link"] = s[itk+1] #link_name_tors_restr_o
430
+ itk += 2
431
+ else:
432
+ pass # raise error?
433
+ while itk < len(s):
434
+ if s[itk].lower().startswith("name"):
435
+ itk += 1
436
+ ret["tors_name"] = s[itk] # RES_NAME_TORS_NAME_O
437
+ elif s[itk].lower().startswith("valu"):
438
+ itk += 1
439
+ ret["tors_value"] = float(s[itk]) # RES_NAME_TORS_VALUE_O
440
+ elif s[itk].lower().startswith("sigm"):
441
+ itk += 1
442
+ ret["tors_sigma"] = float(s[itk]) # RES_NAME_TORS_SIGMA_O
443
+ elif s[itk].lower().startswith("peri"):
444
+ itk += 1
445
+ ret["tors_period"] = int(s[itk]) # RES_NAME_TORS_PERIOD_O
446
+ itk += 1
447
+ return ret, itk
448
+ # read_tors_params()
449
+
450
+ if not s[0].lower().startswith("rest"):
451
+ return r
452
+ if s[1].lower().startswith("excl"):
453
+ # restr_params.f90 subroutine exclude_restraints
454
+ #r["exclude"] = True
455
+ pass
456
+ elif s[1].lower().startswith("conf"): # not implemented in Refmac
457
+ pass
458
+ elif s[1].lower().startswith(("bp", "pair", "base")):
459
+ if s[2].lower().startswith("dist"):
460
+ r["plane_dist"] = True
461
+ else:
462
+ r["basepair"] = True
463
+ # TODO read dnarna_params.txt
464
+ elif s[1].lower().startswith("tors") and len(s) > 2:
465
+ itk = 2
466
+ if s[itk].lower().startswith("hydr"):
467
+ itk += 1
468
+ if s[itk].lower().startswith("incl"):
469
+ itk += 1
470
+ r["htors_restraint"] = True
471
+ if s[itk].lower().startswith("all"): # this is servalcat default for now
472
+ r["htors_restraint_type"] = "all"
473
+ elif s[itk].lower().startswith("sele"):
474
+ r["htors_restraint_type"] = "sele"
475
+ r["htors_restraint_list"] = s[itk+1:]
476
+ else:
477
+ itk += 1
478
+ r["htors_restraint"] = False
479
+ elif s[itk].lower().startswith("fbas"):
480
+ pass # set user_basepair_file
481
+ elif s[itk].lower().startswith("incl"):
482
+ tmp, itk = read_tors_params(itk+1)
483
+ r.setdefault("torsion_include", []).append(tmp)
484
+ elif s[itk].lower().startswith("excl"):
485
+ # Should warn if value/sigma/period given?
486
+ tmp, itk = read_tors_params(itk+1)
487
+ r.setdefault("torsion_exclude", []).append(tmp)
488
+ elif s[1].lower().startswith("resi"):
489
+ pass
490
+ elif s[1].lower().startswith("chir"):
491
+ pass
492
+ elif raise_unknown:
493
+ raise RuntimeError("unrecognized keyword: " + l)
494
+ # read_restr_params()
495
+
496
+ def read_make_params(l, r, raise_unknown=True):
497
+ # TODO: hout,ribo,valu,spec,form,sdmi,segi
498
+ s = l.split()
499
+ assert s[0].lower().startswith("make")
500
+ itk = 1
501
+ ntok = len(s)
502
+ # keyword, key, func, possible, default
503
+ keys = (("hydr", "hydr", lambda x: x[0].lower(), set("aynf"), "a"),
504
+ ("hout", "hout", lambda x: x[0].lower() in ("y", "p"), (True, False), True),
505
+ ("chec", "check", lambda x: "0" if x.lower().startswith(("none", "0"))
506
+ else ("n" if x.lower().startswith(("liga", "n"))
507
+ else ("y" if x.lower().startswith(("all", "y")) else None)),
508
+ set("0ny"), None), # no default
509
+ ("newl", "newligand", lambda x: x.lower().startswith(("c", "y", "noex")), (True, False), False),
510
+ ("buil", "build", lambda x: x[0].lower(), set("ny"), "n"),
511
+ ("pept", "pept", lambda x: x[0].lower(), set("yn"), "y"),
512
+ ("link", "link", lambda x: x[0].lower(), set("ynd0"), "y"),
513
+ ("suga", "sugar", lambda x: x[0].lower(), set("ynds"), "y"),
514
+ #("conn", "conn", lambda x: x[0].lower(), set("nyd0"), "n"), # TODO read conn_tolerance? (make conn tole val)
515
+ ("symm", "symm", lambda x: x[0].lower(), set("ync"), "y"),
516
+ ("chai", "chain", lambda x: x[0].lower(), set("yn"), "y"),
517
+ ("cisp", "cispept", lambda x: x[0].lower(), set("yn"), "y"),
518
+ ("ss", "ss", lambda x: x[0].lower(), set("ydn"), "y"),
519
+ ("exit", "exit", lambda x: x[0].lower() == "y", (True, False), True),
520
+ )
521
+ while itk < ntok:
522
+ found = False
523
+ for k, t, f, p, d in keys:
524
+ if s[itk].lower().startswith(k):
525
+ if itk + 1 < len(s):
526
+ r[t] = f(s[itk+1])
527
+ if r[t] not in p:
528
+ raise SystemExit("Invalid make instruction: {}".format(l))
529
+ itk += 2
530
+ elif d is not None:
531
+ r[t] = d # set default
532
+ itk += 1
533
+ else:
534
+ raise SystemExit("Invalid make instruction: {}".format(l))
535
+ break
536
+ else:
537
+ if raise_unknown:
538
+ raise RuntimeError("unrecognized keyword: " + l)
539
+ else:
540
+ itk += 1
541
+ return r
542
+ # read_make_params()
543
+
544
+ def parse_line(l, ret, raise_unknown=True):
545
+ s = l.split()
546
+ ntok = len(s)
547
+ if ntok == 0: return
548
+ if s[0].lower().startswith("exte"):
549
+ ret.setdefault("exte", []).append(read_exte(s, raise_unknown))
550
+ elif s[0].lower().startswith("make"):
551
+ read_make_params(l, ret.setdefault("make", {}), raise_unknown)
552
+ elif s[0].lower().startswith(("sour", "scat")):
553
+ k = s[1].lower()
554
+ if k.startswith("em"):
555
+ ret["source"] = "em"
556
+ elif k.startswith("e"):
557
+ ret["source"] = "ec"
558
+ elif k.startswith("n"):
559
+ ret["source"] = "ne"
560
+ else:
561
+ ret["source"] = "xr"
562
+ # TODO check mb, lamb
563
+ elif s[0].lower().startswith("refi"): # TODO support this. Note that only valid with hklin
564
+ ret.setdefault("refi", {})
565
+ itk = 1
566
+ while itk < ntok:
567
+ if s[itk].lower().startswith("type"):
568
+ if itk+1 < ntok and s[itk+1].lower().startswith("unre"):
569
+ ret["refi"]["type"] = "unre"
570
+ itk += 2
571
+ else:
572
+ itk += 1
573
+ elif s[0].lower().startswith("dist"):
574
+ try:
575
+ ret["wbond"] = float(s[1])
576
+ except:
577
+ pass
578
+ # TODO read sdex, excu, dele, dmxe, dmne
579
+ elif s[0].lower().startswith("ridg"):
580
+ read_ridge_params(l, ret.setdefault("ridge", {}), raise_unknown)
581
+ elif s[0].lower().startswith("occu"):
582
+ read_occupancy_params(l, ret.setdefault("occu", {}), raise_unknown)
583
+ elif s[0].lower().startswith("rest"):
584
+ read_restr_params(l, ret.setdefault("restr", {}), raise_unknown)
585
+ elif s[0].lower().startswith("angl") and ntok > 1:
586
+ ret["wangle"] = float(s[1])
587
+ elif s[0].lower().startswith("tors") and ntok > 1:
588
+ ret["wtors"] = float(s[1])
589
+ elif s[0].lower().startswith(("bfac", "temp", "bval")):
590
+ pass # TODO
591
+ elif s[0].lower().startswith("plan") and ntok > 1:
592
+ ret["wplane"] = float(s[1])
593
+ elif s[0].lower().startswith("chir") and ntok > 1:
594
+ ret["wchir"] = float(s[1])
595
+ # TODO read calp
596
+ elif s[0].lower().startswith(("vdwr", "vand", "nonb")) and ntok > 1:
597
+ itk = 1
598
+ try:
599
+ ret["wvdw"] = float(s[itk])
600
+ itk += 1
601
+ except ValueError:
602
+ pass
603
+ elif raise_unknown:
604
+ raise RuntimeError(f"unrecognized keyword: {l}")
605
+ # TODO read maxr, over, sigm, incr, chan, vdwc, excl
606
+ # parse_line()
607
+
608
+ def get_lines(lines, depth=0):
609
+ ret = []
610
+ cont = ""
611
+ for l in lines:
612
+ if "!" in l: l = l[:l.index("!")]
613
+ if "#" in l: l = l[:l.index("#")]
614
+ l = l.strip()
615
+ if not l: continue
616
+ if l[0] == "@":
617
+ f = l[1:]
618
+ try:
619
+ yield from get_lines(open(f).readlines(), depth+1)
620
+ except RuntimeError:
621
+ return
622
+ continue
623
+ if l.split()[-1] == "-":
624
+ cont += l[:l.rfind("-")] + " "
625
+ continue
626
+ if cont:
627
+ l = cont + l
628
+ cont = ""
629
+ if l.split()[0].lower().startswith("end"):
630
+ # refmac stops reading keywords when "exit" is seen in stdin or a file
631
+ # but won't do this from nested files
632
+ if depth == 1:
633
+ raise RuntimeError
634
+ break
635
+ yield l
636
+ # get_lines()
637
+
638
+ def update_params(ret, inputs, raise_unknown=True):
639
+ if not inputs:
640
+ return
641
+ for l in get_lines(inputs):
642
+ parse_line(l, ret, raise_unknown=raise_unknown)
643
+ # update_keywords()
644
+
645
+ def parse_keywords(inputs, raise_unknown=True):
646
+ ret = {"make":{}, "ridge":{}, "refi":{}}
647
+ update_params(ret, inputs, raise_unknown)
648
+ return ret
649
+ # parse_keywords()
650
+
651
+ if __name__ == "__main__":
652
+ import sys
653
+ import json
654
+ print("waiting for input")
655
+ ret = {} #{"make":{}, "ridge":{}, "refi":{}}
656
+ for l in get_lines(sys.stdin):
657
+ parse_line(l, ret)
658
+ print()
659
+ print("Parsed:")
660
+ print(json.dumps(ret, indent=1))