LOST  0.0.1
LOST: Open-source Star Tracker
databases.cpp
Go to the documentation of this file.
1 #include "databases.hpp"
2 
3 #include <stdlib.h>
4 #include <assert.h>
5 #include <math.h>
6 #include <vector>
7 #include <cstdint>
8 #include <algorithm>
9 #include <iostream>
10 
11 #include "attitude-utils.hpp"
12 #include "serialize-helpers.hpp"
13 #include "star-utils.hpp"
14 
15 namespace lost {
16 
17 const int32_t PairDistanceKVectorDatabase::kMagicValue = 0x2536f009;
18 
19 inline bool isFlagSet(uint32_t dbFlags, uint32_t flag) {
20  return (dbFlags & flag) != 0;
21 }
22 
23 struct KVectorPair {
24  int16_t index1;
25  int16_t index2;
27 };
28 
29 bool CompareKVectorPairs(const KVectorPair &p1, const KVectorPair &p2) {
30  return p1.distance < p2.distance;
31 }
32 
33 // just the index part of the kvector, doesn't store the sorted list it refers to. This makes it
34 // flexible whether used to store star distance angles, an angle of a triangle, etc.
48 // apparently there's no easy way to accept an iterator argument. Hate java all you want, but at
49 // least it can do that!
50 // https://stackoverflow.com/questions/5054087/declare-a-function-accepting-generic-iterator or
51 // rather, the correct way is to use a template and just use the ++ and * operators willy-nilly,
52 // which will throw an error if the operators are not implemented.
53 
64 void SerializeKVectorIndex(SerializeContext *ser, const std::vector<decimal> &values, decimal min, decimal max, long numBins) {
65  std::vector<int32_t> kVector(numBins+1); // We store sums before and after each bin
66  decimal binWidth = (max - min) / numBins;
67 
68  // generate the k-vector part
69  // Idea: When we find the first star that's across any bin boundary, we want to update all the newly sealed bins
70  long lastBin = 0; // first bin the last star belonged to
71  for (int32_t i = 0; i < (int32_t)values.size(); i++) {
72  if (i > 0) {
73  assert(values[i] >= values[i-1]);
74  }
75  assert(values[i] >= min);
76  assert(values[i] <= max);
77  long thisBin = (long)ceil((values[i] - min) / binWidth); // first bin we belong to
78  assert(thisBin >= 0);
79  assert(thisBin <= numBins); // thisBin == numBins is acceptable since kvector length == numBins + 1
80  // if thisBin > lastBin, then no more stars can be added to those bins between thisBin and lastBin, so set them.
81  for (long bin = lastBin; bin < thisBin; bin++) {
82  kVector[bin] = i; // our index is the number of pairs with shorter distance
83  }
84  lastBin = thisBin;
85  }
86  for (long bin = lastBin; bin <= numBins; bin++) {
87  kVector[bin] = values.size();
88  }
89 
90  // verify kvector
91  int32_t lastBinVal = -1;
92  for (const int32_t &bin : kVector) {
93  assert(bin >= lastBinVal);
94  lastBinVal = bin;
95  }
96 
97  // metadata fields
98  SerializePrimitive<int32_t>(ser, values.size());
99  SerializePrimitive<decimal>(ser, min);
100  SerializePrimitive<decimal>(ser, max);
101  SerializePrimitive<int32_t>(ser, numBins);
102 
103  // kvector index field
104  for (const int32_t &bin : kVector) {
105  SerializePrimitive<int32_t>(ser, bin);
106  }
107 }
108 
111 
112  numValues = DeserializePrimitive<int32_t>(des);
113  min = DeserializePrimitive<decimal>(des);
114  max = DeserializePrimitive<decimal>(des);
115  numBins = DeserializePrimitive<int32_t>(des);
116 
117  assert(min >= DECIMAL(0.0));
118  assert(max > min);
119  binWidth = (max - min) / numBins;
120 
121  bins = DeserializeArray<int32_t>(des, numBins+1);
122 }
123 
129 long KVectorIndex::QueryLiberal(decimal minQueryDistance, decimal maxQueryDistance, long *upperIndex) const {
130  assert(maxQueryDistance > minQueryDistance);
131  if (maxQueryDistance >= max) {
132  maxQueryDistance = max - DECIMAL(0.00001); // TODO: better way to avoid hitting the bottom bin
133  }
134  if (minQueryDistance <= min) {
135  minQueryDistance = min + DECIMAL(0.00001);
136  }
137  if (minQueryDistance > max || maxQueryDistance < min) {
138  *upperIndex = 0;
139  return 0;
140  }
141  long lowerBin = BinFor(minQueryDistance);
142  long upperBin = BinFor(maxQueryDistance);
143  assert(upperBin >= lowerBin);
144  assert(upperBin <= numBins);
145  // bins[lowerBin-1]=number of pairs <= r < query distance, so it is the index of the
146  // first possible item that might be equal to the query distance
147  int lowerIndex = bins[lowerBin-1];
148  if (lowerIndex >= numValues) {
149  // all pairs have distance less than queried. Return value is irrelevant as long as
150  // numReturned=0
151  return 0;
152  }
153  // bins[upperBin]=number of pairs <= r >= query distance
154  *upperIndex = bins[upperBin];
155  return lowerIndex;
156 }
157 
159 long KVectorIndex::BinFor(decimal query) const {
160  long result = (long)ceil((query - min) / binWidth);
161  assert(result >= 0);
162  assert(result <= numBins);
163  return result;
164 }
165 
175 std::vector<KVectorPair> CatalogToPairDistances(const Catalog &catalog, decimal minDistance, decimal maxDistance) {
176  std::vector<KVectorPair> result;
177  for (int16_t i = 0; i < (int16_t)catalog.size(); i++) {
178  for (int16_t k = i+1; k < (int16_t)catalog.size(); k++) {
179 
180  KVectorPair pair = { i, k, AngleUnit(catalog[i].spatial, catalog[k].spatial) };
181  assert(isfinite(pair.distance));
182  assert(pair.distance >= 0);
183  assert(pair.distance <= DECIMAL_M_PI);
184 
185  if (pair.distance >= minDistance && pair.distance <= maxDistance) {
186  // we'll sort later
187  result.push_back(pair);
188  }
189  }
190  }
191  return result;
192 }
193 
198 void SerializePairDistanceKVector(SerializeContext *ser, const Catalog &catalog, decimal minDistance, decimal maxDistance, long numBins) {
199  std::vector<int32_t> kVector(numBins+1); // numBins = length, all elements zero
200  std::vector<KVectorPair> pairs = CatalogToPairDistances(catalog, minDistance, maxDistance);
201 
202  // sort pairs in increasing order.
203  std::sort(pairs.begin(), pairs.end(), CompareKVectorPairs);
204  std::vector<decimal> distances;
205 
206  for (const KVectorPair &pair : pairs) {
207  distances.push_back(pair.distance);
208  }
209 
210  // index field
211  SerializeKVectorIndex(ser, distances, minDistance, maxDistance, numBins);
212 
213  // bulk pairs field
214  for (const KVectorPair &pair : pairs) {
215  SerializePrimitive<int16_t>(ser, pair.index1);
216  SerializePrimitive<int16_t>(ser, pair.index2);
217  }
218 }
219 
222  : index(KVectorIndex(des)) {
223 
224  pairs = DeserializeArray<int16_t>(des, 2*index.NumValues());
225 }
226 
229  return num < low ? low : num > high ? high : num;
230 }
231 
238  decimal minQueryDistance, decimal maxQueryDistance, const int16_t **end) const {
239 
240  assert(maxQueryDistance <= DECIMAL_M_PI);
241 
242  long upperIndex = -1;
243  long lowerIndex = index.QueryLiberal(minQueryDistance, maxQueryDistance, &upperIndex);
244  *end = &pairs[upperIndex * 2];
245  return &pairs[lowerIndex * 2];
246 }
247 
249  decimal minQueryDistance, decimal maxQueryDistance, const int16_t **end) const {
250 
251  // Instead of computing the angle for every pair in the database, we pre-compute the /cosines/
252  // of the min and max query distances so that we can compare against dot products directly! As
253  // angle increases, cosine decreases, up to DECIMAL_M_PI (and queries larger than that don't really make
254  // sense anyway)
255  assert(maxQueryDistance <= DECIMAL_M_PI);
256 
257  decimal maxQueryCos = DECIMAL_COS(minQueryDistance);
258  decimal minQueryCos = DECIMAL_COS(maxQueryDistance);
259 
260  long liberalUpperIndex;
261  long liberalLowerIndex = index.QueryLiberal(minQueryDistance, maxQueryDistance, &liberalUpperIndex);
262  // now we need to find the first and last index that actually matches the query
263  // step the lower index forward
264  // There's no good reason to be using >= and <= for the comparison against max/min, but the tests fail otherwise (because they use angle, with its acos, instead of forward cos like us). It's an insignificant difference.
265  while (liberalLowerIndex < liberalUpperIndex
266  && catalog[pairs[liberalLowerIndex*2]].spatial * catalog[pairs[liberalLowerIndex*2+1]].spatial >= maxQueryCos
267  )
268  { liberalLowerIndex++; }
269 
270  // step the upper index backward
271  while (liberalLowerIndex < liberalUpperIndex
272  // the liberalUpperIndex is past the end of the logically returned range, so we need to subtract 1
273  && catalog[pairs[(liberalUpperIndex-1)*2]].spatial * catalog[pairs[(liberalUpperIndex-1)*2+1]].spatial <= minQueryCos
274  )
275  { liberalUpperIndex--; }
276 
277  *end = &pairs[liberalUpperIndex * 2];
278  return &pairs[liberalLowerIndex * 2];
279 }
280 
283  return index.NumValues();
284 }
285 
287 std::vector<decimal> PairDistanceKVectorDatabase::StarDistances(int16_t star, const Catalog &catalog) const {
288  std::vector<decimal> result;
289  for (int i = 0; i < NumPairs(); i++) {
290  if (pairs[i*2] == star || pairs[i*2+1] == star) {
291  result.push_back(AngleUnit(catalog[pairs[i*2]].spatial, catalog[pairs[i*2+1]].spatial));
292  }
293  }
294  return result;
295 }
296 
316 const unsigned char *MultiDatabase::SubDatabasePointer(int32_t magicValue) const {
317  DeserializeContext desValue(buffer);
318  DeserializeContext *des = &desValue; // just for naming consistency with how we use `des` elsewhere
319 
320  assert(magicValue != 0);
321  while (true) {
322  int32_t curMagicValue = DeserializePrimitive<int32_t>(des);
323  if (curMagicValue == 0) {
324  return nullptr;
325  }
326  uint32_t dbFlags = DeserializePrimitive<uint32_t>(des);
327 
328  // Ensure that our database is using the same type as the runtime.
329  #ifdef LOST_FLOAT_MODE
330  if (!isFlagSet(dbFlags, MULTI_DB_FLOAT_FLAG)) {
331  std::cerr << "LOST was compiled in float mode. This database was serialized in double mode and is incompatible." << std::endl;
332  exit(1);
333  }
334  #else
335  if (isFlagSet(dbFlags, MULTI_DB_FLOAT_FLAG)) {
336  std::cerr << "LOST was compiled in double mode. This database was serialized in float mode and is incompatible." << std::endl;
337  exit(1);
338  }
339  #endif
340 
341  uint32_t dbLength = DeserializePrimitive<uint32_t>(des);
342  assert(dbLength > 0);
343  DeserializePadding<uint64_t>(des); // align to an 8-byte boundary
344  const unsigned char *curSubDatabasePointer = DeserializeArray<unsigned char>(des, dbLength);
345  if (curMagicValue == magicValue) {
346  return curSubDatabasePointer;
347  }
348  }
349  // shouldn't ever make it here. Compiler should remove this assertion as unreachable.
350  assert(false);
351 }
352 
354  const MultiDatabaseDescriptor &dbs,
355  uint32_t flags) {
356  for (const MultiDatabaseEntry &multiDbEntry : dbs) {
357  SerializePrimitive<int32_t>(ser, multiDbEntry.magicValue);
358  SerializePrimitive<uint32_t>(ser, flags);
359  SerializePrimitive<uint32_t>(ser, multiDbEntry.bytes.size());
360  SerializePadding<uint64_t>(ser);
361  std::copy(multiDbEntry.bytes.cbegin(), multiDbEntry.bytes.cend(), std::back_inserter(ser->buffer));
362  }
363  SerializePrimitive<int32_t>(ser, 0); // caboose
364 }
365 
366 }
367 
368 // TODO: after creating the database, print more statistics, such as average number of pairs per
369 // star, stars per bin, space distribution between array and index.
A data structure enabling constant-time range queries into fixed numerical data.
Definition: databases.hpp:23
KVectorIndex(DeserializeContext *des)
Construct from serialized buffer.
Definition: databases.cpp:110
long QueryLiberal(decimal minQueryDistance, decimal maxQueryDistance, long *upperIndex) const
Finds all the entries in the given range, and possibly a few just outside the range on the ends.
Definition: databases.cpp:129
long NumValues() const
The number of data points in the data referred to by the kvector.
Definition: databases.hpp:30
Definition: databases.hpp:113
const unsigned char * SubDatabasePointer(int32_t magicValue) const
MultiDatabase memory layout:
Definition: databases.cpp:316
long NumPairs() const
Exact number of stored pairs.
Definition: databases.cpp:282
std::vector< decimal > StarDistances(int16_t star, const Catalog &) const
Return the distances from the given star to each star it's paired with in the database (for debugging...
Definition: databases.cpp:287
PairDistanceKVectorDatabase(DeserializeContext *des)
Create the database from a serialized buffer.
Definition: databases.cpp:221
static const int32_t kMagicValue
Magic value to use when storing inside a MultiDatabase.
Definition: databases.hpp:70
const int16_t * FindPairsExact(const Catalog &, decimal min, decimal max, const int16_t **end) const
Definition: databases.cpp:248
const int16_t * FindPairsLiberal(decimal min, decimal max, const int16_t **end) const
Return at least all the star pairs whose inter-star distance is between min and max.
Definition: databases.cpp:237
std::vector< unsigned char > buffer
#define MULTI_DB_FLOAT_FLAG
A database that contains multiple databases This is almost always the database that is actually passe...
Definition: databases.hpp:102
double decimal
Definition: decimal.hpp:11
#define DECIMAL(x)
Definition: decimal.hpp:21
#define DECIMAL_M_PI
Definition: decimal.hpp:29
#define DECIMAL_COS(x)
Definition: decimal.hpp:53
LOST starting point.
void SerializeKVectorIndex(SerializeContext *ser, const std::vector< decimal > &values, decimal min, decimal max, long numBins)
K-vector index layout.
Definition: databases.cpp:64
decimal AngleUnit(const Vec3 &vec1, const Vec3 &vec2)
Calculate the inner angle, in radians, between two /unit/ vectors.
bool CompareKVectorPairs(const KVectorPair &p1, const KVectorPair &p2)
Definition: databases.cpp:29
std::vector< KVectorPair > CatalogToPairDistances(const Catalog &catalog, decimal minDistance, decimal maxDistance)
pair K-vector database layout.
Definition: databases.cpp:175
void SerializePairDistanceKVector(SerializeContext *ser, const Catalog &catalog, decimal minDistance, decimal maxDistance, long numBins)
Serialize a pair-distance KVector into buffer.
Definition: databases.cpp:198
bool isFlagSet(uint32_t dbFlags, uint32_t flag)
Definition: databases.cpp:19
std::vector< MultiDatabaseEntry > MultiDatabaseDescriptor
Definition: databases.hpp:123
void SerializeMultiDatabase(SerializeContext *ser, const MultiDatabaseDescriptor &dbs, uint32_t flags)
Definition: databases.cpp:353
decimal Clamp(decimal num, decimal low, decimal high)
Return the value in the range [low,high] which is closest to num.
Definition: databases.cpp:228
std::vector< CatalogStar > Catalog
Definition: star-utils.hpp:100