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.
- servalcat/__init__.py +10 -0
- servalcat/__main__.py +120 -0
- servalcat/ext.cpython-314t-x86_64-linux-gnu.so +0 -0
- servalcat/refine/__init__.py +0 -0
- servalcat/refine/cgsolve.py +100 -0
- servalcat/refine/refine.py +1162 -0
- servalcat/refine/refine_geom.py +245 -0
- servalcat/refine/refine_spa.py +400 -0
- servalcat/refine/refine_xtal.py +339 -0
- servalcat/refine/spa.py +151 -0
- servalcat/refine/xtal.py +312 -0
- servalcat/refmac/__init__.py +0 -0
- servalcat/refmac/exte.py +191 -0
- servalcat/refmac/refmac_keywords.py +660 -0
- servalcat/refmac/refmac_wrapper.py +423 -0
- servalcat/spa/__init__.py +0 -0
- servalcat/spa/fofc.py +488 -0
- servalcat/spa/fsc.py +391 -0
- servalcat/spa/localcc.py +197 -0
- servalcat/spa/realspcc_from_var.py +128 -0
- servalcat/spa/run_refmac.py +979 -0
- servalcat/spa/shift_maps.py +293 -0
- servalcat/spa/shiftback.py +137 -0
- servalcat/spa/translate.py +129 -0
- servalcat/utils/__init__.py +35 -0
- servalcat/utils/commands.py +1629 -0
- servalcat/utils/fileio.py +836 -0
- servalcat/utils/generate_operators.py +296 -0
- servalcat/utils/hkl.py +811 -0
- servalcat/utils/logger.py +140 -0
- servalcat/utils/maps.py +345 -0
- servalcat/utils/model.py +933 -0
- servalcat/utils/refmac.py +759 -0
- servalcat/utils/restraints.py +888 -0
- servalcat/utils/symmetry.py +298 -0
- servalcat/xtal/__init__.py +0 -0
- servalcat/xtal/french_wilson.py +262 -0
- servalcat/xtal/run_refmac_small.py +240 -0
- servalcat/xtal/sigmaa.py +1954 -0
- servalcat/xtal/twin.py +316 -0
- servalcat-0.4.131.dist-info/METADATA +60 -0
- servalcat-0.4.131.dist-info/RECORD +45 -0
- servalcat-0.4.131.dist-info/WHEEL +6 -0
- servalcat-0.4.131.dist-info/entry_points.txt +4 -0
- servalcat-0.4.131.dist-info/licenses/LICENSE +373 -0
|
@@ -0,0 +1,298 @@
|
|
|
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 subprocess
|
|
11
|
+
import numpy
|
|
12
|
+
import re
|
|
13
|
+
from servalcat.utils import logger
|
|
14
|
+
from servalcat.utils import fileio
|
|
15
|
+
from servalcat.utils import model
|
|
16
|
+
from servalcat.utils import generate_operators
|
|
17
|
+
|
|
18
|
+
def add_symmetry_args(parser, require_pg=False):
|
|
19
|
+
parser.add_argument('--pg', required=require_pg, help="Point group symbol")
|
|
20
|
+
parser.add_argument('--twist', type=float, help="Helical twist (degree)")
|
|
21
|
+
parser.add_argument('--rise', type=float, help="Helical rise (Angstrom)")
|
|
22
|
+
parser.add_argument('--center', type=float, nargs=3, help="Origin of symmetry. Default: center of the box")
|
|
23
|
+
parser.add_argument('--axis1', type=float, nargs=3, help="Axis1 (if I: 5-fold, O: 4-fold, T: 3-fold)")
|
|
24
|
+
parser.add_argument('--axis2', type=float, nargs=3, help="Axis2 (if I: 5-fold, O: 4-fold, T: 3-fold, Dn: 2-fold)")
|
|
25
|
+
# add_symmetry_args()
|
|
26
|
+
|
|
27
|
+
def update_ncs_from_args(args, st, map_and_start=None, filter_contacting=False,
|
|
28
|
+
helical_min_n=None, helical_max_n=None):
|
|
29
|
+
is_helical = args.twist is not None
|
|
30
|
+
if not is_helical and not args.pg:
|
|
31
|
+
if len(st.ncs) > 0:
|
|
32
|
+
logger.writeln("Strict NCS detected from model.")
|
|
33
|
+
show_ncs_operators_axis_angle(st.ncs)
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
if len(st.ncs) > 0:
|
|
37
|
+
logger.writeln(" WARNING: NCS information in model file will be ignored")
|
|
38
|
+
|
|
39
|
+
ncsops = ncsops_from_args(args, st.cell, map_and_start=map_and_start, st=st,
|
|
40
|
+
helical_min_n=helical_min_n, helical_max_n=helical_max_n)
|
|
41
|
+
|
|
42
|
+
st.ncs = [x for x in ncsops if not x.tr.is_identity()]
|
|
43
|
+
# To write identity op to the output model
|
|
44
|
+
idop_id = next((x.id for x in ncsops if x.tr.is_identity()), None)
|
|
45
|
+
if idop_id:
|
|
46
|
+
st.info["_struct_ncs_oper.id"] = idop_id
|
|
47
|
+
|
|
48
|
+
if filter_contacting:
|
|
49
|
+
model.filter_contacting_ncs(st)
|
|
50
|
+
# update_ncs_from_args()
|
|
51
|
+
|
|
52
|
+
def ncsops_from_args(args, cell, map_and_start=None, st=None, helical_min_n=None, helical_max_n=None):
|
|
53
|
+
is_helical = args.twist is not None
|
|
54
|
+
|
|
55
|
+
if map_and_start is not None:
|
|
56
|
+
start_xyz = numpy.array(map_and_start[0].get_position(*map_and_start[1]).tolist())
|
|
57
|
+
else:
|
|
58
|
+
start_xyz = numpy.zeros(3)
|
|
59
|
+
|
|
60
|
+
if args.center is None:
|
|
61
|
+
A = cell.orth.mat.array
|
|
62
|
+
center = numpy.sum(A, axis=1) / 2 #+ start_xyz
|
|
63
|
+
logger.writeln("Center: {}".format(center))
|
|
64
|
+
else:
|
|
65
|
+
center = numpy.array(args.center)
|
|
66
|
+
|
|
67
|
+
if is_helical:
|
|
68
|
+
ncsops = generate_helical_operators(start_xyz, center,
|
|
69
|
+
args.pg, args.twist, args.rise,
|
|
70
|
+
axis1=args.axis1, axis2=args.axis2,
|
|
71
|
+
st=st, min_n=helical_min_n, max_n=helical_max_n)
|
|
72
|
+
logger.writeln("{} helical operators found".format(len(ncsops)))
|
|
73
|
+
else:
|
|
74
|
+
_, _, ops = operators_from_symbol(args.pg, axis1=args.axis1, axis2=args.axis2)
|
|
75
|
+
logger.writeln("{} operators found for {}".format(len(ops), args.pg))
|
|
76
|
+
show_operators_axis_angle(ops)
|
|
77
|
+
ncsops = make_NcsOps_from_matrices(ops, cell=cell, center=center)
|
|
78
|
+
|
|
79
|
+
return ncsops
|
|
80
|
+
# ncsops_from_args()
|
|
81
|
+
|
|
82
|
+
def get_matrices_using_relion(sym):
|
|
83
|
+
ps = subprocess.check_output(["relion_refine", "--sym", sym.strip(), "--print_symmetry_ops"])
|
|
84
|
+
|
|
85
|
+
ret = []
|
|
86
|
+
read_flag = -1
|
|
87
|
+
for l in ps.splitlines():
|
|
88
|
+
if b"R(" in l:
|
|
89
|
+
ret.append(numpy.zeros((3,3)))
|
|
90
|
+
read_flag = 0
|
|
91
|
+
elif 0 <= read_flag < 3:
|
|
92
|
+
ret[-1][read_flag,:] = [float(x) for x in l.split()]
|
|
93
|
+
read_flag += 1
|
|
94
|
+
elif read_flag >= 3:
|
|
95
|
+
read_flag = -1
|
|
96
|
+
|
|
97
|
+
return ret
|
|
98
|
+
# get_matrices_using_relion()
|
|
99
|
+
|
|
100
|
+
def operators_from_symbol(op, axis1=None, axis2=None):
|
|
101
|
+
r = re.search("^([CDITO])([0-9]*)$", op.upper())
|
|
102
|
+
if not r:
|
|
103
|
+
raise RuntimeError("Invalid point group symbol: {}".format(op))
|
|
104
|
+
a, n = r.groups()
|
|
105
|
+
if n:
|
|
106
|
+
n = int(n)
|
|
107
|
+
if n <= 0:
|
|
108
|
+
raise RuntimeError("Non positive order given: {}".format(op))
|
|
109
|
+
elif a in ("T", "O"):
|
|
110
|
+
raise RuntimeError("You cannot give number after T,O: {}".format(op))
|
|
111
|
+
elif a == "I" and n > 4:
|
|
112
|
+
raise RuntimeError("Only I1-4 are supported for I variants: {}".format(op))
|
|
113
|
+
|
|
114
|
+
if axis1 is not None: axis1 = numpy.array(axis1)
|
|
115
|
+
if axis2 is not None: axis2 = numpy.array(axis2)
|
|
116
|
+
|
|
117
|
+
# RELION's conventions
|
|
118
|
+
if a == "I":
|
|
119
|
+
order1 = order2 = 5
|
|
120
|
+
if not n or n == 2:
|
|
121
|
+
if axis1 is None: axis1 = numpy.array([0, 0.85065, 0.52573])
|
|
122
|
+
if axis2 is None: axis2 = numpy.array([0.52573, 0, 0.85065])
|
|
123
|
+
elif n == 1:
|
|
124
|
+
if axis1 is None: axis1 = numpy.array([0.85065, 0., 0.52573])
|
|
125
|
+
if axis2 is None: axis2 = numpy.array([0.52573, 0.85065, 0.])
|
|
126
|
+
elif n == 3:
|
|
127
|
+
if axis1 is None: axis1 = numpy.array([0.723606, 0.525732, 0.447214])
|
|
128
|
+
if axis2 is None: axis2 = numpy.array([-0.276391, -0.850645, 0.447216])
|
|
129
|
+
elif n == 4:
|
|
130
|
+
if axis1 is None: axis1 = numpy.array([-0.723606, -0.525732, 0.447214])
|
|
131
|
+
if axis2 is None: axis2 = numpy.array([0., 0., 1.])
|
|
132
|
+
elif a == "O":
|
|
133
|
+
order1 = order2 = 4
|
|
134
|
+
if axis1 is None: axis1 = numpy.array([0,0,1.0])
|
|
135
|
+
if axis2 is None: axis2 = numpy.array([0,1.0,0])
|
|
136
|
+
elif a == "T":
|
|
137
|
+
order1 = order2 = 3
|
|
138
|
+
if axis1 is None: axis1 = numpy.array([0.0,0.0,1.0])
|
|
139
|
+
if axis2 is None: axis2 = numpy.array([0.0,0.94280904,-0.33333333])
|
|
140
|
+
elif a == "D":
|
|
141
|
+
order1 = n
|
|
142
|
+
if axis1 is None: axis1 = numpy.array([0,0,1.0])
|
|
143
|
+
order2 = 2
|
|
144
|
+
if axis2 is None: axis2 = numpy.array([1.,0.,0.])
|
|
145
|
+
elif a == "C":
|
|
146
|
+
order1 = n
|
|
147
|
+
if axis1 is None: axis1 = numpy.array([0,0,1.0])
|
|
148
|
+
order2 = 0
|
|
149
|
+
axis2 = None
|
|
150
|
+
return generate_operators.generate_all_elements(axis1, order1, axis2, order2)
|
|
151
|
+
# operators_from_symbol()
|
|
152
|
+
|
|
153
|
+
def show_operators_axis_angle(ops):
|
|
154
|
+
for i, op in enumerate(ops):
|
|
155
|
+
ax, ang = generate_operators.Rotation2AxisAngle_general(op)
|
|
156
|
+
logger.writeln(" operator {:3d} angle= {:7.3f} deg axis= {}".format(i+1, numpy.rad2deg(ang), numpy.array_str(ax)))
|
|
157
|
+
# show_operators_axis_angle()
|
|
158
|
+
|
|
159
|
+
def show_ncs_operators_axis_angle(ops):
|
|
160
|
+
# ops: List of gemmi.NcsOp
|
|
161
|
+
for i, op in enumerate(ops):
|
|
162
|
+
op2 = op.tr.mat.array
|
|
163
|
+
ax, ang = generate_operators.Rotation2AxisAngle_general(op2)
|
|
164
|
+
axlab = "[{: .4f}, {: .4f}, {: .4f}]".format(*ax)
|
|
165
|
+
trlab = "[{: 9.4f}, {: 9.4f}, {: 9.4f}]".format(*op.tr.vec.tolist())
|
|
166
|
+
logger.writeln(" operator {:3s} angle= {:7.3f} deg axis= {} trans= {} {}".format(op.id, numpy.rad2deg(ang),
|
|
167
|
+
axlab, trlab,
|
|
168
|
+
"given" if op.given else ""))
|
|
169
|
+
# show_operators_axis_angle()
|
|
170
|
+
|
|
171
|
+
def read_helical_parameters_from_mmcif(cif_in):
|
|
172
|
+
doc = fileio.read_cif_safe(cif_in)
|
|
173
|
+
b = list(filter(lambda b: b.find_loop("_atom_site.id"), doc))[0]
|
|
174
|
+
deltaphi = b.find_value("_em_helical_entity.angular_rotation_per_subunit")
|
|
175
|
+
deltaz = b.find_value("_em_helical_entity.axial_rise_per_subunit")
|
|
176
|
+
deltaphi, deltaz = float(deltaphi), float(deltaz)
|
|
177
|
+
axsym = b.find_value("_em_helical_entity.axial_symmetry")
|
|
178
|
+
return axsym, deltaphi, deltaz
|
|
179
|
+
# read_helical_parameters_from_mmcif()
|
|
180
|
+
|
|
181
|
+
def generate_helical_operators(start_xyz, center, axsym, deltaphi, deltaz, axis1=None, axis2=None,
|
|
182
|
+
st=None, min_n=None, max_n=None, padding=2.):
|
|
183
|
+
if not axsym: axsym = "C1"
|
|
184
|
+
if axis1 is None: axis1 = numpy.array([0,0,1.])
|
|
185
|
+
else: axis1 /= numpy.linalg.norm(axis1)
|
|
186
|
+
_, _, axtrs = operators_from_symbol(axsym, axis1, axis2)
|
|
187
|
+
if min_n is None or max_n is None:
|
|
188
|
+
assert st is not None
|
|
189
|
+
all_z = numpy.dot([cra.atom.pos.tolist() for cra in st[0].all()], axis1)
|
|
190
|
+
min_z, max_z = numpy.min(all_z), numpy.max(all_z)
|
|
191
|
+
direc = numpy.argmax([numpy.dot(axis1, v) for v in ((1.,0,0), (0,1.,0), (0,0,1.))]) # assume axis1 along any of a,b,c axis
|
|
192
|
+
if min_n is None:
|
|
193
|
+
min_n = -int((min_z - padding - start_xyz[direc]) / deltaz)
|
|
194
|
+
if max_n is None:
|
|
195
|
+
max_n = int((st.cell.parameters[direc] + start_xyz[direc] - max_z - padding) / deltaz)
|
|
196
|
+
if min_n > max_n:
|
|
197
|
+
min_n, max_n = max_n, min_n
|
|
198
|
+
ops = []
|
|
199
|
+
for i in range(min_n, max_n+1):
|
|
200
|
+
deg = deltaphi*i
|
|
201
|
+
t = numpy.deg2rad(deg)
|
|
202
|
+
m = generate_operators.AngleAxis2rotatin(axis1, t)
|
|
203
|
+
s = numpy.array(axis1 * deltaz*i)
|
|
204
|
+
for a in axtrs:
|
|
205
|
+
mat = numpy.dot(m, a)
|
|
206
|
+
news = s + numpy.dot(mat, -center) + center
|
|
207
|
+
tr = gemmi.Transform(gemmi.Mat33(mat), gemmi.Vec3(*news))
|
|
208
|
+
newop = gemmi.NcsOp(tr, str(len(ops)+1), tr.is_identity())
|
|
209
|
+
ops.append(newop)
|
|
210
|
+
|
|
211
|
+
return ops
|
|
212
|
+
# generate_helical_operators()
|
|
213
|
+
|
|
214
|
+
def make_NcsOps_from_matrices(matrices, cell=None, center=None):
|
|
215
|
+
if center is None:
|
|
216
|
+
A = cell.orth.mat.array
|
|
217
|
+
center = numpy.sum(A,axis=1) / 2
|
|
218
|
+
|
|
219
|
+
center = gemmi.Vec3(*center)
|
|
220
|
+
ops = []
|
|
221
|
+
for i, m in enumerate(matrices):
|
|
222
|
+
m = gemmi.Mat33(m)
|
|
223
|
+
transl = m.multiply(-center) + center
|
|
224
|
+
op = gemmi.NcsOp(gemmi.Transform(m, transl), str(i+1), m.is_identity())
|
|
225
|
+
ops.append(op)
|
|
226
|
+
|
|
227
|
+
return ops
|
|
228
|
+
# make_NcsOps_from_matrices()
|
|
229
|
+
|
|
230
|
+
def find_center_of_origin(mat, vec): # may not be unique.
|
|
231
|
+
tmp = numpy.identity(3) - numpy.array(mat.array)
|
|
232
|
+
ret = numpy.dot(numpy.linalg.pinv(tmp), vec.tolist())
|
|
233
|
+
resid = vec.tolist() - (numpy.dot(mat.array, -ret) + ret)
|
|
234
|
+
return gemmi.Vec3(*ret), gemmi.Vec3(*resid)
|
|
235
|
+
# find_center_of_origin()
|
|
236
|
+
|
|
237
|
+
def ncs_ops_for_refmac(ncs_ops):
|
|
238
|
+
def make_line(tr):
|
|
239
|
+
m = tr.mat.tolist()
|
|
240
|
+
m_str = " ".join([str(m[i][j]) for i in range(3) for j in range(3)])
|
|
241
|
+
t_str = " ".join([str(x) for x in tr.vec.tolist()])
|
|
242
|
+
return "ncsc matrix {} {}".format(m_str, t_str)
|
|
243
|
+
|
|
244
|
+
ret = []
|
|
245
|
+
# REFMAC requires identity op
|
|
246
|
+
if not any(x.tr.is_identity() for x in ncs_ops):
|
|
247
|
+
ret.append(make_line(gemmi.Transform()))
|
|
248
|
+
|
|
249
|
+
for op in ncs_ops:
|
|
250
|
+
if not op.given: ret.append(make_line(op.tr))
|
|
251
|
+
return ret
|
|
252
|
+
# ncs_ops_for_refmac()
|
|
253
|
+
|
|
254
|
+
# TODO def euler2matrix(euler):
|
|
255
|
+
# TODO def polar2matrix(polar):
|
|
256
|
+
|
|
257
|
+
def parse_ncsc_keywords(kwd_str):
|
|
258
|
+
# FIXME handle lines ending with -
|
|
259
|
+
lines = kwd_str.splitlines()
|
|
260
|
+
ret = []
|
|
261
|
+
for l in lines:
|
|
262
|
+
l = l.split()
|
|
263
|
+
if len(l) < 3: continue
|
|
264
|
+
if l[0].lower().startswith("ncsc"):
|
|
265
|
+
if l[1].lower().startswith("matr"):
|
|
266
|
+
vals = [float(x) for x in l[2:]]
|
|
267
|
+
if len(vals) != 12:
|
|
268
|
+
print("Bad nsc matrix line: {}".format(" ".join(l)))
|
|
269
|
+
continue
|
|
270
|
+
op = gemmi.NcsOp()
|
|
271
|
+
op.tr.mat.fromlist([vals[3*x:3*x+3] for x in range(3)])
|
|
272
|
+
op.tr.vec.fromlist(vals[9:])
|
|
273
|
+
op.id = str(len(ret)+1)
|
|
274
|
+
op.given = op.tr.is_identity()
|
|
275
|
+
ret.append(op)
|
|
276
|
+
elif l[1].lower().startswith("eule"):
|
|
277
|
+
pass # TODO
|
|
278
|
+
elif l[1].lower().startswith("pola"):
|
|
279
|
+
pass # TODO
|
|
280
|
+
return ret
|
|
281
|
+
# parse_ncsc_keywords()
|
|
282
|
+
|
|
283
|
+
def apply_shift_for_ncsops(ncsops, shift):
|
|
284
|
+
new_ops = []
|
|
285
|
+
s = gemmi.Vec3(*shift)
|
|
286
|
+
for op in ncsops:
|
|
287
|
+
newt = op.tr.vec + s - op.tr.mat.multiply(s)
|
|
288
|
+
newop = gemmi.NcsOp(gemmi.Transform(op.tr.mat, newt), op.id, op.given)
|
|
289
|
+
new_ops.append(newop)
|
|
290
|
+
|
|
291
|
+
return new_ops
|
|
292
|
+
# apply_shift_for_ncsops()
|
|
293
|
+
|
|
294
|
+
def write_symmetry_expanded_model(st, prefix, pdb=False, cif=False, cif_ref=None):
|
|
295
|
+
st_new = st.clone()
|
|
296
|
+
model.expand_ncs(st_new)
|
|
297
|
+
fileio.write_model(st_new, prefix=prefix, pdb=pdb, cif=cif, cif_ref=cif_ref)
|
|
298
|
+
# write_symmetry_expanded_model()
|
|
File without changes
|
|
@@ -0,0 +1,262 @@
|
|
|
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 argparse
|
|
10
|
+
import gemmi
|
|
11
|
+
import numpy
|
|
12
|
+
import pandas
|
|
13
|
+
import time
|
|
14
|
+
import os
|
|
15
|
+
import scipy.special
|
|
16
|
+
import scipy.optimize
|
|
17
|
+
from servalcat.utils import logger
|
|
18
|
+
from servalcat.xtal import sigmaa
|
|
19
|
+
from servalcat import utils
|
|
20
|
+
from servalcat import ext
|
|
21
|
+
|
|
22
|
+
#import line_profiler
|
|
23
|
+
#profile = line_profiler.LineProfiler()
|
|
24
|
+
#import atexit
|
|
25
|
+
#atexit.register(profile.print_stats)
|
|
26
|
+
|
|
27
|
+
integr = sigmaa.integr
|
|
28
|
+
|
|
29
|
+
def add_arguments(parser):
|
|
30
|
+
parser.description = 'Convert intensity to amplitude'
|
|
31
|
+
parser.add_argument('--hklin', required=True,
|
|
32
|
+
help='Input MTZ file')
|
|
33
|
+
parser.add_argument('--hklin_index', type=int, default=0,
|
|
34
|
+
help='block index if hklin is mmcif file (default: %(default)d)')
|
|
35
|
+
parser.add_argument('--labin',
|
|
36
|
+
help='MTZ column for I,SIGI')
|
|
37
|
+
parser.add_argument('--d_min', type=float)
|
|
38
|
+
parser.add_argument('--d_max', type=float)
|
|
39
|
+
parser.add_argument('--nbins', type=int,
|
|
40
|
+
help="Number of bins (default: auto)")
|
|
41
|
+
parser.add_argument('-o','--output_prefix',
|
|
42
|
+
help='output file name prefix')
|
|
43
|
+
# add_arguments()
|
|
44
|
+
|
|
45
|
+
def parse_args(arg_list):
|
|
46
|
+
parser = argparse.ArgumentParser()
|
|
47
|
+
add_arguments(parser)
|
|
48
|
+
return parser.parse_args(arg_list)
|
|
49
|
+
# parse_args()
|
|
50
|
+
|
|
51
|
+
def determine_Sigma_and_aniso(hkldata):
|
|
52
|
+
# initial estimate
|
|
53
|
+
hkldata.binned_df["ml"]["S"] = 1.
|
|
54
|
+
I_over_eps = hkldata.df.I.to_numpy() / hkldata.df.epsilon.to_numpy()
|
|
55
|
+
x = []
|
|
56
|
+
for i_bin, idxes in hkldata.binned("ml"):
|
|
57
|
+
#S = max(numpy.nanmean(I_over_eps[idxes]), 1e-3)
|
|
58
|
+
# var(I) = var_signal + var_noise, so this overestimates S,
|
|
59
|
+
# but it should be better than having negative values (in noisy shell)
|
|
60
|
+
S = numpy.nanstd(I_over_eps[idxes])
|
|
61
|
+
hkldata.binned_df["ml"].loc[i_bin, "S"] = S
|
|
62
|
+
x.append(S)
|
|
63
|
+
logger.writeln("Initial estimates:")
|
|
64
|
+
logger.writeln(hkldata.binned_df["ml"].to_string())
|
|
65
|
+
|
|
66
|
+
B = gemmi.SMat33d(0,0,0,0,0,0)
|
|
67
|
+
SMattolist = lambda B: [B.u11, B.u22, B.u33, B.u12, B.u13, B.u23]
|
|
68
|
+
adpdirs = utils.model.adp_constraints(hkldata.sg.operations(), hkldata.cell, tr0=True)
|
|
69
|
+
logger.writeln("ADP free parameters = {}".format(adpdirs.shape[0]))
|
|
70
|
+
ssqmat = hkldata.ssq_mat()
|
|
71
|
+
cycle_data = [[0] + SMattolist(B) + list(hkldata.binned_df["ml"].S)]
|
|
72
|
+
for icyc in range(100):
|
|
73
|
+
#logger.writeln("Refine B")
|
|
74
|
+
B_converged = False
|
|
75
|
+
t0 = time.time()
|
|
76
|
+
args=(ssqmat, hkldata, adpdirs)
|
|
77
|
+
for j in range(10):
|
|
78
|
+
x = numpy.dot(SMattolist(B), numpy.linalg.pinv(adpdirs))
|
|
79
|
+
f0 = ll_all_B(x, *args)
|
|
80
|
+
shift = ll_shift_B(x, *args)
|
|
81
|
+
for i in range(3):
|
|
82
|
+
ss = shift / 2**i
|
|
83
|
+
f1 = ll_all_B(x + ss, *args)
|
|
84
|
+
#logger.writeln("f0 = {:.3e} shift = {} df = {:.3e}".format(f0, ss, f1 - f0))
|
|
85
|
+
if f1 < f0:
|
|
86
|
+
B = gemmi.SMat33d(*numpy.dot(x+ss, adpdirs))
|
|
87
|
+
if numpy.max(numpy.abs(ss)) < 1e-4: B_converged = True
|
|
88
|
+
break
|
|
89
|
+
else:
|
|
90
|
+
B_converged = True
|
|
91
|
+
if B_converged: break
|
|
92
|
+
|
|
93
|
+
#logger.writeln("time= {}".format(time.time() - t0))
|
|
94
|
+
#logger.writeln("B_aniso= {}".format(B))
|
|
95
|
+
#logger.writeln("Refine S")
|
|
96
|
+
S_converged = [False for _ in hkldata.binned("ml")]
|
|
97
|
+
k_ani = hkldata.debye_waller_factors(b_cart=B)
|
|
98
|
+
for i, (i_bin, idxes) in enumerate(hkldata.binned("ml")):
|
|
99
|
+
#logger.writeln("Bin {}".format(i_bin))
|
|
100
|
+
for j in range(10):
|
|
101
|
+
S = hkldata.binned_df["ml"].loc[i_bin, "S"]
|
|
102
|
+
f0 = numpy.nansum(integr.ll_int(hkldata.df.I.to_numpy()[idxes], hkldata.df.SIGI.to_numpy()[idxes], k_ani[idxes],
|
|
103
|
+
S * hkldata.df.epsilon.to_numpy()[idxes],
|
|
104
|
+
numpy.zeros(len(idxes)), hkldata.df.centric.to_numpy()[idxes]+1,
|
|
105
|
+
hkldata.df.llweight.to_numpy()[idxes]))
|
|
106
|
+
shift = numpy.exp(ll_shift_bin_S(hkldata.df.I.to_numpy()[idxes], hkldata.df.SIGI.to_numpy()[idxes], k_ani[idxes],
|
|
107
|
+
S, hkldata.df.centric.to_numpy()[idxes]+1, hkldata.df.epsilon.to_numpy()[idxes],
|
|
108
|
+
hkldata.df.llweight.to_numpy()[idxes]))
|
|
109
|
+
for k in range(3):
|
|
110
|
+
ss = shift**(1. / 2**k)
|
|
111
|
+
f1 = numpy.nansum(integr.ll_int(hkldata.df.I.to_numpy()[idxes], hkldata.df.SIGI.to_numpy()[idxes], k_ani[idxes],
|
|
112
|
+
S * ss * hkldata.df.epsilon.to_numpy()[idxes],
|
|
113
|
+
numpy.zeros(len(idxes)), hkldata.df.centric.to_numpy()[idxes]+1,
|
|
114
|
+
hkldata.df.llweight.to_numpy()[idxes]))
|
|
115
|
+
#logger.writeln("bin {:3d} f0 = {:.3e} shift = {:.3e} df = {:.3e}".format(i_bin, f0, ss, f1 - f0))
|
|
116
|
+
if f1 < f0:
|
|
117
|
+
hkldata.binned_df["ml"].loc[i_bin, "S"] = S * ss
|
|
118
|
+
if ss > 0.9999: S_converged[i] = True
|
|
119
|
+
break
|
|
120
|
+
else:
|
|
121
|
+
S_converged[i] = True
|
|
122
|
+
if S_converged[i]: break
|
|
123
|
+
|
|
124
|
+
#logger.writeln("Refined estimates in cycle {}:".format(icyc))
|
|
125
|
+
#logger.writeln(hkldata.binned_df["ml"].to_string())
|
|
126
|
+
#logger.writeln("B_aniso= {}".format(B))
|
|
127
|
+
cycle_data.append([icyc] + SMattolist(B) + list(hkldata.binned_df["ml"].S))
|
|
128
|
+
if B_converged and all(S_converged):
|
|
129
|
+
logger.writeln("Converged in cycle {}".format(icyc))
|
|
130
|
+
logger.writeln("Refined estimates:")
|
|
131
|
+
logger.writeln(hkldata.binned_df["ml"].to_string())
|
|
132
|
+
logger.writeln("B_aniso= {}".format(B))
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
#with open("fw_cycles.dat", "w") as ofs:
|
|
136
|
+
# ofs.write("cycle B11 B22 B33 B12 B13 B23 " + " ".join("S{}".format(i) for i in hkldata.binned_df["ml"].index) + "\n")
|
|
137
|
+
# for data in cycle_data:
|
|
138
|
+
# ofs.write("{:2d} ".format(data[0]+1))
|
|
139
|
+
# ofs.write(" ".join("{:.4e}".format(x) for x in data[1:]))
|
|
140
|
+
# ofs.write("\n")
|
|
141
|
+
|
|
142
|
+
return B
|
|
143
|
+
|
|
144
|
+
def ll_all_B(x, ssqmat, hkldata, adpdirs):
|
|
145
|
+
B = gemmi.SMat33d(*numpy.dot(x, adpdirs))
|
|
146
|
+
k_ani = hkldata.debye_waller_factors(b_cart=B)
|
|
147
|
+
ret = 0.
|
|
148
|
+
for i_bin, idxes in hkldata.binned("ml"):
|
|
149
|
+
ret += numpy.nansum(integr.ll_int(hkldata.df.I.to_numpy()[idxes], hkldata.df.SIGI.to_numpy()[idxes], k_ani[idxes],
|
|
150
|
+
hkldata.binned_df["ml"].S[i_bin] * hkldata.df.epsilon.to_numpy()[idxes],
|
|
151
|
+
numpy.zeros(len(idxes)), hkldata.df.centric.to_numpy()[idxes]+1,
|
|
152
|
+
hkldata.df.llweight.to_numpy()[idxes]))
|
|
153
|
+
return ret
|
|
154
|
+
|
|
155
|
+
def ll_shift_bin_S(Io, sigIo, k_ani, S, c, eps, llw, exp_trans=True):
|
|
156
|
+
tmp = integr.ll_int_fw_der1_S(Io, sigIo, k_ani, S, c, eps, llw)
|
|
157
|
+
g = numpy.nansum(tmp)
|
|
158
|
+
H = numpy.nansum(tmp**2)
|
|
159
|
+
if exp_trans:
|
|
160
|
+
return -g / (H * S + g)
|
|
161
|
+
else:
|
|
162
|
+
return -g / H
|
|
163
|
+
|
|
164
|
+
def ll_shift_B(x, ssqmat, hkldata, adpdirs):
|
|
165
|
+
b_aniso = gemmi.SMat33d(*numpy.dot(x, adpdirs))
|
|
166
|
+
k_ani = hkldata.debye_waller_factors(b_cart=b_aniso)
|
|
167
|
+
Io = hkldata.df.I.to_numpy()
|
|
168
|
+
sigIo = hkldata.df.SIGI.to_numpy()
|
|
169
|
+
c = hkldata.df.centric.to_numpy() + 1
|
|
170
|
+
epsilon = hkldata.df.epsilon.to_numpy()
|
|
171
|
+
llw = hkldata.df.llweight.to_numpy()
|
|
172
|
+
r = numpy.empty(len(Io)) * numpy.nan
|
|
173
|
+
for i_bin, idxes in hkldata.binned("ml"):
|
|
174
|
+
r[idxes] = integr.ll_int_fw_der1_ani(Io[idxes], sigIo[idxes],
|
|
175
|
+
k_ani[idxes], hkldata.binned_df["ml"].S[i_bin],
|
|
176
|
+
c[idxes], epsilon[idxes], llw[idxes])
|
|
177
|
+
g = -numpy.nansum(ssqmat * r, axis=1)
|
|
178
|
+
H = numpy.nansum(numpy.matmul(ssqmat[None,:].T, ssqmat.T[:,None]) * (r**2)[:,None,None], axis=0)
|
|
179
|
+
g, H = numpy.dot(g, adpdirs.T), numpy.dot(adpdirs, numpy.dot(H, adpdirs.T))
|
|
180
|
+
return -numpy.dot(g, numpy.linalg.pinv(H))
|
|
181
|
+
|
|
182
|
+
def expected_F_from_int(Io, sigo, k_ani, eps, c, S):
|
|
183
|
+
to = Io / sigo - sigo / c / k_ani**2 / S / eps
|
|
184
|
+
tf = numpy.zeros(Io.size)
|
|
185
|
+
sig1 = numpy.ones(Io.size)
|
|
186
|
+
k_num = numpy.where(c == 1, 0.5, 0.)
|
|
187
|
+
F = numpy.sqrt(sigo) * ext.integ_J_ratio(k_num, k_num - 0.5, False, to, tf, sig1, c,
|
|
188
|
+
integr.exp2_threshold, integr.h, integr.N, integr.ewmax)
|
|
189
|
+
Fsq = sigo * ext.integ_J_ratio(k_num + 0.5, k_num - 0.5, False, to, tf, sig1, c,
|
|
190
|
+
integr.exp2_threshold, integr.h, integr.N, integr.ewmax)
|
|
191
|
+
varF = Fsq - F**2
|
|
192
|
+
return F, numpy.sqrt(varF)
|
|
193
|
+
|
|
194
|
+
def french_wilson(hkldata, B_aniso, labout=None):
|
|
195
|
+
if labout is None: labout = ["F", "SIGF"]
|
|
196
|
+
k_ani = hkldata.debye_waller_factors(b_cart=B_aniso)
|
|
197
|
+
has_ano = "I(+)" in hkldata.df and "I(-)" in hkldata.df
|
|
198
|
+
if has_ano:
|
|
199
|
+
ano_data = hkldata.df[["I(+)", "SIGI(+)", "I(-)", "SIGI(-)"]].to_numpy()
|
|
200
|
+
if len(labout) == 2:
|
|
201
|
+
labout += [f"{labout[0]}(+)", f"{labout[1]}(+)", f"{labout[0]}(-)", f"{labout[1]}(-)"]
|
|
202
|
+
hkldata.df[labout] = numpy.nan
|
|
203
|
+
for i_bin, idxes in hkldata.binned("ml"):
|
|
204
|
+
S = hkldata.binned_df["ml"].S[i_bin]
|
|
205
|
+
c = hkldata.df.centric.to_numpy()[idxes] + 1 # 1 for acentric, 2 for centric
|
|
206
|
+
Io = hkldata.df.I.to_numpy()[idxes]
|
|
207
|
+
sigo = hkldata.df.SIGI.to_numpy()[idxes]
|
|
208
|
+
eps = hkldata.df.epsilon.to_numpy()[idxes]
|
|
209
|
+
F, sigF = expected_F_from_int(Io, sigo, k_ani[idxes], eps, c, S)
|
|
210
|
+
hkldata.df.loc[idxes, labout[0]] = F
|
|
211
|
+
hkldata.df.loc[idxes, labout[1]] = sigF
|
|
212
|
+
if has_ano:
|
|
213
|
+
Fp, sigFp = expected_F_from_int(ano_data[idxes,0], ano_data[idxes,1], k_ani[idxes], eps, c, S)
|
|
214
|
+
Fm, sigFm = expected_F_from_int(ano_data[idxes,2], ano_data[idxes,3], k_ani[idxes], eps, c, S)
|
|
215
|
+
hkldata.df.loc[idxes, labout[2]] = Fp
|
|
216
|
+
hkldata.df.loc[idxes, labout[3]] = sigFp
|
|
217
|
+
hkldata.df.loc[idxes, labout[4]] = Fm
|
|
218
|
+
hkldata.df.loc[idxes, labout[5]] = sigFm
|
|
219
|
+
|
|
220
|
+
def main(args):
|
|
221
|
+
if not args.output_prefix:
|
|
222
|
+
args.output_prefix = utils.fileio.splitext(os.path.basename(args.hklin))[0] + "_fw"
|
|
223
|
+
try:
|
|
224
|
+
mtz = utils.fileio.read_mmhkl(args.hklin, cif_index=args.hklin_index)
|
|
225
|
+
except RuntimeError as e:
|
|
226
|
+
raise SystemExit("Error: {}".format(e))
|
|
227
|
+
if not args.labin:
|
|
228
|
+
labin = sigmaa.decide_mtz_labels(mtz, require=("K", "J"))
|
|
229
|
+
else:
|
|
230
|
+
labin = args.labin.split(",")
|
|
231
|
+
try:
|
|
232
|
+
hkldata, _, _, _, _ = sigmaa.process_input(hklin=mtz,
|
|
233
|
+
labin=labin,
|
|
234
|
+
n_bins_ml=args.nbins,
|
|
235
|
+
free=None,
|
|
236
|
+
xyzins=[],
|
|
237
|
+
d_min=args.d_min,
|
|
238
|
+
n_per_mlbin=500,
|
|
239
|
+
max_mlbins=30,
|
|
240
|
+
cif_index=args.hklin_index)
|
|
241
|
+
except RuntimeError as e:
|
|
242
|
+
raise SystemExit("Error: {}".format(e))
|
|
243
|
+
|
|
244
|
+
B_aniso = determine_Sigma_and_aniso(hkldata)
|
|
245
|
+
french_wilson(hkldata, B_aniso)
|
|
246
|
+
mtz_out = args.output_prefix+".mtz"
|
|
247
|
+
lab_out = ["F", "SIGF", "I", "SIGI"]
|
|
248
|
+
labo_types = {"F":"F", "SIGF":"Q", "I":"J", "SIGI":"Q"}
|
|
249
|
+
if "I(+)" in hkldata.df and "I(-)" in hkldata.df:
|
|
250
|
+
lab_out += ["F(+)", "SIGF(+)", "F(-)", "SIGF(-)"]
|
|
251
|
+
labo_types.update({"F(+)":"G", "SIGF(+)":"L", "F(-)":"G", "SIGF(-)":"L"})
|
|
252
|
+
if len(labin) == 3:
|
|
253
|
+
lab_out.append("FREE")
|
|
254
|
+
labo_types[lab_out[-1]] = "I"
|
|
255
|
+
hkldata.write_mtz(mtz_out, lab_out, types=labo_types)
|
|
256
|
+
return B_aniso, hkldata
|
|
257
|
+
# main()
|
|
258
|
+
|
|
259
|
+
if __name__ == "__main__":
|
|
260
|
+
import sys
|
|
261
|
+
args = parse_args(sys.argv[1:])
|
|
262
|
+
main(args)
|