Orrery
A GPU-accelerated N-body gravitational simulator
Loading...
Searching...
No Matches
trajectory.hpp File Reference

The trajectory file: what the simulation looked like, frame by frame. More...

#include <cstdint>
#include <filesystem>
#include <fstream>
#include <span>
#include <string_view>
#include <vector>
#include "orrery/core/particle_data.hpp"
#include "orrery/core/types.hpp"
#include "orrery/core/vec3_array.hpp"

Go to the source code of this file.

Classes

struct  orrery::sim::TrajectoryInfo
 What a trajectory file's header says about it. More...
struct  orrery::sim::TrajectoryFrame
 One recorded instant. More...
class  orrery::sim::TrajectoryWriter
 Writes the format above. More...
class  orrery::sim::TrajectoryReader
 Reads the format above. More...

Variables

constexpr std::string_view orrery::sim::kTrajectoryMagic = "ORRERYTJ"
 The eight bytes every trajectory file starts with.
constexpr std::uint32_t orrery::sim::kTrajectoryVersion = 1
 The version of the layout in docs/formats/trajectory.md.

Detailed Description

The trajectory file: what the simulation looked like, frame by frame.

A run that keeps nothing produces one number at the end. A run that writes every step as text produces a file larger than the machine's memory and takes longer to write it than to compute it. This format is the middle: a fixed header, then one frame per recorded step, each holding the positions and optionally the velocities in the same component-array layout the solvers use. docs/formats/trajectory.md is the specification.

Three decisions in it are worth stating here because they are what separate it from a stream of write(&struct) calls.

The masses are in the header, not in the frames. They do not change during a run, and a million-particle trajectory of a thousand frames would otherwise carry eight gigabytes of a number that was already known. Having them at all is what lets a reader compute an energy from a frame without the configuration that produced it.

There is no frame count. The obvious header field cannot be written until the run has finished, and a run that is killed by a full disc, a closed lid or a scheduler is exactly the run whose output someone will want to look at. So frames are self-delimiting and a reader consumes them until the file ends. The cost is that a reader must scan to count them; the benefit is that a file from an interrupted run is a valid file that stops early rather than a file whose header describes frames that are not in it.

Every frame carries its own checksum, rather than the file carrying one. That follows from the same argument: a whole-file checksum can only be verified once the file is complete, so it would be absent from precisely the files that most need checking. A per-frame checksum means a reader can accept every frame that was written completely and reject a final one that was cut in half, and can say which is which.

What this is not

A checkpoint. A trajectory frame is a record for a renderer or an analysis to read, it may hold no velocities at all, and it holds no accelerations even when it does, so a run cannot be resumed from one. sim/checkpoint.hpp exists for that, and ADR-0033 records why the two are separate formats rather than one format with the trajectory as a lossy setting of it.

Variable Documentation

◆ kTrajectoryMagic

std::string_view orrery::sim::kTrajectoryMagic = "ORRERYTJ"
inlineconstexpr

The eight bytes every trajectory file starts with.

A file that does not start with these is not one of these, which is the first thing a reader checks and the reason a checkpoint pointed at a trajectory reader is rejected in the first eight bytes rather than by producing nonsense.

◆ kTrajectoryVersion

std::uint32_t orrery::sim::kTrajectoryVersion = 1
inlineconstexpr

The version of the layout in docs/formats/trajectory.md.

A reader refuses a version it does not know rather than guessing. There is one version and this is it; the field exists so that a second one can be added without the first becoming unreadable.