1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """Representations of a mechanical rotor."""
24
25
26 from math import pi
27 from numpy import array, cross, dot, float64, transpose, zeros
28 from numpy.linalg import norm
29
30
31 from lib.geometry.lines import closest_point_ax
32 from lib.geometry.rotations import axis_angle_to_R
33
34
35 -def rotor(structure=None, rotor_angle=None, axis=None, axis_pt=True, label=None, centre=None, span=2e-9, blade_length=5e-10, model_num=None, staggered=False, half_rotor=False):
36 """Create a PDB representation of a rotor motional model.
37
38 @keyword structure: The internal structural object instance to add the rotor to as a molecule.
39 @type structure: lib.structure.internal.object.Internal instance
40 @keyword rotor_angle: The angle of the rotor motion in radian.
41 @type rotor_angle: float
42 @keyword axis: The vector defining the rotor axis.
43 @type axis: numpy rank-1, 3D array
44 @keyword axis_pt: A point lying anywhere on the rotor axis. This is used to define the position of the axis in 3D space.
45 @type axis_pt: numpy rank-1, 3D array
46 @keyword label: The optional label for the rotor axis. If supplied, this cannot be longer than 4 characters due to the PDB format restriction.
47 @type label: str
48 @keyword centre: The central point of the representation. If this point is not on the rotor axis, then the closest point on the axis will be used for the centre.
49 @type centre: numpy rank-1, 3D array
50 @keyword span: The distance from the central point to the rotor blades (meters).
51 @type span: float
52 @keyword blade_length: The length of the representative rotor blades.
53 @type blade_length: float
54 @keyword model_num: The structural model number to add the rotor to. If not supplied, the same rotor structure will be added to all models.
55 @type model_num: int or None
56 @keyword staggered: A flag which if True will cause the rotor blades to be staggered. This is used to avoid blade overlap.
57 @type staggered: bool
58 @keyword half_rotor: A flag which if True will cause only the positive half of the rotor to be created.
59 @type half_rotor: bool
60 """
61
62
63 axis = array(axis, float64)
64 axis_pt = array(axis_pt, float64)
65 centre = array(centre, float64)
66 span = span * 1e10
67 blade_length = blade_length * 1e10
68
69
70 axis_norm = axis / norm(axis)
71
72
73 if structure.has_molecule(model_num=model_num, name='rotor') and structure.has_molecule(model_num=model_num, name='rotor2'):
74 structure.add_molecule(model_num=model_num, name='rotor3')
75 mol_name = 'rotor3'
76 elif structure.has_molecule(model_num=model_num, name='rotor'):
77 structure.add_molecule(model_num=model_num, name='rotor2')
78 mol_name = 'rotor2'
79 else:
80 structure.add_molecule(model_num=model_num, name='rotor')
81 mol_name = 'rotor'
82
83
84 for model in structure.model_loop(model_num):
85
86 mol = structure.get_molecule(mol_name, model=model.num)
87
88
89 mid_point = closest_point_ax(line_pt=axis_pt, axis=axis, point=centre)
90 pos_index = mol.atom_add(pdb_record='HETATM', atom_name='CTR', res_name='RTX', res_num=1, pos=mid_point, element='PT')
91
92
93 prop1 = mid_point + axis_norm * span
94 prop1_index = pos_index + 1
95 mol.atom_add(pdb_record='HETATM', atom_name='PRP', res_name='RTX', res_num=2, pos=prop1, element='O')
96 mol.atom_connect(index1=pos_index, index2=prop1_index)
97
98
99 if not half_rotor:
100 prop2 = mid_point - axis_norm * span
101 prop2_index = pos_index + 2
102 mol.atom_add(pdb_record='HETATM', atom_name='PRP', res_name='RTX', res_num=3, pos=prop2, element='O')
103 mol.atom_connect(index1=pos_index, index2=prop2_index)
104
105
106 rotor_propellers(mol=mol, rotor_angle=rotor_angle, centre=prop1, axis=axis, blade_length=blade_length, staggered=staggered)
107 if not half_rotor:
108 rotor_propellers(mol=mol, rotor_angle=rotor_angle, centre=prop2, axis=-axis, blade_length=blade_length, staggered=staggered)
109
110
111 res_num = mol.res_num[-1]+1
112 label_pos1 = mid_point + axis_norm * (span + 2.0)
113 mol.atom_add(pdb_record='HETATM', atom_name=label, res_name='RTL', res_num=res_num, pos=label_pos1, element='H')
114 if not half_rotor:
115 label_pos2 = mid_point - axis_norm * (span + 2.0)
116 mol.atom_add(pdb_record='HETATM', atom_name=label, res_name='RTL', res_num=res_num, pos=label_pos2, element='H')
117
118
119 -def rotor_propellers(mol=None, rotor_angle=None, centre=None, axis=None, blade_length=5.0, step_angle=2.0, staggered=False):
120 """Create a PDB representation of a rotor motional model.
121
122 This function will create a fixed number of atoms, placing the propeller blade steps outside of the rotor angle on the edge. This is to allow for model support whereby the rotor angle between models can be different but the atomic count and connectivity must be the same in all models.
123
124
125 @keyword mol: The internal structural object molecule container to add the atoms to.
126 @type mol: MolContainer instance
127 @keyword rotor_angle: The angle of the rotor motion in radians.
128 @type rotor_angle: float
129 @keyword centre: The central point of the propeller.
130 @type centre: numpy rank-1, 3D array
131 @keyword axis: The vector defining the rotor axis.
132 @type axis: numpy rank-1, 3D array
133 @keyword blade_length: The length of the representative rotor blades in Angstrom.
134 @type blade_length: float
135 @keyword step_angle: The angle between propeller blades, in degrees.
136 @type step_angle: float
137 @keyword staggered: A flag which if True will cause the rotor blades to be staggered. This is used to avoid blade overlap.
138 @type staggered: bool
139 """
140
141
142 step_angle = step_angle / 360.0 * 2.0 * pi
143 step_num = int(pi / step_angle)
144 R = zeros((3, 3), float64)
145 res_num = mol.last_residue() + 1
146
147
148 blades = zeros((4, 3), float64)
149 if abs(dot(axis, array([0, 0, 1], float64))) == 1.0:
150 blades[0] = cross(axis, array([1, 0, 0], float64))
151 else:
152 blades[0] = cross(axis, array([0, 0, 1], float64))
153 blades[0] = blades[0] / norm(blades[0])
154 blades[1] = cross(axis, blades[0])
155 blades[1] = blades[1] / norm(blades[1])
156 blades[2] = -blades[0]
157 blades[3] = -blades[1]
158
159
160 for i in range(len(blades)):
161
162 if staggered and i % 2:
163 blade_origin = centre - axis * 2
164
165
166 else:
167 blade_origin = centre
168
169
170 blade_origin_index = mol.atom_add(pdb_record='HETATM', atom_name='BLO', res_name='RTB', res_num=res_num, pos=blade_origin, element='O')
171
172
173 mid_point = blade_origin + blades[i] * blade_length
174 mid_pt_index = mol.atom_add(pdb_record='HETATM', atom_name='BLD', res_name='RTB', res_num=res_num, pos=mid_point, element='N')
175 mol.atom_connect(index1=mid_pt_index, index2=blade_origin_index)
176
177
178 angle = 0.0
179 pos_last_index = mid_pt_index
180 neg_last_index = mid_pt_index
181 for j in range(step_num):
182
183 angle += step_angle
184
185
186 if angle > rotor_angle:
187 axis_angle_to_R(axis, rotor_angle, R)
188
189
190 else:
191 axis_angle_to_R(axis, angle, R)
192
193
194 pos_point = dot(R, mid_point - blade_origin) + blade_origin
195 pos_index = mol.atom_add(pdb_record='HETATM', atom_name='BLD', res_name='RTB', res_num=res_num, pos=pos_point, element='N')
196 mol.atom_connect(index1=pos_index, index2=pos_last_index)
197 mol.atom_connect(index1=pos_index, index2=blade_origin_index)
198
199
200 neg_point = dot(transpose(R), mid_point - blade_origin) + blade_origin
201 neg_index = mol.atom_add(pdb_record='HETATM', atom_name='BLD', res_name='RTB', res_num=res_num, pos=neg_point, element='N')
202 mol.atom_connect(index1=neg_index, index2=neg_last_index)
203 mol.atom_connect(index1=neg_index, index2=blade_origin_index)
204
205
206 pos_last_index = pos_index
207 neg_last_index = neg_index
208
209
210 res_num += 1
211