tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it./opt/orbit2d/include/orbit2d/types.hpp
1 #ifndef ORBIT2D_TYPES_HPP
2 #define ORBIT2D_TYPES_HPP
3
4 #include <cstddef>
5
6 namespace orbit2d {
7
8 /// Planar phase-space state of the massless test particle, expressed in the
9 /// co-rotating (synodic) frame of the two primaries: position (x, y) and
10 /// velocity (vx, vy), both measured *relative to the rotating frame*. All
11 /// quantities are nondimensional (see instruction.md for the unit system).
12 /// This is a plain value type used throughout the engine.
13 struct State {
14 double x{0.0};
15 double y{0.0};
16 double vx{0.0};
17 double vy{0.0};
18 };
19
20 /// Element-wise scaled-add: returns a + h * b. Convenience for combining
21 /// weighted sums of states. Fully implemented (support code).
22 inline State axpy(const State& a, double h, const State& b) {
23 return State{a.x + h * b.x, a.y + h * b.y, a.vx + h * b.vx,
24 a.vy + h * b.vy};
25 }
26
27 } // namespace orbit2d
28
29 #endif // ORBIT2D_TYPES_HPP
30
List project files
find /opt/orbit2d -type f -not -path '*/build/*' -not -path '*/.git/*' | sort
/opt/orbit2d/CMakeLists.txt /opt/orbit2d/examples/l4_neighbourhood.orbit /opt/orbit2d/include/orbit2d/integrator.hpp /opt/orbit2d/include/orbit2d/io.hpp /opt/orbit2d/include/orbit2d/system.hpp /opt/orbit2d/include/orbit2d/types.hpp /opt/orbit2d/src/io.cpp /opt/orbit2d/src/main.cpp /opt/orbit2d/src/solver.cpp /opt/orbit2d/src/system.cpp /opt/orbit2d/tests/CMakeLists.txt /opt/orbit2d/tests/test_smoke.cpp /opt/orbit2d/tests/test_util.hpp [stdout] /opt/orbit2d/CMakeLists.txt /opt/orbit2d/examples/l4_neighbourhood.orbit /opt/orbit2d/include/orbit2d/integrator.hpp /opt/orbit2d/include/orbit2d/io.hpp /opt/orbit2d/include/orbit2d/system.hpp /opt/orbit2d/include/orbit2d/types.hpp /opt/orbit2d/src/io.cpp /opt/orbit2d/src/main.cpp /opt/orbit2d/src/solver.cpp /opt/orbit2d/src/system.cpp /opt/orbit2d/tests/CMakeLists.txt /opt/orbit2d/tests/test_smoke.cpp /opt/orbit2d/tests/test_util.hpp
/opt/orbit2d/include/orbit2d/system.hpp
1 #ifndef ORBIT2D_SYSTEM_HPP
2 #define ORBIT2D_SYSTEM_HPP
3
4 #include "orbit2d/types.hpp"
5
6 namespace orbit2d {
7
8 /// Planar two-primary model in the co-rotating (synodic) frame.
9 ///
10 /// Two primaries move on a fixed circular orbit about their common barycenter;
11 /// the engine works in the frame that rotates with them, so both primaries sit
12 /// at FIXED positions on the x-axis and the (massless) test particle moves under
13 /// their combined gravity plus the fictitious forces of the rotating frame. The
14 /// unit system is nondimensional: total primary mass = 1, primary separation =
15 /// 1, and the frame's angular rate = 1 (all units chosen accordingly). See
16 /// instruction.md for the full physical specification and conventions.
17 ///
18 /// The single physical parameter is the mass parameter
19 /// mu = m_secondary / (m_primary + m_secondary) in (0, 1/2],
20 /// the mass FRACTION carried by the smaller primary (this is NOT G*M). With this
21 /// convention the heavier primary has mass (1 - mu) and the lighter has mass mu.
22 ///
23 /// This class is a data container plus pure-geometry helpers (the primary
24 /// positions and the two primary-relative distances) that ARE provided, and the
25 /// dynamics core (the assembled right-hand side of the equations of motion and
26 /// the model's conserved integral) which is NOT , those are implemented in
27 /// src/system.cpp and ship as stubs for the candidate to complete.
28 class System {
29 public:
30 /// Construct with mass parameter `mu`. Throws std::invalid_argument unless
31 /// 0 < mu <= 1/2.
32 explicit System(double mu);
33
34 double mu() const { return mu_; }
35
36 /// x-coordinate of the heavier primary (mass 1 - mu). Support code.
37 double primary1_x() const { return -mu_; }
38
39 /// x-coordinate of the lighter primary (mass mu). Support code.
40 double primary2_x() const { return 1.0 - mu_; }
41
42 /// Distance from the test particle to the heavier primary. Support code.
43 double r1(const State& s) const;
44
45 /// Distance from the test particle to the lighter primary. Support code.
46 double r2(const State& s) const;
47
48 /// Distance from the barycentre (frame origin), sqrt(x^2 + y^2). Support
49 /// code; provided for convenience only.
50 double radius(const State& s) const;
51
52 // ------------------------------------------------------------------------
53 // DYNAMICS CORE -- ships as a stub in src/system.cpp (see the TODO there).
54 // ------------------------------------------------------------------------
55
56 /// Right-hand side f(s) of the first-order system s' = f(s) for this model,
57 /// with s = (x, y, vx, vy). The first two components are the kinematic
58 /// identities x' = vx, y' = vy; the velocity-derivative components encode
59 /// the particle's acceleration in the rotating frame (the combined gravity
60 /// of both primaries together with the rotating-frame fictitious forces).
61 /// Throws std::runtime_error if the particle sits exactly on either primary
62 /// (a gravitational singularity). NOT IMPLEMENTED in the shipped stub.
63 State rhs(const State& s) const;
64
65 /// The model's single isolating integral of motion -- the Jacobi integral
66 /// (the rotating-frame analogue of energy), conserved along every exact
67 /// trajectory. See instruction.md for the sign/scale convention and numeric
68 /// anchors. Throws std::runtime_error on a primary singularity. NOT
69 /// IMPLEMENTED in the shipped stub.
70 double jacobi(const State& s) const;
71
72 private:
73 double mu_{0.0};
74 };
75
76 } // namespace orbit2d
77
78 #endif // ORBIT2D_SYSTEM_HPP
79
/opt/orbit2d/include/orbit2d/integrator.hpp
1 #ifndef ORBIT2D_INTEGRATOR_HPP
2 #define ORBIT2D_INTEGRATOR_HPP
3
4 #include <cstddef>
5 #include <vector>
6
7 #include "orbit2d/system.hpp"
8 #include "orbit2d/types.hpp"
9
10 namespace orbit2d {
11
12 /// Result of a fixed-step propagation.
13 struct Trajectory {
14 /// Sampled states, one per recorded step. states[0] is the initial state
15 /// and states.back() is the state at the final time. Length is
16 /// n_steps + 1.
17 std::vector<State> states;
18
19 /// Number of integration steps actually taken.
20 std::size_t steps{0};
21
22 /// Final simulated time = steps * dt (in nondimensional time units).
23 double time{0.0};
24 };
25
26 /// Result of an event-terminated propagation (see Integrator::propagate_to_event).
27 struct EventHit {
28 /// The state at the located event, refined onto the event surface.
29 State state{};
30
31 /// The time (from the start of the propagation) at which the event occurs.
32 double time{0.0};
33
34 /// Number of whole base steps taken before the bracketing interval that
35 /// contained the event (diagnostic; not checked for an exact value).
36 std::size_t steps{0};
37
38 /// True iff an event was located before the time budget was exhausted.
39 bool found{false};
40 };
41
42 /// Fixed-step time integrator for the dynamical model defined by `System`. The
43 /// integrator is method-agnostic to the caller: it advances `System::rhs` and
44 /// records states; the numerical scheme is part of the contract (see
45 /// instruction.md), not of this interface.
46 class Integrator {
47 public:
48 explicit Integrator(const System& system) : system_(system) {}
49
50 /// Advance a single state by ONE step of size `dt`, returning the state at
51 /// the next time level. `dt` must be strictly positive (throws
52 /// std::invalid_argument otherwise).
53 State step(const State& s, double dt) const;
54
55 /// Propagate `initial` for `n_steps` steps of size `dt`, recording every
56 /// intermediate state. Returns a Trajectory with states.size() ==
57 /// n_steps + 1, steps == n_steps, time == n_steps * dt. `dt` must be
58 /// strictly positive (throws std::invalid_argument otherwise). Zero steps
59 /// returns just the initial state at time 0.
60 Trajectory propagate(const State& initial, double dt,
61 std::size_t n_steps) const;
62
63 /// Propagate `initial` with base step `dt` until the FIRST x-axis crossing
64 /// in the negative-y sense strictly after t = 0 (the value y(t) passing
65 /// from positive to negative), then refine the crossing onto the event
66 /// surface y = 0. Search no longer than `t_max`. See instruction.md for the
67 /// precise event definition, the crossing sense, and the refinement
68 /// tolerance the located state must satisfy. `dt` must be strictly positive
69 /// and `t_max` strictly positive (throws std::invalid_argument otherwise).
70 /// If no qualifying crossing occurs within `t_max`, returns an EventHit with
71 /// found == false.
72 EventHit propagate_to_event(const State& initial, double dt,
73 double t_max) const;
74
75 private:
76 const System& system_;
77 };
78
79 } // namespace orbit2d
80
81 #endif // ORBIT2D_INTEGRATOR_HPP
82
/opt/orbit2d/src/system.cpp
1 #include "orbit2d/system.hpp"
2
3 #include <cmath>
4 #include <stdexcept>
5
6 namespace orbit2d {
7
8 System::System(double mu) : mu_(mu) {
9 if (!(mu_ > 0.0) || !(mu_ <= 0.5)) {
10 throw std::invalid_argument("System: mu must satisfy 0 < mu <= 1/2");
11 }
12 }
13
14 double System::radius(const State& s) const {
15 return std::sqrt(s.x * s.x + s.y * s.y);
16 }
17
18 double System::r1(const State& s) const {
19 const double dx = s.x - primary1_x();
20 return std::sqrt(dx * dx + s.y * s.y);
21 }
22
23 double System::r2(const State& s) const {
24 const double dx = s.x - primary2_x();
25 return std::sqrt(dx * dx + s.y * s.y);
26 }
27
28 // ============================================================================
29 // TODO(candidate): Implement the dynamics core described in instruction.md:
30 // the assembled right-hand side rhs() and the conserved integral jacobi().
31 // Both ship as stubs:
32 // * rhs() returns an all-zero derivative, so the particle never accelerates
33 // (and, with the stubbed integrator, never moves);
34 // * jacobi() returns 0, which is not the model's integral of motion.
35 // Replace both bodies with correct implementations derived from the physical
36 // model and conventions in instruction.md. Use the provided geometry helpers
37 // (primary positions, r1(), r2()). Do NOT change the public signatures.
38 //
39 // rhs(): the first two derivative components are the kinematic identities
40 // x' = vx, y' = vy. The velocity-derivative components are the particle's
41 // rotating-frame acceleration. Throw std::runtime_error if the particle sits
42 // exactly on a primary (r1 == 0 or r2 == 0).
43 // jacobi(): return the model's isolating integral in the sign/scale convention
44 // fixed by instruction.md. Throw std::runtime_error on a primary singularity.
45 // ============================================================================
46
47 State System::rhs(const State& s) const {
48 // TODO(candidate): assemble and return the first-order right-hand side.
49 (void)s;
50 return State{}; // zero derivative -> wrong (no acceleration)
51 }
52
53 double System::jacobi(const State& s) const {
54 // TODO(candidate): compute and return the model's conserved integral.
55 (void)s;
56 return 0.0; // wrong: not the Jacobi integral
57 }
58
59 } // namespace orbit2d
60
/opt/orbit2d/src/solver.cpp
1 #include "orbit2d/integrator.hpp"
2
3 #include <cstddef>
4 #include <stdexcept>
5 #include <vector>
6
7 #include "orbit2d/system.hpp"
8 #include "orbit2d/types.hpp"
9
10 namespace orbit2d {
11
12 // ============================================================================
13 // TODO(candidate): Implement the fixed-step time integrator and the
14 // event-terminated propagation for the model described in instruction.md.
15 //
16 // The three member functions below ship as stubs: step() returns its input
17 // state UNCHANGED (the particle never moves), propagate() merely records that
18 // frozen state with the wrong final time, and propagate_to_event() reports that
19 // no event was found. Every trajectory is therefore wrong and the test suite
20 // FAILS. Replace the stub bodies with correct implementations.
21 //
22 // Do NOT change the public signatures declared in
23 // include/orbit2d/integrator.hpp. Advance the dynamics through System::rhs
24 // (System holds the model and its parameter); the State helper axpy(a, h, b) ==
25 // a + h*b from include/orbit2d/types.hpp is available for combining stages and
26 // forming weighted sums. You should only need to edit this file together with
27 // the dynamics core in src/system.cpp.
28 //
29 // The required behaviour -- the numerical scheme and its order of accuracy, the
30 // trajectory bookkeeping, the conserved-quantity tolerances, the event
31 // definition and its refinement onto the event surface -- is specified in
32 // instruction.md and locked down by the hidden test suite. A do-nothing
33 // integrator trivially "conserves" the integral of motion but fails because the
34 // particle does not move; the suite pairs every invariant with an evolution
35 // check.
36 //
37 // step(): require dt > 0 (else std::invalid_argument).
38 // propagate(): require dt > 0 (else std::invalid_argument); record the
39 // initial state as states[0], advance n_steps times.
40 // propagate_to_event(): require dt > 0 and t_max > 0 (else
41 // std::invalid_argument); locate and refine the event as
42 // specified in instruction.md.
43 // ============================================================================
44
45 State Integrator::step(const State& s, double dt) const {
46 // TODO(candidate): perform one valid step using system_.rhs and return the
47 // advanced state.
48 (void)dt;
49 (void)system_;
50 return s; // unchanged -> wrong (particle frozen)
51 }
52
53 Trajectory Integrator::propagate(const State& initial, double dt,
54 std::size_t n_steps) const {
55 // TODO(candidate): record the initial state, then step n_steps times.
56 (void)dt;
57 Trajectory traj;
58 traj.states.assign(n_steps + 1, initial); // never evolved -> wrong
59 traj.steps = n_steps;
60 traj.time = 0.0; // wrong: should be n_steps * dt
61 return traj;
62 }
63
64 EventHit Integrator::propagate_to_event(const State& initial, double dt,
65 double t_max) const {
66 // TODO(candidate): integrate until the first qualifying x-axis crossing and
67 // refine it onto the event surface.
68 (void)dt;
69 (void)t_max;
70 EventHit hit;
71 hit.state = initial;
72 hit.time = 0.0;
73 hit.steps = 0;
74 hit.found = false; // wrong: never locates the event
75 return hit;
76 }
77
78 } // namespace orbit2d
79
/opt/orbit2d/tests/test_smoke.cpp
1 // Trivial smoke test: confirms the engine links and that the provided geometry
2 // support code (mass-parameter validation and primary positions / distances)
3 // behaves. It deliberately does NOT exercise the dynamics core or the
4 // integrator, so it passes against the shipped stub. The behavioural test suite
5 // that pins the dynamics is provided separately by the grader.
6
7 #include <cmath>
8 #include <stdexcept>
9
10 #include "orbit2d/integrator.hpp"
11 #include "orbit2d/system.hpp"
12 #include "orbit2d/types.hpp"
13 #include "test_util.hpp"
14
15 using orbit2d::State;
16 using orbit2d::System;
17
18 ORBIT2D_TEST("system_construction_and_geometry") {
19 System sys(0.2);
20 orbit2d_test::expect_near(sys.mu(), 0.2, 1e-15, "mu stored");
21 orbit2d_test::expect_near(sys.primary1_x(), -0.2, 1e-15, "primary1 at -mu");
22 orbit2d_test::expect_near(sys.primary2_x(), 0.8, 1e-15, "primary2 at 1-mu");
23
24 // Distances to the two primaries from a sample point.
25 const State s{0.3, 0.4, 0.0, 0.0};
26 const double want_r1 = std::sqrt((0.3 + 0.2) * (0.3 + 0.2) + 0.16);
27 const double want_r2 = std::sqrt((0.3 - 0.8) * (0.3 - 0.8) + 0.16);
28 orbit2d_test::expect_near(sys.r1(s), want_r1, 1e-12, "r1 geometry");
29 orbit2d_test::expect_near(sys.r2(s), want_r2, 1e-12, "r2 geometry");
30 orbit2d_test::expect_near(sys.radius(s), 0.5, 1e-12, "radius geometry");
31 }
32
33 ORBIT2D_TEST("mass_parameter_validation") {
34 bool too_small = false, too_big = false, ok = true;
35 try {
36 System bad(0.0);
37 (void)bad;
38 } catch (const std::exception&) {
39 too_small = true;
40 }
41 try {
42 System bad(0.7);
43 (void)bad;
44 } catch (const std::exception&) {
45 too_big = true;
46 }
47 try {
48 System good(0.5);
49 (void)good;
50 } catch (const std::exception&) {
51 ok = false;
52 }
53 orbit2d_test::expect(too_small, "mu = 0 rejected");
54 orbit2d_test::expect(too_big, "mu > 1/2 rejected");
55 orbit2d_test::expect(ok, "mu = 1/2 accepted");
56 }
57
58 ORBIT2D_TEST_MAIN()
59
/opt/orbit2d/tests/test_util.hpp
1 #ifndef ORBIT2D_TEST_UTIL_HPP
2 #define ORBIT2D_TEST_UTIL_HPP
3
4 // Minimal dependency-free test harness for the orbit2d suite.
5 //
6 // Each test executable defines test functions and registers them with
7 // ORBIT2D_TEST(name) { ... }. main() (provided by ORBIT2D_TEST_MAIN) runs every
8 // registered test and returns non-zero if any assertion fails, which CTest
9 // interprets as a failure.
10
11 #include <cmath>
12 #include <cstddef>
13 #include <functional>
14 #include <iostream>
15 #include <string>
16 #include <vector>
17
18 namespace orbit2d_test {
19
20 struct Case {
21 std::string name;
22 std::function<void()> fn;
23 };
24
25 inline std::vector<Case>& registry() {
26 static std::vector<Case> cases;
27 return cases;
28 }
29
30 struct Registrar {
31 Registrar(const std::string& name, std::function<void()> fn) {
32 registry().push_back(Case{name, std::move(fn)});
33 }
34 };
35
36 // Thrown by an assertion failure to abort the current test case.
37 struct AssertionError {
38 std::string message;
39 };
40
41 inline void expect(bool cond, const std::string& what) {
42 if (!cond) {
43 throw AssertionError{what};
44 }
45 }
46
47 inline void expect_near(double got, double want, double tol, const std::string& what) {
48 if (std::fabs(got - want) > tol) {
49 throw AssertionError{what + " (got " + std::to_string(got) +
50 ", want " + std::to_string(want) +
51 ", tol " + std::to_string(tol) + ")"};
52 }
53 }
54
55 // Relative-error check (with an absolute floor) for quantities whose scale
56 // varies widely across cases.
57 inline void expect_rel(double got, double want, double rel_tol,
58 const std::string& what) {
59 const double denom = std::fabs(want) > 1e-300 ? std::fabs(want) : 1.0;
60 if (std::fabs(got - want) / denom > rel_tol) {
61 throw AssertionError{what + " (got " + std::to_string(got) +
62 ", want " + std::to_string(want) +
63 ", rel_tol " + std::to_string(rel_tol) + ")"};
64 }
65 }
66
67 inline int run_all() {
68 int failures = 0;
69 for (const auto& c : registry()) {
70 try {
71 c.fn();
72 std::cout << "[ PASS ] " << c.name << "\n";
73 } catch (const AssertionError& e) {
74 std::cout << "[ FAIL ] " << c.name << ": " << e.message << "\n";
75 ++failures;
76 } catch (const std::exception& e) {
77 std::cout << "[ FAIL ] " << c.name
78 << ": unexpected exception: " << e.what() << "\n";
79 ++failures;
80 } catch (...) {
81 std::cout << "[ FAIL ] " << c.name << ": unknown exception\n";
82 ++failures;
83 }
84 }
85 std::cout << "----\n"
86 << (registry().size() - failures) << "/" << registry().size()
87 << " tests passed\n";
88 return failures == 0 ? 0 : 1;
89 }
90
91 } // namespace orbit2d_test
92
93 #define ORBIT2D_CONCAT_INNER(a, b) a##b
94 #define ORBIT2D_CONCAT(a, b) ORBIT2D_CONCAT_INNER(a, b)
95
96 #define ORBIT2D_TEST(NAME) \
97 static void ORBIT2D_CONCAT(orbit2d_test_fn_, __LINE__)(); \
98 static ::orbit2d_test::Registrar ORBIT2D_CONCAT(orbit2d_test_reg_, \
99 __LINE__){ \
100 NAME, &ORBIT2D_CONCAT(orbit2d_test_fn_, __LINE__)}; \
101 static void ORBIT2D_CONCAT(orbit2d_test_fn_, __LINE__)()
102
103 #define ORBIT2D_TEST_MAIN() \
104 int main() { return ::orbit2d_test::run_all(); }
105
106 #endif // ORBIT2D_TEST_UTIL_HPP
107
/opt/orbit2d/src/system.cpp
State System::rhs(const State& s) const {
// TODO(candidate): assemble and return the first-order right-hand side.
(void)s;
return State{}; // zero derivative -> wrong (no acceleration)
}
double System::jacobi(const State& s) const {
// TODO(candidate): compute and return the model's conserved integral.
(void)s;
return 0.0; // wrong: not the Jacobi integral
}State System::rhs(const State& s) const {
const double d1 = r1(s);
const double d2 = r2(s);
if (d1 == 0.0 || d2 == 0.0) {
throw std::runtime_error("System::rhs: particle on a primary singularity");
}
const double m1 = 1.0 - mu_; // heavier primary, at x = -mu
const double m2 = mu_; // lighter primary, at x = 1 - mu
const double r1_3 = d1 * d1 * d1;
const double r2_3 = d2 * d2 * d2;
// Gradient of the effective potential Omega = (x^2+y^2)/2 + m1/r1 + m2/r2:
// Omega_x = x - m1 (x - x1)/r1^3 - m2 (x - x2)/r2^3
// Omega_y = y - m1 y /r1^3 - m2 y /r2^3
const double dx1 = s.x - primary1_x();
const double dx2 = s.x - primary2_x();
const double Ox = s.x - m1 * dx1 / r1_3 - m2 * dx2 / r2_3;
const double Oy = s.y - m1 * s.y / r1_3 - m2 * s.y / r2_3;
// Rotating-frame equations of motion (unit angular rate about +z):
// x'' = 2 vy + Omega_x
// y'' = -2 vx + Omega_y
const double ax = 2.0 * s.vy + Ox;
const double ay = -2.0 * s.vx + Oy;
return State{s.vx, s.vy, ax, ay};
}
double System::jacobi(const State& s) const {
const double d1 = r1(s);
const double d2 = r2(s);
if (d1 == 0.0 || d2 == 0.0) {
throw std::runtime_error("System::jacobi: particle on a primary singularity");
}
const double m1 = 1.0 - mu_;
const double m2 = mu_;
// C = x^2 + y^2 + 2 m1/r1 + 2 m2/r2 - (vx^2 + vy^2) = 2*Omega - v^2.
const double v2 = s.vx * s.vx + s.vy * s.vy;
return s.x * s.x + s.y * s.y + 2.0 * m1 / d1 + 2.0 * m2 / d2 - v2;
}/opt/orbit2d/src/solver.cpp
State Integrator::step(const State& s, double dt) const {
// TODO(candidate): perform one valid step using system_.rhs and return the
// advanced state.
(void)dt;
(void)system_;
return s; // unchanged -> wrong (particle frozen)
}
Trajectory Integrator::propagate(const State& initial, double dt,
std::size_t n_steps) const {
// TODO(candidate): record the initial state, then step n_steps times.
(void)dt;
Trajectory traj;
traj.states.assign(n_steps + 1, initial); // never evolved -> wrong
traj.steps = n_steps;
traj.time = 0.0; // wrong: should be n_steps * dt
return traj;
}
EventHit Integrator::propagate_to_event(const State& initial, double dt,
double t_max) const {
// TODO(candidate): integrate until the first qualifying x-axis crossing and
// refine it onto the event surface.
(void)dt;
(void)t_max;
EventHit hit;
hit.state = initial;
hit.time = 0.0;
hit.steps = 0;
hit.found = false; // wrong: never locates the event
return hit;
}State Integrator::step(const State& s, double dt) const {
if (!(dt > 0.0)) {
throw std::invalid_argument("Integrator::step: dt must be > 0");
}
// Classical fourth-order Runge-Kutta. Global error is O(dt^4); for an
// autonomous, time-reversible field it respects the system's reflection
// symmetry to that order.
const State k1 = system_.rhs(s);
const State k2 = system_.rhs(axpy(s, 0.5 * dt, k1));
const State k3 = system_.rhs(axpy(s, 0.5 * dt, k2));
const State k4 = system_.rhs(axpy(s, dt, k3));
State next = s;
next = axpy(next, dt / 6.0, k1);
next = axpy(next, dt / 3.0, k2);
next = axpy(next, dt / 3.0, k3);
next = axpy(next, dt / 6.0, k4);
return next;
}
Trajectory Integrator::propagate(const State& initial, double dt,
std::size_t n_steps) const {
if (!(dt > 0.0)) {
throw std::invalid_argument("Integrator::propagate: dt must be > 0");
}
Trajectory traj;
traj.states.reserve(n_steps + 1);
traj.states.push_back(initial);
State s = initial;
for (std::size_t i = 0; i < n_steps; ++i) {
s = step(s, dt);
traj.states.push_back(s);
}
traj.steps = n_steps;
traj.time = static_cast<double>(n_steps) * dt;
return traj;
}
EventHit Integrator::propagate_to_event(const State& initial, double dt,
double t_max) const {
if (!(dt > 0.0)) {
throw std::invalid_argument(
"Integrator::propagate_to_event: dt must be > 0");
}
if (!(t_max > 0.0)) {
throw std::invalid_argument(
"Integrator::propagate_to_event: t_max must be > 0");
}
constexpr double kTol = 1e-9;
EventHit hit;
State s = initial;
double t = 0.0;
std::size_t whole_steps = 0;
while (t < t_max) {
const double h = dt; // base step
const State s_next = step(s, h);
// Descending crossing of y = 0: y goes from strictly positive to
// non-positive across this step.
if (s.y > 0.0 && s_next.y <= 0.0) {
// Refine the crossing time tau in (0, h] by bisection on a single
// step taken from the bracket's start state s.
double lo = 0.0; // y(lo) = s.y > 0
double hi = h; // y(hi) = s_next.y <= 0
State located = s_next;
for (int it = 0; it < 200; ++it) {
const double mid = 0.5 * (lo + hi);
const State sm = step(s, mid);
located = sm;
if (std::fabs(sm.y) < kTol) {
break;
}
if (sm.y > 0.0) {
lo = mid;
} else {
hi = mid;
}
if (hi - lo < 1e-16) {
break;
}
}
// Recover tau accurately from the converged bracket midpoint.
const double tau = 0.5 * (lo + hi);
const double event_time = t + tau;
if (event_time <= t_max) {
hit.state = located;
hit.time = event_time;
hit.steps = whole_steps;
hit.found = true;
return hit;
}
// Crossing lies beyond the time budget.
hit.found = false;
return hit;
}
s = s_next;
t += h;
++whole_steps;
}
hit.found = false;
return hit;
}/opt/orbit2d/src/solver.cpp
double lo = 0.0; // y(lo) = s.y > 0
double hi = h; // y(hi) = s_next.y <= 0
State located = s_next;
for (int it = 0; it < 200; ++it) {
const double mid = 0.5 * (lo + hi);
const State sm = step(s, mid);
located = sm;
if (std::fabs(sm.y) < kTol) {
break;
}
if (sm.y > 0.0) {
lo = mid;
} else {
hi = mid;
}
if (hi - lo < 1e-16) {
break;
}
}
// Recover tau accurately from the converged bracket midpoint.
const double tau = 0.5 * (lo + hi);
const double event_time = t + tau; double lo = 0.0; // y(lo) = s.y > 0
double hi = h; // y(hi) = s_next.y <= 0
State located = s_next;
double tau = h;
for (int it = 0; it < 200; ++it) {
const double mid = 0.5 * (lo + hi);
const State sm = step(s, mid);
located = sm;
tau = mid;
if (std::fabs(sm.y) < kTol) {
break;
}
if (sm.y > 0.0) {
lo = mid;
} else {
hi = mid;
}
if (hi - lo < 1e-16) {
break;
}
}
const double event_time = t + tau;Configure, build, run tests
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1 && cmake --build build --parallel 2>&1 | tail -5 && cd build && ctest --output-on-failure 2>&1 | tail -20
| labs
gmake[2]: *** [CMakeFiles/orbit2d.dir/build.make:90: CMakeFiles/orbit2d.dir/src/solver.cpp.o] Error 1
gmake[2]: *** Waiting for unfinished jobs....
gmake[1]: *** [CMakeFiles/Makefile2:102: CMakeFiles/orbit2d.dir/all] Error 2
gmake: *** [Makefile:101: all] Error 2
opt/orbit2d/build/tests/Debug/test_smoke
opt/orbit2d/build/tests/Debug/test_smoke
opt/orbit2d/build/tests/MinSizeRel/test_smoke
opt/orbit2d/build/tests/MinSizeRel/test_smoke
opt/orbit2d/build/tests/RelWithDebInfo/test_smoke
opt/orbit2d/build/tests/RelWithDebInfo/test_smoke
opt/orbit2d/build/tests/Deployment/test_smoke
opt/orbit2d/build/tests/Deployment/test_smoke
opt/orbit2d/build/tests/Development/test_smoke
opt/orbit2d/build/tests/Development/test_smoke
Unable to find executable: /opt/orbit2d/build/tests/test_smoke
1/1 Test #1: smoke ............................***Not Run 0.00 sec
0% tests passed, 1 tests failed out of 1
Total Test time (real) = 0.00 sec
The following tests FAILED:
1 - smoke (Not Run)
Errors while running CTest
[stdout]
| labs
gmake[2]: *** [CMakeFiles/orbit2d.dir/build.make:90: CMakeFiles/orbit2d.dir/src/solver.cpp.o] Error 1
gmake[2]: *** Waiting for unfinished jobs....
gmake[1]: *** [CMakeFiles/Makefile2:102: CMakeFiles/orbit2d.dir/all] Error 2
gmake: *** [Makefile:101: all] Error 2
opt/orbit2d/build/tests/Debug/test_smoke
opt/orbit2d/build/tests/Debug/test_smoke
opt/orbit2d/build/tests/MinSizeRel/test_smoke
opt/orbit2d/build/tests/MinSizeRel/test_smoke
opt/orbit2d/build/tests/RelWithDebInfo/test_smoke
opt/orbit2d/build/tests/RelWithDebInfo/test_smoke
opt/orbit2d/build/tests/Deployment/test_smoke
opt/orbit2d/build/tests/Deployment/test_smoke
opt/orbit2d/build/tests/Development/test_smoke
opt/orbit2d/build/tests/Development/test_smoke
Unable to find executable: /opt/orbit2d/build/tests/test_smoke
1/1 Test #1: smoke ............................***Not Run 0.00 sec
0% tests passed, 1 tests failed out of 1
Total Test time (real) = 0.00 sec
The following tests FAILED:
1 - smoke (Not Run)
Errors while running CTestShow compile error
cd /opt/orbit2d && cmake --build build 2>&1 | grep -A3 -i error | head -30
/opt/orbit2d/src/solver.cpp:123:26: error: 'fabs' is not a member of 'std'; did you mean 'labs'?
123 | if (std::fabs(sm.y) < kTol) {
| ^~~~
| labs
gmake[2]: *** [CMakeFiles/orbit2d.dir/build.make:90: CMakeFiles/orbit2d.dir/src/solver.cpp.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:102: CMakeFiles/orbit2d.dir/all] Error 2
gmake: *** [Makefile:101: all] Error 2
[stdout]
/opt/orbit2d/src/solver.cpp:123:26: error: 'fabs' is not a member of 'std'; did you mean 'labs'?
123 | if (std::fabs(sm.y) < kTol) {
| ^~~~
| labs
gmake[2]: *** [CMakeFiles/orbit2d.dir/build.make:90: CMakeFiles/orbit2d.dir/src/solver.cpp.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:102: CMakeFiles/orbit2d.dir/all] Error 2
gmake: *** [Makefile:101: all] Error 2/opt/orbit2d/src/solver.cpp
#include "orbit2d/integrator.hpp" #include <cstddef>
#include "orbit2d/integrator.hpp" #include <cmath> #include <cstddef>
Rebuild and test
cmake --build build --parallel 2>&1 | tail -3 && cd build && ctest --output-on-failure 2>&1 | tail -8
[ 87%] Built target orbit2d_cli
[100%] Linking CXX executable test_smoke
[100%] Built target test_smoke
Test project /opt/orbit2d/build
Start 1: smoke
1/1 Test #1: smoke ............................ Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.00 sec
[stdout]
[ 87%] Built target orbit2d_cli
[100%] Linking CXX executable test_smoke
[100%] Built target test_smoke
Test project /opt/orbit2d/build
Start 1: smoke
1/1 Test #1: smoke ............................ Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.00 secStandalone behavioural verification
cat > /tmp/verify.cpp <<'EOF'
#include <cmath>
#include <cstdio>
#include "orbit2d/system.hpp"
#include "orbit2d/integrator.hpp"
using namespace orbit2d;
int main(){
System sys(0.1);
// anchors
printf("jacobi(0.5,0,0,0)=%.6f (want 3.75)\n", sys.jacobi({0.5,0,0,0}));
printf("jacobi(0.4,sqrt3/2,0,0)=%.6f (want 2.91)\n", sys.jacobi({0.4,std::sqrt(3.0)/2,0,0}));
// L4 equilibrium: at rest, rhs accel ~ 0
State l4{0.5-0.1, std::sqrt(3.0)/2, 0,0};
State f = sys.rhs(l4);
printf("L4 accel = (%.2e, %.2e)\n", f.vx, f.vy);
Integrator it(sys);
State adv = it.step(l4, 0.01);
printf("L4 drift after 1000 steps: ");
State s=l4; for(int i=0;i<1000;i++) s=it.step(s,0.01);
printf("dx=%.2e dy=%.2e\n", s.x-l4.x, s.y-l4.y);
// energy conservation on a generic orbit
State s0{0.5,0.0,0.0,0.5};
double j0=sys.jacobi(s0);
s=s0; for(int i=0;i<2000;i++) s=it.step(s,0.005);
printf("jacobi drift over T=10: %.3e\n", sys.jacobi(s)-j0);
// RK4 order: error vs dt should scale ~dt^4
auto solveTo=[&](double dt,int n){State a=s0;for(int i=0;i<n;i++)a=it.step(a,dt);return a;};
State ref=solveTo(0.0005,20000); // fine ref, T=10
double T=10;
double e1,e2;
{State a=solveTo(0.01,1000);e1=std::fabs(a.x-ref.x)+std::fabs(a.y-ref.y);}
{State a=solveTo(0.005,2000);e2=std::fabs(a.x-ref.x)+std::fabs(a.y-ref.y);}
printf("err(0.01)=%.3e err(0.005)=%.3e ratio=%.2f (want ~16)\n", e1,e2,e1/e2);
// time reversal: M=(x,-y,-vx,vy); forward, reflect, forward = reflected initial
auto M=[](State a){return State{a.x,-a.y,-a.vx,a.vy};};
State A=s0; for(int i=0;i<400;i++)A=it.step(A,0.01);
State B=M(A); for(int i=0;i<400;i++)B=it.step(B,0.01);
State want=M(s0);
printf("reversal err: %.3e\n", std::fabs(B.x-want.x)+std::fabs(B.y-want.y)+std::fabs(B.vx-want.vx)+std::fabs(B.vy-want.vy));
// event: start with y>0 descending
State e0{0.5, 0.3, 0.0, -0.4};
EventHit h=it.propagate_to_event(e0,0.01,50.0);
printf("event found=%d |y|=%.2e t=%.4f\n", h.found, std::fabs(h.state.y), h.time);
return 0;
}
EOF
g++ -std=c++17 -O2 -I/opt/orbit2d/include /tmp/verify.cpp /opt/orbit2d/src/system.cpp /opt/orbit2d/src/solver.cpp -o /tmp/verify && /tmp/verifyjacobi(0.5,0,0,0)=3.750000 (want 3.75) jacobi(0.4,sqrt3/2,0,0)=2.910000 (want 2.91) L4 accel = (-1.39e-16, -2.78e-16) L4 drift after 1000 steps: dx=0.00e+00 dy=0.00e+00 jacobi drift over T=10: 5.022e-08 err(0.01)=1.822e-05 err(0.005)=7.943e-07 ratio=22.94 (want ~16) reversal err: 5.267e-06 event found=1 |y|=6.59e-10 t=0.5986 [stdout] jacobi(0.5,0,0,0)=3.750000 (want 3.75) jacobi(0.4,sqrt3/2,0,0)=2.910000 (want 2.91) L4 accel = (-1.39e-16, -2.78e-16) L4 drift after 1000 steps: dx=0.00e+00 dy=0.00e+00 jacobi drift over T=10: 5.022e-08 err(0.01)=1.822e-05 err(0.005)=7.943e-07 ratio=22.94 (want ~16) reversal err: 5.267e-06 event found=1 |y|=6.59e-10 t=0.5986
No file changes, the agent left the workspace untouched.
== Configuring (cmake) ==
-- The CXX compiler identification is GNU 11.4.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/tmp.jlQe8ZR2xS/orbit2d/build_grade
== Building ==
[ 5%] Building CXX object CMakeFiles/orbit2d.dir/src/system.cpp.o
[ 10%] Building CXX object CMakeFiles/orbit2d.dir/src/solver.cpp.o
[ 15%] Building CXX object CMakeFiles/orbit2d.dir/src/io.cpp.o
[ 20%] Linking CXX static library liborbit2d.a
[ 20%] Built target orbit2d
[ 25%] Building CXX object CMakeFiles/orbit2d_cli.dir/src/main.cpp.o
[ 30%] Building CXX object tests/CMakeFiles/test_field.dir/test_field.cpp.o
[ 35%] Building CXX object tests/CMakeFiles/test_orbit.dir/test_orbit.cpp.o
[ 40%] Building CXX object tests/CMakeFiles/test_symmetry.dir/test_symmetry.cpp.o
[ 45%] Building CXX object tests/CMakeFiles/test_equilibria.dir/test_equilibria.cpp.o
[ 50%] Building CXX object tests/CMakeFiles/test_event.dir/test_event.cpp.o
[ 55%] Building CXX object tests/CMakeFiles/test_invariants.dir/test_invariants.cpp.o
[ 60%] Building CXX object tests/CMakeFiles/test_inertial.dir/test_inertial.cpp.o
[ 65%] Linking CXX executable orbit2d_cli
[ 65%] Built target orbit2d_cli
[ 70%] Linking CXX executable test_field
[ 75%] Linking CXX executable test_inertial
[ 80%] Linking CXX executable test_symmetry
[ 80%] Built target test_field
[ 80%] Built target test_symmetry
[ 85%] Linking CXX executable test_orbit
[ 85%] Built target test_inertial
[ 90%] Linking CXX executable test_event
[ 90%] Built target test_orbit
[ 95%] Linking CXX executable test_invariants
[100%] Linking CXX executable test_equilibria
[100%] Built target test_event
[100%] Built target test_invariants
[100%] Built target test_equilibria
== Running hidden test suite (ctest) ==
Test project /tmp/tmp.jlQe8ZR2xS/orbit2d/build_grade
Start 1: test_field
1/7 Test #1: test_field ....................... Passed 0.00 sec
Start 2: test_orbit
2/7 Test #2: test_orbit ....................... Passed 0.01 sec
Start 3: test_symmetry
3/7 Test #3: test_symmetry .................... Passed 0.02 sec
Start 4: test_equilibria
4/7 Test #4: test_equilibria .................. Passed 0.00 sec
Start 5: test_event
5/7 Test #5: test_event ....................... Passed 0.00 sec
Start 6: test_invariants
6/7 Test #6: test_invariants .................. Passed 0.01 sec
Start 7: test_inertial
7/7 Test #7: test_inertial .................... Passed 0.00 sec
100% tests passed, 0 tests failed out of 7
Label Time Summary:
hidden = 0.05 sec*proc (7 tests)
Total Test time (real) = 0.06 sec
PASS: all hidden tests passedReproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_460df64780054530. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_460df64780054530 · verifier authoritative; classifier explanatory.