• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Stellarium / stellarium / 10609521019

29 Aug 2024 05:56AM UTC coverage: 12.113% (-0.03%) from 12.138%
10609521019

Pull #3847

github

alex-w
[SSE] Add info for total candidates for addiing...
Pull Request #3847: Threaded planet computation (and a few more speedups)

30 of 686 new or added lines in 51 files covered. (4.37%)

41 existing lines in 18 files now uncovered.

14434 of 119159 relevant lines covered (12.11%)

19024.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.59
/src/core/planetsephems/jpleph.cpp
1
/* jpleph.cpp: JPL ephemeris functions
2

3
Copyright (C) 2011, Project Pluto
4

5
This program is free software; you can redistribute it and/or
6
modify it under the terms of the GNU General Public License
7
as published by the Free Software Foundation; either version 2
8
of the License, or (at your option) any later version.
9

10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
GNU General Public License for more details.
14

15
You should have received a copy of the GNU General Public License
16
along with this program; if not, write to the Free Software
17
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
02110-1301, USA.    */
19

20
/*****************************************************************************
21
*        *****    jpl planetary and lunar ephemerides    *****     C ver.1.2 *
22
******************************************************************************
23
*                                                                            *
24
*  This program was written in standard fortran-77 and it was manually       *
25
*  translated to C language by Piotr A. Dybczynski (dybol@phys.amu.edu.pl),  *
26
*  subsequently revised heavily by Bill J Gray (pluto@gwi.net),  just short  *
27
*  of a total re-write.                                                      *
28
*                                                                            *
29
******************************************************************************
30
*                 Last modified: July 23, 1997 by PAD                        *
31
******************************************************************************
32
21 Apr 2010:  Revised by Bill J. Gray.  The code now determines the kernel
33
size,  then allocates memory accordingly.  This should 'future-proof' us in
34
case JPL (or someone else) creates kernels that are larger than the previously
35
arbitrary MAX_KERNEL_SIZE parameter.  'swap_long' and 'swap_double' have
36
been replaced with 'swap_32_bit_val' and 'swap_64_bit_val'.  It also works
37
on 64-bit compiles now.
38

39
16 Mar 2001:  Revised by Bill J. Gray.  You can now use binary
40
ephemerides with either byte order ('big-endian' or 'small-endian');
41
the code checks to see if the data is in the "wrong" order for the
42
current platform,  and swaps bytes on-the-fly if needed.  (Yes,  this
43
can result in a slowdown... sometimes as much as 1%.  The function is
44
so mathematically intensive that the byte-swapping is the least of our
45
troubles.)  You can also use DE-200, 403, 404, 405,  or 406 without
46
recompiling (the constant() function now determines which ephemeris is
47
in use and its byte order).
48

49
Also,  I did some minor optimization of the interp() (Chebyshev
50
interpolation) function,  resulting in a bit of a speedup.
51

52
The code has been modified to be a separately linkable component,  with
53
details of the implementation encapsulated.
54
*****************************************************************************/
55
// Make Large File Support work also on ARM boards, explicitly.
56
#define _FILE_OFFSET_BITS 64
57

58
#include "jpleph.h"
59
#include "jpl_int.h"
60

61
#include <assert.h>
62
#include <errno.h>
63
#include <math.h>
64
#include <stdio.h>
65
#include <string.h>
66
#include <stdlib.h>
67
#include <stdint.h>
68
#include <QDebug>
69
#include <QMutex>
70
#include <QMutexLocker>
71

72
/**** include variable and type definitions, specific for this C version */
73

74

75
// GZ patches for Large File Support for DE431 past AD10100...
76
#if defined(Q_OS_WIN)
77
#define FSeek(__FILE, __OFFSET, _MODE) _fseeki64(__FILE, __OFFSET, _MODE)
78
#else
79
#define FSeek(__FILE, __OFFSET, _MODE) fseeko(__FILE, __OFFSET, _MODE)
80
#endif
81

82
static QMutex mutex;
83

84
double DLL_FUNC jpl_get_double(const void *ephem, const int value)
4✔
85
{
86
        return(*reinterpret_cast<const double *>(static_cast<const char *>(ephem) + value));
4✔
87
}
88

89
long DLL_FUNC jpl_get_long(const void *ephem, const int value)
×
90
{
91
        return(*reinterpret_cast<const int32_t *>(static_cast<const char *>(ephem) + value));
×
92
}
93

94

95
/*****************************************************************************
96
**           jpl_pleph(ephem,et,ntar,ncent,rrd,calc_velocity)              **
97
******************************************************************************
98
**                                                                          **
99
**    This subroutine reads the jpl planetary ephemeris                     **
100
**    and gives the position and velocity of the point 'ntarg'              **
101
**    with respect to 'ncent'.                                              **
102
**                                                                          **
103
**    Calling sequence parameters:                                          **
104
**                                                                          **
105
**      et = (double) julian ephemeris date at which interpolation          **
106
**           is wanted.                                                     **
107
**                                                                          **
108
**    ntarg = integer number of 'target' point.                             **
109
**                                                                          **
110
**    ncent = integer number of center point.                               **
111
**                                                                          **
112
**    The numbering convention for 'ntarg' and 'ncent' is:                  **
113
**                                                                          **
114
**            1 = mercury           8 = neptune                             **
115
**            2 = venus             9 = pluto                               **
116
**            3 = earth            10 = moon                                **
117
**            4 = mars             11 = sun                                 **
118
**            5 = jupiter          12 = solar-system barycenter             **
119
**            6 = saturn           13 = earth-moon barycenter               **
120
**            7 = uranus           14 = nutations (longitude and obliq)     **
121
**                                 15 = librations, if on eph. file         **
122
**                                 16 = lunar mantle omega_x,omega_y,omega_z**
123
**                                 17 = TT-TDB, if on eph. file             **
124
**                                                                          **
125
**            (If nutations are wanted, set ntarg = 14.                     **
126
**             For librations, set ntarg = 15. set ncent= 0.                **
127
**             For TT-TDB,  set ntarg = 17.  I've not actually              **
128
**             seen an ntarg = 16 case yet.)                                **
129
**                                                                          **
130
**     rrd = output 6-element, double array of position and velocity        **
131
**           of point 'ntarg' relative to 'ncent'. The units are au and     **
132
**           au/day. For librations the units are radians and radians       **
133
**           per day. In the case of nutations the first four words of      **
134
**           rrd will be set to nutations and rates, having units of        **
135
**           radians and radians/day.                                       **
136
**                                                                          **
137
**           The option is available to have the units in km and km/sec.    **
138
**           for this, set km=TRUE at the beginning of the program.         **
139
**                                                                          **
140
**     calc_velocity = integer flag;  if nonzero,  velocities will be       **
141
**           computed,  otherwise not.                                      **
142
**                                                                          **
143
*****************************************************************************/
144
int DLL_FUNC jpl_pleph(void *ephem, const double et, const int ntarg,
716✔
145
                      const int ncent, double rrd[], const int calc_velocity)
146
{
147
    struct jpl_eph_data *eph = static_cast<struct jpl_eph_data *>(ephem);
716✔
148
    double pv[13][6]={{0.}};/* pv is the position/velocity array
716✔
149
                             NUMBERED FROM ZERO: 0=Mercury,1=Venus,...
150
                             8=Pluto,9=Moon,10=Sun,11=SSBary,12=EMBary
151
                             First 10 elements (0-9) are affected by
152
                             jpl_state(), all are adjusted here.         */
153

154

155
    int rval = 0;
716✔
156
    const int list_val = (calc_velocity ? 2 : 1);
716✔
157
    unsigned i;
158
    int list[14];    /* list is a vector denoting, for which "body"
159
                            ephemeris values should be calculated by
160
                            jpl_state():  0=Mercury,1=Venus,2=EMBary,...,
161
                            8=Pluto,  9=geocentric Moon, 10=nutations in
162
                            long. & obliq.  11= lunar librations;
163
                            12 = TT-TDB, 13=lunar mantle omegas */
164

165
    for(i = 0; i < 6; ++i) rrd[i] = 0.0;
5,012✔
166

167
    if(ntarg == ncent) return(0);
716✔
168

169
    for(i = 0; i < sizeof(list) / sizeof(list[0]); i++)
10,740✔
170
      list[i] = 0;
10,024✔
171

172
         /* Because of the whacko indexing in JPL ephemerides,  we need */
173
         /* to work out way through the following indexing schemes :   */
174
         /* ntarg       ipt         list     */
175
         /*  14          11          10      Nutations */
176
         /*  15          12          11      Librations */
177
         /*  16          13          12      Lunar mantle angular vel */
178
         /*  17          14          13      TT - TDB */
179

180
    for(i = 0; i < 4; i++)
3,580✔
181
      if(ntarg == static_cast<int>(i) + 14)
2,864✔
182
      {
183
        if(eph->ipt[i + 11][1] > 0) /* quantity is in ephemeris */
×
184
        {
185
          list[i + 10] = list_val;
×
186

187
          // GZ: Coverity Scan (travis) automatic test chokes on next line with:
188
          // CID 134920:  Memory - corruptions  (OVERRUN)
189
          //     Overrunning array "list" of 56 bytes by passing it to a function
190
          //     which accesses it at byte offset 56.
191
          // I see it does explicitly NOT access list[14].
192
          // TODO: check again after next round of travis.
193
          rval = jpl_state(ephem, et, list, pv, rrd, 0);
×
194
        }
195
        else          /*  quantity doesn't exist in the ephemeris file  */
196
          rval = JPL_EPH_QUANTITY_NOT_IN_EPHEMERIS;
×
197
        return(rval);
×
198
      }
199
    if(ntarg > 13 || ncent > 13 || ntarg < 1 || ncent < 1)
716✔
200
      return(JPL_EPH_INVALID_INDEX);
×
201

202
/*  force barycentric output by 'state'     */
203

204
/*  set up proper entries in 'list' array for state call     */
205

206
    for(i = 0; i < 2; i++) /* list[] IS NUMBERED FROM ZERO ! */
2,148✔
207
    {
208
      const unsigned k = static_cast<unsigned int>(i ? ncent : ntarg) - 1;
1,432✔
209

210
      if(k <= 9) list[k] = list_val;   /* Major planets */
1,432✔
211
      if(k == 9) list[2] = list_val;   /* for moon,  earth state is needed */
1,432✔
212
      if(k == 2) list[9] = list_val;   /* for earth,  moon state is needed */
1,432✔
213
      if(k == 12) list[2] = list_val;  /* EMBary state additionally */
1,432✔
214
    }
215

216
  /*   make call to state   */
217
   rval = jpl_state(eph, et, list, pv, rrd, 1);
716✔
218
   /* Solar System barycentric Sun state goes to pv[10][] */
219
   if(ntarg == 11 || ncent == 11)
716✔
220
      for(i = 0; i < 6; i++)
5,012✔
221
         pv[10][i] = eph->pvsun[i];
4,296✔
222

223
   /* Solar System Barycenter coordinates & velocities equal to zero */
224
   if(ntarg == 12 || ncent == 12)
716✔
225
      for(i = 0; i < 6; i++)
×
226
         pv[11][i] = 0.0;
×
227

228
   /* Solar System barycentric EMBary state:  */
229
   if(ntarg == 13 || ncent == 13)
716✔
230
      for(i = 0; i < 6; i++)
×
231
         pv[12][i] = pv[2][i];
×
232

233
   /* if moon from earth or earth from moon ..... */
234
   if((ntarg*ncent) == 30 && (ntarg+ncent) == 13)
716✔
235
      for(i = 0; i < 6; ++i) pv[2][i]=0.0;
×
236
   else
237
      {
238
      if(list[2])           /* calculate earth state from EMBary */
716✔
239
         for(i = 0; i < static_cast<unsigned int>(list[2]) * 3u; ++i)
×
240
            pv[2][i] -= pv[9][i]/(1.0+eph->emrat);
×
241

242
      if(list[9]) /* calculate Solar System barycentric moon state */
716✔
243
         for(i = 0; i < static_cast<unsigned int>(list[9]) * 3u; ++i)
×
244
            pv[9][i] += pv[2][i];
×
245
      }
246

247
   for(i = 0; i < static_cast<unsigned int>(list_val) * 3u; ++i)
5,012✔
248
      rrd[i] = pv[ntarg-1][i] - pv[ncent-1][i];
4,296✔
249

250
   return(rval);
716✔
251
}
252

253
/* Some notes about the information stored in 'iinfo':  the posn_coeff[]
254
array contains the Chebyshev polynomials for tc,
255

256
posn_coeff[i]=T (tc).
257
               i
258

259
The vel_coeff[] array contains the derivatives of the same polynomials,
260

261
vel_coeff[i]=T'(tc).
262
              i
263

264
   Evaluating these polynomials is a little expensive,  and we don't want
265
to evaluate any more than we have to.  (Some planets require many more
266
Chebyshev polynomials than others.)  So if 'tc' is unchanged,  we can
267
rest assured that 'n_posn_avail' Chebyshev polynomials,  and 'n_vel_avail'
268
derivatives of Chebyshev polynomials,  have already been evaluated,  and
269
we start from there,  using the recurrence formulae
270

271
T (x) = 1      T (x) = x      T   (x) = 2xT (x) - T   (x)
272
 0              1              n+1         n       n-1
273

274
T'(x) = 0      T'(x) = 1      T'  (x) = 2xT'(x) + 2T (x) - T'   (x)
275
 0              1              n+1         n        n        n-1
276

277
   (the second set being just the derivatives of the first).  To get the
278
_acceleration_ of an object,  we just keep going and get the second
279
derivatives as
280

281
T"(x) = 0      T"(x) = 1      T"  (x) = 2xT"(x) + 4T'(x) - T"   (x)
282
 0              1              n+1         n        n        n-1
283

284
   At present, i can range from 0 to 17.  If future JPL ephems require
285
Chebyshev polynomials beyond T  ,  those arrays may need to be expanded.
286
                              17                                            */
287

288
/*****************************************************************************
289
**                     interp(buf,t,ncf,ncm,na,ifl,pv)                      **
290
******************************************************************************
291
**                                                                          **
292
**    this subroutine differentiates and interpolates a                     **
293
**    set of chebyshev coefficients to give position and velocity           **
294
**                                                                          **
295
**    calling sequence parameters:                                          **
296
**                                                                          **
297
**      input:                                                              **
298
**                                                                          **
299
**      iinfo   stores certain chunks of interpolation info,  in hopes      **
300
**              that if you call again with similar parameters,  the        **
301
**              function won't have to re-compute all coefficients/data.    **
302
**                                                                          **
303
**       coef   1st location of array of d.p. chebyshev coefficients        **
304
**              of position                                                 **
305
**                                                                          **
306
**          t   t[0] is double fractional time in interval covered by       **
307
**              coefficients at which interpolation is wanted               **
308
**              (0 <= t[0] <= 1).  t[1] is dp length of whole               **
309
**              interval in input time units.                               **
310
**                                                                          **
311
**        ncf   # of coefficients per component                             **
312
**                                                                          **
313
**        ncm   # of components per set of coefficients                     **
314
**                                                                          **
315
**         na   # of sets of coefficients in full array                     **
316
**              (i.e., # of sub-intervals in full interval)                 **
317
**                                                                          **
318
**         ifl  integer flag: =1 for positions only                         **
319
**                            =2 for pos and vel                            **
320
**                            =3 for pos, vel, accel (currently used for    **
321
**                               pvsun only)                                **
322
**                                                                          **
323
**      output:                                                             **
324
**                                                                          **
325
**    posvel   interpolated quantities requested.  dimension                **
326
**              expected is posvel[ncm*ifl], double precision.              **
327
**                                                                          **
328
*****************************************************************************/
329
static void interp(struct interpolation_info *iinfo,
1,432✔
330
        const double coef[], const double t[2], const unsigned ncf, const unsigned ncm,
331
        const unsigned na, const int velocity_flag, double posvel[])
332
{
333
    const double dna = static_cast<double>(na);
1,432✔
334
    const double temp = dna * t[0];
1,432✔
335
    unsigned l = static_cast<unsigned>(temp);
1,432✔
336
    double vfac, unused_temp1;
337
    double tc = 2.0 * modf(temp, &unused_temp1) - 1.0;
1,432✔
338
    unsigned i, j;
339

340
    assert(ncf < MAX_CHEBY);
1,432✔
341
    if(l == na)
1,432✔
342
    {
343
      l--;
44✔
344
      tc = 1.;
44✔
345
    }
346
    assert(tc >= -1.);
1,432✔
347
    assert(tc <=  1.);
1,432✔
348

349
/*  check to see whether chebyshev time has changed,  and compute new
350
    polynomial values if it has.
351
    (the element iinfo->posn_coeff[1] is the value of t1[tc] and hence
352
    contains the value of tc on the previous call.)     */
353

354

355
    if(tc != iinfo->posn_coeff[1])
1,432✔
356
    {
357
      iinfo->n_posn_avail = 2;
1,288✔
358
      iinfo->n_vel_avail = 2;
1,288✔
359
      iinfo->posn_coeff[1] = tc;
1,288✔
360
      iinfo->twot = tc+tc;
1,288✔
361
    }
362

363
/*  be sure that at least 'ncf' polynomials have been evaluated and are
364
    stored in the array 'iinfo->posn_coeff'.  Note that we start out with
365
    posn_coeff[0] = 1. and posn_coeff[1] = tc (see 'jpl_init_ephemeris'
366
    below),  and vel_coeff[0] and [1] are similarly preset.  We do that
367
    because you need the first two coeffs of those series to start the
368
    Chebyshev recurrence;  see the comments above this function.   */
369

370
    if(iinfo->n_posn_avail < ncf)
1,432✔
371
    {
372
      double *pc_ptr = iinfo->posn_coeff + iinfo->n_posn_avail;
1,396✔
373

374
      for(i=ncf - iinfo->n_posn_avail; i; i--, pc_ptr++)
11,680✔
375
         *pc_ptr = iinfo->twot * pc_ptr[-1] - pc_ptr[-2];
10,284✔
376
      iinfo->n_posn_avail=ncf;
1,396✔
377
    }
378

379
/*  interpolate to get position for each component  */
380

381
    for(i = 0; i < ncm; ++i)        /* ncm is a number of coordinates */
5,728✔
382
    {
383
      const double *coeff_ptr = coef + ncf * (i + l * ncm + 1);
4,296✔
384
      const double *pc_ptr = iinfo->posn_coeff + ncf;
4,296✔
385

386
      *posvel = 0.0;
4,296✔
387
      for(j = ncf; j; j--)
46,932✔
388
         *posvel += (*--pc_ptr) * (*--coeff_ptr);
42,636✔
389
      posvel++;
4,296✔
390
    }
391

392
    if(velocity_flag <= 1) return;
1,432✔
393

394
/*  if velocity interpolation is wanted, be sure enough
395
    derivative polynomials have been generated and stored.    */
396

397
    if(iinfo->n_vel_avail < ncf)
1,432✔
398
    {
399
      double *vc_ptr = iinfo->vel_coeff + iinfo->n_vel_avail;
1,396✔
400
      const double *pc_ptr = iinfo->posn_coeff + iinfo->n_vel_avail - 1;
1,396✔
401

402
      for(i = ncf - iinfo->n_vel_avail; i; i--, vc_ptr++, pc_ptr++)
11,680✔
403
         *vc_ptr = iinfo->twot * vc_ptr[-1] + *pc_ptr + *pc_ptr - vc_ptr[-2];
10,284✔
404
      iinfo->n_vel_avail = ncf;
1,396✔
405
    }
406

407
/*  interpolate to get velocity for each component    */
408

409
    vfac = (dna + dna) / t[1];
1,432✔
410
    for(i = 0; i < ncm; ++i)
5,728✔
411
    {
412
      double tval = 0.;
4,296✔
413
      const double *coeff_ptr = coef + ncf * (i + l * ncm + 1);
4,296✔
414
      const double *vc_ptr = iinfo->vel_coeff + ncf;
4,296✔
415

416
      for(j = ncf - 1; j; j--)
42,636✔
417
         tval += (*--vc_ptr) * (*--coeff_ptr);
38,340✔
418
      *posvel++ = tval * vfac;
4,296✔
419
    }
420

421
            /* Accelerations are rarely computed -- at present,  only */
422
            /* for pvsun -- so we don't get so tricky in optimizing.  */
423
            /* The accel_coeffs (the second derivative of the Chebyshev */
424
            /* polynomials) are not stored for repeated use,  for example. */
425
    if(velocity_flag == 3)
1,432✔
426
    {
427
      double accel_coeffs[MAX_CHEBY];
428

429
      accel_coeffs[0] = accel_coeffs[1] = 0.;
716✔
430
      for(i = 2; i < ncf; i++)              /* recurrence for T"(x) */
7,160✔
431
         accel_coeffs[i] = 4. * iinfo->vel_coeff[i - 1]
6,444✔
432
                        + iinfo->twot * accel_coeffs[i - 1]
6,444✔
433
                        - accel_coeffs[i - 2];
6,444✔
434

435
      for(i = 0; i < ncm; ++i)        /* ncm is a number of coordinates */
2,864✔
436
      {
437
         double tval = 0.;
2,148✔
438
         const double *coeff_ptr = coef + ncf * (i + l * ncm + 1);
2,148✔
439
         const double *ac_ptr = accel_coeffs + ncf;
2,148✔
440

441
         for(j = ncf; j; j--)
25,776✔
442
            tval += (*--ac_ptr) * (*--coeff_ptr);
23,628✔
443
         *posvel++ = tval * vfac * vfac;
2,148✔
444
      }
445
    }
446
    
447
    return;
1,432✔
448
}
449

450
/* swap_32_bit_val() and swap_64_bit_val() are used when reading a binary
451
ephemeris that was created on a machine with 'opposite' byte order to
452
the currently-used machine (signalled by the 'swap_bytes' flag in the
453
jpl_eph_data structure).  In such cases,  every double and integer
454
value read from the ephemeris must be byte-swapped by these two functions. */
455

456
#define SWAP_MACRO(A, B, TEMP)   { TEMP = A;  A = B;  B = TEMP; }
457

458
static void swap_32_bit_val(void *ptr)
×
459
{
460
   char *tptr = static_cast<char *>(ptr), tchar;
×
461

462
   SWAP_MACRO(tptr[0], tptr[3], tchar)
×
463
   SWAP_MACRO(tptr[1], tptr[2], tchar)
×
464
}
×
465

466
static void swap_64_bit_val(void *ptr, long count)
×
467
{
468
    char *tptr = static_cast<char *>(ptr), tchar;
×
469
    
470
    while(count--)
×
471
    {
472
      SWAP_MACRO(tptr[0], tptr[7], tchar)
×
473
      SWAP_MACRO(tptr[1], tptr[6], tchar)
×
474
      SWAP_MACRO(tptr[2], tptr[5], tchar)
×
475
      SWAP_MACRO(tptr[3], tptr[4], tchar)
×
476

477
      tptr += 8;
×
478
    }
479
}
×
480

481
/* Most ephemeris quantities have a dimension of three.  Planet positions
482
have an x, y, and z;  librations and lunar mantle angles have three Euler
483
angles.  But TDT-TT is a single quantity,  and nutation is expressed as
484
two angles.   */
485

486
static unsigned int dimension(const unsigned int idx)
1,462✔
487
{
488
    unsigned int rval;
489

490
    if(idx == 11)             /* Nutations */
1,462✔
491
      rval = 2;
2✔
492
    else if(idx == 14)        /* TDT - TT */
1,460✔
493
      rval = 1;
2✔
494
    else                       /* planets, lunar mantle angles, librations */
495
      rval = 3;
1,458✔
496
    return(rval);
1,462✔
497
}
498

499
/*****************************************************************************
500
**                        jpl_state(ephem,et2,list,pv,nut,bary)             **
501
******************************************************************************
502
** This subroutine reads and interpolates the jpl planetary ephemeris file  **
503
**                                                                          **
504
**    Calling sequence parameters:                                          **
505
**                                                                          **
506
**    Input:                                                                **
507
**                                                                          **
508
**        et2[] double, 2-element JED epoch at which interpolation          **
509
**              is wanted.  Any combination of et2[0]+et2[1] which falls    **
510
**              within the time span on the file is a permissible epoch.    **
511
**                                                                          **
512
**               a. for ease in programming, the user may put the           **
513
**                  entire epoch in et2[0] and set et2[1]=0.0               **
514
**                                                                          **
515
**               b. for maximum interpolation accuracy, set et2[0] =        **
516
**                  the most recent midnight at or before interpolation     **
517
**                  epoch and set et2[1] = fractional part of a day         **
518
**                  elapsed between et2[0] and epoch.                       **
519
**                                                                          **
520
**               c. as an alternative, it may prove convenient to set       **
521
**                  et2[0] = some fixed epoch, such as start of integration,**
522
**                  and et2[1] = elapsed interval between then and epoch.   **
523
**                                                                          **
524
**       list   13-element integer array specifying what interpolation      **
525
**              is wanted for each of the "bodies" on the file.             **
526
**                                                                          **
527
**                        list[i]=0, no interpolation for body i            **
528
**                               =1, position only                          **
529
**                               =2, position and velocity                  **
530
**                                                                          **
531
**              the designation of the astronomical bodies by i is:         **
532
**                                                                          **
533
**                        i = 0: mercury                                    **
534
**                          = 1: venus                                      **
535
**                          = 2: earth-moon barycenter                      **
536
**                          = 3: mars                                       **
537
**                          = 4: jupiter                                    **
538
**                          = 5: saturn                                     **
539
**                          = 6: uranus                                     **
540
**                          = 7: neptune                                    **
541
**                          = 8: pluto                                      **
542
**                          = 9: geocentric moon                            **
543
**                          =10: nutations in lon & obliq (if on file)      **
544
**                          =11: lunar librations (if on file)              **
545
**                          =12: lunar mantle omegas                        **
546
**                          =13: TT-TDB (if on file)                        **
547
**                                                                          **
548
** Note that I've not actually seen case 12 yet.  It probably doesn't work. **
549
**                                                                          **
550
**    output:                                                               **
551
**                                                                          **
552
**    pv[][6]   double array that will contain requested interpolated       **
553
**              quantities.  The body specified by list[i] will have its    **
554
**              state in the array starting at pv[i][0]  (on any given      **
555
**              call, only those words in 'pv' which are affected by the    **
556
**              first 10 'list' entries (and by list(11) if librations are  **
557
**              on the file) are set.  The rest of the 'pv' array           **
558
**              is untouched.)  The order of components in pv[][] is:       **
559
**              pv[][0]=x,....pv[][5]=dz.                                   **
560
**                                                                          **
561
**              All output vectors are referenced to the earth mean         **
562
**              equator and equinox of epoch. The moon state is always      **
563
**              geocentric; the other nine states are either heliocentric   **
564
**              or solar-system barycentric, depending on the setting of    **
565
**              global variables (see below).                               **
566
**                                                                          **
567
**              Lunar librations, if on file, are put into pv[10][k] if     **
568
**              list[11] is 1 or 2.                                         **
569
**                                                                          **
570
**        nut   dp 4-word array that will contain nutations and rates,      **
571
**              depending on the setting of list[10].  the order of         **
572
**              quantities in nut is:                                       **
573
**                                                                          **
574
**                       d psi  (nutation in longitude)                     **
575
**                       d epsilon (nutation in obliquity)                  **
576
**                       d psi dot                                          **
577
**                       d epsilon dot                                      **
578
**                                                                          **
579
*****************************************************************************/
580
int DLL_FUNC jpl_state(void *ephem, const double et, const int list[14],
716✔
581
                          double pv[][6], double nut[4], const int bary)
582
{
583
        struct jpl_eph_data *eph = static_cast<struct jpl_eph_data *>(ephem);
716✔
584
        unsigned i, j, n_intervals;
585
        double *buf = eph->cache;
716✔
586
        double t[2];
587
        const double block_loc = (et - eph->ephem_start) / eph->ephem_step;
716✔
588
        bool recompute_pvsun;
589
        const double aufac = 1.0 / eph->au;
716✔
590

591
        QMutexLocker locker(&mutex);
716✔
592

593
        /*   error return for epoch out of range  */
594
        if(et < eph->ephem_start || et > eph->ephem_end)
716✔
595
                return(JPL_EPH_OUTSIDE_RANGE);
×
596

597
        /*   calculate record # and relative time in interval   */
598

599
        uint32_t nr = static_cast<uint32_t>(block_loc);
716✔
600
        t[0] = block_loc - static_cast<double>(nr);
716✔
601
        if(t[0]==0.0 && nr)
716✔
602
        {
603
                t[0] = 1.;
22✔
604
                nr--;
22✔
605
        }
606

607
        /*   read correct record if not in core (static vector buf[])   */
608
        if(nr != eph->curr_cache_loc)
716✔
609
        {
610
                eph->curr_cache_loc = nr;
716✔
611
                /* Read two blocks ahead to account for header: */
612
                if(FSeek(eph->ifile, static_cast<unsigned long long>(nr + 2) * eph->recsize, SEEK_SET)) // lgtm [cpp/integer-multiplication-cast-to-long]
716✔
613
                {
614
                        // GZ: Make sure we will try again on next call...
615
                        eph->curr_cache_loc=0;
×
616
                        return(JPL_EPH_FSEEK_ERROR);
×
617
                }
618
                if(fread(buf, sizeof(double), static_cast<size_t>(eph->ncoeff), eph->ifile)
716✔
619
                                != static_cast<size_t>(eph->ncoeff))
716✔
620
                {
UNCOV
621
                        return(JPL_EPH_READ_ERROR);
×
622
                }
623

624
                if(eph->swap_bytes)
716✔
625
                        swap_64_bit_val(buf, eph->ncoeff);
×
626
        }
627
        t[1] = eph->ephem_step;
716✔
628

629
        if(eph->pvsun_t != et)   /* If several calls are made for the same et, */
716✔
630
        {                      /* don't recompute pvsun each time... only on */
631
                recompute_pvsun = true;   /* the first run through.                     */
716✔
632
                eph->pvsun_t = et;
716✔
633
        }
634
        else
635
                recompute_pvsun = false;
×
636

637
        /* Here, i loops through the "traditional" 14 listed items -- 10
638
          solar system objects,  nutations,  librations,  lunar mantle angles,
639
          and TT-TDT -- plus a fifteenth:  the solar system barycenter.  That
640
          last is quite different:  it's computed 'as needed',  rather than
641
          from list[];  the output goes to pvsun rather than the pv array;
642
          and three quantities (position,  velocity,  acceleration) are
643
          computed (nobody else gets accelerations at present.)  */
644
        for(n_intervals = 1; n_intervals <= 8; n_intervals *= 2)
3,580✔
645
                for(i = 0; i < 15; i++)
45,824✔
646
                {
647
                        unsigned quantities;
648
                        uint32_t *iptr = &eph->ipt[i + 1][0];
42,960✔
649

650
                        if(i == 14)
42,960✔
651
                        {
652
                                quantities = (recompute_pvsun ? 3 : 0);
2,864✔
653
                                iptr = &eph->ipt[10][0];
2,864✔
654
                        }
655
                        else
656
                        {
657
                                quantities = static_cast<unsigned int>(list[i]);
40,096✔
658
                                iptr = &eph->ipt[i < 10 ? i : i + 1][0];
40,096✔
659
                        }
660
                        if(n_intervals == iptr[2] && quantities)
42,960✔
661
                        {
662
                                double *dest = ((i == 10) ? eph->pvsun : pv[i]);
1,432✔
663

664
                                if(i < 10)
1,432✔
665
                                        dest = pv[i];
716✔
666
                                else if(i == 14)
716✔
667
                                        dest = eph->pvsun;
716✔
668
                                else
669
                                        dest = nut;
×
670

671
                                interp(&eph->iinfo, &buf[iptr[0]-1], t, static_cast<unsigned int>(iptr[1]),
1,432✔
672
                                                dimension(i + 1),
673
                                                n_intervals, static_cast<int>(quantities), dest);
674

675
                                if(i < 10 || i == 14)        /*  convert km to AU */
1,432✔
676
                                        for(j = 0; j < quantities * 3; j++)
12,172✔
677
                                                dest[j] *= aufac;
10,740✔
678
                        }
679
                }
680
        if(!bary)                             /* gotta correct everybody for */
716✔
681
                for(i = 0; i < 9; i++)            /* the solar system barycenter */
×
682
                        for(j = 0; j < static_cast<unsigned>(list[i]) * 3; j++)
×
683
                                pv[i][j] -= eph->pvsun[j];
×
684
        return(0);
716✔
685
}
716✔
686

687
static int init_err_code = JPL_INIT_NOT_CALLED;
688

689
int DLL_FUNC jpl_init_error_code(void)
2✔
690
{
691
   return(init_err_code);
2✔
692
}
693

694
const char * jpl_init_error_message(void)
×
695
{
696
  switch(init_err_code)
×
697
  {
698
    case 0:
×
699
      return "JPL_INIT_NO_ERROR";
×
700
    case -1:
×
701
      return "JPL_INIT_FILE_NOT_FOUND";
×
702
    case -2:
×
703
      return "JPL_INIT_FSEEK_FAILED";
×
704
    case -3:
×
705
      return "JPL_INIT_FREAD_FAILED";
×
706
    case -4:
×
707
      return "JPL_INIT_FREAD2_FAILED";
×
708
    case -5:
×
709
      return "JPL_INIT_FILE_CORRUPT";
×
710
    case -6:
×
711
      return "JPL_INIT_MEMORY_FAILURE";
×
712
    case -7:
×
713
      return "JPL_INIT_FREAD3_FAILED";
×
714
    case -8:
×
715
      return "JPL_INIT_FREAD4_FAILED";
×
716
    case -9:
×
717
      return "JPL_INIT_NOT_CALLED";
×
718
    case -10:
×
719
      return "JPL_INIT_FREAD5_FAILED";
×
720
    default:
×
721
      return "ERROR_NOT_RECOGNIZED";
×
722
  }
723
}
724

725
   /* DE-430 has 572 constants.  That's more than the 400 constants */
726
   /* originally expected.  The remaining 172 are stored after the  */
727
   /* other header data :                                           */
728

729
#define START_400TH_CONSTANT_NAME   (84 * 3 + 400 * 6 + 5 * sizeof(double) \
730
                                                    + 41 * sizeof(int32_t))
731

732
   /* ...which comes out to 2856.  See comments in 'jpl_int.h'.   */
733

734
/****************************************************************************
735
**    jpl_init_ephemeris(ephemeris_filename, nam, val, n_constants)       **
736
*****************************************************************************
737
**                                                                         **
738
**    this function does the initial prep work for use of binary JPL       **
739
**    ephemerides.                                                         **
740
**      const char *ephemeris_filename = full path/filename of the binary  **
741
**          ephemeris (on the Willmann-Bell CDs,  this is UNIX.200, 405,   **
742
**          or 406)                                                        **
743
**      char nam[][6] = array of constant names (max 6 characters each)    **
744
**          You can pass nam=NULL if you don't care about the names        **
745
**      double *val = array of values of constants                         **
746
**          You can pass val=NULL if you don't care about the constants    **
747
**      Return value is a pointer to the jpl_eph_data structure            **
748
**      NULL is returned if the file isn't opened or memory isn't alloced  **
749
**      Errors can be determined with the above jpl_init_error_code()     **
750
****************************************************************************/
751

752
void * DLL_FUNC jpl_init_ephemeris(const char *ephemeris_filename,
2✔
753
                          char nam[][6], double *val)
754
{
755
    unsigned i, j;
756
    unsigned long de_version;
757
    char title[84];
758

759
    errno=0;
2✔
760
    FILE *ifile = fopen(ephemeris_filename, "rb");
2✔
761
    int err=errno;
2✔
762
    if (err!=0)
2✔
763
    {
764
            qDebug() << "Error" << err << " -- Problem opening file" << ephemeris_filename << ":" << strerror(err);
×
765
#ifdef Q_OS_WIN
766
            qDebug() << "It seems on Windows filename translation sometimes fails on Debug builds. Disable the DE* on this build!";
767
#endif
768
    }
769
    Q_ASSERT_X(err==0, "Problem opening file", strerror(err));
2✔
770

771
    struct jpl_eph_data *rval;
772
    struct jpl_eph_data temp_data;
773

774
    init_err_code = 0;
2✔
775
    temp_data.ifile = ifile;
2✔
776
    if(!ifile)
2✔
777
      init_err_code = JPL_INIT_FILE_NOT_FOUND;
×
778
    else if(fread(title, 84, 1, ifile) != 1)
2✔
779
      init_err_code = JPL_INIT_FREAD_FAILED;
×
780
    else if(FSeek(ifile, 2652L, SEEK_SET))
2✔
781
      init_err_code = JPL_INIT_FSEEK_FAILED;
×
782
    else if(fread(&temp_data, JPL_HEADER_SIZE, 1, ifile) != 1)
2✔
783
      init_err_code = JPL_INIT_FREAD2_FAILED;
×
784

785
    if(init_err_code)
2✔
786
    {
787
      if(ifile)
×
788
        fclose(ifile);
×
789
      return(Q_NULLPTR);
×
790
    }
791

792
    de_version = strtoul(title+26, Q_NULLPTR, 10);
2✔
793
    
794
    /* A small piece of trickery:  in the binary file,  data is stored */
795
    /* for ipt[0...11],  then the ephemeris version,  then the         */
796
    /* remaining ipt[12] data.  A little switching is required to get  */
797
    /* the correct order. */
798
    temp_data.ipt[12][0] = temp_data.ipt[12][1];
2✔
799
    temp_data.ipt[12][1] = temp_data.ipt[12][2];
2✔
800
    temp_data.ipt[12][2] = temp_data.ipt[13][0];
2✔
801
    temp_data.ephemeris_version = de_version;
2✔
802

803
    //qDebug() << "DE_Version: " << de_version;
804

805

806
    temp_data.swap_bytes = (temp_data.ncon > 65536L);
2✔
807
    if(temp_data.swap_bytes)     /* byte order is wrong for current platform */
2✔
808
    {
809
      qDebug() << "Byte order is wrong for current platform";
×
810
      
811
      swap_64_bit_val(&temp_data.ephem_start, 1);
×
812
      swap_64_bit_val(&temp_data.ephem_end, 1);
×
813
      swap_64_bit_val(&temp_data.ephem_step, 1);
×
814
      swap_32_bit_val(&temp_data.ncon);
×
815
      swap_64_bit_val(&temp_data.au, 1);
×
816
      swap_64_bit_val(&temp_data.emrat, 1);
×
817
    }
818

819
            /* It's a little tricky to tell if an ephemeris really has  */
820
            /* TT-TDB data (with offsets in ipt[13][] and ipt[14][]).   */
821
            /* Essentially,  we read the data and sanity-check it,  and */
822
            /* zero it if it "doesn't add up" correctly.                */
823
            /*    Also:  certain ephems I've generated with ncon capped */
824
            /* at 400 have no TT-TDB data.  So if ncon == 400,  don't   */
825
            /* try to read such data;  you may get garbage.             */
826
    if(de_version >= 430 && temp_data.ncon != 400)
2✔
827
    {
828
         /* If there are 400 or fewer constants,  data for ipt[13][0...2] */
829
         /* immediately follows that for ipt[12][0..2];  i.e.,  we don't  */
830
         /* need to fseek().  Otherwise,  we gotta skip 6*(n_constants-400) */
831
         /* bytes. */
832
      if(temp_data.ncon > 400)
2✔
833
      {
834
        if ( FSeek(ifile, static_cast<long long>(static_cast<size_t>(temp_data.ncon - 400) * 6), SEEK_CUR) != 0)
2✔
835
        {
836
              qWarning() << "jpl_init_ephemeris(): Cannot seek in file. Result will be undefined.";
×
837
        }
838
      }
839
      if(fread(&temp_data.ipt[13][0], sizeof(int32_t), 6, ifile) != 6)
2✔
840
         init_err_code = JPL_INIT_FREAD5_FAILED;
×
841
    }
842
    else                 /* mark header data as invalid */
843
      temp_data.ipt[13][0] = static_cast<uint32_t>(-1);
×
844

845
    if(temp_data.swap_bytes)     /* byte order is wrong for current platform */
2✔
846
    {  
847
        for(j = 0; j < 3; j++)
×
848
        {
849
            for(i = 0; i < 15; i++)
×
850
            { 
851
                swap_32_bit_val(&temp_data.ipt[i][j]);
×
852
            }
853
        }
854
    }
855

856
    if(temp_data.ipt[13][0] !=       /* if these don't add up correctly, */
2✔
857
          temp_data.ipt[12][0] + temp_data.ipt[12][1] * temp_data.ipt[12][2] * 3
2✔
858
    || temp_data.ipt[14][0] !=       /* zero them out (they've garbage data) */
1✔
859
          temp_data.ipt[13][0] + temp_data.ipt[13][1] * temp_data.ipt[13][2] * 3)
1✔
860
    {     /* not valid pointers to TT-TDB data */
861
      memset(&temp_data.ipt[13][0], 0, 6 * sizeof(int32_t));
1✔
862
    }
863

864
         /* A sanity check:  if the earth-moon ratio is outside reasonable */
865
         /* bounds,  we must be looking at a wrong or corrupted file.      */
866
         /* In DE-102,  emrat = 81.3007;  in DE-405/406, emrat = 81.30056. */
867
         /* Those are the low and high ranges.  We'll allow some slop in   */
868
         /* case the earth/moon mass ratio changes:                        */
869
    if(temp_data.emrat > 81.3008 || temp_data.emrat < 81.30055)
2✔
870
    {   
871
      init_err_code = JPL_INIT_FILE_CORRUPT;
×
872
      qWarning() << "temp_data: " << temp_data.emrat << "(should have been =~81). JPL_INIT_FILE_CORRUPT!";
×
873
    }
874

875
    if(init_err_code)
2✔
876
    {
877
      fclose(ifile);
×
878
      return(Q_NULLPTR);
×
879
    }
880

881
         /* Once upon a time,  the kernel size was determined from the */
882
         /* DE version.  This was not a terrible idea,  except that it */
883
         /* meant that when the code faced a new version,  it broke.   */
884
         /* Now we use some logic to compute the kernel size.          */
885
    temp_data.kernel_size = 4;
2✔
886
    for(i = 0; i < 15; i++)
32✔
887
      temp_data.kernel_size +=
30✔
888
             2 * temp_data.ipt[i][1] * temp_data.ipt[i][2] * dimension(i);
30✔
889
// for(i = 0; i < 13; i++)
890
//    temp_data.kernel_size +=
891
//                     temp_data.ipt[i][1] * temp_data.ipt[i][2] * ((i == 11) ? 4 : 6);
892
//       /* ...and then add in space required for the TT-TDB data : */
893
// temp_data.kernel_size += temp_data.ipt[14][1] * temp_data.ipt[14][2] * 2;
894
    temp_data.recsize = temp_data.kernel_size * 4L;
2✔
895
    temp_data.ncoeff = temp_data.kernel_size / 2L;
2✔
896

897
               /* Rather than do two separate allocations,  everything     */
898
               /* we need is allocated in _one_ chunk,  then parceled out. */
899
               /* This looks a little weird,  but it simplifies error      */
900
               /* handling and cleanup.                                    */
901
    rval = static_cast<struct jpl_eph_data *>(calloc(sizeof(struct jpl_eph_data)
2✔
902
                        + temp_data.recsize, 1));
2✔
903
    if(!rval)
2✔
904
    {
905
      init_err_code = JPL_INIT_MEMORY_FAILURE;
×
906
      fclose(ifile);
×
907
      return(Q_NULLPTR);
×
908
    }
909
    memcpy(rval, &temp_data, sizeof(struct jpl_eph_data));
2✔
910
    rval->iinfo.posn_coeff[0] = 1.0;
2✔
911
            /* Seed a bogus value here.  The first and subsequent calls to */
912
            /* 'interp' will correct it to a value between -1 and +1.      */
913
    rval->iinfo.posn_coeff[1] = -2.0;
2✔
914
    rval->iinfo.vel_coeff[0] = 0.0;
2✔
915
    rval->iinfo.vel_coeff[1] = 1.0;
2✔
916
    rval->curr_cache_loc = static_cast<uint32_t>(-1);
2✔
917
    
918
              /* The 'cache' data is right after the 'jpl_eph_data' struct: */
919
    rval->cache = reinterpret_cast<double *>(rval + 1);
2✔
920
               /* If there are more than 400 constants,  the names of       */
921
               /* the extra constants are stored in what would normally     */
922
               /* be zero-padding after the header record.  However,        */
923
               /* older ephemeris-reading software won't know about that.   */
924
               /* So we store ncon=400,  then actually check the names to   */
925
               /* see how many constants there _really_ are.  Older readers */
926
               /* will just see 400 names and won't know about the others.  */
927
               /* But on the upside, they won't crash.                      */
928

929
    if(rval->ncon == 400)
2✔
930
    {
931
      char buff[7];
932

933
      buff[6] = '\0';
×
934
      FSeek(ifile, START_400TH_CONSTANT_NAME, SEEK_SET);
×
935
      while(fread(buff, 6, 1, ifile) && strlen(buff) == 6)
×
936
      {
937
         rval->ncon++;
×
938
      }
939
    }
940

941
    if(val)
2✔
942
    {
943
      FSeek(ifile, rval->recsize, SEEK_SET);
2✔
944
      if(fread(val, sizeof(double), static_cast<size_t>(rval->ncon), ifile)
2✔
945
                        != static_cast<size_t>(rval->ncon))
2✔
946
         init_err_code = JPL_INIT_FREAD3_FAILED;
×
947
      else if(rval->swap_bytes)     /* gotta swap the constants,  too */
2✔
948
         swap_64_bit_val(val, rval->ncon);
×
949
      }
950

951
   if(!init_err_code && nam)
2✔
952
      {
953
      FSeek(ifile, 84L * 3L, SEEK_SET);   /* just after the 3 'title' lines */
2✔
954
      for(i = 0; i < rval->ncon && !init_err_code; i++)
1,219✔
955
      {
956
        if(i == 400)
1,217✔
957
          FSeek(ifile, START_400TH_CONSTANT_NAME, SEEK_SET);
2✔
958
        if(fread(nam[i], 6, 1, ifile) != 1)
1,217✔
959
          init_err_code = JPL_INIT_FREAD4_FAILED;
×
960
        }
961
      }
962
  return(rval);
2✔
963
}
964

965
/****************************************************************************
966
**    jpl_close_ephemeris(ephem)                                          **
967
*****************************************************************************
968
**                                                                         **
969
**    this function closes files and frees up memory allocated by the      **
970
**    jpl_init_ephemeris() function.                                      **
971
****************************************************************************/
972
void DLL_FUNC jpl_close_ephemeris(void *ephem)
×
973
{
974
   struct jpl_eph_data *eph = static_cast<struct jpl_eph_data *>(ephem);
×
975

976
   fclose(eph->ifile);
×
977
   free(ephem);
×
978
}
×
979

980
/* Added 2011 Jan 18:  random access to any desired JPL constant */
981

982

983
double DLL_FUNC jpl_get_constant(const int idx, void *ephem, char *constant_name)
×
984
{
985
        struct jpl_eph_data *eph = static_cast<struct jpl_eph_data *>(ephem);
×
986
        double rval = 0.;
×
987

988
        *constant_name = '\0';
×
989
        if(idx >= 0 && idx < static_cast<int>(eph->ncon))
×
990
        {
991
                // GZ extended from const long to const long long
992
                const long long seek_loc = (idx < 400 ? 84L * 3L + static_cast<long>(idx) * 6 :
×
993
                                                        static_cast<long long>(START_400TH_CONSTANT_NAME) + (idx - 400) * 6);
×
994

995
                if (FSeek(eph->ifile, seek_loc, SEEK_SET) != 0)
×
996
                {
997
                        qWarning() << "jpl_get_constant(): Cannot seek in file. Result will be undefined.";
×
998
                }
999
                if(fread(constant_name, 1, 6, eph->ifile) == 6 )
×
1000
                {
1001
                        constant_name[6] = '\0';
×
1002
                        if (FSeek(eph->ifile, static_cast<long long>(eph->recsize) + static_cast<long long>(idx) * static_cast<long long>(sizeof(double)), SEEK_SET) != 0)
×
1003
                        {
1004
                                qWarning() << "jpl_get_constant(): Cannot seek in file (call2). Result will be undefined.";
×
1005
                        }
1006

1007
                        // GZ added tests to make travis test suite (Coverity Scan) happier
1008
                        if( fread(&rval, 1, sizeof(double), eph->ifile) == sizeof(double) )
×
1009
                        {
1010
                                if(eph->swap_bytes)     /* gotta swap the constants,  too */
×
1011
                                        swap_64_bit_val(&rval, 1);
×
1012
                        }
1013
                        else
1014
                                qWarning() << "jpl_get_constant(): fread() failed to read a double. Result will be undefined.";
×
1015
                }
1016
                else
1017
                        qWarning() << "jpl_get_constant(): fread() failed to read a constant name. Result will be 0, likely wrong.";
×
1018
        }
1019
        return(rval);
×
1020
}
1021
/*************************** THE END ***************************************/
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc