├── .gitignore ├── README.md └── vsop2013.py /.gitignore: -------------------------------------------------------------------------------- 1 | ephemerides 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vsop2013 2 | 3 | ## A Python implementation of the VSOP2013 Ephemeris functionality. 4 | 5 | This module provides basic support for the VSOP2013 ephemerides which can be used to compute the 6 | heliocentric positions and velocities of the 8 planets Mercury, Venus, the Earth-Moon barycenter, 7 | Mars, Jupiter, Saturn, Uranus, Neptune and the dwarf planet Pluto (only in the period from +1500 8 | to +3000). 9 | 10 | The ephemerides are provided in 6 sequential text files (ASCII) and can be downloaded at: 11 | 12 | ftp://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides 13 | 14 | **NOTE that you will likely have to use an ftp client to download the ephemerides files. 15 | For MacOSX I use the app ForkLift and have verified that the files are still available as of July 20, 2022.** 16 | 17 | The following provides the name of each file and the period that it covers: 18 | 19 | * `VSOP2013.m4000: -4500 to -3000` 20 | * `VSOP2013.m2000: -3000 to -1500` 21 | * `VSOP2013.m1000: -1500 to 0` 22 | * `VSOP2013.p1000: 0 to +1500` 23 | * `VSOP2013.p2000: +1500 to +3000` 24 | * `VSOP2013.p4000: +3000 to +4500` 25 | 26 | While these text files could be used directly, this would have a serious impact on the performance 27 | of computing any significant number of planetary positions. Instead it is suggested that these 28 | files are parsed and stored into binary format for the platform on which they will be used. 29 | **NOTE THAT THE BINARY FILE GENERATED FOR ONE PLATFORM MAY NOT BE COMPATIBLE FOR USE ON A DIFFERENT 30 | PLATFORM.** Once converted to binary form the text files may be deleted to save storage space where 31 | necessary. The binary form of the files provides a much more efficient mechanism for performing 32 | large numbers of position calculations. 33 | 34 | For reference the authors have provided the programs (written in Fortran) that they used for binary 35 | file generation and basic calculations. 36 | 37 | These are: 38 | 39 | VSOP2013_binfile.f : This program converts the sequential files into direct access files. 40 | 41 | VSOP2013_compute.f : This program computes planetary coordinates from a direct access file. 42 | 43 | The file VSOP2013_ctl.txt contains planetary coordinates computed by the program VSOP2013_compute.f 44 | and is provided as a means of validating the calculations using these ephemerides. 45 | 46 | The file README.pdf also provides this information as well as other info that may be of interest. 47 | 48 | This module has been developed to provide a degree of convenience for the Python user, not just 49 | a reference implementation. 50 | 51 | TXT_FILES_ROOT is the path to the directory containing the VSOP2013 source text files. You may 52 | change this to reflect where your source text files are actually located. The default is to place 53 | the source text files into a subdirectory called "ephemerides" at the location of the vsop2013.py 54 | script. If the vsop2013.py functionality is being used by other python scripts then this variable 55 | can be changed once the module is loaded. 56 | 57 | TXT_FILES is an array of the filenames of the VSOP2013 source text files. 58 | 59 | BIN_FILES_ROOT is the path to the directory containing the VSOP2013 binary files. You may 60 | change this to reflect where your binary files are actually located. This variable is similar to 61 | the TXT_FILES_ROOT variable discussed previously. 62 | 63 | BIN_FILES is an array of the filenames of the VSOP2013 binary files. 64 | 65 | PLANET_NAMES is an array of the names of the supported planets and is used ONLY for the display 66 | of the results by the print_results function. 67 | 68 | At the end of the source file is code that shows how to load the files, convert them to binary, 69 | and then to perform basic calculations. 70 | 71 | =============================================================== 72 | 73 | To use this program: 74 | 75 | 1. create a directory called 'vsop' 76 | 2. change to directory 'vsop' 77 | 3. create a directory called 'ephemerides' 78 | 4. change to directory 'ephemerides' 79 | 5. download file 'VSOP2013.m4000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 80 | 6. download file 'VSOP2013.m2000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 81 | 7. download file 'VSOP2013.m1000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 82 | 8. download file 'VSOP2013.p1000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 83 | 9. download file 'VSOP2013.p2000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 84 | 10. download file 'VSOP2013.p4000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 85 | 11. change back to directory 'vsop' 86 | 12. download the python program 'vsop2013.py' 87 | 13. run 'python ./vsop2013.py' 88 | 89 | That will decompress and prepare the ephemerides. 90 | Once complete the you can use in idle or ipython as follows: 91 | 92 | vsop2013 = VSOP2013File() 93 | r = vsop2013.calculate_for(, ) 94 | print_results(, , r) 95 | 96 | This will output somthing like the following: 97 | 98 | Jupiter JD2816818.5 X : -4.6312422242673 ua Y : 2.7235071301370 ua Z : 0.0885945013174 ua 99 | X': -0.0039324916289 ua/d Y': -0.0061510013748 ua/d Z': 0.0001158736935 ua/d 100 | 101 | if was 4, was 2816818.5. 102 | -------------------------------------------------------------------------------- /vsop2013.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides basic support for the VSOP2013 ephemerides which can be used to compute the 3 | heliocentric positions and velocities of the 8 planets Mercury, Venus, the Earth-Moon barycenter, 4 | Mars, Jupiter, Saturn, Uranus, Neptune and the dwarf planet Pluto (only in the period from +1500 5 | to +3000). 6 | 7 | The ephemerides are provided in 6 sequential text files (ASCII) and can be downloaded at: 8 | 9 | https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/ 10 | 11 | NOTE: You will likely have to use an ftp client to download the ephemerides files. 12 | For MacOSX I use the app ForkLift and have verified that the files are still 13 | available as of July 20, 2022. 14 | 15 | The following provides the name of each file and the period that it covers: 16 | VSOP2013.m4000: -4500 to -3000 17 | VSOP2013.m2000: -3000 to -1500 18 | VSOP2013.m1000: -1500 to 0 19 | VSOP2013.p1000: 0 to +1500 20 | VSOP2013.p2000: +1500 to +3000 21 | VSOP2013.p4000: +3000 to +4500 22 | 23 | While these text files could be used directly, this would have a serious impact on the performance 24 | of computing any significant number of planetary positions. Instead it is suggested that these 25 | files are parsed and stored into binary format for the platform on which they will be used. 26 | NOTE THAT THE BINARY FILE GENERATED FOR ONE PLATFORM MAY NOT BE COMPATIBLE FOR USE ON A DIFFERENT 27 | PLATFORM. Once converted to binary form the text files may be deleted to save storage space where 28 | necessary. The binary form of the files provides a much more efficient mechanism for performing 29 | large numbers of position calculations. 30 | 31 | For reference the authors have provided the programs (Fortran) that they used for these purposes. 32 | These are: 33 | 34 | VSOP2013_binfile.f : 35 | This program converts the sequential files into direct access files. 36 | 37 | VSOP2013_compute.f: 38 | This program computes planetary coordinates from a direct access file. 39 | 40 | The file VSOP2013_ctl.txt contains planetary coordinates computed by the program VSOP2013_compute.f 41 | and given as control values for the users. 42 | 43 | The file README.pdf also provides this information as well as other info that may be of interest. 44 | 45 | This module has been developed to provide a degree of convenience for the Python user, not just 46 | a reference implementation. 47 | 48 | TXT_FILES_ROOT is the path to the directory containing the VSOP2013 source text files. You may 49 | change this to reflect where your source text files are actually located. The default is to place 50 | the source text files into a subdirectory called "ephemerides" at the location of the vsop2013.py 51 | script. If the vsop2013.py functionality is being used by other python scripts then this variable 52 | can be changed once the module is loaded. 53 | 54 | TXT_FILES is an array of the filenames of the VSOP2013 source text files. 55 | 56 | BIN_FILES_ROOT is the path to the directory containing the VSOP2013 binary files. You may 57 | change this to reflect where your binary files are actually located. This variable is similar to 58 | the TXT_FILES_ROOT variable discussed previously. 59 | 60 | BIN_FILES is an array of the filenames of the VSOP2013 binary files. 61 | 62 | PLANET_NAMES is an array of the names of the supported planets and is used ONLY for the display 63 | of the results by the print_results function. 64 | 65 | At the end of this file is code that shows how to load the files, convert them to binary, 66 | and then to perform basic calculations. 67 | 68 | To use this program: 69 | 70 | 1. create a directory called 'vsop' 71 | 2. change to directory 'vsop' 72 | 3. create a directory called 'ephemerides' 73 | 4. change to directory 'ephemerides' 74 | 5. download file 'VSOP2013.m4000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 75 | 6. download file 'VSOP2013.m2000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 76 | 7. download file 'VSOP2013.m1000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 77 | 8. download file 'VSOP2013.p1000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 78 | 9. download file 'VSOP2013.p2000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 79 | 10. download file 'VSOP2013.p4000' from 'https://ftp.imcce.fr/pub/ephem/planets/vsop2013/ephemerides/' 80 | 11. change back to directory 'vsop' 81 | 12. download the python program 'vsop2013.py' 82 | 13. run 'python ./vsop2013.py' 83 | 84 | That will decompress and prepare the ephemerides. 85 | Once complete the you can use as follows: 86 | 87 | vsop2013 = VSOP2013File() 88 | r = vsop2013.calculate_for(, ) 89 | print_results(, , r) 90 | 91 | This will output somthing like the following: 92 | 93 | Jupiter JD2816818.5 X : -4.6312422242673 ua Y : 2.7235071301370 ua Z : 0.0885945013174 ua 94 | X': -0.0039324916289 ua/d Y': -0.0061510013748 ua/d Z': 0.0001158736935 ua/d 95 | 96 | if was 4, was 2816818.5. 97 | """ 98 | from array import array 99 | import os 100 | import struct 101 | 102 | 103 | TXT_FILES_ROOT = "./ephemerides/" 104 | TXT_FILES = ["VSOP2013.m4000", 105 | "VSOP2013.m2000", 106 | "VSOP2013.m1000", 107 | "VSOP2013.p1000", 108 | "VSOP2013.p2000", 109 | "VSOP2013.p4000"] 110 | 111 | BIN_FILES_ROOT = "./ephemerides/" 112 | BIN_FILES = ["VSOP2013.m4000.pybin", 113 | "VSOP2013.m2000.pybin", 114 | "VSOP2013.m1000.pybin", 115 | "VSOP2013.p1000.pybin", 116 | "VSOP2013.p2000.pybin", 117 | "VSOP2013.p4000.pybin"] 118 | 119 | 120 | PLANET_NAMES = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto'] 121 | 122 | def print_results(iplanet, jde, r): 123 | name = PLANET_NAMES[iplanet] 124 | print("{:8s} JD{:9.1f} X : {: 17.13f} ua Y : {: 17.13f} ua Z : {: 17.13f} ua".format(name, jde, r[0], r[1], r[2])) 125 | print(" X': {: 17.13f} ua/d Y': {: 17.13f} ua/d Z': {: 17.13f} ua/d".format(r[3], r[4], r[5])) 126 | 127 | 128 | class VSOP2013File: 129 | 130 | N_PLANETS = 9 131 | DATE_RANGES = [(77294.5, 625198.5), # -4500 to -3000 132 | (625198.5, 1173102.5), # -3000 to -1500 133 | (1173102.5, 1721006.5), # -1500 to 0 134 | (1721006.5, 2268910.5), # 0 to +1500 135 | (2268910.5, 2816814.5), # +1500 to +3000 136 | (2816814.5, 3364718.5)] # +3000 to +4500 137 | 138 | def __init__(self): 139 | self.bfref = None 140 | self.bfi = None 141 | self.doff = None 142 | self.idf = None 143 | self.t1 = None 144 | self.t2 = None 145 | self.delta = None 146 | self.nintv = None 147 | self.ncoef = None 148 | self.loc = None 149 | 150 | # text file support methods 151 | # 152 | # These methods are here to support the conversion of the coefficient text files to binary files. 153 | # This conversions decreases the size of the coefficient files, eliminates the text parsing by putting 154 | # the data into the proper format, and makes random access within a file possible, dramatically 155 | # improving performance. 156 | 157 | def txtfile_path(self, i): 158 | """ 159 | Build a source text file path. 160 | :param i: index of the source text file in the TXT_FILES array 161 | :return: the source text file path (None if error) 162 | """ 163 | if (i >= 0) and (i < self.N_PLANETS): 164 | return os.path.join(TXT_FILES_ROOT, TXT_FILES[i]) 165 | return None 166 | 167 | def txtfile_exists(self, i): 168 | """ 169 | Test if a source text file exists. 170 | :param i: index of the source text file in the TXT_FILES array 171 | :return: true=exists and is a file 172 | """ 173 | tfp = self.txtfile_path(i) 174 | if tfp is not None: 175 | return os.path.isfile(tfp) 176 | return False 177 | 178 | def bin_txtfile(self, i): 179 | """ 180 | Convert a source text file to a binary file. 181 | :param i: index of the source text file in the TXT_FILES array 182 | """ 183 | self.bfref = None 184 | self.bfi = None 185 | self.doff = None 186 | self.idf = None 187 | self.t1 = None 188 | self.t2 = None 189 | self.delta = None 190 | self.nintv = None 191 | self.ncoef = None 192 | self.loc = None 193 | tfp = self.txtfile_path(i) 194 | if tfp is None: 195 | raise ValueError("Unable to build text file path with i = {}!".format(i)) 196 | bfp = self.binfile_path(i) 197 | if bfp is None: 198 | raise ValueError("Unable to build binary file path with i = {}!".format(i)) 199 | if not os.path.isfile(tfp): 200 | raise ValueError("File '{}' not found!".format(tfp)) 201 | print("Creating '{}' from '{}'".format(bfp, tfp)) 202 | with open(tfp, 'r') as tfref: 203 | self.__load_idf(tfref) 204 | self.__load_t1(tfref) 205 | self.__load_t2(tfref) 206 | self.__load_delta(tfref) 207 | self.__load_nintv(tfref) 208 | self.__load_ncoef(tfref) 209 | self.__load_loc(tfref) 210 | self.bfref = open(bfp, 'wb') 211 | self.__write_binfile_header() 212 | self.__bin_records(tfref) 213 | self.bfref.close() 214 | self.bfref = None 215 | 216 | def __load_idf(self, tfref): 217 | line = tfref.readline() 218 | try: 219 | v = int(line.strip()) 220 | except: 221 | raise ValueError("Invalid value for idf!") 222 | if v != 2013: 223 | raise ValueError("Invalid value for idf!") 224 | self.idf = "vsop2013" 225 | 226 | def __load_t1(self, tfref): 227 | line = tfref.readline() 228 | try: 229 | self.t1 = float(line.strip()) 230 | except: 231 | raise ValueError("Invalid value for t1!") 232 | 233 | def __load_t2(self, tfref): 234 | line = tfref.readline() 235 | try: 236 | self.t2 = float(line.strip()) 237 | except: 238 | raise ValueError("Invalid value for t2!") 239 | 240 | def __load_delta(self, tfref): 241 | line = tfref.readline() 242 | try: 243 | self.delta = float(line.strip()) 244 | except: 245 | raise ValueError("Invalid value for delta!") 246 | 247 | def __load_nintv(self, tfref): 248 | line = tfref.readline() 249 | try: 250 | self.nintv = int(line.strip()) 251 | except: 252 | raise ValueError("Invalid value for nintv!") 253 | 254 | def __load_ncoef(self, tfref): 255 | line = tfref.readline() 256 | try: 257 | self.ncoef = int(line.strip()) 258 | except: 259 | raise ValueError("Invalid value for ncoef!") 260 | 261 | def __load_loc(self, tfref): 262 | line = tfref.readline() 263 | p1 = line.strip().split(" ") 264 | line = tfref.readline() 265 | p2 = line.strip().split(" ") 266 | line = tfref.readline() 267 | p3 = line.strip().split(" ") 268 | self.loc = [[], [], []] 269 | for i in range(0, len(p1)): 270 | self.loc[0].append(int(p1[i])) 271 | self.loc[1].append(int(p2[i])) 272 | self.loc[2].append(int(p3[i])) 273 | 274 | def __write_binfile_header(self): 275 | self.bfref.write(struct.pack('8s', bytes(self.idf, 'utf-8'))) 276 | self.bfref.write(struct.pack('fffii', self.t1, self.t2, self.delta, self.nintv, self.ncoef)) 277 | self.bfref.write(struct.pack('9i', *self.loc[0])) 278 | self.bfref.write(struct.pack('9i', *self.loc[1])) 279 | self.bfref.write(struct.pack('9i', *self.loc[2])) 280 | 281 | def __bin_record(self, tfref): 282 | line = tfref.readline() 283 | p = line.strip().split(" ") 284 | try: 285 | d1 = float(p[0]) 286 | d2 = float(p[-1]) 287 | except: 288 | raise ValueError("Invalid value for date!") 289 | n = self.ncoef 290 | coefs = [] 291 | while n > 0: 292 | line = tfref.readline() 293 | line = line.strip().replace(" ", " ") 294 | p = line.split(" ") 295 | i = 0 296 | while i < len(p): 297 | try: 298 | v = float(p[i]) 299 | e = float(p[i+1]) 300 | c = v * (10 ** e) 301 | coefs.append(c) 302 | n -= 1 303 | i += 2 304 | except: 305 | raise ValueError("Invalid coefficient!") 306 | self.bfref.write(struct.pack('2f', d1, d2)) 307 | self.bfref.write(struct.pack('%id' % len(coefs), *coefs)) 308 | 309 | def __bin_records(self, tfref): 310 | for i in range(0, self.nintv): 311 | if i % 1000 == 0: 312 | print("{} / {}".format(i, self.nintv)) 313 | self.__bin_record(tfref) 314 | 315 | # bin file support methods 316 | 317 | def binfile_path(self, i): 318 | """ 319 | Build a binary file path. 320 | :param i: index of the file name in the BIN_FILES array 321 | :return: the bin file path (None if error) 322 | """ 323 | if (i >= 0) and (i < self.N_PLANETS): 324 | return os.path.join(BIN_FILES_ROOT, BIN_FILES[i]) 325 | return None 326 | 327 | def binfile_exists(self, i): 328 | """ 329 | Test if a binary file exists. 330 | :param i: index of the binary file name in the BIN_FILES array 331 | :return: true if the file exists 332 | """ 333 | bfp = self.binfile_path(i) 334 | if bfp is not None: 335 | return os.path.isfile(bfp) 336 | return False 337 | 338 | def close_binfile(self): 339 | """ 340 | Close the current binary file if necessary. 341 | """ 342 | if self.bfref is not None: 343 | self.bfref.close() 344 | self.bfref = None 345 | self.bfi = None 346 | 347 | def open_binfile(self, i): 348 | """ 349 | Open the specified binary file. 350 | :param i: index of the binary file in the BIN_FILES array 351 | """ 352 | self.close_binfile() 353 | bfp = self.binfile_path(i) 354 | self.bfref = open(bfp, 'rb') 355 | self.bfi = i 356 | self.__read_binfile_header() 357 | self.doff = self.bfref.tell() 358 | 359 | def __read_binfile_header(self): 360 | self.idf = struct.unpack('8s', self.bfref.read(8))[0] 361 | if self.idf != b'vsop2013': 362 | raise ValueError("Invalid file id! {}".format(self.idf)) 363 | hdr = struct.unpack('fffii', self.bfref.read(5 * 4)) 364 | self.t1 = hdr[0] 365 | self.t2 = hdr[1] 366 | self.delta = hdr[2] 367 | self.nintv = hdr[3] 368 | self.ncoef = hdr[4] 369 | self.loc = [None, None, None] 370 | self.loc[0] = struct.unpack('9i', self.bfref.read(9 * 4)) 371 | self.loc[1] = struct.unpack('9i', self.bfref.read(9 * 4)) 372 | self.loc[2] = struct.unpack('9i', self.bfref.read(9 * 4)) 373 | 374 | def calculate_for(self, ip, jde): 375 | """ 376 | Attempt to calculate the position and velocity for the specified planet. 377 | :param ip: index of planet to perform calculation for 378 | :param jde: the julian date to perform the calculation for 379 | :return: an array of the X, Y, and Z heliocentric position of the planet and the X', Y', and Z' heliocentric velocity 380 | """ 381 | if (ip < 0) or (ip >= self.N_PLANETS): 382 | raise ValueError("Invalid planet index! Is outside range of [{}:{}]".format(0, self.N_PLANETS)) 383 | i = None 384 | rng = None 385 | for i, r in enumerate(self.DATE_RANGES): 386 | if (jde >= r[0]) and (jde < r[1]): 387 | rng = r 388 | break 389 | if rng is None: 390 | raise ValueError("Invalid jde! Is outside range of [{}:{}]".format(self.DATE_RANGES[0][0], self.DATE_RANGES[-1][1])) 391 | # open bin file and read header 392 | if i != self.bfi: 393 | self.open_binfile(i) 394 | # calculate offsets 395 | irec = int((jde - self.t1) / self.delta) 396 | coefs_size = self.ncoef * 8 397 | off = irec * ((2 * 4) + coefs_size) 398 | # read the record for the date 399 | self.bfref.seek(self.doff + off) 400 | dj1, dj2 = struct.unpack('2f', self.bfref.read(2 * 4)) 401 | coefs = array('d') 402 | coefs.fromfile(self.bfref, coefs_size) 403 | iad = self.loc[0][ip] - 1 404 | ncf = self.loc[1][ip] 405 | nsi = self.loc[2][ip] 406 | delta2 = self.delta / nsi 407 | ik = int((jde - dj1) / delta2) 408 | if ik == nsi: 409 | ik -= 1 410 | iloc = iad + (6 * ncf * ik) 411 | dj0 = dj1 + (ik * delta2) 412 | x = (2.0 * (jde - dj0) / delta2) - 1.0 413 | tn = [0.0] * ncf 414 | tn[0] = 1.0 415 | tn[1] = x 416 | for i in range(2, ncf): 417 | tn[i] = (2.0 * x * tn[i - 1]) - tn[i - 2] 418 | r = [0.0] * 6 419 | for i in range(0, 6): 420 | for j in range(0, ncf): 421 | jp = ncf - j - 1 422 | jt = iloc + (ncf * i) + jp 423 | r[i] = r[i] + (tn[jp] * coefs[jt]) 424 | return r 425 | 426 | 427 | if __name__ == "__main__": 428 | 429 | # Create an instance of the VSOP2013File class. 430 | vsop2013 = VSOP2013File() 431 | 432 | # Generate bin files from source text files if necessary. 433 | for i in range(0, len(TXT_FILES)): 434 | if vsop2013.txtfile_exists(i) and not vsop2013.binfile_exists(i): 435 | vsop2013.bin_txtfile(i) 436 | 437 | # Calculate positions and velocities as a control set for algorithm verification. 438 | ndat = 5 439 | YEAR = (-4500, -3000, -1500, 0, 1500, 3000) 440 | TZERO = (77432.5, 625307.5, 1173182.5, 1721057.5, 2268932.5, 2816818.5) 441 | step = 136798.0 442 | for i, tzero in enumerate(TZERO): 443 | print() 444 | print("*** {:>5d} to {:<5d} ***".format(YEAR[i], YEAR[i] + 1500)) 445 | print() 446 | for ip in range(0, vsop2013.N_PLANETS): 447 | for n in range(0, ndat): 448 | jd = tzero + (n * step) 449 | r = vsop2013.calculate_for(ip, jd) 450 | if r[0] != 0.0: 451 | print_results(ip, jd, r) 452 | vsop2013.close_binfile() 453 | --------------------------------------------------------------------------------