206 lines
7.7 KiB
C
206 lines
7.7 KiB
C
#include "spacetime.h"
|
|
|
|
#include <math.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
|
|
/* Escape sphere lies this many wall thicknesses 1/sigma beyond R. At
|
|
* r = R + span/sigma the shape has decayed to ~2*exp(-2*span) for a thin wall
|
|
* and ~4*exp(-2*span) for a broad bump, i.e. below binary64 epsilon for
|
|
* span = 20, so the escape sphere is Minkowski to machine accuracy. */
|
|
#define ALCUBIERRE_ESCAPE_SPAN 20.0
|
|
|
|
/* For 2 sigma R below this threshold the direct difference of two nearby
|
|
* tanh values loses about 1/(2 sigma R) digits and can round the shape to
|
|
* zero while it is still O(1). Switch to an algebraically equivalent form
|
|
* that is free of cancellation in that regime. */
|
|
#define ALCUBIERRE_SMALL_WALL 0.5
|
|
|
|
typedef struct {
|
|
double vs;
|
|
double radius;
|
|
double sigma;
|
|
double escape_radius;
|
|
} AlcubierreContext;
|
|
|
|
/* Alcubierre shape function
|
|
* f(r) = (tanh(sigma (r + R)) - tanh(sigma (r - R))) / (2 tanh(sigma R)),
|
|
* positive, equal to 1 at r = 0 for any sigma R > 0, and decaying to zero
|
|
* past r = R over a transition width ~1/sigma.
|
|
*
|
|
* The identity f = (1 - s^2) / (1 - s^2 t^2) with s = tanh(sigma r),
|
|
* t = tanh(sigma R) is exact and has no cancellation when sigma R is small,
|
|
* where the shape tends to sech^2(sigma r). */
|
|
static double alcubierre_shape(double r, double radius, double sigma) {
|
|
const double sR = sigma * radius;
|
|
if (2.0 * sR < ALCUBIERRE_SMALL_WALL) {
|
|
const double s = tanh(sigma * r);
|
|
const double t = tanh(sR);
|
|
return (1.0 - s * s) / (1.0 - s * s * t * t);
|
|
}
|
|
return (tanh(sigma * (r + radius)) - tanh(sigma * (r - radius))) /
|
|
(2.0 * tanh(sR));
|
|
}
|
|
|
|
/* d f / d r. The thin-wall branch uses sech^2(x) = 1 - tanh(x)^2; the
|
|
* broad-bump branch uses the cancellation-free derivative of the identity
|
|
* above. Both underflow to zero far outside the bubble, which is the
|
|
* intended exactly-flat limit. */
|
|
static double alcubierre_shape_derivative(double r, double radius,
|
|
double sigma) {
|
|
const double sR = sigma * radius;
|
|
if (2.0 * sR < ALCUBIERRE_SMALL_WALL) {
|
|
const double s = tanh(sigma * r);
|
|
const double c = cosh(2.0 * sR);
|
|
const double denom = 1.0 + s * s + c * (1.0 - s * s);
|
|
return -(c + 1.0) * 4.0 * sigma * s * (1.0 - s * s) / (denom * denom);
|
|
}
|
|
const double tanh_plus = tanh(sigma * (r + radius));
|
|
const double tanh_minus = tanh(sigma * (r - radius));
|
|
const double sech2_plus = 1.0 - tanh_plus * tanh_plus;
|
|
const double sech2_minus = 1.0 - tanh_minus * tanh_minus;
|
|
return sigma * (sech2_plus - sech2_minus) / (2.0 * tanh(sR));
|
|
}
|
|
|
|
/* Moving Alcubierre bubble in the lab coordinates
|
|
* ds^2 = -dt^2 + (dx - v_s f(r_s) dt)^2 + dy^2 + dz^2,
|
|
* r_s = sqrt((x - x_s)^2 + y^2 + z^2), x_s(t) = v_s t,
|
|
* with x_s(0) = 0. This is not a comoving (x_s = 0) slicing: the bubble
|
|
* propagates through the coordinates. The spatial slices stay flat, so
|
|
* alpha = 1, gamma_ij = delta_ij, beta^x = -v_s f(r_s), and
|
|
* K_ij = (D_i beta_j + D_j beta_i) / (2 alpha)
|
|
* = -v_s (delta_jx d_i f + delta_ix d_j f) / 2,
|
|
* where d_i differentiates at fixed t (only the spatial argument of f moves
|
|
* with t). K encodes the time dependence required by the 3+1 null-ray RHS. */
|
|
static SpacetimePointStatus alcubierre_eval(const SpacetimeSource *source,
|
|
double t, const double x[3],
|
|
MetricData *metric) {
|
|
const AlcubierreContext *context = source->context;
|
|
const double vs = context->vs;
|
|
const double dx = x[0] - vs * t;
|
|
const double r2 = dx * dx + x[1] * x[1] + x[2] * x[2];
|
|
double df[3] = {0.0, 0.0, 0.0};
|
|
double f;
|
|
if (!isfinite(r2))
|
|
return SPACETIME_POINT_INVALID_METRIC;
|
|
const double r = sqrt(r2);
|
|
*metric = (MetricData){
|
|
.alpha = 1.0,
|
|
.gamma = {{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}};
|
|
if (r > 0.0) {
|
|
f = alcubierre_shape(r, context->radius, context->sigma);
|
|
const double radial_scale =
|
|
alcubierre_shape_derivative(r, context->radius, context->sigma) / r;
|
|
df[0] = radial_scale * dx;
|
|
df[1] = radial_scale * x[1];
|
|
df[2] = radial_scale * x[2];
|
|
} else {
|
|
f = alcubierre_shape(0.0, context->radius, context->sigma);
|
|
}
|
|
metric->beta[0] = -vs * f;
|
|
for (int i = 0; i < 3; ++i) {
|
|
metric->d_beta[i][0] = -vs * df[i];
|
|
for (int j = 0; j < 3; ++j)
|
|
metric->K[i][j] =
|
|
-0.5 * vs * ((j == 0 ? df[i] : 0.0) + (i == 0 ? df[j] : 0.0));
|
|
}
|
|
return SPACETIME_POINT_OK;
|
|
}
|
|
|
|
/* The exotic matter that would source the bubble is treated as optically
|
|
* transparent. The escape sphere follows the moving bubble. */
|
|
static SpacetimeRayStatus alcubierre_classify(const SpacetimeSource *source,
|
|
double t, const double x[3]) {
|
|
const AlcubierreContext *context = source->context;
|
|
const double dx = x[0] - context->vs * t;
|
|
const double r2 = dx * dx + x[1] * x[1] + x[2] * x[2];
|
|
return r2 >= context->escape_radius * context->escape_radius
|
|
? SPACETIME_RAY_ESCAPED
|
|
: SPACETIME_RAY_ACTIVE;
|
|
}
|
|
|
|
static void alcubierre_destroy(SpacetimeSource *source) {
|
|
free(source->context);
|
|
source->context = NULL;
|
|
source->ops = NULL;
|
|
}
|
|
|
|
static size_t alcubierre_asymptotic_end_count(const SpacetimeSource *source) {
|
|
(void)source;
|
|
return 1;
|
|
}
|
|
|
|
static int alcubierre_asymptotic_end(const SpacetimeSource *source,
|
|
size_t index,
|
|
SpacetimeAsymptoticEnd *out) {
|
|
(void)source;
|
|
if (index != 0)
|
|
return -1;
|
|
*out = (SpacetimeAsymptoticEnd){
|
|
.end_id = 0,
|
|
.exterior_kind = ASYMPTOTIC_EXTERIOR_MINKOWSKI,
|
|
.mass = 0.0,
|
|
.frame_origin = {0.0, 0.0, 0.0},
|
|
.frame_axes = {{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}};
|
|
return 0;
|
|
}
|
|
|
|
static int alcubierre_escape_worldtube_sample(
|
|
const SpacetimeSource *source, SpacetimeEndId end_id, double t,
|
|
SpacetimeEscapeWorldtubeSample *out) {
|
|
const AlcubierreContext *context = source->context;
|
|
if (end_id != 0)
|
|
return -1;
|
|
*out = (SpacetimeEscapeWorldtubeSample){
|
|
.center = {context->vs * t, 0.0, 0.0},
|
|
.velocity = {context->vs, 0.0, 0.0},
|
|
.radius = context->escape_radius,
|
|
.radius_rate = 0.0,
|
|
.velocity_constant = 1,
|
|
.valid = 1};
|
|
return 0;
|
|
}
|
|
|
|
static const SpacetimeOps alcubierre_ops = {
|
|
.eval = alcubierre_eval,
|
|
.classify = alcubierre_classify,
|
|
.asymptotic_end_count = alcubierre_asymptotic_end_count,
|
|
.asymptotic_end = alcubierre_asymptotic_end,
|
|
.escape_worldtube_sample = alcubierre_escape_worldtube_sample,
|
|
.destroy = alcubierre_destroy,
|
|
};
|
|
|
|
double spacetime_alcubierre_escape_radius(double radius, double sigma) {
|
|
return radius + ALCUBIERRE_ESCAPE_SPAN / sigma;
|
|
}
|
|
|
|
int spacetime_create_alcubierre(SpacetimeSource *source, double vs,
|
|
double radius, double sigma) {
|
|
if (source == NULL || !isfinite(vs) ||
|
|
!isfinite(radius) || radius <= 0.0 || !isfinite(sigma) || sigma <= 0.0)
|
|
return -1;
|
|
/* Reject parameter combinations whose derived domain overflows or does not
|
|
* actually extend beyond the bubble. */
|
|
const double escape_radius = spacetime_alcubierre_escape_radius(radius, sigma);
|
|
if (!isfinite(escape_radius) || escape_radius <= radius)
|
|
return -1;
|
|
AlcubierreContext *context = malloc(sizeof *context);
|
|
if (context == NULL)
|
|
return -1;
|
|
context->vs = vs;
|
|
context->radius = radius;
|
|
context->sigma = sigma;
|
|
context->escape_radius = escape_radius;
|
|
source->ops = &alcubierre_ops;
|
|
source->context = context;
|
|
if (spacetime_source_finalize(source)) {
|
|
alcubierre_destroy(source);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spacetime_create_default(SpacetimeSource *source) {
|
|
return spacetime_create_alcubierre(source, 0.5, 5.0, 1.0);
|
|
}
|