servalcat 0.4.88__cp38-cp38-macosx_11_0_arm64.whl → 0.4.99__cp38-cp38-macosx_11_0_arm64.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.

servalcat/__init__.py CHANGED
@@ -6,5 +6,5 @@ This software is released under the
6
6
  Mozilla Public License, version 2.0; see LICENSE.
7
7
  """
8
8
 
9
- __version__ = '0.4.88'
10
- __date__ = '2024-09-13'
9
+ __version__ = '0.4.99'
10
+ __date__ = '2024-12-04'
Binary file
@@ -28,7 +28,7 @@ b_to_u = utils.model.b_to_u
28
28
  #atexit.register(profile.print_stats)
29
29
 
30
30
  class Geom:
31
- def __init__(self, st, topo, monlib, adpr_w=1, shake_rms=0,
31
+ def __init__(self, st, topo, monlib, adpr_w=1, occr_w=1, shake_rms=0,
32
32
  params=None, unrestrained=False, use_nucleus=False,
33
33
  ncslist=None, atom_pos=None):
34
34
  self.st = st
@@ -53,7 +53,7 @@ class Geom:
53
53
  n_sym = len(images) + 1
54
54
  self.geom.specials.append(ext.Geometry.Special(atom, matp, mata, n_sym))
55
55
  self.adpr_w = adpr_w
56
- self.occr_w = 1.
56
+ self.occr_w = occr_w
57
57
  self.unrestrained = unrestrained
58
58
  if shake_rms > 0:
59
59
  numpy.random.seed(0)
@@ -225,6 +225,26 @@ def write_stats_json_safe(stats, json_out):
225
225
  logger.writeln(f"Refinement statistics saved: {json_out}")
226
226
  # write_stats_json_safe()
227
227
 
228
+ def print_h_options(h_change, h_present, refine_h, hout, geom_only):
229
+ if not h_present:
230
+ h_change = gemmi.HydrogenChange.Remove
231
+ logger.writeln("Hydrogen related options")
232
+ logger.write(" use in refinement{}: hydrogen atoms ".format("" if geom_only else "/map calculation"))
233
+ logger.writeln({gemmi.HydrogenChange.ReAddButWater: "have been (re)generated",
234
+ gemmi.HydrogenChange.ReAdd: "(including water) have been (re)generated",
235
+ gemmi.HydrogenChange.ReAddKnown: "(except for rotatable) have been (re) generated",
236
+ gemmi.HydrogenChange.NoChange: "from the input model have been retained",
237
+ gemmi.HydrogenChange.Remove: "have either been removed or were not present"}[h_change])
238
+ if h_present:
239
+ logger.write(" target: hydrogen atoms will be ")
240
+ if geom_only or not refine_h:
241
+ logger.writeln("just optimized according to geometric restraints")
242
+ else:
243
+ logger.writeln("refined against experimental data")
244
+ logger.writeln(" in output model: " + ("written" if hout and h_present else "not written"))
245
+ logger.writeln("")
246
+ # print_hydrogen_options()
247
+
228
248
  class GroupOccupancy:
229
249
  # TODO max may not be one. should check multiplicity
230
250
  def __init__(self, st, params):
@@ -293,6 +313,7 @@ class GroupOccupancy:
293
313
  vals = []
294
314
  for _, atoms in self.groups:
295
315
  occ = numpy.mean([a.occ for a in atoms])
316
+ occ = min(1, max(1e-3, occ))
296
317
  vals.append(occ)
297
318
  for is_comp, idxes in self.consts:
298
319
  sum_occ = sum(vals[i] for i in idxes)
@@ -311,6 +332,7 @@ class GroupOccupancy:
311
332
  for p, (_, atoms) in zip(x, self.groups):
312
333
  for a in atoms:
313
334
  a.occ = p
335
+ #a.occ = max(1, min(1e-3, p))
314
336
 
315
337
  def target(self, x, ll, ls, u):
316
338
  self.set_x(x)
@@ -425,7 +447,7 @@ class Refine:
425
447
  self.geom.set_h_parents()
426
448
  if params and params.get("write_trajectory"):
427
449
  self.st_traj = self.st.clone()
428
- self.st_traj[-1].name = "0"
450
+ self.st_traj[-1].num = 0
429
451
  assert self.geom.group_occ.groups or self.n_params() > 0
430
452
  # __init__()
431
453
 
@@ -442,6 +464,9 @@ class Refine:
442
464
  logger.writeln(" sigmas: {}".format(" ".join("{:.2f}".format(x) for x in g.adpr_kl_sigs)))
443
465
  else:
444
466
  raise LookupError("unknown adpr_mode")
467
+ if self.refine_occ:
468
+ logger.writeln(" Occupancy restraints")
469
+ logger.writeln(" weight: {}".format(self.geom.occr_w))
445
470
 
446
471
  def scale_shifts(self, dx, scale):
447
472
  n_atoms = self.geom.n_refine_atoms
@@ -525,7 +550,7 @@ class Refine:
525
550
  elif self.adp_mode == 2:
526
551
  a = x[offset_b + 6 * j: offset_b + 6 * (j+1)]
527
552
  a = gemmi.SMat33d(*a)
528
- M = numpy.array(a.as_mat33())
553
+ M = a.as_mat33().array
529
554
  v, Q = numpy.linalg.eigh(M) # eig() may return complex due to numerical precision?
530
555
  v = numpy.maximum(v, 0.5) # avoid NPD with minimum B = 0.5
531
556
  M2 = Q.dot(numpy.diag(v)).dot(Q.T)
@@ -736,7 +761,7 @@ class Refine:
736
761
  weight /= 1.1
737
762
  if self.st_traj is not None:
738
763
  self.st_traj.add_model(self.st[0])
739
- self.st_traj[-1].name = str(len(self.st_traj))
764
+ self.st_traj[-1].num = len(self.st_traj)
740
765
  if stats_json_out:
741
766
  write_stats_json_safe(stats, stats_json_out)
742
767
 
@@ -781,43 +806,101 @@ class Refine:
781
806
  lstr = utils.make_loggraph_str(df, "stats vs cycle", forplot,
782
807
  float_format="{:.4f}".format)
783
808
  logger.writeln(lstr)
784
- self.update_meta(stats[-1])
785
809
  return stats
786
810
 
787
- def update_meta(self, stats):
788
- # TODO write stats. probably geom.reporting.get_summary_table should return with _refine_ls_restr.type names
789
- # should remove st.mod_residues?
790
- self.st.helices.clear()
791
- self.st.sheets.clear()
792
- raw_remarks = [f'REMARK 3',
793
- f'REMARK 3 REFINEMENT.',
794
- f'REMARK 3 PROGRAM : SERVALCAT {servalcat.__version__}',
795
- f'REMARK 3 AUTHORS : YAMASHITA,MURSHUDOV',
796
- f'REMARK 3',
797
- ]
798
- si = gemmi.SoftwareItem()
799
- si.classification = gemmi.SoftwareItem.Classification.Refinement
800
- si.name = "Servalcat"
801
- si.version = servalcat.__version__
802
- si.date = servalcat.__date__
803
- self.st.meta.software = [si]
804
-
805
- ri = gemmi.RefinementInfo()
806
- if "geom" in stats:
807
- restr_stats = []
808
- raw_remarks.append("REMARK 3 RMS DEVIATIONS FROM IDEAL VALUES COUNT RMS WEIGHT")
809
- for k, n, l, pl in (("r.m.s.d.", "Bond distances, non H", "s_bond_nonh_d", "BOND LENGTHS REFINED ATOMS (A)"),
810
- ("r.m.s.d.", "Bond angles, non H", "s_angle_nonh_d", "BOND ANGLES REFINED ATOMS (DEGREES)")):
811
- if k in stats["geom"]["summary"] and n in stats["geom"]["summary"][k]:
812
- rr = gemmi.RefinementInfo.Restr(l)
813
- rr.dev_ideal = stats["geom"]["summary"][k].get(n)
814
- rr.count = stats["geom"]["summary"]["N restraints"].get(n)
815
- rr.weight = stats["geom"]["summary"]["Mn(sigma)"].get(n)
816
- restr_stats.append(rr)
817
- raw_remarks.append(f"REMARK 3 {pl}:{rr.count:6d} ;{rr.dev_ideal:6.3f} ;{rr.weight:6.3f}")
818
- ri.restr_stats = restr_stats
819
- raw_remarks.append("REMARK 3")
820
- self.st.meta.refinement = [ri]
821
- self.st.raw_remarks = raw_remarks
822
-
823
811
  # class Refine
812
+
813
+ def update_meta(st, stats, ll=None):
814
+ # TODO write stats. probably geom.reporting.get_summary_table should return with _refine_ls_restr.type names
815
+ # should remove st.mod_residues?
816
+ st.helices.clear()
817
+ st.sheets.clear()
818
+ raw_remarks = [f'REMARK 3',
819
+ f'REMARK 3 REFINEMENT.',
820
+ f'REMARK 3 PROGRAM : SERVALCAT {servalcat.__version__}',
821
+ f'REMARK 3 AUTHORS : YAMASHITA,MURSHUDOV',
822
+ f'REMARK 3',
823
+ ]
824
+ si = gemmi.SoftwareItem()
825
+ si.classification = gemmi.SoftwareItem.Classification.Refinement
826
+ si.name = "Servalcat"
827
+ si.version = servalcat.__version__
828
+ si.date = servalcat.__date__
829
+ st.meta.software = [si]
830
+
831
+ ri = gemmi.RefinementInfo()
832
+ if "geom" in stats:
833
+ restr_stats = []
834
+ raw_remarks.append("REMARK 3 RMS DEVIATIONS FROM IDEAL VALUES COUNT RMS WEIGHT")
835
+ for k, n, l, pl in (("r.m.s.d.", "Bond distances, non H", "s_bond_nonh_d", "BOND LENGTHS REFINED ATOMS (A)"),
836
+ ("r.m.s.d.", "Bond angles, non H", "s_angle_nonh_deg", "BOND ANGLES REFINED ATOMS (DEGREES)"),
837
+ ("r.m.s.d.", "Torsion angles, period 1", "s_dihedral_angle_1_deg", "TORSION ANGLES, PERIOD 1 (DEGREES)"),
838
+ ("r.m.s.d.", "Torsion angles, period 2", "s_dihedral_angle_2_deg", "TORSION ANGLES, PERIOD 2 (DEGREES)"),
839
+ ("r.m.s.d.", "Torsion angles, period 3", "s_dihedral_angle_3_deg", "TORSION ANGLES, PERIOD 3 (DEGREES)"),
840
+ ("r.m.s.d.", "Torsion angles, period 6", "s_dihedral_angle_6_deg", "TORSION ANGLES, PERIOD 6 (DEGREES)"),
841
+ ("r.m.s.d.", "Chiral centres", "s_chiral_restr", "CHIRAL-CENTER RESTRAINTS (A**3)"),
842
+ ("r.m.s.d.", "Planar groups", "s_planes", "GENERAL PLANES REFINED ATOMS (A)"),
843
+ ("r.m.s.d.", "VDW nonbonded", "s_nbd", ""),
844
+ ("r.m.s.d.", "VDW torsion", "s_nbtor", ""),
845
+ ("r.m.s.d.", "VDW hbond", "s_hbond_nbd", ""),
846
+ ("r.m.s.d.", "VDW metal", "s_metal_ion", ""),
847
+ ("r.m.s.d.", "VDW dummy", "s_dummy_nbd", ""),
848
+ ("r.m.s.d.", "VDW nonbonded, symmetry", "s_symmetry_nbd", ""),
849
+ ("r.m.s.d.", "VDW torsion, symmetry", "s_symmetry_nbtor", ""),
850
+ ("r.m.s.d.", "VDW hbond, symmetry", "s_symmetry_hbond_nbd", ""),
851
+ ("r.m.s.d.", "VDW metal, symmetry", "s_symmetry_metal_ion", ""),
852
+ ("r.m.s.d.", "VDW dummy, symmetry", "s_symmetry_dummy_nbd", "")):
853
+ if k in stats["geom"]["summary"] and n in stats["geom"]["summary"][k]:
854
+ rr = gemmi.RefinementInfo.Restr(l)
855
+ rr.dev_ideal = round(stats["geom"]["summary"][k].get(n), 4)
856
+ rr.count = stats["geom"]["summary"]["N restraints"].get(n)
857
+ rr.weight = round(stats["geom"]["summary"]["Mn(sigma)"].get(n), 4)
858
+ restr_stats.append(rr)
859
+ if pl:
860
+ raw_remarks.append(f"REMARK 3 {pl}:{rr.count:6d} ;{rr.dev_ideal:6.3f} ;{rr.weight:6.3f}")
861
+ ri.restr_stats = restr_stats
862
+ raw_remarks.append("REMARK 3")
863
+ if ll is not None:
864
+ ri.id = ll.refine_id()
865
+ ri.mean_b = round(numpy.mean([cra.atom.b_iso for cra in st[0].all()]), 2)
866
+ if ll.b_aniso is not None:
867
+ ri.aniso_b = ll.b_aniso
868
+ for k, kd, nd in (("Rwork", "r_work", 4), ("Rfree", "r_free", 4), ("R", "r_all", 4),
869
+ ("FSCaverage", "fsc_work", 4),
870
+ ("FSCaverage_half1", "fsc_work", 4), ("FSCaverage_half2", "fsc_free", 4)):
871
+ if k in stats["data"]["summary"]:
872
+ setattr(ri, kd, round(stats["data"]["summary"][k], nd))
873
+ bins = []
874
+ n_all = 0
875
+ for b in stats["data"]["binned"]:
876
+ bri = gemmi.BasicRefinementInfo()
877
+ bri.resolution_high = round(b["d_min"], 3)
878
+ bri.resolution_low = round(b["d_max"], 3)
879
+ for k, kd, nd in (("Rwork", "r_work", 4), ("Rfree", "r_free", 4),
880
+ ("R1work", "r_work", 4), ("R1free", "r_free", 4),
881
+ ("R", "r_all", 4), ("R1", "r_all", 4),
882
+ ("CCI", "cc_intensity_work", 4), ("CCF", "cc_fo_fc_work", 4),
883
+ ("CCIwork", "cc_intensity_work", 4), ("CCIfree", "cc_intensity_free", 4),
884
+ ("CCFwork", "cc_fo_fc_work", 4), ("CCFfree", "cc_fo_fc_free", 4),
885
+ ("fsc_FC_full", "fsc_work", 4), ("fsc_model", "fsc_work", 4),
886
+ ("fsc_model_half1", "fsc_work", 4), ("fsc_model_half2", "fsc_free", 4),
887
+ ("n_work", "work_set_count", 0), ("n_free", "rfree_set_count", 0),
888
+ ("n_obs", "reflection_count", 0), ("ncoeffs", "reflection_count", 0)):
889
+ if k in b: setattr(bri, kd, round(b[k], nd))
890
+ if "n_all" in b and "n_obs" in b:
891
+ bri.completeness = round(b["n_obs"] / b["n_all"] * 100, 2)
892
+ n_all += b["n_all"]
893
+ bins.append(bri)
894
+ ri.rfree_set_count = max(-1, sum(b.rfree_set_count for b in bins))
895
+ ri.work_set_count = max(-1, sum(b.work_set_count for b in bins))
896
+ ri.reflection_count = max(-1, sum(b.reflection_count for b in bins))
897
+ ri.resolution_high = round(min(b.resolution_high for b in bins), 3)
898
+ ri.resolution_low = round(max(b.resolution_low for b in bins), 3)
899
+ if ri.reflection_count > 0 and n_all > 0:
900
+ ri.completeness = round(ri.reflection_count / n_all * 100, 2)
901
+ ri.bins = bins
902
+ if ri.rfree_set_count > 0:
903
+ ri.cross_validation_method = "THROUGHOUT"
904
+ st.meta.refinement = [ri]
905
+ st.raw_remarks = raw_remarks
906
+ # update_meta()
@@ -14,7 +14,7 @@ import json
14
14
  import servalcat # for version
15
15
  from servalcat.utils import logger
16
16
  from servalcat import utils
17
- from servalcat.refine.refine import Geom, Refine, convert_stats_to_dicts
17
+ from servalcat.refine.refine import Geom, Refine, convert_stats_to_dicts, update_meta, print_h_options
18
18
  from servalcat.refmac import refmac_keywords
19
19
 
20
20
  def add_arguments(parser):
@@ -93,9 +93,14 @@ def refine_and_update_dictionary(cif_in, monomer_dir, output_prefix, randomize=0
93
93
  if len(st) > 0: break
94
94
  else:
95
95
  raise SystemExit("No model in the cif file")
96
- monlib = utils.restraints.load_monomer_library(st, monomer_dir=monomer_dir, # monlib is needed for ener_lib
97
- cif_files=[cif_in],
98
- stop_for_unknowns=True)
96
+ for i in range(len(st)-1):
97
+ del st[1]
98
+ try:
99
+ monlib = utils.restraints.load_monomer_library(st, monomer_dir=monomer_dir, # monlib is needed for ener_lib
100
+ cif_files=[cif_in],
101
+ stop_for_unknowns=True)
102
+ except RuntimeError as e:
103
+ raise SystemExit("Error: {}".format(e))
99
104
  all_stats = []
100
105
  for i_macro in 0, 1:
101
106
  try:
@@ -121,16 +126,19 @@ def refine_and_update_dictionary(cif_in, monomer_dir, output_prefix, randomize=0
121
126
  for row in block.find("_chem_comp_atom.", ["atom_id", "?x", "?y", "?z",
122
127
  "?pdbx_model_Cartn_x_ideal",
123
128
  "?pdbx_model_Cartn_y_ideal",
124
- "?pdbx_model_Cartn_z_ideal"]):
129
+ "?pdbx_model_Cartn_z_ideal",
130
+ "?model_Cartn_x", "?model_Cartn_y", "?model_Cartn_z"]):
125
131
  p = pos[row.str(0)]
126
132
  for i in range(3):
127
133
  if row.has(i+1):
128
134
  row[i+1] = "{:.3f}".format(p[i])
129
135
  if row.has(i+4):
130
136
  row[i+4] = "{:.3f}".format(p[i])
137
+ if row.has(i+7):
138
+ row[i+7] = "{:.3f}".format(p[i])
131
139
  # add description
132
140
  add_program_info_to_dictionary(block, st[0][0][0].name)
133
- doc.write_file(output_prefix + "_updated.cif", style=gemmi.cif.Style.Aligned)
141
+ doc.write_file(output_prefix + "_updated.cif", options=gemmi.cif.Style.Aligned)
134
142
  logger.writeln("Updated dictionary saved: {}".format(output_prefix + "_updated.cif"))
135
143
  with open(output_prefix + "_stats.json", "w") as ofs:
136
144
  json.dump([convert_stats_to_dicts(x) for x in all_stats],
@@ -147,11 +155,13 @@ def refine_geom(model_in, monomer_dir, cif_files, h_change, ncycle, output_prefi
147
155
  logger.writeln("Take NCS constraints into account.")
148
156
  st2.expand_ncs(gemmi.HowToNameCopiedChain.Dup, merge_dist=0)
149
157
  utils.fileio.write_model(st2, file_name="input_expanded.pdb")
150
-
151
- monlib = utils.restraints.load_monomer_library(st, monomer_dir=monomer_dir,
152
- cif_files=cif_files,
153
- stop_for_unknowns=True,
154
- params=params)
158
+ try:
159
+ monlib = utils.restraints.load_monomer_library(st, monomer_dir=monomer_dir,
160
+ cif_files=cif_files,
161
+ stop_for_unknowns=True,
162
+ params=params)
163
+ except RuntimeError as e:
164
+ raise SystemExit("Error: {}".format(e))
155
165
  utils.restraints.find_and_fix_links(st, monlib, find_metal_links=find_links,
156
166
  add_found=find_links) # should remove unknown id here?
157
167
  try:
@@ -161,6 +171,8 @@ def refine_geom(model_in, monomer_dir, cif_files, h_change, ncycle, output_prefi
161
171
  except RuntimeError as e:
162
172
  raise SystemExit("Error: {}".format(e))
163
173
 
174
+ print_h_options(h_change, st[0].has_hydrogen(), refine_h=True, hout=True, geom_only=True)
175
+
164
176
  if use_ncsr:
165
177
  ncslist = utils.restraints.prepare_ncs_restraints(st)
166
178
  else:
@@ -169,6 +181,7 @@ def refine_geom(model_in, monomer_dir, cif_files, h_change, ncycle, output_prefi
169
181
  refiner = Refine(st, geom, params=params)
170
182
  stats = refiner.run_cycles(ncycle,
171
183
  stats_json_out=output_prefix + "_stats.json")
184
+ update_meta(st, stats[-1])
172
185
  refiner.st.name = output_prefix
173
186
  utils.fileio.write_model(refiner.st, output_prefix, pdb=True, cif=True)
174
187
  if params["write_trajectory"]:
@@ -14,12 +14,12 @@ from servalcat import utils
14
14
  from servalcat.spa.run_refmac import check_args, process_input, calc_fsc, calc_fofc
15
15
  from servalcat.spa import fofc
16
16
  from servalcat.refine import spa
17
- from servalcat.refine.refine import Geom, Refine
17
+ from servalcat.refine.refine import Geom, Refine, update_meta, print_h_options
18
18
  from servalcat.refmac import refmac_keywords
19
19
  b_to_u = utils.model.b_to_u
20
20
 
21
21
  def add_arguments(parser):
22
- parser.description = "EXPERIMENTAL program to refine cryo-EM SPA structures"
22
+ parser.description = "program to refine cryo-EM SPA structures"
23
23
  group = parser.add_mutually_exclusive_group(required=True)
24
24
  group.add_argument("--halfmaps", nargs=2, help="Input half map files")
25
25
  group.add_argument("--map", help="Use this only if you really do not have half maps.")
@@ -68,7 +68,7 @@ def add_arguments(parser):
68
68
  help="Jelly body only (experimental, may not be useful)")
69
69
  utils.symmetry.add_symmetry_args(parser) # add --pg etc
70
70
  parser.add_argument('--contacting_only', action="store_true", help="Filter out non-contacting strict NCS copies")
71
- parser.add_argument('--ignore_symmetry',
71
+ parser.add_argument('--ignore_symmetry', action='store_true',
72
72
  help='Ignore symmetry information (MTRIX/_struct_ncs_oper) in the model file')
73
73
  parser.add_argument('--find_links', action='store_true',
74
74
  help='Automatically add links')
@@ -94,6 +94,8 @@ def add_arguments(parser):
94
94
  help='Bond rmsz range for weight adjustment (default: %(default)s)')
95
95
  parser.add_argument('--adpr_weight', type=float, default=1.,
96
96
  help="ADP restraint weight (default: %(default)f)")
97
+ parser.add_argument('--occr_weight', type=float, default=1.,
98
+ help="Occupancy restraint weight (default: %(default)f)")
97
99
  parser.add_argument('--ncsr', action='store_true',
98
100
  help='Use local NCS restraints')
99
101
  parser.add_argument('--bfactor', type=float,
@@ -108,8 +110,9 @@ def add_arguments(parser):
108
110
  parser.add_argument('--adp_restraint_exp_fac', type=float)
109
111
  parser.add_argument('--adp_restraint_no_long_range', action='store_true')
110
112
  parser.add_argument('--adp_restraint_mode', choices=["diff", "kldiv"], default="diff")
113
+ parser.add_argument('--unrestrained', action='store_true', help="No positional restraints")
111
114
  parser.add_argument('--refine_h', action="store_true", help="Refine hydrogen against data (default: only restraints apply)")
112
- parser.add_argument("--source", choices=["electron", "xray", "neutron"], default="electron")
115
+ parser.add_argument("-s", "--source", choices=["electron", "xray", "neutron"], default="electron")
113
116
  parser.add_argument('-o','--output_prefix', default="refined")
114
117
  parser.add_argument('--cross_validation', action='store_true',
115
118
  help='Run cross validation. Only "throughout" mode is available (no "shake" mode)')
@@ -142,12 +145,23 @@ def main(args):
142
145
  params["write_trajectory"] = args.write_trajectory
143
146
 
144
147
  st = utils.fileio.read_structure(args.model)
145
- try:
146
- monlib = utils.restraints.load_monomer_library(st, monomer_dir=args.monlib, cif_files=args.ligand,
147
- stop_for_unknowns=not args.newligand_continue,
148
- params=params)
149
- except RuntimeError as e:
150
- raise SystemExit("Error: {}".format(e))
148
+ if args.unrestrained:
149
+ monlib = gemmi.MonLib()
150
+ topo = None
151
+ if args.hydrogen == "all":
152
+ logger.writeln("\nWARNING: in unrestrained refinement hydrogen atoms are not generated.\n")
153
+ args.hydrogen = "yes"
154
+ elif args.hydrogen == "no":
155
+ st.remove_hydrogens()
156
+ for i, cra in enumerate(st[0].all()):
157
+ cra.atom.serial = i + 1
158
+ else:
159
+ try:
160
+ monlib = utils.restraints.load_monomer_library(st, monomer_dir=args.monlib, cif_files=args.ligand,
161
+ stop_for_unknowns=not args.newligand_continue,
162
+ params=params)
163
+ except RuntimeError as e:
164
+ raise SystemExit("Error: {}".format(e))
151
165
  if not args.keep_entities:
152
166
  utils.model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
153
167
  if not args.keep_charges:
@@ -186,6 +200,8 @@ def main(args):
186
200
  except RuntimeError as e:
187
201
  raise SystemExit("Error: {}".format(e))
188
202
 
203
+ print_h_options(h_change, st[0].has_hydrogen(), args.refine_h, args.hout, geom_only=False)
204
+
189
205
  # initialize ADP
190
206
  utils.model.reset_adp(st[0], args.bfactor, args.adp)
191
207
 
@@ -214,8 +230,8 @@ def main(args):
214
230
  ncslist = utils.restraints.prepare_ncs_restraints(st)
215
231
  else:
216
232
  ncslist = False
217
- geom = Geom(st, topo, monlib, shake_rms=args.randomize, adpr_w=args.adpr_weight,
218
- params=params, unrestrained=args.jellyonly,
233
+ geom = Geom(st, topo, monlib, shake_rms=args.randomize, adpr_w=args.adpr_weight, occr_w=args.occr_weight,
234
+ params=params, unrestrained=args.unrestrained or args.jellyonly,
219
235
  ncslist=ncslist)
220
236
  ll = spa.LL_SPA(hkldata, st, monlib,
221
237
  lab_obs="F_map1" if args.cross_validation else "FP",
@@ -224,6 +240,7 @@ def main(args):
224
240
  refine_xyz=not args.fix_xyz,
225
241
  adp_mode=dict(fix=0, iso=1, aniso=2)[args.adp],
226
242
  refine_h=args.refine_h,
243
+ unrestrained=args.unrestrained,
227
244
  params=params,
228
245
  refine_occ=args.refine_all_occ)
229
246
 
@@ -247,12 +264,8 @@ def main(args):
247
264
  refiner.st.cell = maps[0][0].unit_cell
248
265
  refiner.st.setup_cell_images()
249
266
 
250
- refiner.st.name = args.output_prefix
251
- utils.fileio.write_model(refiner.st, args.output_prefix, pdb=True, cif=True, hout=args.hout)
252
267
  if params["write_trajectory"]:
253
268
  utils.fileio.write_model(refiner.st_traj, args.output_prefix + "_traj", cif=True)
254
- if args.hklin:
255
- return
256
269
 
257
270
  # Expand sym here
258
271
  st_expanded = refiner.st.clone()
@@ -261,20 +274,28 @@ def main(args):
261
274
  utils.fileio.write_model(st_expanded, args.output_prefix+"_expanded", pdb=True, cif=True, hout=args.hout)
262
275
 
263
276
  # Calc FSC
264
- mask = utils.fileio.read_ccp4_map(args.mask)[0] if args.mask else None
265
- fscavg_text = calc_fsc(st_expanded, args.output_prefix, maps,
266
- args.resolution, mask=mask, mask_radius=args.mask_radius if not args.no_mask else None,
267
- soft_edge=args.mask_soft_edge,
268
- b_before_mask=args.b_before_mask,
269
- no_sharpen_before_mask=args.no_sharpen_before_mask,
270
- make_hydrogen="yes", # no change needed in the model
271
- monlib=monlib,
272
- blur=args.blur,
273
- d_min_fsc=args.fsc_resolution,
274
- cross_validation=args.cross_validation,
275
- cross_validation_method=args.cross_validation_method
276
- )
277
-
277
+ if args.hklin: # cannot update a mask
278
+ stats_for_meta = stats[-1]
279
+ else:
280
+ mask = utils.fileio.read_ccp4_map(args.mask)[0] if args.mask else None
281
+ fscavg_text, stats2 = calc_fsc(st_expanded, args.output_prefix, maps,
282
+ args.resolution, mask=mask, mask_radius=args.mask_radius if not args.no_mask else None,
283
+ soft_edge=args.mask_soft_edge,
284
+ b_before_mask=args.b_before_mask,
285
+ no_sharpen_before_mask=args.no_sharpen_before_mask,
286
+ make_hydrogen="yes", # no change needed in the model
287
+ monlib=monlib,
288
+ blur=args.blur,
289
+ d_min_fsc=args.fsc_resolution,
290
+ cross_validation=args.cross_validation,
291
+ cross_validation_method=args.cross_validation_method
292
+ )
293
+ stats_for_meta = {"geom": stats[-1]["geom"], "data": stats2}
294
+ update_meta(refiner.st, stats_for_meta, ll)
295
+ refiner.st.name = args.output_prefix
296
+ utils.fileio.write_model(refiner.st, args.output_prefix, pdb=True, cif=True, hout=args.hout)
297
+ if args.hklin:
298
+ return
278
299
  # Calc Fo-Fc (and updated) maps
279
300
  diffmap_prefix = "{}_diffmap".format(args.output_prefix)
280
301
  calc_fofc(refiner.st, st_expanded, maps, monlib, ".mmcif", args, diffmap_prefix=diffmap_prefix)
@@ -16,13 +16,13 @@ from servalcat.utils import logger
16
16
  from servalcat import utils
17
17
  from servalcat.xtal.sigmaa import decide_mtz_labels, process_input, calculate_maps, calculate_maps_int, calculate_maps_twin
18
18
  from servalcat.refine.xtal import LL_Xtal
19
- from servalcat.refine.refine import Geom, Refine
19
+ from servalcat.refine.refine import Geom, Refine, update_meta, print_h_options
20
20
  from servalcat.refmac import refmac_keywords
21
21
  from servalcat import ext
22
22
  b_to_u = utils.model.b_to_u
23
23
 
24
24
  def add_arguments(parser):
25
- parser.description = "EXPERIMENTAL program to refine crystallographic structures"
25
+ parser.description = "program to refine crystallographic structures"
26
26
  parser.add_argument("--hklin", required=True)
27
27
  parser.add_argument("-d", '--d_min', type=float)
28
28
  parser.add_argument('--d_max', type=float)
@@ -64,10 +64,14 @@ def add_arguments(parser):
64
64
  help="refinement weight (default: auto)")
65
65
  parser.add_argument('--no_weight_adjust', action='store_true',
66
66
  help='Do not adjust weight during refinement')
67
+ parser.add_argument('--target_bond_rmsz_range', nargs=2, type=float, default=[0.5, 1.],
68
+ help='Bond rmsz range for weight adjustment (default: %(default)s)')
67
69
  parser.add_argument('--ncsr', action='store_true',
68
70
  help='Use local NCS restraints')
69
71
  parser.add_argument('--adpr_weight', type=float, default=1.,
70
72
  help="ADP restraint weight (default: %(default)f)")
73
+ parser.add_argument('--occr_weight', type=float, default=1.,
74
+ help="Occupancy restraint weight (default: %(default)f)")
71
75
  parser.add_argument('--bfactor', type=float,
72
76
  help="reset all atomic B values to specified value")
73
77
  parser.add_argument('--fix_xyz', action="store_true")
@@ -88,6 +92,8 @@ def add_arguments(parser):
88
92
  help="Use work reflections in ML parameter estimates")
89
93
  parser.add_argument('--keep_charges', action='store_true',
90
94
  help="Use scattering factor for charged atoms. Use it with care.")
95
+ parser.add_argument("--keep_entities", action='store_true',
96
+ help="Do not override entities")
91
97
  parser.add_argument('--allow_unusual_occupancies', action="store_true", help="Allow negative or more than one occupancies")
92
98
  parser.add_argument('-o','--output_prefix')
93
99
  parser.add_argument("--write_trajectory", action='store_true',
@@ -159,10 +165,12 @@ def main(args):
159
165
  if args.unrestrained:
160
166
  monlib = gemmi.MonLib()
161
167
  topo = None
168
+ h_change = gemmi.HydrogenChange.NoChange
162
169
  if args.hydrogen == "all":
163
- logger.writeln("WARNING: in unrestrained refinement hydrogen atoms are not generated.")
170
+ logger.writeln("\nWARNING: in unrestrained refinement hydrogen atoms are not generated.\n")
164
171
  elif args.hydrogen == "no":
165
172
  st.remove_hydrogens()
173
+ h_change = gemmi.HydrogenChange.Remove
166
174
  for i, cra in enumerate(st[0].all()):
167
175
  cra.atom.serial = i + 1
168
176
  else:
@@ -172,7 +180,8 @@ def main(args):
172
180
  params=params)
173
181
  except RuntimeError as e:
174
182
  raise SystemExit("Error: {}".format(e))
175
- utils.model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
183
+ if not args.keep_entities:
184
+ utils.model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
176
185
  utils.restraints.find_and_fix_links(st, monlib, find_metal_links=args.find_links,
177
186
  add_found=args.find_links)
178
187
  h_change = {"all":gemmi.HydrogenChange.ReAddKnown,
@@ -185,6 +194,8 @@ def main(args):
185
194
  except RuntimeError as e:
186
195
  raise SystemExit("Error: {}".format(e))
187
196
 
197
+ print_h_options(h_change, st[0].has_hydrogen(), args.refine_h, args.hout, geom_only=False)
198
+
188
199
  # initialize ADP
189
200
  utils.model.reset_adp(st[0], args.bfactor, args.adp)
190
201
 
@@ -199,7 +210,7 @@ def main(args):
199
210
  ncslist = utils.restraints.prepare_ncs_restraints(st)
200
211
  else:
201
212
  ncslist = False
202
- geom = Geom(st, topo, monlib, shake_rms=args.randomize, adpr_w=args.adpr_weight, params=params,
213
+ geom = Geom(st, topo, monlib, shake_rms=args.randomize, adpr_w=args.adpr_weight, occr_w=args.occr_weight, params=params,
203
214
  unrestrained=args.unrestrained or args.jellyonly, use_nucleus=(args.source=="neutron"),
204
215
  ncslist=ncslist)
205
216
  geom.geom.angle_von_mises = args.vonmises
@@ -225,7 +236,9 @@ def main(args):
225
236
 
226
237
  stats = refiner.run_cycles(args.ncycle, weight=args.weight,
227
238
  weight_adjust=not args.no_weight_adjust,
239
+ weight_adjust_bond_rmsz_range=args.target_bond_rmsz_range,
228
240
  stats_json_out=args.output_prefix + "_stats.json")
241
+ update_meta(st, stats[-1], ll)
229
242
  refiner.st.name = args.output_prefix
230
243
  utils.fileio.write_model(refiner.st, args.output_prefix, pdb=True, cif=True, hout=args.hout)
231
244
  if params["write_trajectory"]:
@@ -244,7 +257,7 @@ def main(args):
244
257
 
245
258
  # Write mtz file
246
259
  if ll.twin_data:
247
- labs = ["F_est"]
260
+ labs = ["F_est", "F_exp", "FOM"]
248
261
  elif is_int:
249
262
  labs = ["I", "SIGI", "FOM"]
250
263
  else:
@@ -258,7 +271,7 @@ def main(args):
258
271
  labs.append("FREE")
259
272
  labs += ll.D_labs + ["S"] # for debugging, for now
260
273
  mtz_out = args.output_prefix+".mtz"
261
- hkldata.write_mtz(mtz_out, labs=labs, types={"FOM": "W", "FP":"F", "SIGFP":"Q", "I":"J", "SIGI":"Q", "F_est": "F"})
274
+ hkldata.write_mtz(mtz_out, labs=labs, types={"FOM": "W", "FP":"F", "SIGFP":"Q", "I":"J", "SIGI":"Q", "F_est": "F", "F_exp": "F"})
262
275
 
263
276
  # main()
264
277
 
servalcat/refine/spa.py CHANGED
@@ -38,8 +38,14 @@ class LL_SPA:
38
38
  self.lab_obs = lab_obs
39
39
  self.st = st
40
40
  self.monlib = monlib
41
- self.d_min = hkldata.d_min_max()[0]
41
+ self.d_min_max = hkldata.d_min_max()
42
42
  self.ll = None
43
+ self.b_aniso = None
44
+
45
+ def refine_id(self):
46
+ if self.source == "electron":
47
+ return "ELECTRON MICROSCOPY"
48
+ return "NON-EM SPA" # does not happen, I guess
43
49
 
44
50
  def update_ml_params(self):
45
51
  # FIXME make sure D > 0
@@ -52,7 +58,7 @@ class LL_SPA:
52
58
  else:
53
59
  st = self.st
54
60
 
55
- self.hkldata.df["FC"] = utils.model.calc_fc_fft(st, self.d_min - 1e-6,
61
+ self.hkldata.df["FC"] = utils.model.calc_fc_fft(st, self.d_min_max[0] - 1e-6,
56
62
  monlib=self.monlib,
57
63
  source=self.source,
58
64
  mott_bethe=self.mott_bethe,
@@ -92,15 +98,17 @@ class LL_SPA:
92
98
  stats = fsc.calc_fsc_all(self.hkldata, labs_fc=["FC"], lab_f=self.lab_obs)
93
99
  fsca = fsc.fsc_average(stats.ncoeffs, stats.fsc_FC_full)
94
100
  logger.writeln("FSCaverage = {:.4f}".format(fsca))
101
+ ret = {"summary": {"FSCaverage": fsca, "-LL": self.calc_target()}}
95
102
  # XXX in fsc object, _full is misleading - it's not full in cross validation mode
96
103
  if "D" in self.hkldata.binned_df and "S" in self.hkldata.binned_df:
97
104
  stats[["D", "S"]] = self.hkldata.binned_df[["D", "S"]]
98
- return {"bin_stats": stats, "summary": {"FSCaverage": fsca, "-LL": self.calc_target()}}
105
+ ret["bin_stats"] = stats
106
+ return ret
99
107
 
100
108
  def calc_grad(self, atom_pos, refine_xyz, adp_mode, refine_occ, refine_h, specs):
101
109
  dll_dab = numpy.empty_like(self.hkldata.df[self.lab_obs])
102
110
  d2ll_dab2 = numpy.zeros(len(self.hkldata.df.index))
103
- blur = utils.model.determine_blur_for_dencalc(self.st, self.d_min / 3) # TODO need more work
111
+ blur = utils.model.determine_blur_for_dencalc(self.st, self.d_min_max[0] / 3) # TODO need more work
104
112
  logger.writeln("blur for deriv= {:.2f}".format(blur))
105
113
  for i_bin, idxes in self.hkldata.binned():
106
114
  D = self.hkldata.binned_df.D[i_bin]