servalcat 0.4.72__cp312-cp312-macosx_11_0_arm64.whl → 0.4.88__cp312-cp312-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/refine/xtal.py CHANGED
@@ -14,18 +14,18 @@ from servalcat.utils import logger
14
14
  from servalcat.xtal import sigmaa
15
15
  from servalcat import utils
16
16
  from servalcat import ext
17
+ from servalcat.xtal.twin import find_twin_domains_from_data, estimate_twin_fractions_from_model
17
18
  b_to_u = utils.model.b_to_u
18
19
  u_to_b = utils.model.u_to_b
19
20
  integr = sigmaa.integr
20
21
 
21
22
  class LL_Xtal:
22
23
  def __init__(self, hkldata, centric_and_selections, free, st, monlib, source="xray", mott_bethe=True,
23
- use_solvent=False, use_in_est="all", use_in_target="all"):
24
+ use_solvent=False, use_in_est="all", use_in_target="all", twin=False):
24
25
  assert source in ("electron", "xray", "neutron")
25
26
  self.source = source
26
27
  self.mott_bethe = False if source != "electron" else mott_bethe
27
28
  self.hkldata = hkldata
28
- self.is_int = "I" in self.hkldata.df
29
29
  self.centric_and_selections = centric_and_selections
30
30
  self.free = free
31
31
  self.st = st
@@ -44,39 +44,58 @@ class LL_Xtal:
44
44
  self.use_in_target = use_in_target
45
45
  self.ll = None
46
46
  self.scaling = sigmaa.LsqScale()
47
+ if twin:
48
+ self.twin_data = find_twin_domains_from_data(self.hkldata)
49
+ else:
50
+ self.twin_data = None
51
+ if self.twin_data:
52
+ self.twin_data.setup_f_calc(len(self.fc_labs))
53
+ self.is_int = "I" in self.hkldata.df
47
54
  logger.writeln("will use {} reflections for parameter estimation".format(self.use_in_est))
48
55
  logger.writeln("will use {} reflections for refinement".format(self.use_in_target))
49
56
 
50
57
  def update_ml_params(self):
51
58
  self.b_aniso = sigmaa.determine_ml_params(self.hkldata, self.is_int, self.fc_labs, self.D_labs, self.b_aniso,
52
- self.centric_and_selections, use=self.use_in_est,
53
- )#D_trans="splus", S_trans="splus")
59
+ self.centric_and_selections, use=self.use_in_est,
60
+ twin_data=self.twin_data)#D_trans="splus", S_trans="splus")
54
61
  self.hkldata.df["k_aniso"] = self.hkldata.debye_waller_factors(b_cart=self.b_aniso)
55
62
  #determine_mlf_params_from_cc(self.hkldata, self.fc_labs, self.D_labs,
56
63
  # self.centric_and_selections)
57
-
58
-
59
64
  def update_fc(self):
60
- if self.st.ncs:
61
- st = self.st.clone()
62
- st.expand_ncs(gemmi.HowToNameCopiedChain.Dup, merge_dist=0)
63
- else:
64
- st = self.st
65
+ sigmaa.update_fc(st_list=[self.st], fc_labs=self.fc_labs,
66
+ d_min=self.d_min, monlib=self.monlib,
67
+ source=self.source, mott_bethe=self.mott_bethe,
68
+ hkldata=self.hkldata, twin_data=self.twin_data)
65
69
 
66
- self.hkldata.df[self.fc_labs[0]] = utils.model.calc_fc_fft(st, self.d_min - 1e-6,
67
- monlib=self.monlib,
68
- source=self.source,
69
- mott_bethe=self.mott_bethe,
70
- miller_array=self.hkldata.miller_array())
71
- self.hkldata.df["FC"] = self.hkldata.df[self.fc_labs].sum(axis=1)
70
+ def prepare_target(self):
71
+ if self.twin_data:
72
+ if self.use_in_target == "all":
73
+ idxes = numpy.concatenate([sel[i] for i_bin, _ in self.hkldata.binned()
74
+ for sel in self.centric_and_selections[i_bin] for i in (1,2)])
75
+ else:
76
+ i = 1 if self.use_in_target == "work" else 2
77
+ idxes = numpy.concatenate([sel[i] for i_bin, _ in self.hkldata.binned()
78
+ for sel in self.centric_and_selections[i_bin]])
79
+ mask = numpy.empty(len(self.hkldata.df.index)) * numpy.nan
80
+ mask[idxes] = 1 / self.hkldata.debye_waller_factors(b_cart=self.b_aniso)[idxes]**2
81
+ self.twin_data.est_f_true(self.hkldata.df.I * mask,
82
+ self.hkldata.df.SIGI * mask)
72
83
 
73
84
  def overall_scale(self, min_b=0.1):
74
- fc_list = [self.hkldata.df[self.fc_labs[0]].to_numpy()]
85
+ miller_array = self.twin_data.asu if self.twin_data else self.hkldata.miller_array()
75
86
  if self.use_solvent:
76
- Fmask = sigmaa.calc_Fmask(self.st, self.d_min - 1e-6, self.hkldata.miller_array())
77
- fc_list.append(Fmask)
78
-
79
- self.scaling.set_data(self.hkldata, fc_list, self.is_int, sigma_cutoff=0)
87
+ Fmask = sigmaa.calc_Fmask(self.st, self.d_min, miller_array)
88
+ if self.twin_data:
89
+ fc_sum = self.twin_data.f_calc[:,:-1].sum(axis=1)
90
+ else:
91
+ fc_sum = self.hkldata.df[self.fc_labs[:-1]].sum(axis=1).to_numpy()
92
+ fc_list = [fc_sum, Fmask]
93
+ else:
94
+ if twin_data:
95
+ fc_list = [self.twin_data.f_calc.sum(axis=1)]
96
+ else:
97
+ fc_list = [self.hkldata.df[self.fc_labs].sum(axis=1).to_numpy()]
98
+ self.scaling.set_data(self.hkldata, fc_list, self.is_int, sigma_cutoff=0, twin_data=self.twin_data)
80
99
  self.scaling.scale()
81
100
  self.b_aniso = self.scaling.b_aniso
82
101
  b = self.scaling.b_iso
@@ -90,9 +109,15 @@ class LL_Xtal:
90
109
  k_iso = self.hkldata.debye_waller_factors(b_iso=b)
91
110
  self.hkldata.df["k_aniso"] = self.hkldata.debye_waller_factors(b_cart=self.b_aniso)
92
111
  if self.use_solvent:
93
- solvent_scale = self.scaling.get_solvent_scale(self.scaling.k_sol, self.scaling.b_sol,
94
- 1. / self.hkldata.d_spacings().to_numpy()**2)
95
- self.hkldata.df[self.fc_labs[-1]] = Fmask * solvent_scale
112
+ if self.twin_data:
113
+ s2 = numpy.asarray(self.twin_data.s2_array)
114
+ else:
115
+ s2 = 1. / self.hkldata.d_spacings().to_numpy()**2
116
+ Fbulk = Fmask * self.scaling.get_solvent_scale(self.scaling.k_sol, self.scaling.b_sol, s2)
117
+ if self.twin_data:
118
+ self.twin_data.f_calc[:,-1] = Fbulk
119
+ else:
120
+ self.hkldata.df[self.fc_labs[-1]] = Fbulk
96
121
  if self.is_int:
97
122
  o_labs = self.hkldata.df.columns.intersection(["I", "SIGI",
98
123
  "I(+)","SIGI(+)", "I(-)", "SIGI(-)"])
@@ -102,103 +127,125 @@ class LL_Xtal:
102
127
  "F(+)","SIGF(+)", "F(-)", "SIGF(-)"])
103
128
  self.hkldata.df[o_labs] /= self.scaling.k_overall
104
129
 
105
- for lab in self.fc_labs: self.hkldata.df[lab] *= k_iso
106
- self.hkldata.df["FC"] = self.hkldata.df[self.fc_labs].sum(axis=1)
130
+ if self.twin_data:
131
+ self.twin_data.f_calc[:] *= self.twin_data.debye_waller_factors(b_iso=b)[:,None]
132
+ else:
133
+ for lab in self.fc_labs: self.hkldata.df[lab] *= k_iso
134
+ self.hkldata.df["FC"] = self.hkldata.df[self.fc_labs].sum(axis=1)
107
135
 
108
136
  # for next cycle
109
137
  self.scaling.k_overall = 1.
110
138
  self.scaling.b_iso = 0.
139
+ if self.twin_data:
140
+ estimate_twin_fractions_from_model(self.twin_data, self.hkldata)
111
141
  # overall_scale()
112
142
 
113
143
  def calc_target(self): # -LL target for MLF or MLI
114
144
  ret = 0
115
- k_aniso = self.hkldata.debye_waller_factors(b_cart=self.b_aniso)
116
- f = sigmaa.mli if self.is_int else sigmaa.mlf
117
- for i_bin, _ in self.hkldata.binned():
118
- if self.use_in_target == "all":
119
- idxes = numpy.concatenate([sel[i] for sel in self.centric_and_selections[i_bin] for i in (1,2)])
120
- else:
121
- i = 1 if self.use_in_target == "work" else 2
122
- idxes = numpy.concatenate([sel[i] for sel in self.centric_and_selections[i_bin]])
123
- ret += f(self.hkldata.df,
124
- self.fc_labs,
125
- numpy.vstack([self.hkldata.df[lab].to_numpy()[idxes] for lab in self.D_labs]).T,
126
- self.hkldata.df.S.to_numpy()[idxes],
127
- k_aniso,
128
- idxes)
145
+ if self.twin_data:
146
+ ret = self.twin_data.ll()
147
+ else:
148
+ k_aniso = self.hkldata.debye_waller_factors(b_cart=self.b_aniso)
149
+ f = sigmaa.mli if self.is_int else sigmaa.mlf
150
+ for i_bin, _ in self.hkldata.binned():
151
+ if self.use_in_target == "all":
152
+ idxes = numpy.concatenate([sel[i] for sel in self.centric_and_selections[i_bin] for i in (1,2)])
153
+ else:
154
+ i = 1 if self.use_in_target == "work" else 2
155
+ idxes = numpy.concatenate([sel[i] for sel in self.centric_and_selections[i_bin]])
156
+ ret += f(self.hkldata.df,
157
+ self.fc_labs,
158
+ numpy.vstack([self.hkldata.df[lab].to_numpy()[idxes] for lab in self.D_labs]).T,
159
+ self.hkldata.df.S.to_numpy()[idxes],
160
+ k_aniso,
161
+ idxes)
129
162
  return ret * 2 # friedel mates
130
163
  # calc_target()
131
164
 
132
165
  def calc_stats(self, bin_stats=False):
133
- stats, overall = sigmaa.calc_r_and_cc(self.hkldata, self.centric_and_selections)
166
+ stats, overall = sigmaa.calc_r_and_cc(self.hkldata, self.centric_and_selections, self.twin_data)
134
167
  ret = {"summary": overall}
135
168
  ret["summary"]["-LL"] = self.calc_target()
169
+ if self.twin_data:
170
+ ret["twin_alpha"] = self.twin_data.alphas
136
171
  if bin_stats:
137
172
  ret["bin_stats"] = stats
138
173
  for lab in "R", "CC":
139
174
  logger.writeln(" ".join("{} = {:.4f}".format(x, overall[x]) for x in overall if x.startswith(lab)))
175
+ if self.is_int:
176
+ logger.writeln("R1 is calculated for reflections with I/sigma>2.")
140
177
  return ret
141
178
 
142
179
  def calc_grad(self, atom_pos, refine_xyz, adp_mode, refine_occ, refine_h, specs=None):
143
- dll_dab = numpy.zeros(len(self.hkldata.df.FC), dtype=numpy.complex128)
144
- d2ll_dab2 = numpy.empty(len(self.hkldata.df.index))
145
- d2ll_dab2[:] = numpy.nan
146
180
  blur = utils.model.determine_blur_for_dencalc(self.st, self.d_min / 3) # TODO need more work
147
181
  logger.writeln("blur for deriv= {:.2f}".format(blur))
148
- k_ani = self.hkldata.debye_waller_factors(b_cart=self.b_aniso)
149
- for i_bin, _ in self.hkldata.binned():
150
- for c, work, test in self.centric_and_selections[i_bin]:
151
- if self.use_in_target == "all":
152
- cidxes = numpy.concatenate([work, test])
153
- else:
154
- cidxes = work if self.use_in_target == "work" else test
155
- epsilon = self.hkldata.df.epsilon.to_numpy()[cidxes]
156
- Fcs = numpy.vstack([self.hkldata.df[lab].to_numpy()[cidxes] for lab in self.fc_labs]).T
157
- Ds = numpy.vstack([self.hkldata.df[lab].to_numpy()[cidxes] for lab in self.D_labs]).T
158
- S = self.hkldata.df["S"].to_numpy()[cidxes]
159
- Fc = (Ds * Fcs).sum(axis=1)
160
- Fc_abs = numpy.abs(Fc)
161
- expip = numpy.exp(1j * numpy.angle(Fc))
162
- if self.is_int:
163
- Io = self.hkldata.df.I.to_numpy()
164
- sigIo = self.hkldata.df.SIGI.to_numpy()
165
- to = Io[cidxes] / sigIo[cidxes] - sigIo[cidxes] / (c+1) / k_ani[cidxes]**2 / S / epsilon
166
- tf = k_ani[cidxes] * Fc_abs / numpy.sqrt(sigIo[cidxes])
167
- sig1 = k_ani[cidxes]**2 * epsilon * S / sigIo[cidxes]
168
- k_num = 0.5 if c == 0 else 0. # acentric:0.5, centric: 0.
169
- r = ext.integ_J_ratio(k_num, k_num - 0.5, True, to, tf, sig1, c+1,
170
- integr.exp2_threshold, integr.h, integr.N, integr.ewmax)
171
- r *= numpy.sqrt(sigIo[cidxes]) / k_ani[cidxes]
172
- g = (2-c) * (Fc_abs - r) / epsilon / S * Ds[:,0]
173
- dll_dab[cidxes] = g * expip
174
- #d2ll_dab2[cidxes] = (2-c)**2 / S / epsilon * Ds[0]**2 # approximation
175
- #d2ll_dab2[cidxes] = ((2-c) / S / epsilon + ((2-c) * r / k_ani[cidxes] / epsilon / S)**2) * Ds[0]**2
176
- d2ll_dab2[cidxes] = g**2
177
- else:
178
- Fo = self.hkldata.df.FP.to_numpy()[cidxes] / k_ani[cidxes]
179
- SigFo = self.hkldata.df.SIGFP.to_numpy()[cidxes] / k_ani[cidxes]
180
- if c == 0: # acentric
181
- Sigma = 2 * SigFo**2 + epsilon * S
182
- X = 2 * Fo * Fc_abs / Sigma
183
- m = gemmi.bessel_i1_over_i0(X)
184
- g = 2 * (Fc_abs - m * Fo) / Sigma * Ds[:,0] # XXX assuming 0 is atomic structure
185
- dll_dab[cidxes] = g * expip
186
- d2ll_dab2[cidxes] = (2 / Sigma - (1 - m / X - m**2) * (2 * Fo / Sigma)**2) * Ds[:,0]**2
182
+ if self.twin_data:
183
+ dll_dab, d2ll_dab2 = self.twin_data.ll_der_fc0()
184
+ dll_dab *= self.twin_data.debye_waller_factors(b_iso=-blur)
185
+ else:
186
+ dll_dab = numpy.zeros(len(self.hkldata.df.FC), dtype=numpy.complex128)
187
+ d2ll_dab2 = numpy.empty(len(self.hkldata.df.index))
188
+ d2ll_dab2[:] = numpy.nan
189
+ k_ani = self.hkldata.debye_waller_factors(b_cart=self.b_aniso)
190
+ for i_bin, _ in self.hkldata.binned():
191
+ for c, work, test in self.centric_and_selections[i_bin]:
192
+ if self.use_in_target == "all":
193
+ cidxes = numpy.concatenate([work, test])
187
194
  else:
188
- Sigma = SigFo**2 + epsilon * S
189
- X = Fo * Fc_abs / Sigma
190
- #X = X.astype(numpy.float64)
191
- m = numpy.tanh(X)
192
- g = (Fc_abs - m * Fo) / Sigma * Ds[:,0]
195
+ cidxes = work if self.use_in_target == "work" else test
196
+ epsilon = self.hkldata.df.epsilon.to_numpy()[cidxes]
197
+ Fcs = numpy.vstack([self.hkldata.df[lab].to_numpy()[cidxes] for lab in self.fc_labs]).T
198
+ Ds = numpy.vstack([self.hkldata.df[lab].to_numpy()[cidxes] for lab in self.D_labs]).T
199
+ S = self.hkldata.df["S"].to_numpy()[cidxes]
200
+ Fc = (Ds * Fcs).sum(axis=1)
201
+ Fc_abs = numpy.abs(Fc)
202
+ expip = numpy.exp(1j * numpy.angle(Fc))
203
+ if self.is_int:
204
+ Io = self.hkldata.df.I.to_numpy()
205
+ sigIo = self.hkldata.df.SIGI.to_numpy()
206
+ to = Io[cidxes] / sigIo[cidxes] - sigIo[cidxes] / (c+1) / k_ani[cidxes]**2 / S / epsilon
207
+ tf = k_ani[cidxes] * Fc_abs / numpy.sqrt(sigIo[cidxes])
208
+ sig1 = k_ani[cidxes]**2 * epsilon * S / sigIo[cidxes]
209
+ k_num = 0.5 if c == 0 else 0. # acentric:0.5, centric: 0.
210
+ r = ext.integ_J_ratio(k_num, k_num - 0.5, True, to, tf, sig1, c+1,
211
+ integr.exp2_threshold, integr.h, integr.N, integr.ewmax)
212
+ r *= numpy.sqrt(sigIo[cidxes]) / k_ani[cidxes]
213
+ g = (2-c) * (Fc_abs - r) / epsilon / S * Ds[:,0]
193
214
  dll_dab[cidxes] = g * expip
194
- d2ll_dab2[cidxes] = (1. / Sigma - (Fo / (Sigma * numpy.cosh(X)))**2) * Ds[:,0]**2
215
+ #d2ll_dab2[cidxes] = (2-c)**2 / S / epsilon * Ds[0]**2 # approximation
216
+ #d2ll_dab2[cidxes] = ((2-c) / S / epsilon + ((2-c) * r / k_ani[cidxes] / epsilon / S)**2) * Ds[0]**2
217
+ d2ll_dab2[cidxes] = g**2
218
+ else:
219
+ Fo = self.hkldata.df.FP.to_numpy()[cidxes] / k_ani[cidxes]
220
+ SigFo = self.hkldata.df.SIGFP.to_numpy()[cidxes] / k_ani[cidxes]
221
+ if c == 0: # acentric
222
+ Sigma = 2 * SigFo**2 + epsilon * S
223
+ X = 2 * Fo * Fc_abs / Sigma
224
+ m = gemmi.bessel_i1_over_i0(X)
225
+ g = 2 * (Fc_abs - m * Fo) / Sigma * Ds[:,0] # XXX assuming 0 is atomic structure
226
+ dll_dab[cidxes] = g * expip
227
+ d2ll_dab2[cidxes] = (2 / Sigma - (1 - m / X - m**2) * (2 * Fo / Sigma)**2) * Ds[:,0]**2
228
+ else:
229
+ Sigma = SigFo**2 + epsilon * S
230
+ X = Fo * Fc_abs / Sigma
231
+ #X = X.astype(numpy.float64)
232
+ m = numpy.tanh(X)
233
+ g = (Fc_abs - m * Fo) / Sigma * Ds[:,0]
234
+ dll_dab[cidxes] = g * expip
235
+ d2ll_dab2[cidxes] = (1. / Sigma - (Fo / (Sigma * numpy.cosh(X)))**2) * Ds[:,0]**2
236
+ dll_dab *= self.hkldata.debye_waller_factors(b_iso=-blur)
195
237
 
196
238
  if self.mott_bethe:
197
- dll_dab *= self.hkldata.d_spacings()**2 * gemmi.mott_bethe_const()
239
+ d2 = 1 / self.twin_data.s2_array if self.twin_data else self.hkldata.d_spacings()**2
240
+ dll_dab *= d2 * gemmi.mott_bethe_const()
198
241
  d2ll_dab2 *= gemmi.mott_bethe_const()**2
199
242
 
243
+
200
244
  # we need V**2/n for gradient.
201
- dll_dab_den = self.hkldata.fft_map(data=dll_dab * self.hkldata.debye_waller_factors(b_iso=-blur))
245
+ if self.twin_data:
246
+ dll_dab_den = utils.hkl.fft_map(self.hkldata.cell, self.hkldata.sg, self.twin_data.asu, data=dll_dab)
247
+ else:
248
+ dll_dab_den = self.hkldata.fft_map(data=dll_dab)
202
249
  dll_dab_den.array[:] *= self.hkldata.cell.volume**2 / dll_dab_den.point_count
203
250
  #asu = dll_dab_den.masked_asu()
204
251
  #dll_dab_den.array[:] *= 1 - asu.mask_array # 0 to use
@@ -211,13 +258,12 @@ class LL_Xtal:
211
258
  self.ll.calc_grad_it92(dll_dab_den, blur)
212
259
 
213
260
  # second derivative
261
+ s_array = numpy.sqrt(self.twin_data.s2_array) if self.twin_data else 1./self.hkldata.d_spacings().to_numpy()
214
262
  if self.source == "neutron":
215
- self.ll.make_fisher_table_diag_direct_n92(1./self.hkldata.d_spacings().to_numpy(),
216
- d2ll_dab2)
263
+ self.ll.make_fisher_table_diag_direct_n92(s_array, d2ll_dab2)
217
264
  self.ll.fisher_diag_from_table_n92()
218
265
  else:
219
- self.ll.make_fisher_table_diag_direct_it92(1./self.hkldata.d_spacings().to_numpy(),
220
- d2ll_dab2)
266
+ self.ll.make_fisher_table_diag_direct_it92(s_array, d2ll_dab2)
221
267
  self.ll.fisher_diag_from_table_it92()
222
268
  #json.dump(dict(b=ll.table_bs, pp1=ll.pp1, bb=ll.bb),
223
269
  # open("ll_fisher.json", "w"), indent=True)
servalcat/refmac/exte.py CHANGED
@@ -120,18 +120,20 @@ def read_external_restraints(params, st, geom):
120
120
  if r["rest_type"] == "dist":
121
121
  if not (defs["dist_min_external"] < r["restr"]["value"] < defs["dist_max_external"]):
122
122
  continue
123
- if ex.atoms[0].serial > ex.atoms[1].serial:
124
- ex.atoms = ex.atoms[::-1]
125
123
  ex.alpha = r["restr"].get("alpha_in", defs["alpha_default"])
126
124
  ex.type = r["restr"].get("itype_in", defs["type_default"])
127
125
  symm1 = any([spec.get("symm") for spec in r["restr"]["specs"]]) # is it the intention?
128
126
  if r["restr"].get("symm_in", defs["symall_block"]) or symm1:
129
127
  asu = gemmi.Asu.Different if defs["exclude_self_block"] else gemmi.Asu.Any
130
- im = st.cell.find_nearest_image(ex.atoms[0].pos, ex.atoms[1].pos, asu)
131
- ex.set_image(im)
128
+ ex.set_image(st.cell, asu)
132
129
  #print("dist=", ex.alpha, ex.type, ex.values[-1].value, ex.values[-1].sigma, ex.sym_idx, ex.pbc_shift, ex.atoms)
133
130
  elif r["rest_type"] == "angl":
134
- pass
131
+ if any(spec.get("symm") for spec in r["restr"]["specs"]):
132
+ asus = [gemmi.Asu.Different if r["restr"]["specs"][i].get("symm") else gemmi.Asu.Same
133
+ for i in range(3)]
134
+ if atoms[0].serial > atoms[2].serial:
135
+ asus = asus[::-1]
136
+ ex.set_images(st.cell, asus[0], asus[2])
135
137
  #print("angl=", ex.values[-1].value, ex.values[-1].sigma, ex.atoms)
136
138
  elif r["rest_type"] == "tors":
137
139
  pass
@@ -37,6 +37,7 @@ def parse_atom_spec(s, itk):
37
37
  itk += 2
38
38
  elif s[itk].lower().startswith("symm"):
39
39
  ret["symm"] = s[itk+1][0].lower() == "y"
40
+ itk += 2
40
41
  else:
41
42
  break
42
43
 
@@ -167,10 +168,10 @@ def read_exte(s):
167
168
  except ValueError:
168
169
  ret["restr"]["itype_in"] = dict(o=0, f=2).get(s[itk+1][0].lower(), 1)
169
170
  if not (0 <= ret["restr"]["itype_in"] <= 2):
170
- logger.writeln("WARNING: wrong type is given. setting to 2.\n=> {}".format(l))
171
+ logger.writeln("WARNING: wrong type is given. setting to 2.\n=> {}".format(" ".join(s)))
171
172
  ret["restr"]["itype_in"] = 2
172
173
  itk += 2
173
- elif s[itk].lower().startswith("symm"): # only for distance
174
+ elif s[itk].lower().startswith("symm"): # only for distance and angle
174
175
  ret["restr"]["symm_in"] = s[itk+1][0].lower() == "y"
175
176
  itk += 2
176
177
  else:
@@ -181,7 +182,8 @@ def read_exte(s):
181
182
  ret["restr"][d[k]] = float(s[itk+1])
182
183
  itk += 2
183
184
  else:
184
- logger.writeln("unrecognised key: {}\n=> {}".format(s[itk], l))
185
+ logger.writeln("unrecognised key: {}\n=> {}".format(s[itk], " ".join(s)))
186
+ break
185
187
  elif s[1].lower().startswith("stac"):
186
188
  ret["rest_type"] = "stac"
187
189
  ret["restr"] = {}
@@ -194,7 +196,7 @@ def read_exte(s):
194
196
  ip = int(s[itk+1])
195
197
  itk += 2
196
198
  if ip not in (1, 2):
197
- raise RuntimeError("Problem with stacking instructions. Plane number can be 1 or 2.\n=> {}".format(l))
199
+ raise RuntimeError("Problem with stacking instructions. Plane number can be 1 or 2.\n=> {}".format(" ".join(s)))
198
200
  elif s[itk].lower().startswith(("firs", "next")):
199
201
  atoms, itk = parse_atom_spec(s, itk+1)
200
202
  ret["restr"]["specs"][ip-1] = atoms
@@ -203,7 +205,7 @@ def read_exte(s):
203
205
  ret["restr"][k] = float(s[itk+1]) if k != "type_r" else int(s[itk+1])
204
206
  itk += 2
205
207
  else:
206
- logger.writeln("WARNING: unrecognised keyword: {}\n=> {}".format(s[itk], l))
208
+ logger.writeln("WARNING: unrecognised keyword: {}\n=> {}".format(s[itk], " ".join(s)))
207
209
  itk += 1
208
210
  elif s[1].lower().startswith(("harm", "spec")):
209
211
  ret["rest_type"] = s[1][:4].lower() # in Refmac, irest_type = 1 if harm else 2
@@ -241,11 +243,11 @@ def read_exte(s):
241
243
  ret["restr"]["sigma_u"] = float(s[itk+1]) * b_to_u
242
244
  itk += 2
243
245
  else:
244
- logger.writeln("WARNING: unrecognised keyword: {}\n=> {}".format(s[itk], l))
246
+ logger.writeln("WARNING: unrecognised keyword: {}\n=> {}".format(s[itk], " ".join(s)))
245
247
  itk += 1
246
248
 
247
249
  else:
248
- logger.writeln("WARNING: cannot parse: {}".format(l))
250
+ logger.writeln("WARNING: cannot parse: {}".format(" ".join(s)))
249
251
  return ret
250
252
  # read_exte()
251
253
 
@@ -543,8 +545,8 @@ def parse_line(l, ret):
543
545
  ret.setdefault("refi", {})
544
546
  itk = 1
545
547
  while itk < ntok:
546
- if s[itk].startswith("type"):
547
- if itk+1 < ntok and s[itk+1].startswith("unre"):
548
+ if s[itk].lower().startswith("type"):
549
+ if itk+1 < ntok and s[itk+1].lower().startswith("unre"):
548
550
  ret["refi"]["type"] = "unre"
549
551
  itk += 2
550
552
  else:
@@ -11,10 +11,12 @@ import numpy
11
11
  import json
12
12
  import os
13
13
  import sys
14
+ import io
14
15
  import tempfile
15
16
  import subprocess
16
17
  import argparse
17
18
  from collections import OrderedDict
19
+ import servalcat # for version
18
20
  from servalcat.utils import logger
19
21
  from servalcat.refmac import refmac_keywords
20
22
  from servalcat import utils
@@ -64,7 +66,7 @@ def read_stdin(stdin):
64
66
 
65
67
  def prepare_crd(st, crdout, ligand, make, monlib_path=None, h_pos="elec",
66
68
  no_adjust_hydrogen_distances=False, fix_long_resnames=True,
67
- keep_entities=False):
69
+ keep_entities=False, unre=False):
68
70
  assert h_pos in ("elec", "nucl")
69
71
  h_change = dict(a=gemmi.HydrogenChange.ReAddButWater,
70
72
  y=gemmi.HydrogenChange.NoChange,
@@ -80,67 +82,87 @@ def prepare_crd(st, crdout, ligand, make, monlib_path=None, h_pos="elec",
80
82
  if at.occ > 1: # XXX should I check special positions?
81
83
  at.occ = 1.
82
84
 
83
- if not keep_entities:
85
+ refmac_fixes = utils.refmac.FixForRefmac()
86
+ if keep_entities:
87
+ refmac_fixes.store_res_labels(st)
88
+ else:
84
89
  utils.model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
85
- # TODO read dictionary from xyzin (priority: user cif -> monlib -> xyzin
86
- try:
87
- monlib = utils.restraints.load_monomer_library(st,
88
- monomer_dir=monlib_path,
89
- cif_files=ligand,
90
- stop_for_unknowns=not make.get("newligand"))
91
- except RuntimeError as e:
92
- raise SystemExit("Error: {}".format(e))
93
90
 
94
- use_cispeps = make.get("cispept", "y") != "y"
95
- make_link = make.get("link", "n")
96
- make_ss = make.get("ss", "y")
97
- only_from = set()
98
- if make_link == "y":
99
- # add all links
100
- add_found = True
101
- elif make_ss == "y":
102
- add_found = True
103
- only_from.add("disulf")
91
+ if unre:
92
+ logger.writeln("Monomer library will not be loaded due to unrestrained refinement request")
93
+ monlib = gemmi.MonLib()
104
94
  else:
105
- add_found = False
106
-
107
- utils.restraints.fix_elements_in_model(monlib, st)
108
- utils.restraints.find_and_fix_links(st, monlib, add_found=add_found, find_symmetry_related=False, add_only_from=only_from)
109
- for con in st.connections:
110
- if con.link_id not in ("?", "", "gap") and con.link_id not in monlib.links:
111
- logger.writeln(" removing unknown link id ({}). Ad-hoc link will be generated.".format(con.link_id))
112
- con.link_id = ""
95
+ # TODO read dictionary from xyzin (priority: user cif -> monlib -> xyzin
96
+ try:
97
+ monlib = utils.restraints.load_monomer_library(st,
98
+ monomer_dir=monlib_path,
99
+ cif_files=ligand,
100
+ stop_for_unknowns=not make.get("newligand"))
101
+ except RuntimeError as e:
102
+ raise SystemExit("Error: {}".format(e))
103
+
104
+ use_cispeps = make.get("cispept", "y") != "y"
105
+ make_link = make.get("link", "n")
106
+ make_ss = make.get("ss", "y")
107
+ only_from = set()
108
+ if make_link == "y":
109
+ # add all links
110
+ add_found = True
111
+ elif make_ss == "y":
112
+ add_found = True
113
+ only_from.add("disulf")
114
+ else:
115
+ add_found = False
116
+
117
+ utils.restraints.fix_elements_in_model(monlib, st)
118
+ utils.restraints.find_and_fix_links(st, monlib, add_found=add_found,
119
+ find_metal_links=(make_link == "y"),
120
+ find_symmetry_related=False, add_only_from=only_from)
121
+ for con in st.connections:
122
+ if con.link_id not in ("?", "", "gap") and con.link_id not in monlib.links:
123
+ logger.writeln(" removing unknown link id ({}). Ad-hoc link will be generated.".format(con.link_id))
124
+ con.link_id = ""
113
125
 
114
- refmac_fixes = utils.refmac.FixForRefmac()
115
126
  max_seq_num = max([max(res.seqid.num for res in chain) for model in st for chain in model])
116
127
  if max_seq_num > 9999:
117
128
  logger.writeln("Max residue number ({}) exceeds 9999. Needs workaround.".format(max_seq_num))
118
- topo = gemmi.prepare_topology(st, monlib, ignore_unknown_links=True)
129
+ sio = io.StringIO()
130
+ topo = gemmi.prepare_topology(st, monlib, warnings=sio, ignore_unknown_links=True)
119
131
  refmac_fixes.fix_before_topology(st, topo,
120
132
  fix_microheterogeneity=False,
121
133
  fix_resimax=True,
122
134
  fix_nonpolymer=False)
123
135
 
124
- if make.get("hydr") == "a": logger.writeln("(re)generating hydrogen atoms")
125
- try:
126
- topo, metal_kws = utils.restraints.prepare_topology(st, monlib, h_change=h_change, ignore_unknown_links=False,
127
- check_hydrogen=(h_change==gemmi.HydrogenChange.NoChange),
128
- use_cispeps=use_cispeps)
129
- except RuntimeError as e:
130
- raise SystemExit("Error: {}".format(e))
136
+ if unre:
137
+ # Refmac5 does not seem to do anything to hydrogen when unre regardless of "make hydr"
138
+ sio = io.StringIO()
139
+ topo = gemmi.prepare_topology(st, monlib, warnings=sio, ignore_unknown_links=True)
140
+ metal_kws = []
141
+ else:
142
+ if make.get("hydr") == "a": logger.writeln("(re)generating hydrogen atoms")
143
+ try:
144
+ topo, metal_kws = utils.restraints.prepare_topology(st, monlib, h_change=h_change, ignore_unknown_links=False,
145
+ check_hydrogen=(h_change==gemmi.HydrogenChange.NoChange),
146
+ use_cispeps=use_cispeps)
147
+ except RuntimeError as e:
148
+ raise SystemExit("Error: {}".format(e))
131
149
 
132
- if make.get("hydr") != "n" and st[0].has_hydrogen():
133
- if h_pos == "nucl" and (make.get("hydr") == "a" or not no_adjust_hydrogen_distances):
134
- resnames = st[0].get_all_residue_names()
135
- utils.restraints.check_monlib_support_nucleus_distances(monlib, resnames)
136
- logger.writeln("adjusting hydrogen position to nucleus")
137
- topo.adjust_hydrogen_distances(gemmi.Restraints.DistanceOf.Nucleus, default_scale=1.1)
138
- elif h_pos == "elec" and make.get("hydr") == "y" and not no_adjust_hydrogen_distances:
139
- logger.writeln("adjusting hydrogen position to electron cloud")
140
- topo.adjust_hydrogen_distances(gemmi.Restraints.DistanceOf.ElectronCloud)
150
+ if make.get("hydr") != "n" and st[0].has_hydrogen():
151
+ if h_pos == "nucl" and (make.get("hydr") == "a" or not no_adjust_hydrogen_distances):
152
+ resnames = st[0].get_all_residue_names()
153
+ utils.restraints.check_monlib_support_nucleus_distances(monlib, resnames)
154
+ logger.writeln("adjusting hydrogen position to nucleus")
155
+ topo.adjust_hydrogen_distances(gemmi.Restraints.DistanceOf.Nucleus, default_scale=1.1)
156
+ elif h_pos == "elec" and make.get("hydr") == "y" and not no_adjust_hydrogen_distances:
157
+ logger.writeln("adjusting hydrogen position to electron cloud")
158
+ topo.adjust_hydrogen_distances(gemmi.Restraints.DistanceOf.ElectronCloud)
141
159
 
142
160
  if fix_long_resnames: refmac_fixes.fix_long_resnames(st)
143
161
 
162
+ # remove "given" ncs matrices
163
+ # TODO write them back to the output files
164
+ st.ncs = gemmi.NcsOpList(x for x in st.ncs if not x.given)
165
+
144
166
  # for safety
145
167
  if "_entry.id" in st.info:
146
168
  st.info["_entry.id"] = st.info["_entry.id"].replace(" ", "")
@@ -221,11 +243,22 @@ def modify_output(pdbout, cifout, fixes, hout, cispeps, keep_original_output=Fal
221
243
  # should we check metals and put MetalC?
222
244
 
223
245
  # fix entity (Refmac seems to make DNA non-polymer; as seen in 1fix)
224
- utils.model.setup_entities(st, clear=True, overwrite_entity_type=True, force_subchain_names=True)
225
- for e in st.entities:
226
- if not e.full_sequence and e.entity_type == gemmi.EntityType.Polymer and e.subchains:
227
- rspan = st[0].get_subchain(e.subchains[0])
228
- e.full_sequence = [r.name for r in rspan]
246
+ if not fixes or not fixes.res_labels:
247
+ utils.model.setup_entities(st, clear=True, overwrite_entity_type=True, force_subchain_names=True)
248
+ for e in st.entities:
249
+ if not e.full_sequence and e.entity_type == gemmi.EntityType.Polymer and e.subchains:
250
+ rspan = st[0].get_subchain(e.subchains[0])
251
+ e.full_sequence = [r.name for r in rspan]
252
+
253
+ # fix label_seq_id
254
+ for chain in st[0]:
255
+ for res in chain:
256
+ res.label_seq = None
257
+ st.assign_label_seq_id()
258
+
259
+ # add servalcat version
260
+ if len(st.meta.software) > 0 and st.meta.software[-1].name == "refmac":
261
+ st.meta.software[-1].version += f" (refmacat {servalcat.__version__})"
229
262
 
230
263
  suffix = ".org"
231
264
  os.rename(cifout, cifout + suffix)
@@ -292,11 +325,12 @@ def main(args):
292
325
  crdout = None
293
326
  refmac_fixes = None
294
327
  cispeps = []
295
- if xyzin is not None and keywords["refi"].get("type") != "unre":
328
+ unre = keywords["refi"].get("type") == "unre"
329
+ if xyzin is not None:
296
330
  #tmpfd, crdout = tempfile.mkstemp(prefix="gemmi_", suffix=".crd") # TODO use dir=CCP4_SCR
297
331
  #os.close(tmpfd)
298
332
  st = utils.fileio.read_structure(xyzin)
299
- if not st.cell.is_crystal():
333
+ if not st.cell.is_crystal() and not unre:
300
334
  if args.auto_box_with_padding is not None:
301
335
  st.cell = utils.model.box_from_model(st[0], args.auto_box_with_padding)
302
336
  st.spacegroup_hm = "P 1"
@@ -309,7 +343,8 @@ def main(args):
309
343
  refmac_fixes, metal_kws = prepare_crd(st, crdout, args.ligand, make=keywords["make"], monlib_path=args.monlib,
310
344
  h_pos="nucl" if keywords.get("source")=="ne" else "elec",
311
345
  no_adjust_hydrogen_distances=args.no_adjust_hydrogen_distances,
312
- keep_entities=args.keep_entities)
346
+ keep_entities=args.keep_entities,
347
+ unre=unre)
313
348
  inputs = metal_kws + inputs # add metal exte first; otherwise it may be affected by user-defined inputs
314
349
  opts["xyzin"] = crdout
315
350
  cispeps = st.cispeps