summaryrefslogtreecommitdiff
path: root/cad/src/operations/reposition_baggage.py
blob: fdb66948530661c6fe885ec7b5064795dc6bfd92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# Copyright 2006-2008 Nanorex, Inc.  See LICENSE file for details.
"""
reposition_baggage.py -- help reposition baggage atoms after real neighbor
atoms have moved

@author: Bruce
@version: $Id$
@copyright: 2006-2008 Nanorex, Inc.  See LICENSE file for details.
"""

import math

from utilities import debug_flags
from geometry.VQT import V, norm, cross, vlen
from model.bond_constants import find_bond
from model.bond_constants import V_SINGLE

from geometry.geometryUtilities import arbitrary_perpendicular

debugprints = False

# geometry of a tetrahedron (for finding sp3 bonding positions)
coef1, coef2 = norm( V( 1, math.sqrt(2) ) )
coef1 = - coef1

def reposition_baggage_0(self, baggage = None, planned_atom_nupos = None):
    """
    Your baggage atoms (or the given subset of them) might no longer
    be sensibly located, since you and/or some neighbor atoms have moved
    (or are about to move, re planned_atom_nupos as explained below),
    so fix up their positions based on your other neighbors' positions,
    using old baggage positions only as hints.

    BUT one of your other neighbors (but not self) might be about to move
    (rather than already having moved) -- if so,

      planned_atom_nupos = (that neighbor, its planned posn),

    and use that posn instead of its actual posn to decide what to do.

    @warning: we assume baggage is a subset of self.baggageNeighbors(),
              but we don't check this except when ATOM_DEBUG is set.
    """
    #bruce 060629 for bondpoint problem -- second guess what caller did so far
    if baggage is None:
        baggage, other = self.baggage_and_other_neighbors()
    else:
        other = -1 # set later if needed
        if debug_flags.atom_debug:
            # assert baggage is a subset of self.baggageNeighbors()
            _bn = map(id, self.baggageNeighbors())
            for atom in baggage:
                assert id(atom) in _bn
            atom = 0
            del _bn, atom

    _reposition_baggage_1(self, baggage, other, planned_atom_nupos)

    if self.element.pam and self.element.role in ('axis', 'strand'):
        # Let the dna updater 3rd-guess this when it has a DnaLadder and can
        # do a better job. REVIEW: are the things already done in _reposition_baggage_1
        # good or bad in this case? At least in most cases, they seem good (by testing).
        # [bruce 080404/080405 new feature / bugfix]
        self._f_dna_updater_should_reposition_baggage = True
        # do it immediately if possible
        ladder = getattr(self.molecule, 'ladder', None) ## self.molecule.ladder
        if ladder and ladder.valid and not ladder.error:
            # usual case, at least when dragging a real neighbor of self;
            # in these cases the dna updater is never running;
            # by test it's clear this is doing more good than harm, usually,
            # as of the late 080405 improvements to this method
            self.reposition_baggage_using_DnaLadder()
        else:
            # don't know if this ever happens
            # (it does happen for ghost bases created within ordinary chunks)
            self._changed_structure() # make sure dna updater runs on self
                # (probably NOT redundant with other changes by caller)
        pass
    return

def _reposition_baggage_1(self, baggage, other, planned_atom_nupos):
    """
    """
    # trivial cases
    len_baggage = len(baggage)
    if not len_baggage:
        return

    # cases handled well enough by calling code (as of 060629),
    # needing no correction here
    len_other = len(self.bonds) - len_baggage

    if not len_other:
        # should never happen, as we are called as of 060629, i think,
        # though if it did, there would be things we could do in theory,
        # like rotate atomtype.bondvecs to best match baggage...
        print "bug?: %r.reposition_baggage(%r) finds no other atoms -- " \
              "nim, not thought to ever happen" % (self, baggage)
        return

    if len_other == 1:
        # the Q(old, new) code in the callers ought to handle it properly --
        # UNLESS other is a pi_bond, and its alignment ought to affect a pair
        # of baggage atoms.
        if self.atomtype.spX == 2: # note: true for sp2 and sp2(graphitic)
            pass # let the main code run, including some other
                 # "if len_other == 1" scattered around
            ##e someday: don't we want to also notice sp, and propogate
             # twisting of a pi_bond through an sp_chain?
            # I recall some code in depositMode for that...
            # I can't remember its scope, thus whether it covers this already...
            # I think not. ###@@@
        else:
            return

    # at least 2 other (except sp2 pi_bond other), and at least one baggage...
    # might as well get other_posns we'll want to use
    # (handling planned_atom_nupos once and for all).
    if other == -1:
        other = []
        baggage_keys = [atom.key for atom in baggage]
        for b in self.bonds:
            atom = b.other(self)
            if atom.key not in baggage_keys:
                other.append(atom)
        if len(other) != len_other:
            # must mean baggage is not a subset of neighbors
            args = (self, baggage, planned_atom_nupos)
            print "bug in reposition_baggage%r: len(other == %r) != len_other %r" % \
                  (args, other, len_other)
            return
    if len_other == 1:
        other0_bond = find_bond(other[0], self)
        if other0_bond.v6 == V_SINGLE:
            # calling code handled this case well enough
            return
    planned_atom, nupos = None, None
    if planned_atom_nupos:
        planned_atom, nupos = planned_atom_nupos
        if planned_atom not in other:
            print "likely bug in reposition_baggage: " \
                  "planned_atom not in other", planned_atom, other
    other_posns = [(atom.posn(), nupos)[atom is planned_atom] for atom in other]
        #e we might later wish we'd kept a list of the bonds to baggage and
        # other, to grab the v6's -- make a dict from atom.key above?
    selfposn = self.posn()
    othervecs = [norm(pos - selfposn) for pos in other_posns]

    bag_posns = [atom.posn() for atom in baggage]
    bagvecs = [norm(pos - selfposn) for pos in bag_posns]

    # The alg is specific to atomtype, and number and sometimes *type* of all
    # bonds involved. We'll code the most important and/or easiest cases first.
    # Someday we might move them into methods of the atomtypes themselves.

    algchoice = (self.atomtype.spX, len_baggage, len_other)
        # len_baggage >= 1, len_other >= 2 (except pi_bond case)
    extra = 0 # might be altered below
    if algchoice == (3, 2, 2) or algchoice == (3, 1, 2):
        # (3, 2, 2) -- e.g. C(sp3) with 2 bp's and 2 real bonds
        # This is not the easiest case, but it's arguably the most important.
        # For alignment purposes we can assume bonds are single.
        # (Due to monovalent atoms being baggage, that doesn't mean the baggage
        #  atoms are equivalent to each other.)
        #
        # (3, 1, 2) -- e.g. N(sp3) with 1 bondpoint and 2 real bonds;
        # use same code and coefs, but pretend a phantom baggage atom is present
        if len_baggage == 1: # (3,1,2)
            extra = 1
            if debugprints:
                print "debug repos baggage: sp3,1,2"
        plane = cross( othervecs[0], othervecs[1] )
        if vlen(plane) < 0.001:
            # othervecs are nearly parallel (same or opposite);
            # could force existing bonds perp to them, at correct angle,
            # as close to existing posns as you can, but this case can be left
            # nim for now since it's rare and probably transient.
            if debugprints:
                print "debug repos baggage: othervecs are nearly parallel; " \
                      "this case is nim", self, other ###@@@
            return
        plane = norm(plane)
        back = norm(othervecs[0] + othervecs[1])
        res = [coef1 * back + coef2 * plane, coef1 * back - coef2 * plane]
        pass # fall thru to assigning res vecs to specific baggage
    elif algchoice == (3, 1, 3):
        back = norm(othervecs[0] + othervecs[1] + othervecs[2])
        if back:
            res = [-back]
            ##e might not be as good as averaging the three crossproducts,
            # after choosing their sign close to -back; or something better,
            # since real goal is just "be repelled from them all";
            # consider case where two othervecs are similar ###@@@
        else:
            plane0 = norm( cross( othervecs[0], othervecs[1] ))
            if plane0:
                if debugprints:
                    print "debug repos baggage: sp3 with 3 real bonds in a plane"
                # pick closest of plane0, -plane0 to existing posn
##                # one way:
##                if dot(plane0, bagvecs[0]) < 0:
##                    res = [-plane0]
##                else:
##                    res = [plane0]
                # another way:
                res = [-plane0, plane0]
                extra = 1
            else:
                # not possible -- if othervecs[0], othervecs[1] are antiparallel,
                # overall sum (in back) can't be zero; if parallel, ditto.
                print "can't happen: back and plane0 vanish", othervecs
                return
            pass
        pass
    elif algchoice == (2, 1, 2): # e.g. C(sp2) with 1 bondpoint and 2 real bonds
        back = norm(othervecs[0] + othervecs[1])
        if back:
            res = [-back] # tested
        else:
            # real bonds are antiparallel; find closest point on equator to
            # existing posn, or arb point on equator
            p0 = cross( bagvecs[0], othervecs[0] )
            if debugprints:
                print "debug repos baggage: antiparallel sp2 1 2 case, " \
                      "not p0 == %r" % (not p0) # untested so far
            if not p0:
                # bagvec is parallel too
                res = [arbitrary_perpendicular(othervecs[0])]
            else:
                # choose closest perpendicular to existing direction
                res0 = - norm( cross(p0, othervecs[0]) )
                #k this ought to be positive of, but might be (buggily)
                # negative of, desired value -- need to test this ###@@@
                # but being too lazy to test it, just make it work either way:
                res = [res0, -res0]
                extra = 1
            pass
        pass
    elif algchoice == (2, 2, 1):
        # This only matters for twisting a pi_bond, and we verified above that
        # we have >single bond. A difficulty: how can we update the geometry,
        # not knowing whether the caller moved all the source atoms yet,
        # and with the bond code not knowing which direction along the bond
        # effects are propogating?
        # BTW, I guess that when you drag singlets, depositMode implems this
        # (along sp_chains too), but when you move chain atoms (let alone
        # their neighbors), I just don't recall.
        if debugprints:
            print "debug repos baggage: sp2 with twisting pi_bond is nim", self ###@@@
        return
    else:
        #bruce 080515 bugfix: fallback case
        # (otherwise following code has UnboundLocalError for 'res')
        print "bug?: reposition_baggage (for %r) can't yet handle this algchoice:" % self, algchoice
        return
    # now work out the best assignment of posns in res to baggage; reorder res
    # to fit bags_ordered
    assert len(res) == len_baggage + extra
    bags_ordered = baggage # in case len(res) == 1
    if len(res) > 1:
        dists = []
        for atom_junk, vec, i in zip(baggage, bagvecs, range(len_baggage)):
            for pos in res:
                dists.append(( vlen(pos - vec), i, pos ))
        dists.sort()
        res0 = res
        res = []
        bags_ordered = []
        bagind_matched = [0 for bag in baggage]
        for dist, bagind, pos in dists:
            # assume not yet done matching, and don't yet know if bagind or pos
            # are still in the running;
            # when a bag matches, set bagind_matched[bagind];
            # when a pos matches, remove it from res0.
            if bagind_matched[bagind] or pos not in res0:
                continue
            # found a match
            res0.remove(pos)
            bagind_matched[bagind] = 1
            res.append(pos)
            bags_ordered.append(baggage[bagind])
            if len(bags_ordered) >= len_baggage:
                break
        assert len(bags_ordered) == len_baggage, \
               "somehow failed to match up some baggage at all, should never happen"
        assert len_baggage == len(res) # whether or not extra > 0
    # now move the atoms, preserving distance from self
    # (assume calling code got that part right)
    for atom, vec in zip(bags_ordered, res):
        dist = vlen( selfposn - atom.posn() )
        if abs(1.0 - vlen(vec)) > 0.00001:
            print "bug in reposition_baggage: vec not len 1:", vec
        atom.setposn( selfposn + norm(vec) * dist )
            # norm(vec) is hoped to slightly reduce accumulated
            # numerical errors...
            ###e ideally we'd correct the bond lengths too, but as of 060630,
            # even Build doesn't get them right (and it can't, unless bond tools
            #  would also change them when at most one real atom would need
            #  moving, which I suppose they ought to...)
    if debugprints and 0:
        print "done"
    return # from _reposition_baggage_1

# end... tested: sp3 2 2, sp2 1 2
# not tested: the others, the extreme cases
# (need try/except in practice since it's hard to test them all;
#  put it in calling code?)