File: Conrec.h

package info (click to toggle)
rdkit 202503.1-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 220,160 kB
  • sloc: cpp: 399,240; python: 77,453; ansic: 25,517; java: 8,173; javascript: 4,005; sql: 2,389; yacc: 1,565; lex: 1,263; cs: 1,081; makefile: 580; xml: 229; fortran: 183; sh: 105
file content (295 lines) | stat: -rw-r--r-- 10,355 bytes parent folder | download | duplicates (2)
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
//
//  Copyright (C) 2019-2023 Greg Landrum
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
#include <vector>
#include <list>
#include <unordered_map>
#include <Geometry/point.h>
#include <cmath>

#include <RDGeneral/BoostStartInclude.h>
#include <boost/dynamic_bitset.hpp>
#include <boost/functional/hash.hpp>
#include <RDGeneral/BoostEndInclude.h>

namespace conrec {
struct ConrecSegment {
  RDGeom::Point2D p1;
  RDGeom::Point2D p2;
  double isoVal;
  ConrecSegment(double x1, double y1, double x2, double y2, double isoVal)
      : p1(x1, y1), p2(x2, y2), isoVal(isoVal) {}
  ConrecSegment(const RDGeom::Point2D &p1, const RDGeom::Point2D &p2,
                double isoVal)
      : p1(p1), p2(p2), isoVal(isoVal) {}
};
// adapted from conrec.c by Paul Bourke:
// http://paulbourke.net/papers/conrec/conrec.c
/*
   Derivation from the fortran version of CONREC by Paul Bourke
   d               ! matrix of data to contour
   ilb,iub,jlb,jub ! index bounds of data matrix
   x               ! data matrix column coordinates
   y               ! data matrix row coordinates
   nc              ! number of contour levels
   z               ! contour levels in increasing order
*/
inline void Contour(const double *d, size_t ilb, size_t iub, size_t jlb,
                    size_t jub, const double *x, const double *y, size_t nc,
                    double *z, std::vector<ConrecSegment> &res) {
  PRECONDITION(d, "no data");
  PRECONDITION(x, "no data");
  PRECONDITION(y, "no data");
  PRECONDITION(z, "no data");
  PRECONDITION(nc > 0, "no contours");
  PRECONDITION(iub > ilb, "bad bounds");
  PRECONDITION(jub > jlb, "bad bounds");

  int m1, m2, m3, case_value;
  double dmin, dmax, x1 = 0, x2 = 0, y1 = 0, y2 = 0;
  int i, j, m;
  size_t k;
  double h[5];
  int sh[5];
  double xh[5], yh[5];
  int im[4] = {0, 1, 1, 0}, jm[4] = {0, 0, 1, 1};
  int castab[3][3][3] = {{{0, 0, 8}, {0, 2, 5}, {7, 6, 9}},
                         {{0, 3, 4}, {1, 3, 1}, {4, 3, 0}},
                         {{9, 6, 7}, {5, 2, 0}, {8, 0, 0}}};
  double temp1, temp2;
  size_t ny = jub - jlb + 1;

  auto xsect = [&](int p1, int p2) {
    return (h[p2] * xh[p1] - h[p1] * xh[p2]) / (h[p2] - h[p1]);
  };
  auto ysect = [&](int p1, int p2) {
    return (h[p2] * yh[p1] - h[p1] * yh[p2]) / (h[p2] - h[p1]);
  };

  for (j = (jub - 1); j >= (int)jlb; j--) {
    for (i = ilb; i <= (int)iub - 1; i++) {
      temp1 = std::min(d[i * ny + j], d[i * ny + j + 1]);
      temp2 = std::min(d[(i + 1) * ny + j], d[(i + 1) * ny + j + 1]);
      dmin = std::min(temp1, temp2);
      temp1 = std::max(d[i * ny + j], d[i * ny + j + 1]);
      temp2 = std::max(d[(i + 1) * ny + j], d[(i + 1) * ny + j + 1]);
      dmax = std::max(temp1, temp2);
      if (dmax < z[0] || dmin > z[nc - 1]) {
        continue;
      }
      for (k = 0; k < nc; k++) {
        if (z[k] < dmin || z[k] > dmax) {
          continue;
        }
        for (m = 4; m >= 0; m--) {
          if (m > 0) {
            h[m] = d[(i + im[m - 1]) * ny + j + jm[m - 1]] - z[k];
            xh[m] = x[i + im[m - 1]];
            yh[m] = y[j + jm[m - 1]];
          } else {
            h[0] = 0.25 * (h[1] + h[2] + h[3] + h[4]);
            xh[0] = 0.50 * (x[i] + x[i + 1]);
            yh[0] = 0.50 * (y[j] + y[j + 1]);
          }
          if (h[m] > 0.0) {
            sh[m] = 1;
          } else if (h[m] < 0.0) {
            sh[m] = -1;
          } else {
            sh[m] = 0;
          }
        }

        /*
           Note: at this stage the relative heights of the corners and the
           centre are in the h array, and the corresponding coordinates are
           in the xh and yh arrays. The centre of the box is indexed by 0
           and the 4 corners by 1 to 4 as shown below.
           Each triangle is then indexed by the parameter m, and the 3
           vertices of each triangle are indexed by parameters m1,m2,and m3.
           It is assumed that the centre of the box is always vertex 2
           though this isimportant only when all 3 vertices lie exactly on
           the same contour level, in which case only the side of the box
           is drawn.
              vertex 4 +-------------------+ vertex 3
                       | \               / |
                       |   \    m-3    /   |
                       |     \       /     |
                       |       \   /       |
                       |  m=2    X   m=2   |       the centre is vertex 0
                       |       /   \       |
                       |     /       \     |
                       |   /    m=1    \   |
                       | /               \ |
              vertex 1 +-------------------+ vertex 2
        */
        /* Scan each triangle in the box */
        for (m = 1; m <= 4; m++) {
          m1 = m;
          m2 = 0;
          if (m != 4) {
            m3 = m + 1;
          } else {
            m3 = 1;
          }
          if ((case_value = castab[sh[m1] + 1][sh[m2] + 1][sh[m3] + 1]) == 0) {
            continue;
          }
          switch (case_value) {
            case 1: /* Line between vertices 1 and 2 */
              x1 = xh[m1];
              y1 = yh[m1];
              x2 = xh[m2];
              y2 = yh[m2];
              break;
            case 2: /* Line between vertices 2 and 3 */
              x1 = xh[m2];
              y1 = yh[m2];
              x2 = xh[m3];
              y2 = yh[m3];
              break;
            case 3: /* Line between vertices 3 and 1 */
              x1 = xh[m3];
              y1 = yh[m3];
              x2 = xh[m1];
              y2 = yh[m1];
              break;
            case 4: /* Line between vertex 1 and side 2-3 */
              x1 = xh[m1];
              y1 = yh[m1];
              x2 = xsect(m2, m3);
              y2 = ysect(m2, m3);
              break;
            case 5: /* Line between vertex 2 and side 3-1 */
              x1 = xh[m2];
              y1 = yh[m2];
              x2 = xsect(m3, m1);
              y2 = ysect(m3, m1);
              break;
            case 6: /* Line between vertex 3 and side 1-2 */
              x1 = xh[m3];
              y1 = yh[m3];
              x2 = xsect(m1, m2);
              y2 = ysect(m1, m2);
              break;
            case 7: /* Line between sides 1-2 and 2-3 */
              x1 = xsect(m1, m2);
              y1 = ysect(m1, m2);
              x2 = xsect(m2, m3);
              y2 = ysect(m2, m3);
              break;
            case 8: /* Line between sides 2-3 and 3-1 */
              x1 = xsect(m2, m3);
              y1 = ysect(m2, m3);
              x2 = xsect(m3, m1);
              y2 = ysect(m3, m1);
              break;
            case 9: /* Line between sides 3-1 and 1-2 */
              x1 = xsect(m3, m1);
              y1 = ysect(m3, m1);
              x2 = xsect(m1, m2);
              y2 = ysect(m1, m2);
              break;
            default:
              break;
          }

          /* Finally draw the line */
          res.emplace_back(RDGeom::Point2D(x1, y1), RDGeom::Point2D(x2, y2),
                           z[k]);
        } /* m */
      } /* k - contour */
    } /* i */
  } /* j */
}

struct tplHash {
  template <class T1, class T2, class T3>
  std::size_t operator()(const std::tuple<T1, T2, T3> &p) const {
    std::size_t res = 0;
    boost::hash_combine(res, std::get<0>(p));
    boost::hash_combine(res, std::get<1>(p));
    boost::hash_combine(res, std::get<2>(p));
    return res;
  }
};

inline std::vector<std::pair<std::vector<RDGeom::Point2D>, double>>
connectLineSegments(const std::vector<ConrecSegment> &segments,
                    double coordMultiplier = 1000,
                    double isoValMultiplier = 1e6) {
  std::vector<std::pair<std::vector<RDGeom::Point2D>, double>> res;
  std::unordered_map<std::tuple<int, int, long>, std::list<size_t>, tplHash>
      endPointHashes;

  auto makePointKey = [&coordMultiplier, &isoValMultiplier](const auto &pt,
                                                            double isoVal) {
    return std::make_tuple<int, int, long>(
        std::lround(coordMultiplier * pt.x),
        std::lround(coordMultiplier * pt.y),
        std::lround(isoValMultiplier * isoVal));
  };

  // first hash all of the endpoints
  for (auto i = 0u; i < segments.size(); ++i) {
    const auto &seg = segments[i];
    endPointHashes[makePointKey(seg.p1, seg.isoVal)].push_back(i);
    endPointHashes[makePointKey(seg.p2, seg.isoVal)].push_back(i);
  }

  boost::dynamic_bitset<> segmentsDone(segments.size());
  // a candidate end point hasn't been used already.
  auto isCandidate = [&segmentsDone](const auto &pr) {
    return !pr.second.empty() && !segmentsDone[pr.second.front()];
  };
  auto singlePoint =
      std::find_if(endPointHashes.begin(), endPointHashes.end(), isCandidate);
  while (singlePoint != endPointHashes.end()) {
    auto segId = singlePoint->second.front();
    auto currKey = singlePoint->first;
    auto currVal = segments[segId].isoVal;
    std::vector<RDGeom::Point2D> contour;
    while (1) {
      segmentsDone.set(segId, true);
      // move onto the next segment
      const auto seg = segments[segId];
      auto k1 = makePointKey(seg.p1, seg.isoVal);
      auto k2 = makePointKey(seg.p2, seg.isoVal);
      auto endPtKey = k2;
      if (k1 == currKey) {
        contour.push_back(seg.p1);
      } else if (k2 == currKey) {
        contour.push_back(seg.p2);
        endPtKey = k1;
      }
      // remove this segment from the two hash lists:
      auto &segs1 = endPointHashes[currKey];
      segs1.erase(std::find(segs1.begin(), segs1.end(), segId));

      auto &segs = endPointHashes[endPtKey];
      segs.erase(std::find(segs.begin(), segs.end(), segId));
      if (segs.empty()) {
        // we're at the end, push on the last point
        if (k1 == currKey) {
          contour.push_back(seg.p2);
        } else if (k2 == currKey) {
          contour.push_back(seg.p1);
        }
        break;
      }
      segId = segs.front();
      currKey = endPtKey;
    }
    res.push_back(std::make_pair(contour, currVal));
    singlePoint = std::find_if(singlePoint, endPointHashes.end(), isCandidate);
  }
  return res;
}

}  // namespace conrec