diff --git a/makefile b/makefile index e825bf3..d349682 100644 --- a/makefile +++ b/makefile @@ -45,7 +45,7 @@ ifneq (,$(findstring stampede2,$(HOST))) -include $(MAKEFILE_PATH)/machines/stampede2.make endif ifneq (,$(findstring frontera,$(HOST))) - -include $(MAKEFILE_PATH)/machines/frontera.make + -include $(MAKEFILE_PATH)/machines/frontera.make endif # Hack to check only whether host begins with bh* ifneq (,$(findstring beginsbh,begins$(HOST))) diff --git a/model/analytic/model.c b/model/analytic/model.c index 4cee660..6a2be6c 100644 --- a/model/analytic/model.c +++ b/model/analytic/model.c @@ -16,10 +16,7 @@ double Te_unit; // This one is useful double RHO_unit; -// Model parameters: public -double rmax_geo = 1000.0; // Model parameters: private -static double rmin_geo = 0; static double MBH_solar = 4.e6; static double RHO_unit_in = 0; @@ -58,7 +55,7 @@ void try_set_model_parameter(const char *word, const char *value) set_by_word_val(word, value, "model", &model, TYPE_INT); set_by_word_val(word, value, "MBH", &MBH_solar, TYPE_DBL); set_by_word_val(word, value, "rho_unit", &RHO_unit_in, TYPE_DBL); - // TODO NEED to move this into main parameters + // TODO move these calls into the main parameters set_by_word_val(word, value, "rmax_geo", &rmax_geo, TYPE_DBL); set_by_word_val(word, value, "rmin_geo", &rmin_geo, TYPE_DBL); @@ -137,7 +134,8 @@ void set_units() Rh = 1 + sqrt(1. - a * a); Rin = Rh; Rout = 1000.0; - // Limit rmax_geo? + rmin_geo = 1.; + rmax_geo = 10000.; double z1 = 1. + pow(1. - a * a, 1. / 3.) * (pow(1. + a, 1. / 3.) + pow(1. - a, 1. / 3.)); double z2 = sqrt(3. * a * a + z1 * z1); @@ -317,8 +315,9 @@ int radiating_region(double X[NDIM]) // Define these to specify a fluid model: e- density/temperature for // synchrotron radiation based on an energy distribution double get_model_thetae(double X[NDIM]) {return 0;} -void get_model_powerlaw_vals(double X[NDIM], double *p, double *n, - double *gamma_min, double *gamma_max, double *gamma_cut) {return;} - +double get_model_sigma(double X[NDIM]) {return 0;} +double get_model_beta(double X[NDIM]) {return 0;} +void update_data(double *tA, double *tB) {return;} +void update_data_until(double *tA, double *tB, double tgt) {return;} // This is only called for trace file output, and doesn't really apply to analytic models void get_model_primitives(double X[NDIM], double *p) {return;} diff --git a/model/analytic/model_params.h b/model/analytic/model_params.h index 2a7169e..4d43416 100644 --- a/model/analytic/model_params.h +++ b/model/analytic/model_params.h @@ -9,7 +9,4 @@ #define THIN_DISK (0) -// Necessary model parameters, can be used or not -extern double rmax_geo; - #endif /* MODEL_PARAMS_H */ diff --git a/model/iharm/model.c b/model/iharm/model.c index 4fead2a..efd9db4 100644 --- a/model/iharm/model.c +++ b/model/iharm/model.c @@ -2,14 +2,19 @@ #include "decs.h" #include "hdf5_utils.h" +#include "debug_tools.h" #include "coordinates.h" #include "geometry.h" #include "grid.h" -#include "model_radiation.h" // Only for outputting emissivities +#include "model_radiation.h" // Only for outputting emissivities +#include "simcoords.h" // For interpolating arbitrary grids #include "par.h" #include "utils.h" +#include "debug_tools.h" + +#include #include #define NVAR (10) @@ -21,6 +26,7 @@ #define FORMAT_HAMR_EKS (3) #define FORMAT_IHARM_v1 (1) +#define FORMAT_KORAL_v2 (4) int dumpfile_format = 0; // UNITS @@ -32,12 +38,17 @@ double U_unit; double B_unit; double Te_unit; +double target_mdot = 0.0; // if this value > 0, use to renormalize M_unit &c. + +// MOLECULAR WEIGHTS +static double Ne_factor = 1.; // e.g., used for He with 2 protons+neutrons per 2 electrons +static double mu_i, mu_e, mu_tot; + // MODEL PARAMETERS: PUBLIC double DTd; -double rmax_geo = 100.; -double rmin_geo = 1.; double sigma_cut = 1.0; double beta_crit = 1.0; +double sigma_cut_high = -1.0; // MODEL PARAMETERS: PRIVATE static char fnam[STRLEN] = "dump.h5"; @@ -45,12 +56,10 @@ static char fnam[STRLEN] = "dump.h5"; static double tp_over_te = 3.; static double trat_small = 1.; static double trat_large = 40.; - -static double powerlaw_gamma_min = 1e2; -static double powerlaw_gamma_max = 1e5; -static double powerlaw_gamma_cut = 1e10; -static double powerlaw_eta = 0.02; -static double powerlaw_p = 3.25; +// Minimum number of dynamical times the cooling time must +// undershoot to be considered "small" +// lower values -> higher max T_e, higher values are restrictive +static double cooling_dynamical_times = 1.e-20; static int dumpskip = 1; static int dumpmin, dumpmax, dumpidx; @@ -60,14 +69,21 @@ static double Mdot_dump; static double MdotEdd_dump; static double Ladv_dump; +static int reverse_field = 0; + +double tf; + // MAYBES //static double t0; -// ELECTRONS -> +// ELECTRONS // 0 : constant TP_OVER_TE // 1 : use dump file model (kawazura?) // 2 : use mixed TP_OVER_TE (beta model) +// 3 : use mixed TP_OVER_TE (beta model) with fluid temperature +// 9 : load Te (in Kelvin) from dump file (KORAL etc.) // TODO the way this is selected is horrid. Make it a parameter. +#define ELECTRONS_TFLUID (3) static int RADIATION, ELECTRONS; static double gam = 1.444444, game = 1.333333, gamp = 1.666667; static double Thetae_unit, Mdotedd; @@ -87,6 +103,7 @@ struct of_data { double ***thetae; double ***b; double ***sigma; + double ***beta; }; static struct of_data dataA, dataB, dataC; static struct of_data *data[NSUP]; @@ -95,12 +112,14 @@ static struct of_data *data[NSUP]; void set_units(); void load_data(int n, char *, int dumpidx, int verbose); void load_iharm_data(int n, char *, int dumpidx, int verbose); +void load_koral_data(int n, char *, int dumpidx, int verbose); void load_hamr_data(int n, char *, int dumpidx, int verbose); double get_dump_t(char *fnam, int dumpidx); void init_grid(char *fnam, int dumpidx); void init_iharm_grid(char *fnam, int dumpidx); +void init_koral_grid(char *fnam, int dumpidx); void init_hamr_grid(char *fnam, int dumpidx); -void init_physical_quantities(int n); +void init_physical_quantities(int n, double rescale_factor); void init_storage(void); void try_set_model_parameter(const char *word, const char *value) @@ -111,6 +130,7 @@ void try_set_model_parameter(const char *word, const char *value) // assume params is populated set_by_word_val(word, value, "MBH", &MBH_solar, TYPE_DBL); set_by_word_val(word, value, "M_unit", &M_unit, TYPE_DBL); + set_by_word_val(word, value, "mdot", &target_mdot, TYPE_DBL); set_by_word_val(word, value, "dump", (void *)fnam, TYPE_STR); @@ -118,17 +138,14 @@ void try_set_model_parameter(const char *word, const char *value) set_by_word_val(word, value, "trat_small", &trat_small, TYPE_DBL); set_by_word_val(word, value, "trat_large", &trat_large, TYPE_DBL); set_by_word_val(word, value, "sigma_cut", &sigma_cut, TYPE_DBL); + set_by_word_val(word, value, "sigma_cut_high", &sigma_cut_high, TYPE_DBL); set_by_word_val(word, value, "beta_crit", &beta_crit, TYPE_DBL); - - set_by_word_val(word, value, "powerlaw_gamma_min", &powerlaw_gamma_min, TYPE_DBL); - set_by_word_val(word, value, "powerlaw_gamma_max", &powerlaw_gamma_max, TYPE_DBL); - set_by_word_val(word, value, "powerlaw_gamma_cut", &powerlaw_gamma_cut, TYPE_DBL); - set_by_word_val(word, value, "powerlaw_eta", &trat_large, TYPE_DBL); - set_by_word_val(word, value, "powerlaw_p", &trat_large, TYPE_DBL); + set_by_word_val(word, value, "cooling_dynamical_times", &cooling_dynamical_times, TYPE_DBL); set_by_word_val(word, value, "rmax_geo", &rmax_geo, TYPE_DBL); set_by_word_val(word, value, "rmin_geo", &rmin_geo, TYPE_DBL); + set_by_word_val(word, value, "reverse_field", &reverse_field, TYPE_INT); // allow cutting out the spine set_by_word_val(word, value, "polar_cut_deg", &polar_cut, TYPE_DBL); @@ -137,6 +154,11 @@ void try_set_model_parameter(const char *word, const char *value) set_by_word_val(word, value, "dump_max", &dumpmax, TYPE_INT); set_by_word_val(word, value, "dump_skip", &dumpskip, TYPE_INT); dumpidx = dumpmin; + + // override parameters based on input + if (target_mdot > 0.) { + M_unit = 1.; + } } // Advance through dumps until we are closer to the next set @@ -235,15 +257,29 @@ void get_dumpfile_type(char *fnam, int dumpidx) char fname[256]; snprintf(fname, 255, fnam, dumpidx); - // TODO needs KORAL/MKS3 support - hdf5_open(fname); int hamr_attr_exists = hdf5_attr_exists("", "dscale"); + if (!hamr_attr_exists) { - dumpfile_format = FORMAT_IHARM_v1; - fprintf(stderr, "iharm!\n"); + if (!hdf5_exists("header/version")) { + // Converted BHAC dumps and very old iharm3d output do not include a version + // BHAC output requires nothing special from ipole so we mark it "iharm_v1" + dumpfile_format = FORMAT_IHARM_v1; + fprintf(stderr, "bhac! (or old iharm)\n"); + } else { + char harmversion[256]; + hdf5_read_single_val(harmversion, "header/version", hdf5_make_str_type(255)); + + if ( strcmp(harmversion, "KORALv2") == 0 ) { + dumpfile_format = FORMAT_KORAL_v2; + fprintf(stderr, "koral!\n"); + } else { + dumpfile_format = FORMAT_IHARM_v1; + fprintf(stderr, "iharm!\n"); + } + } } else { - // note this will return -1 if the "header" group does not exists + // note this will return -1 if the "header" group does not exist dumpfile_format = FORMAT_HAMR_EKS; fprintf(stderr, "hamr!\n"); } @@ -270,7 +306,8 @@ void init_model(double *tA, double *tB) // read fluid data fprintf(stderr, "Reading data...\n"); - load_data(0, fnam, dumpidx, dumpmin); + load_data(0, fnam, dumpidx, 2); + // replaced dumpmin -> 2 because apparently that argument was just .. removed dumpidx += dumpskip; #if SLOW_LIGHT update_data(tA, tB); @@ -420,12 +457,17 @@ double get_model_thetae(double X[NDIM]) double tfac = set_tinterp_ns(X, &nA, &nB); double thetae = interp_scalar_time(X, data[nA]->thetae, data[nB]->thetae, tfac); - if (thetae < 0.) { - printf("thetae negative!\n"); +#if DEBUG + if (thetae < 0. || isnan(thetae)) { + printf("thetae negative or NaN!\n"); printf("X[] = %g %g %g %g\n", X[0], X[1], X[2], X[3]); printf("t = %e %e %e\n", data[0]->t, data[1]->t, data[2]->t); - printf("thetae = %e\n", thetae); + double thetaeA = interp_scalar(X, data[nA]->thetae); + double thetaeB = interp_scalar(X, data[nB]->thetae); + printf("thetaeA, thetaeB = %e %e", thetaeA, thetaeB); + printf("thetae, tfac = %e %e\n", thetae, tfac); } +#endif return thetae; } @@ -452,19 +494,44 @@ double get_model_sigma(double X[NDIM]) return interp_scalar_time(X, data[nA]->sigma, data[nB]->sigma, tfac); } +double get_model_beta(double X[NDIM]) +{ + if ( X_in_domain(X) == 0 ) return 0.; // TODO inf? + + double betaA, betaB, tfac; + int nA, nB; + tfac = set_tinterp_ns(X, &nA, &nB); + betaA = interp_scalar(X, data[nA]->beta); + betaB = interp_scalar(X, data[nB]->beta); + return tfac*betaA + (1. - tfac)*betaB; +} + +double get_sigma_smoothfac(double sigma) +{ + double sigma_above = sigma_cut; + if (sigma_cut_high > 0) sigma_above = sigma_cut_high; + if (sigma < sigma_cut) return 1; + if (sigma >= sigma_above) return 0; + double dsig = sigma_above - sigma_cut; + return cos(M_PI / 2. / dsig * (sigma - sigma_cut)); +} + double get_model_ne(double X[NDIM]) { if ( X_in_domain(X) == 0 ) return 0.; + double sigma_smoothfac = 1; + #if USE_GEODESIC_SIGMACUT double sigma = get_model_sigma(X); if (sigma > sigma_cut) return 0.; + sigma_smoothfac = get_sigma_smoothfac(sigma); #endif int nA, nB; double tfac = set_tinterp_ns(X, &nA, &nB); - return interp_scalar_time(X, data[nA]->ne, data[nB]->ne, tfac); + return interp_scalar_time(X, data[nA]->ne, data[nB]->ne, tfac) * sigma_smoothfac; } void set_units() @@ -482,37 +549,99 @@ void set_units() fprintf(stderr,"rho,u,B units: %g [g cm^-3] %g [g cm^-1 s^-2] %g [G] \n",RHO_unit,U_unit,B_unit) ; } -void init_physical_quantities(int n) +void init_physical_quantities(int n, double rescale_factor) { +#if DEBUG + int ceilings = 0; +#endif + + rescale_factor = sqrt(rescale_factor); + // cover everything, even ghost zones #pragma omp parallel for collapse(3) for (int i = 0; i < N1+2; i++) { for (int j = 0; j < N2+2; j++) { for (int k = 0; k < N3+2; k++) { - data[n]->ne[i][j][k] = data[n]->p[KRHO][i][j][k] * RHO_unit/(MP+ME) ; + data[n]->ne[i][j][k] = data[n]->p[KRHO][i][j][k] * RHO_unit/(MP+ME) * Ne_factor; + + data[n]->b[i][j][k] *= rescale_factor; double bsq = data[n]->b[i][j][k] / B_unit; bsq = bsq*bsq; double sigma_m = bsq/data[n]->p[KRHO][i][j][k]; - + double beta_m = data[n]->p[UU][i][j][k]*(gam-1.)/0.5/bsq; +#if DEBUG + if(isnan(sigma_m)) { + sigma_m = 0; + fprintf(stderr, "Setting zero sigma!\n"); + } + if(isnan(beta_m)) { + beta_m = INFINITY; + fprintf(stderr, "Setting INF beta!\n"); + } +#endif if (ELECTRONS == 1) { - data[n]->thetae[i][j][k] = data[n]->p[KEL][i][j][k]*pow(data[n]->p[KRHO][i][j][k],game-1.)*Thetae_unit; + data[n]->thetae[i][j][k] = data[n]->p[KEL][i][j][k] * + pow(data[n]->p[KRHO][i][j][k],game-1.)*Thetae_unit; } else if (ELECTRONS == 2) { - double beta = data[n]->p[UU][i][j][k]*(gam-1.)/0.5/bsq; - double betasq = beta*beta / beta_crit/beta_crit; + double betasq = beta_m*beta_m / beta_crit/beta_crit; double trat = trat_large * betasq/(1. + betasq) + trat_small /(1. + betasq); //Thetae_unit = (gam - 1.) * (MP / ME) / trat; // see, e.g., Eq. 8 of the EHT GRRT formula list - Thetae_unit = (MP/ME) * (game-1.) * (gamp-1.) / ( (gamp-1.) + (game-1.)*trat ); - data[n]->thetae[i][j][k] = Thetae_unit*data[n]->p[UU][i][j][k]/data[n]->p[KRHO][i][j][k]; + double lcl_Thetae_u = (MP/ME) * (game-1.) * (gamp-1.) / ( (gamp-1.) + (game-1.)*trat ); + Thetae_unit = lcl_Thetae_u; + data[n]->thetae[i][j][k] = lcl_Thetae_u*data[n]->p[UU][i][j][k]/data[n]->p[KRHO][i][j][k]; + } else if (ELECTRONS == 9) { + // convert Kelvin -> Thetae + data[n]->thetae[i][j][k] = data[n]->p[TFLK][i][j][k] * KBOL / ME / CL / CL; + } else if (ELECTRONS == ELECTRONS_TFLUID) { + double beta = data[n]->p[UU][i][j][k]*(gam-1.)/0.5/bsq; + double betasq = beta*beta / beta_crit/beta_crit; + double trat = trat_large * betasq/(1. + betasq) + trat_small /(1. + betasq); + double dfactor = mu_tot / mu_e + mu_tot / mu_i * trat; + data[n]->thetae[i][j][k] = data[n]->p[THF][i][j][k] / dfactor; } else { data[n]->thetae[i][j][k] = Thetae_unit*data[n]->p[UU][i][j][k]/data[n]->p[KRHO][i][j][k]; } +#if DEBUG + if(isnan(data[n]->thetae[i][j][k])) { + data[n]->thetae[i][j][k] = 0.0; + fprintf(stderr, "\nNaN Thetae! Prims %g %g %g %g %g %g %g %g\n", data[n]->p[KRHO][i][j][k], data[n]->p[UU][i][j][k], + data[n]->p[U1][i][j][k], data[n]->p[U2][i][j][k], data[n]->p[U3][i][j][k], data[n]->p[B1][i][j][k], + data[n]->p[B2][i][j][k], data[n]->p[B3][i][j][k]); + fprintf(stderr, "Setting floor temp!\n"); + } +#endif + + // Enforce a max on Thetae based on cooling time == dynamical time + if (cooling_dynamical_times > 1e-20) { + double X[NDIM]; + ijktoX(i, j, k, X); + double r, th; + bl_coord(X, &r, &th); + // Calculate thetae_max based on matching the cooling time w/dynamical time + // Makes sure to use b w/units, but r has already been rescaled + double Thetae_max_dynamical = 1 / cooling_dynamical_times * 7.71232e46 / 2 / MBH * pow(data[n]->b[i][j][k], -2) * pow(r * sin(th), -1.5); +#if DEBUG + if (Thetae_max_dynamical < data[n]->thetae[i][j][k]) { + if (r > 2) fprintf(stderr, "Ceiling on temp! %g < %g, r, th %g %g\n", Thetae_max_dynamical, data[n]->thetae[i][j][k], r, th); + ceilings++; + } +#endif + data[n]->thetae[i][j][k] = fmin(data[n]->thetae[i][j][k], Thetae_max_dynamical); + } + + // Apply floor last in case the above is a very restrictive ceiling data[n]->thetae[i][j][k] = fmax(data[n]->thetae[i][j][k], 1.e-3); - //strongly magnetized = empty, no shiny spine - data[n]->sigma[i][j][k] = sigma_m; // allow sigma cut per geodesic step + // Preserve sigma for cutting along geodesics, and for variable-kappa model + data[n]->sigma[i][j][k] = fmax(sigma_m, SMALL); + // Also record beta, for variable-kappa model + data[n]->beta[i][j][k] = fmax(beta_m, SMALL); + + // Cut Ne (i.e. emission) based on sigma, if we're not doing so along each geodesic + // Strongly magnetized = empty, no shiny spine if (sigma_m > sigma_cut && !USE_GEODESIC_SIGMACUT) { data[n]->b[i][j][k]=0.0; data[n]->ne[i][j][k]=0.0; @@ -521,6 +650,9 @@ void init_physical_quantities(int n) } } } +#if DEBUG + fprintf(stderr, "TOTAL TEMPERATURE CEILING ZONES: %d of %d\n", ceilings, (N1+2)*(N2+2)*(N3+2)); +#endif } @@ -533,6 +665,7 @@ void init_storage(void) data[n]->thetae = malloc_rank3(N1+2,N2+2,N3+2); data[n]->b = malloc_rank3(N1+2,N2+2,N3+2); data[n]->sigma = malloc_rank3(N1+2,N2+2,N3+2); + data[n]->beta = malloc_rank3(N1+2,N2+2,N3+2); } } @@ -540,6 +673,8 @@ void init_grid(char *fnam, int dumpidx) { if (dumpfile_format == FORMAT_IHARM_v1) { init_iharm_grid(fnam, dumpidx); + } else if (dumpfile_format == FORMAT_KORAL_v2) { + init_koral_grid(fnam, dumpidx); } else if (dumpfile_format == FORMAT_HAMR_EKS) { init_hamr_grid(fnam, dumpidx); } @@ -625,13 +760,6 @@ void init_hamr_grid(char *fnam, int dumpidx) stopx[2] = startx[2]+N2*dx[2]; stopx[3] = startx[3]+N3*dx[3]; - // set boundary of coordinate system - MULOOP cstartx[mu] = 0.; - cstopx[0] = 0; - cstopx[1] = log(Rout); - cstopx[2] = 1; - cstopx[3] = 2*M_PI; - // now translate from hamr x2 \in (-1, 1) -> mks x2 \in (0, 1) startx[2] = (startx[2] + 1)/2.; stopx[2] = (stopx[2] + 1)/2.; @@ -641,10 +769,8 @@ void init_hamr_grid(char *fnam, int dumpidx) rmax_geo = fmin(rmax_geo, Rout); rmin_geo = fmax(rmin_geo, Rin); - cstartx[0] = 0; - cstartx[1] = 0; - cstartx[2] = 0; - cstartx[3] = 0; + // set boundary of coordinate system + MULOOP cstartx[mu] = 0.; cstopx[0] = 0; cstopx[1] = log(Rout); cstopx[2] = 1.0; @@ -690,6 +816,17 @@ void init_iharm_grid(char *fnam, int dumpidx) exit(-3); } + if ( hdf5_exists("weights") ) { + hdf5_set_directory("/header/weights/"); + hdf5_read_single_val(&mu_i, "mu_i", H5T_IEEE_F64LE); + hdf5_read_single_val(&mu_e, "mu_e", H5T_IEEE_F64LE); + hdf5_read_single_val(&mu_tot, "mu_tot", H5T_IEEE_F64LE); + fprintf(stderr, "Loaded molecular weights (mu_i, mu_e, mu_tot): %g %g %g\n", mu_i, mu_e, mu_tot); + Ne_factor = 1. / mu_e; + hdf5_set_directory("/header/"); + ELECTRONS = ELECTRONS_TFLUID; + } + char metric_name[20]; hid_t HDF5_STR_TYPE = hdf5_make_str_type(20); hdf5_read_single_val(&metric_name, "metric", HDF5_STR_TYPE); @@ -712,6 +849,9 @@ void init_iharm_grid(char *fnam, int dumpidx) use_eKS_internal = 1; metric = METRIC_MKS3; cstopx[2] = 1.0; + } else if ( strncmp(metric_name, "EKS", 19) == 0 ) { + metric = METRIC_EKS; + cstopx[2] = M_PI; } else { fprintf(stderr, "File is in unknown metric %s. Cannot continue.\n", metric_name); exit(-1); @@ -739,6 +879,9 @@ void init_iharm_grid(char *fnam, int dumpidx) } ELECTRONS = 1; Thetae_unit = MP/ME; + } else if (ELECTRONS == ELECTRONS_TFLUID) { + fprintf(stderr, "Using Ressler/Athena electrons with mixed tp_over_te and\n"); + fprintf(stderr, "trat_small = %g, trat_large = %g, and beta_crit = %g\n", trat_small, trat_large, beta_crit); } else if (USE_FIXED_TPTE && !USE_MIXED_TPTE) { ELECTRONS = 0; // force TP_OVER_TE to overwrite bad electrons fprintf(stderr, "Using fixed tp_over_te ratio = %g\n", tp_over_te); @@ -799,6 +942,10 @@ void init_iharm_grid(char *fnam, int dumpidx) fprintf(stderr, "Using logarithmic KS coordinates internally\n"); fprintf(stderr, "Converting from KORAL-style Modified Kerr-Schild coordinates MKS3\n"); break; + case METRIC_EKS: + hdf5_set_directory("/header/geom/eks/"); + fprintf(stderr, "Using Kerr-Schild coordinates with exponential radial coordiante\n"); + break; } if ( metric == METRIC_MKS3 ) { @@ -809,6 +956,12 @@ void init_iharm_grid(char *fnam, int dumpidx) hdf5_read_single_val(&mks3MY2, "MY2", H5T_IEEE_F64LE); hdf5_read_single_val(&mks3MP0, "MP0", H5T_IEEE_F64LE); Rout = 100.; + } else if ( metric == METRIC_EKS ) { + hdf5_read_single_val(&a, "a", H5T_IEEE_F64LE); + hdf5_read_single_val(&Rin, "r_in", H5T_IEEE_F64LE); + hdf5_read_single_val(&Rout, "r_out", H5T_IEEE_F64LE); + fprintf(stderr, "eKS parameters a: %f Rin: %f Rout: %f\n", a, Rin, Rout); + } else { // Some brand of MKS. All have the same parameters hdf5_read_single_val(&a, "a", H5T_IEEE_F64LE); hdf5_read_single_val(&hslope, "hslope", H5T_IEEE_F64LE); @@ -860,6 +1013,184 @@ void init_iharm_grid(char *fnam, int dumpidx) hdf5_close(); } +void init_koral_grid(char *fnam, int dumpidx) +{ + // called at the beginning of the run and sets the static parameters + // along with setting up the grid + + // assert(42==0); + // this version of the code has not been validated to have the + // right units and four-vector recovery. use at your own peril + + char fname[256]; + snprintf(fname, 255, fnam, dumpidx); + fprintf(stderr, "filename: %s\n", fname); + + // always load ks rh from the file to get proper extents + // and potentially as a fallback for when we cannnot get + // the inverse/reverse transformation eKS -> simcoords. + load_simcoord_info_from_file(fname); + + // beecause of legacy global state choices, we can't open two + // files at once, so we have to call this after the above. + hdf5_open(fname); + + // get dump info to copy to ipole output + fluid_header = hdf5_get_blob("/header"); + + // get time information for slow light + // currently unsupported + //hdf5_read_single_val(&DTd, "dump_cadence", H5T_IEEE_F64LE); + + // in general, we assume KORAL should use the simcoords feature. first + // ensure that metric_out is KS and then set simcoords + hdf5_set_directory("/header/"); + char metric_out[20], metric_run[20]; + hid_t HDF5_STR_TYPE = hdf5_make_str_type(20); + hdf5_read_single_val(&metric_out, "metric_out", HDF5_STR_TYPE); + hdf5_read_single_val(&metric_run, "metric_run", HDF5_STR_TYPE); + + if (strcmp(metric_out, "KS") != 0) { + fprintf(stderr, "! expected koral metric_out==KS but got %s instead. quitting.\n", metric_out); + exit(5); + } + + // at this point, assume we will load data as eKS and trace as eKS. + use_simcoords = 1; + metric = METRIC_MKS; + hslope = 1.; + + // get simulation grid coordinates + if (strcmp(metric_run, "MKS3") == 0) { + simcoords = SIMCOORDS_KORAL_MKS3; + hdf5_read_single_val(&a, "bhspin", H5T_IEEE_F64LE); + hdf5_set_directory("/header/geom/mks3/"); + hdf5_read_single_val(&(mp_koral_mks3.r0), "mksr0", H5T_IEEE_F64LE); + hdf5_read_single_val(&(mp_koral_mks3.h0), "mksh0", H5T_IEEE_F64LE); + hdf5_read_single_val(&(mp_koral_mks3.my1), "mksmy1", H5T_IEEE_F64LE); + hdf5_read_single_val(&(mp_koral_mks3.my2), "mksmy2", H5T_IEEE_F64LE); + hdf5_read_single_val(&(mp_koral_mks3.mp0), "mksmp0", H5T_IEEE_F64LE); + fprintf(stderr, "KORAL simulation was run with MKS3 coordinates.\n"); + } else if (strcmp(metric_run, "MKS2") == 0) { + simcoords = SIMCOORDS_KORAL_MKS3; + hdf5_read_single_val(&a, "bhspin", H5T_IEEE_F64LE); + hdf5_set_directory("/header/geom/mks2/"); + hdf5_read_single_val(&(mp_koral_mks3.r0), "mksr0", H5T_IEEE_F64LE); + hdf5_read_single_val(&(mp_koral_mks3.h0), "mksh0", H5T_IEEE_F64LE); + mp_koral_mks3.my1 = 0; + mp_koral_mks3.my2 = 0; + mp_koral_mks3.mp0 = 0; + fprintf(stderr, "KORAL simulation was run with MKS2 coordinates.\n"); + } else if (strcmp(metric_run, "JETCOORDS") == 0) { + simcoords = SIMCOORDS_KORAL_JETCOORDS; + hdf5_read_single_val(&a, "bhspin", H5T_IEEE_F64LE); + hdf5_set_directory("/header/geom/jetcoords/"); + hdf5_read_single_val(&(mp_koral_jetcoords.alpha_1), "alpha1", H5T_IEEE_F64LE); + hdf5_read_single_val(&(mp_koral_jetcoords.alpha_2), "alpha2", H5T_IEEE_F64LE); + hdf5_read_single_val(&(mp_koral_jetcoords.cylindrify), "cylindrify", H5T_IEEE_F64LE); + hdf5_set_directory("/header/geom/"); + fprintf(stderr, "KORAL simulation was run with JETCOORDS coordinates.\n"); + } else { + fprintf(stderr, "! unknown koral metric_run (%s). quitting.\n", metric_run); + exit(5); + } + + // get grid information + hdf5_set_directory("/header/"); + hdf5_read_single_val(&N1, "n1", H5T_STD_I32LE); + hdf5_read_single_val(&N2, "n2", H5T_STD_I32LE); + hdf5_read_single_val(&N3, "n3", H5T_STD_I32LE); + hdf5_read_single_val(&gam, "gam", H5T_IEEE_F64LE); + + hdf5_set_directory("/header/geom/"); + hdf5_read_single_val(&startx[1], "startx1", H5T_IEEE_F64LE); + hdf5_read_single_val(&startx[2], "startx2", H5T_IEEE_F64LE); + hdf5_read_single_val(&startx[3], "startx3", H5T_IEEE_F64LE); + hdf5_read_single_val(&dx[1], "dx1", H5T_IEEE_F64LE); + hdf5_read_single_val(&dx[2], "dx2", H5T_IEEE_F64LE); + hdf5_read_single_val(&dx[3], "dx3", H5T_IEEE_F64LE); + hdf5_set_directory("/header/"); + + // reset x3 grid. this makes it easier for simcoords + Xtoijk to handle the + // translation, but it also means ipole and KORAL disagree about where x3=0 + if ( fabs(2.*M_PI - dx[3]*N3) >= dx[3] ) { + fprintf(stderr, "! base koral domain extent in x3 is not 2 PI. quitting.\n"); + exit(5); + } else { + startx[3] = 0.; + } + + stopx[0] = 1.; + stopx[1] = startx[1]+N1*dx[1]; + stopx[2] = startx[2]+N2*dx[2]; + stopx[3] = startx[3]+N3*dx[3]; + MULOOP cstartx[mu] = startx[mu]; + MULOOP cstopx[mu] = stopx[mu]; + + fprintf(stderr, "KORAL coordinates dx: %g %g %g\n", dx[1], dx[2], dx[3]); + fprintf(stderr, "Native coordinate start: %g %g %g stop: %g %g %g\n", + cstartx[1], cstartx[2], cstartx[3], cstopx[1], cstopx[2], cstopx[3]); + fprintf(stderr, "Grid start: %g %g %g stop: %g %g %g\n", + startx[1], startx[2], startx[3], stopx[1], stopx[2], stopx[3]); + + // unsupported features / undocumented elements + hdf5_set_directory("/header/geom/mks3/"); + if (hdf5_exists("gam_e")) { + fprintf(stderr, "! found gam_e in KORAL simulation. undocumented. quitting.\n"); + exit(5); + } + + // maybe load radiation units from dump file + RADIATION = 0; + hdf5_set_directory("/header/"); + if ( hdf5_exists("has_radiation") ) { + hdf5_read_single_val(&RADIATION, "has_radiation", H5T_STD_I32LE); + if (RADIATION) { + // Note set_units(...) get called AFTER this function returns + fprintf(stderr, "koral dump file was from radiation run. loading units...\n"); + hdf5_set_directory("/header/units/"); + hdf5_read_single_val(&MBH_solar, "M_bh", H5T_IEEE_F64LE); + hdf5_read_single_val(&RHO_unit, "M_unit", H5T_IEEE_F64LE); + hdf5_read_single_val(&T_unit, "T_unit", H5T_IEEE_F64LE); + hdf5_read_single_val(&L_unit, "L_unit", H5T_IEEE_F64LE); + hdf5_read_single_val(&U_unit, "U_unit", H5T_IEEE_F64LE); + hdf5_read_single_val(&B_unit, "B_unit", H5T_IEEE_F64LE); + M_unit = RHO_unit * L_unit*L_unit*L_unit; + } + } + + ELECTRONS = 0; + hdf5_set_directory("/header/"); + if ( hdf5_exists("has_electrons") ) { + hdf5_read_single_val(&ELECTRONS, "has_electrons", H5T_STD_I32LE); + if (ELECTRONS) { + fprintf(stderr, "koral dump has native electron temperature. forcing Thetae...\n"); + ELECTRONS = 9; + } else { + if (USE_MIXED_TPTE && !USE_FIXED_TPTE) { + fprintf(stderr, "Using mixed tp_over_te with trat_small = %g, trat_large = %g, and beta_crit = %g\n", trat_small, trat_large, beta_crit); + ELECTRONS = 2; + } else { + fprintf(stderr, "! koral unsupported without native electrons or mixed tp_over_te.\n"); + exit(6); + } + } + } + + fprintf(stderr, "sigma_cut = %g\n", sigma_cut); + + fprintf(stderr, "generating simcoords grid... "); + int interp_n1 = 1024; + int interp_n2 = 1024; + initialize_simgrid(interp_n1, interp_n2, + startx[1], startx[1]+N1*dx[1], + startx[2], startx[2]+N2*dx[2]); + fprintf(stderr, "done!\n"); + + init_storage(); + hdf5_close(); +} + void output_hdf5() { hdf5_set_directory("/"); @@ -888,10 +1219,15 @@ void output_hdf5() hdf5_write_single_val(&trat_small, "rlow", H5T_IEEE_F64LE); hdf5_write_single_val(&trat_large, "rhigh", H5T_IEEE_F64LE); hdf5_write_single_val(&beta_crit, "beta_crit", H5T_IEEE_F64LE); + } else if (ELECTRONS == ELECTRONS_TFLUID) { + hdf5_write_single_val(&mu_i, "mu_i", H5T_IEEE_F64LE); + hdf5_write_single_val(&mu_e, "mu_e", H5T_IEEE_F64LE); + hdf5_write_single_val(&mu_tot, "mu_tot", H5T_IEEE_F64LE); } hdf5_write_single_val(&ELECTRONS, "type", H5T_STD_I32LE); hdf5_set_directory("/header/"); + hdf5_write_single_val(&reverse_field,"field_config",H5T_STD_I32LE); hdf5_make_directory("units"); hdf5_set_directory("/header/units/"); hdf5_write_single_val(&L_unit, "L_unit", H5T_IEEE_F64LE); @@ -908,6 +1244,8 @@ void load_data(int n, char *fnam, int dumpidx, int verbose) { if (dumpfile_format == FORMAT_IHARM_v1) { load_iharm_data(n, fnam, dumpidx, verbose); + } else if (dumpfile_format == FORMAT_KORAL_v2) { + load_koral_data(n, fnam, dumpidx, verbose); } else if (dumpfile_format == FORMAT_HAMR_EKS) { load_hamr_data(n, fnam, dumpidx, verbose); } @@ -1197,6 +1535,224 @@ void load_hamr_data(int n, char *fnam, int dumpidx, int verbose) MdotEdd_dump = Mdotedd; Ladv_dump = Ladv; + double rescale_factor = 1.; + + if (target_mdot > 0) { + fprintf(stderr, "Resetting M_unit to match target_mdot = %g ", target_mdot); + + double current_mdot = Mdot_dump/MdotEdd_dump; + fprintf(stderr, "... is now %g\n", M_unit * fabs(target_mdot / current_mdot)); + rescale_factor = fabs(target_mdot / current_mdot); + M_unit *= rescale_factor; + + set_units(); + } + + Mdot_dump = -dMact*M_unit/T_unit; + MdotEdd_dump = Mdotedd; + Ladv_dump = Ladv; + + if (verbose == 2) { + fprintf(stderr,"dMact: %g [code]\n",dMact); + fprintf(stderr,"Ladv: %g [code]\n",Ladv_dump); + fprintf(stderr,"Mdot: %g [g/s] \n",Mdot_dump); + fprintf(stderr,"Mdot: %g [MSUN/YR] \n",Mdot_dump/(MSUN / YEAR)); + fprintf(stderr,"Mdot: %g [Mdotedd]\n",Mdot_dump/MdotEdd_dump); + fprintf(stderr,"Mdotedd: %g [g/s]\n",MdotEdd_dump); + fprintf(stderr,"Mdotedd: %g [MSUN/YR]\n",MdotEdd_dump/(MSUN/YEAR)); + } else if (verbose == 1) { + fprintf(stderr,"Mdot: %g [MSUN/YR] \n",Mdot_dump/(MSUN / YEAR)); + fprintf(stderr,"Mdot: %g [Mdotedd]\n",Mdot_dump/MdotEdd_dump); + } + + // now construct useful scalar quantities (over full (+ghost) zones of data) + init_physical_quantities(n, rescale_factor); +} + +void load_koral_data(int n, char *fnam, int dumpidx, int verbose) +{ + // loads relevant information from fluid dump file stored at fname + // to the n'th copy of data (e.g., for slow light) + + double dMact, Ladv; + + char fname[256]; + snprintf(fname, 255, fnam, dumpidx); + + nloaded++; + + if ( hdf5_open(fname) < 0 ) { + fprintf(stderr, "! unable to open file %s. Exiting!\n", fname); + exit(-1); + } + + hdf5_set_directory("/"); + hdf5_read_single_val(&(data[n]->t), "t", H5T_IEEE_F64LE); + + hdf5_set_directory("/quants/"); + + // load into "center" of data + hsize_t fdims[] = { N1, N2, N3 }; + hsize_t fstart[] = { 0, 0, 0 }; + hsize_t fcount[] = { N1, N2, N3, 1 }; + hsize_t mdims[] = { N1+2, N2+2, N3+2 }; + hsize_t mstart[] = { 1, 1, 1 }; + + hdf5_read_array(data[n]->p[KRHO][0][0], "rho", 3, fdims, fstart, fcount, + mdims, mstart, H5T_IEEE_F64LE); + + hdf5_read_array(data[n]->p[UU][0][0], "uint", 3, fdims, fstart, fcount, + mdims, mstart, H5T_IEEE_F64LE); + + hdf5_read_array(data[n]->p[U1][0][0], "U1", 3, fdims, fstart, fcount, + mdims, mstart, H5T_IEEE_F64LE); + + hdf5_read_array(data[n]->p[U2][0][0], "U2", 3, fdims, fstart, fcount, + mdims, mstart, H5T_IEEE_F64LE); + + hdf5_read_array(data[n]->p[U3][0][0], "U3", 3, fdims, fstart, fcount, + mdims, mstart, H5T_IEEE_F64LE); + + hdf5_read_array(data[n]->p[B1][0][0], "B1", 3, fdims, fstart, fcount, + mdims, mstart, H5T_IEEE_F64LE); + + hdf5_read_array(data[n]->p[B2][0][0], "B2", 3, fdims, fstart, fcount, + mdims, mstart, H5T_IEEE_F64LE); + + hdf5_read_array(data[n]->p[B3][0][0], "B3", 3, fdims, fstart, fcount, + mdims, mstart, H5T_IEEE_F64LE); + + if (ELECTRONS == 9) { + hdf5_read_array(data[n]->p[TFLK][0][0], "te", 3, fdims, fstart, fcount, + mdims, mstart, H5T_IEEE_F64LE); + } + + hdf5_close(); + + dMact = Ladv = 0.; + + // construct four-vectors over "real" zones +#pragma omp parallel for collapse(2) reduction(+:dMact,Ladv) + for(int i = 1; i < N1+1; i++) { + for(int j = 1; j < N2+1; j++) { + + double X[NDIM] = { 0. }; + double gcov[NDIM][NDIM], gcon[NDIM][NDIM], gcov_KS[NDIM][NDIM], gcon_KS[NDIM][NDIM]; + double g, r, th; + + // this assumes axisymmetry in the coordinates + ijktoX(i-1,j-1,0, X); + gcov_func(X, gcov); + gcon_func(gcov, gcon); + g = gdet_zone(i-1,j-1,0); + + bl_coord(X, &r, &th); + + // the file is output in KS, so get KS metric + gcov_ks(r, th, gcov_KS); + gcon_func(gcov_KS, gcon_KS); + + for(int k = 1; k < N3+1; k++){ + + ijktoX(i-1,j-1,k,X); + double UdotU = 0.; + + for(int l = 1; l < NDIM; l++) + for(int m = 1; m < NDIM; m++) + UdotU += gcov_KS[l][m]*data[n]->p[U1+l-1][i][j][k]*data[n]->p[U1+m-1][i][j][k]; + double ufac = sqrt(-1./gcon_KS[0][0]*(1 + fabs(UdotU))); + + double ucon[NDIM] = { 0. }; + ucon[0] = -ufac * gcon_KS[0][0]; + + for(int l = 1; l < NDIM; l++) + ucon[l] = data[n]->p[U1+l-1][i][j][k] - ufac*gcon_KS[0][l]; + + double ucov[NDIM] = { 0. }; + flip_index(ucon, gcov_KS, ucov); + + // reconstruct the magnetic field three vectors + double udotB = 0.; + + for (int l = 1; l < NDIM; l++) { + udotB += ucov[l]*data[n]->p[B1+l-1][i][j][k]; + } + + double bcon[NDIM] = { 0. }; + double bcov[NDIM] = { 0. }; + + bcon[0] = udotB; + for (int l = 1; l < NDIM; l++) { + bcon[l] = (data[n]->p[B1+l-1][i][j][k] + ucon[l]*udotB)/ucon[0]; + } + flip_index(bcon, gcov_KS, bcov); + + double bsq = 0.; + for (int l=0; lb[i][j][k] = sqrt(bsq) * B_unit; + + if(i <= 21) { dMact += g * data[n]->p[KRHO][i][j][k] * ucon[1]; } + if(i >= 21 && i < 41 && 0) Ladv += g * data[n]->p[UU][i][j][k] * ucon[1] * ucov[0]; + if(i <= 21) Ladv += g * data[n]->p[UU][i][j][k] * ucon[1] * ucov[0]; + + // trust ... + //double udb1 = 0., udu1 = 0., bdb1 = 0.; + //MULOOP { udb1 += ucon[mu]*bcov[mu]; udu1 += ucon[mu]*ucov[mu]; bdb1 += bcon[mu]*bcov[mu]; } + + // now translate from KS (outcoords) -> MKS:hslope=1 + ucon[1] /= r; + ucon[2] /= M_PI; + bcon[1] /= r; + bcon[2] /= M_PI; + + // resynthesize the primitives. note we have assumed that B^i = *F^{ti} + double alpha = sqrt(-1. / gcon[0][0]); + data[n]->p[U1][i][j][k] = (gcon[0][1]*alpha*alpha + ucon[1]/ucon[0]) * ucon[0]; + data[n]->p[U2][i][j][k] = (gcon[0][2]*alpha*alpha + ucon[2]/ucon[0]) * ucon[0]; + data[n]->p[U3][i][j][k] = (gcon[0][3]*alpha*alpha + ucon[3]/ucon[0]) * ucon[0]; + data[n]->p[B1][i][j][k] = ucon[0] * bcon[1] - bcon[0] * ucon[1]; + data[n]->p[B2][i][j][k] = ucon[0] * bcon[2] - bcon[0] * ucon[2]; + data[n]->p[B3][i][j][k] = ucon[0] * bcon[3] - bcon[0] * ucon[3]; + + // ... but verify + //flip_index(bcon, gcov, bcov); + //flip_index(ucon, gcov, ucov); + //double udb2 = 0., udu2 = 0., bdb2 = 0.; + //MULOOP { udb2 += ucon[mu]*bcov[mu]; udu2 += ucon[mu]*ucov[mu]; bdb2 += bcon[mu]*bcov[mu]; } + //fprintf(stderr, "u.u %g %g u.b %g %g b.b %g %g\n", udu1,udu2, udb1,udb2, bdb1,bdb2); + } + } + } + + // now copy primitives and four-vectors according to boundary conditions + populate_boundary_conditions(n); + + dMact *= dx[3]*dx[2] ; + dMact /= 21. ; + Ladv *= dx[3]*dx[2] ; + Ladv /= 21. ; + + Mdot_dump = -dMact*M_unit/T_unit; + MdotEdd_dump = Mdotedd; + Ladv_dump = Ladv; + + double rescale_factor = 1.; + + if (target_mdot > 0) { + fprintf(stderr, "Resetting M_unit to match target_mdot = %g ", target_mdot); + + double current_mdot = Mdot_dump/MdotEdd_dump; + fprintf(stderr, "... is now %g\n", M_unit * fabs(target_mdot / current_mdot)); + rescale_factor = fabs(target_mdot / current_mdot); + M_unit *= rescale_factor; + + set_units(); + } + + Mdot_dump = -dMact*M_unit/T_unit; + MdotEdd_dump = Mdotedd; + Ladv_dump = Ladv; + if (verbose == 2) { fprintf(stderr,"dMact: %g [code]\n",dMact); fprintf(stderr,"Ladv: %g [code]\n",Ladv_dump); @@ -1211,7 +1767,50 @@ void load_hamr_data(int n, char *fnam, int dumpidx, int verbose) } // now construct useful scalar quantities (over full (+ghost) zones of data) - init_physical_quantities(n); + init_physical_quantities(n, rescale_factor); +} + +// get dMact in the i'th radial zone (0 = 0 of the dump, so ignore ghost zones) +double get_code_dMact(int i, int n) +{ + i += 1; + double dMact = 0; +#pragma omp parallel for collapse(1) reduction(+:dMact) + for (int j=1; jp[U1+l-1][i][j][k]*data[n]->p[U1+m-1][i][j][k]; + double ufac = sqrt(-1./gcon[0][0]*(1 + fabs(UdotU))); + + double ucon[NDIM] = { 0. }; + ucon[0] = -ufac * gcon[0][0]; + + for(int l = 1; l < NDIM; l++) + ucon[l] = data[n]->p[U1+l-1][i][j][k] - ufac*gcon[0][l]; + + double ucov[NDIM] = { 0. }; + flip_index(ucon, gcov, ucov); + + dMact += g * data[n]->p[KRHO][i][j][k] * ucon[1]; + } + + } + return dMact; } void load_iharm_data(int n, char *fnam, int dumpidx, int verbose) @@ -1267,8 +1866,26 @@ void load_iharm_data(int n, char *fnam, int dumpidx, int verbose) hdf5_read_array(data[n]->p[KTOT][0][0], "prims", 4, fdims, fstart, fcount, mdims, mstart, H5T_IEEE_F64LE); } + //Reversing B Field + if(reverse_field) { + double multiplier = -1.0; + for(int i=0;ip[B1][i][j][k] = multiplier*data[n]->p[B1][i][j][k]; + data[n]->p[B2][i][j][k] = multiplier*data[n]->p[B2][i][j][k]; + data[n]->p[B3][i][j][k] = multiplier*data[n]->p[B3][i][j][k]; + } + } + } + } hdf5_read_single_val(&(data[n]->t), "t", H5T_IEEE_F64LE); + if (ELECTRONS == ELECTRONS_TFLUID) { + fstart[3] = 8; + hdf5_read_array(data[n]->p[THF][0][0], "prims", 4, fdims, fstart, fcount, mdims, mstart, H5T_IEEE_F64LE); + } + hdf5_close(); dMact = Ladv = 0.; @@ -1294,7 +1911,7 @@ void load_iharm_data(int n, char *fnam, int dumpidx, int verbose) ijktoX(i-1,j-1,k,X); double UdotU = 0.; - + // the four-vector reconstruction should have gcov and gcon and gdet using the modified coordinates // interpolating the four vectors to the zone center !!!! for(int l = 1; l < NDIM; l++) @@ -1314,11 +1931,11 @@ void load_iharm_data(int n, char *fnam, int dumpidx, int verbose) // reconstruct the magnetic field three vectors double udotB = 0.; - + for (int l = 1; l < NDIM; l++) { udotB += ucov[l]*data[n]->p[B1+l-1][i][j][k]; } - + double bcon[NDIM] = { 0. }; double bcov[NDIM] = { 0. }; @@ -1332,7 +1949,8 @@ void load_iharm_data(int n, char *fnam, int dumpidx, int verbose) for (int l=0; lb[i][j][k] = sqrt(bsq) * B_unit; - if(i <= 21) { dMact += g * data[n]->p[KRHO][i][j][k] * ucon[1];; } + + if(i <= 21) { dMact += g * data[n]->p[KRHO][i][j][k] * ucon[1]; } if(i >= 21 && i < 41 && 0) Ladv += g * data[n]->p[UU][i][j][k] * ucon[1] * ucov[0]; if(i <= 21) Ladv += g * data[n]->p[UU][i][j][k] * ucon[1] * ucov[0]; @@ -1340,6 +1958,31 @@ void load_iharm_data(int n, char *fnam, int dumpidx, int verbose) } } + // check if 21st zone (i.e., 20th without ghost zones) is beyond r_eh + // otherwise recompute + if (1==1) { + double r_eh = 1. + sqrt(1. - a*a); + int N2_by_2 = (int)(N2/2); + + double X[NDIM] = { 0. }; + ijktoX(20, N2_by_2, 0, X); + + double r, th; + bl_coord(X, &r, &th); + + if (r < r_eh) { + for (int i=1; i= r_eh) { + fprintf(stderr, "r_eh is beyond regular zones. recomputing at %g...\n", r); + dMact = get_code_dMact(i, n) * 21; + break; + } + } + } + } + // now copy primitives and four-vectors according to boundary conditions populate_boundary_conditions(n); @@ -1352,6 +1995,23 @@ void load_iharm_data(int n, char *fnam, int dumpidx, int verbose) MdotEdd_dump = Mdotedd; Ladv_dump = Ladv; + double rescale_factor = 1.; + + if (target_mdot > 0) { + fprintf(stderr, "Resetting M_unit to match target_mdot = %g ", target_mdot); + + double current_mdot = Mdot_dump/MdotEdd_dump; + fprintf(stderr, "... is now %g\n", M_unit * fabs(target_mdot / current_mdot)); + rescale_factor = fabs(target_mdot / current_mdot); + M_unit *= rescale_factor; + + set_units(); + } + + Mdot_dump = -dMact*M_unit/T_unit; + MdotEdd_dump = Mdotedd; + Ladv_dump = Ladv; + if (verbose == 2) { fprintf(stderr,"dMact: %g [code]\n",dMact); fprintf(stderr,"Ladv: %g [code]\n",Ladv_dump); @@ -1366,9 +2026,10 @@ void load_iharm_data(int n, char *fnam, int dumpidx, int verbose) } // now construct useful scalar quantities (over full (+ghost) zones of data) - init_physical_quantities(n); + init_physical_quantities(n, rescale_factor); } + int radiating_region(double X[NDIM]) { double r, th; @@ -1376,20 +2037,6 @@ int radiating_region(double X[NDIM]) return (r > rmin_geo && r < rmax_geo && th > th_beg && th < (M_PI-th_beg)); } -void get_model_powerlaw_vals(double X[NDIM], double *p, double *n, - double *gamma_min, double *gamma_max, double *gamma_cut) -{ - *gamma_min = powerlaw_gamma_min; - *gamma_max = powerlaw_gamma_max; - *gamma_cut = powerlaw_gamma_cut; - *p = powerlaw_p; - - double b = get_model_b(X); - double u_nth = powerlaw_eta*b*b/2; - // Number density of nonthermals - *n = u_nth * (*p - 2)/(*p - 1) * 1/(ME * CL*CL * *gamma_min); -} - // In case we want to mess with emissivities directly void get_model_jar(double X[NDIM], double Kcon[NDIM], double *jI, double *jQ, double *jU, double *jV, diff --git a/model/iharm/model_params.h b/model/iharm/model_params.h index 23b45f2..0167fa7 100644 --- a/model/iharm/model_params.h +++ b/model/iharm/model_params.h @@ -14,9 +14,12 @@ #define B3 7 #define KEL 8 #define KTOT 9 +// These two will never be used simultaneously, +// and never with KEL. +#define TFLK 8 // temperature of fluid in Kelvin +#define THF 8 // fluid temperature in me c^2 extern double DTd; extern double sigma_cut; -extern double rmax_geo; #endif // MODEL_PARAMS_H diff --git a/model/ldi2/model.c b/model/ldi2/model.c index a06de3b..9ce36d5 100644 --- a/model/ldi2/model.c +++ b/model/ldi2/model.c @@ -18,8 +18,6 @@ double U_unit; double B_unit; double Te_unit; -// TODO get rid of these in ipole proper to get rid of them here -double rmax_geo = 1e30; double model_dl; // TODO this default needs to be kept in sync with maxnstep, // to avoid difficulties @@ -191,3 +189,5 @@ double get_model_thetae(double X[NDIM]) {return 0;} double get_model_b(double X[NDIM]) {return 1;} double get_model_ne(double X[NDIM]) {return 1;} // Otherwise we trigger the "empty space" emissivity void get_model_primitives(double X[NDIM], double *p) {return;} +void update_data(double *tA, double *tB) {return;} +void update_data_until(double *tA, double *tB, double tgt) {return;} \ No newline at end of file diff --git a/model/ldi2/model_params.h b/model/ldi2/model_params.h index f12f210..e2d1422 100644 --- a/model/ldi2/model_params.h +++ b/model/ldi2/model_params.h @@ -11,7 +11,6 @@ #define INTEGRATOR_TEST (1) // Model parameters (TODO eliminate if we can these are unused) -extern double rmax_geo; extern double model_dl; void record_stokes_parameters(double SI, double SQ, double SU, double SV, double lam); diff --git a/model/riaf/example.par b/model/riaf/example.par new file mode 100644 index 0000000..4e3c47f --- /dev/null +++ b/model/riaf/example.par @@ -0,0 +1,47 @@ +# RIAF model options + +outfile image.h5 + +a 0.9375 +Ne_unit 3e7 +Te_unit 3e11 + +# Model parameters from Odyssey (proprietary) +# c.f. Themis src/model/model_image_sed_fitted_riaf.cpp/h +# Parameters correspond to n_e,th^0, T_e^0, H, -alpha, -gamma in Pu & Broderick '18 +# Note that nth0/Te0 are in terms of Ne_unit & Te_unit, which should be used to set scale +nth0 1 +Te0 1 +disk_h 0.1 +pow_nth -1.1 +pow_T -0.84 + +# Inflow parameters for Shearing Inflow model from VRT2, +# Themis src/VRT2/src/AccretionFlows/afv_shearing_inflow.cpp/h +keplerian_factor 1.0 +infall_factor 0.0 + +# RIAF model gives e- Ne/Te, so use Dexter emissivity fits +emission_type 1 + +# Camera location +rcam 1000 +thetacam 85 +phicam 0 + +# Camera FOV in r_g +fovx_dsource 200 +fovy_dsource 200 + +# Image size +nx 100 +ny 100 + +# Distance to source in pc +dsource 8.3e3 + +# Standard frequency nu_p +freqcgs 230.e9 + +# MBH in solar masses +MBH 4.3e6 diff --git a/model/riaf/model.c b/model/riaf/model.c new file mode 100644 index 0000000..8d16490 --- /dev/null +++ b/model/riaf/model.c @@ -0,0 +1,387 @@ +#include "model.h" + +#include "decs.h" +#include "coordinates.h" +#include "geometry.h" +#include "radiation.h" +#include "par.h" +#include "tetrads.h" + +// Globals we're in charge of +double M_unit; +double L_unit; +double T_unit; +double U_unit; +double B_unit; +double RHO_unit; +// These actually set problem scale +double Te_unit = 1e11; +double Ne_unit = 5e6; + +// Model parameters: private +static double MBH_solar = 4.3e6; + +static double MBH; + +double gam = 4./3; +// Require all these +double nth0, Te0, disk_h, pow_nth, pow_T; +// Default fully keplerian, no infall velocity +double keplerian_factor = 1.; +double infall_factor = 0.; +double r_isco; + +/** + * This is a template for analytic problems, which consist of prescription: + * X,K -> 4-vectors Ucon/cov, Bcon/cov + * And either: + * X,K -> emission coefficients jS, alphaS, rhoS + * or: + * X,K -> e- density and temperature ne, Thetae + */ + +// Forward declarations for non-public functions +void set_units(); + +//// INITIALIZATION: Functions called from elsewhere in ipole //// + +/** + * This function is called for each word/value pair ipole encounters, + * either from a parfile or the command line. + * You can define new pairs here, skim what you need of ipole's defaults, or whatever + * + * ipole will not warn on unspecified parameters. Have good defaults (set on declaration) + */ +void try_set_model_parameter(const char *word, const char *value) +{ + // Test the given word against our parameters' names, + // and if it matches set the corresponding global + set_by_word_val(word, value, "MBH", &MBH_solar, TYPE_DBL); + set_by_word_val(word, value, "Ne_unit", &Ne_unit, TYPE_DBL); + set_by_word_val(word, value, "Te_unit", &Te_unit, TYPE_DBL); + // TODO NEED to move this into main parameters + set_by_word_val(word, value, "rmax_geo", &rmax_geo, TYPE_DBL); + set_by_word_val(word, value, "rmin_geo", &rmin_geo, TYPE_DBL); + + // Geometry parameters + set_by_word_val(word, value, "a", &a, TYPE_DBL); + // Fluid parameter (only used for pressure calculation for beta) + set_by_word_val(word, value, "gam", &gam, TYPE_DBL); + // RIAF model parameters, from Odyssey. + // Note nth0,Te0 are *different* as they are unitless! + set_by_word_val(word, value, "nth0", &nth0, TYPE_DBL); + set_by_word_val(word, value, "Te0", &Te0, TYPE_DBL); + set_by_word_val(word, value, "disk_h", &disk_h, TYPE_DBL); + set_by_word_val(word, value, "pow_nth", &pow_nth, TYPE_DBL); + set_by_word_val(word, value, "pow_T", &pow_T, TYPE_DBL); + // Inflow model parameters + set_by_word_val(word, value, "keplerian_factor", &keplerian_factor, TYPE_DBL); + set_by_word_val(word, value, "infall_factor", &infall_factor, TYPE_DBL); + + // Normal ipole pulls this, but we also need it for the GRRT problems + // and this is easier than grabbing it from the 'params' struct + //set_by_word_val(word, value, "freqcgs", &freqcgs, TYPE_DBL); +} + +/** + * Initialization takes boundary times, for slow light. Most analytic models won't use them. + */ +void init_model(double *tA, double *tB) +{ + // Set all the geometry globals we need + // TODO do this in geometry? Deal with model/geom interface... + use_eKS_internal = 0; + metric = METRIC_MKS; + hslope = 1.0; + + // We already set stuff from parameters, so set_units here + set_units(); + + fprintf(stderr, "Running RIAF model with a=%g, nth0=%g, Te0=%g, disk_h=%g, pow_nth=%g, pow_T=%g\n", + a, nth0, Te0, disk_h, pow_nth, pow_T); + fprintf(stderr, "Velocity model: Keplerian by %g, infall rate %g\n", + keplerian_factor, infall_factor); + // TODO B field when there are params +} + +void set_units() +{ + // Derive units we actually need + // We already have Te_unit as a parameter + MBH = MBH_solar * MSUN; + L_unit = GNEWT * MBH / (CL * CL); + T_unit = L_unit / CL; + + RHO_unit = Ne_unit * (MP + ME); + B_unit = CL * sqrt(4.*M_PI*RHO_unit); + M_unit = RHO_unit * pow(L_unit, 3); + + // Set all the geometry for coordinates.c + // TODO function like initialize_coordinates, that makes sure these are all set. + R0 = 0.; + Rh = 1 + sqrt(1. - a * a); + // These are for tracing. We only *emit* inside *_geo parameters + Rin = Rh; + Rout = rmax_geo; + + double z1 = 1. + pow(1. - a * a, 1. / 3.) * (pow(1. + a, 1. / 3.) + pow(1. - a, 1. / 3.)); + double z2 = sqrt(3. * a * a + z1 * z1); + r_isco = 3. + z2 - copysign(sqrt((3. - z1) * (3. + z1 + 2. * z2)), a); + startx[0] = 0.0; + startx[1] = log(Rin); + startx[2] = 0.0; + startx[3] = 0.0; + stopx[0] = 0.0; + stopx[1] = log(Rout); + stopx[2] = 1.0; + stopx[3] = 2*M_PI; + MULOOP cstartx[mu] = startx[mu]; + MULOOP cstopx[mu] = stopx[mu]; +} + +void output_hdf5() +{ + hdf5_set_directory("/header/"); + double zero = 0; + hdf5_write_single_val(&zero, "t", H5T_IEEE_F64LE); + hdf5_write_single_val(&a, "a", H5T_IEEE_F64LE); + + // TODO output all riaf terms + + hdf5_make_directory("units"); + hdf5_set_directory("/header/units/"); + // TODO set M_unit correctly + hdf5_write_single_val(&zero, "M_unit", H5T_IEEE_F64LE); + hdf5_write_single_val(&L_unit, "L_unit", H5T_IEEE_F64LE); + hdf5_write_single_val(&T_unit, "T_unit", H5T_IEEE_F64LE); + + hdf5_set_directory("/"); +} + +//// INTERFACE: Functions called from elsewhere in ipole //// +double get_model_ne(double X[NDIM]) +{ + // Matter model defined in Gold et al 2020 section 3 + double r, th; + bl_coord(X, &r, &th); + double zc=r*cos(th); + double rc=r*sin(th); + return nth0 * exp(-zc*zc/2./rc/rc/disk_h/disk_h) * pow(r,pow_nth) * Ne_unit; +} + +double get_model_thetae(double X[NDIM]) +{ + double r, th; + bl_coord(X, &r, &th); + return Te0 * pow(r, pow_T) * Te_unit * KBOL / (ME*CL*CL); +} + +double get_model_b(double X[NDIM]) +{ + // double Ucon[NDIM],Bcon[NDIM]; + // double Ucov[NDIM],Bcov[NDIM]; + // double Kcon[NDIM] = {0}; // TODO interface change if we ever need a real one here + // get_model_fourv(X, Kcon, Ucon, Ucov, Bcon, Bcov); + // return sqrt(Bcon[0]*Bcov[0] + Bcon[1]*Bcov[1] + Bcon[2]*Bcov[2] + Bcon[3]*Bcov[3]) * B_unit; + + double r, th; + bl_coord(X, &r, &th); + double nth = get_model_ne(X); + // Get local field strength + double eps = 0.1; + double b = sqrt(8. * M_PI * eps * nth * MP * CL * CL / 6. / r); + if (b == 0) b = 1.e-6; + return b; +} +// TODO? +double get_model_sigma(double X[NDIM]) {return 0.;} +double get_model_beta(double X[NDIM]) {return 0.;} + +void get_model_fourv(double X[NDIM], double Kcon[NDIM], double Ucon[NDIM], double Ucov[NDIM], + double Bcon[NDIM], double Bcov[NDIM]) +{ + double r, th; + bl_coord(X, &r, &th); + + // Metrics: BL + double bl_gcov[NDIM][NDIM], bl_gcon[NDIM][NDIM]; + gcov_bl(r, th, bl_gcov); + gcon_func(bl_gcov, bl_gcon); + // Native + double gcov[NDIM][NDIM], gcon[NDIM][NDIM]; + gcov_func(X, gcov); + gcon_func(gcov, gcon); + + // Get the 4-velocity + double bl_Ucon[NDIM]; + double omegaK, omegaFF, omega; + double K, ur, ut; + if (r < Rh) { + // Inside r_h, none + double bl_Ucov[NDIM]; + bl_Ucov[0] = -1; + bl_Ucov[1] = 0.; + bl_Ucov[2] = 0.; + bl_Ucov[3] = 0.; + flip_index(bl_Ucov, bl_gcon, bl_Ucon); + } else if (r < r_isco) { + // Inside r_isco, freefall + double omegaK_isco = 1. / (pow(r_isco, 3./2) + a); + + // Get conserved quantities at the ISCO... + double bl_Ucon_isco[NDIM], bl_Ucov_isco[NDIM]; + bl_Ucon_isco[0] = 1.0; + bl_Ucon_isco[1] = 0.0; + bl_Ucon_isco[2] = 0.0; + bl_Ucon_isco[3] = omegaK_isco; + + double bl_gcov_isco[NDIM][NDIM]; + gcov_bl(r_isco, th, bl_gcov_isco); + + normalize(bl_Ucon_isco, bl_gcov_isco); + flip_index(bl_Ucon_isco, bl_gcov_isco, bl_Ucov_isco); + double e = bl_Ucov_isco[0]; + double l = bl_Ucov_isco[3]; + + // ...then set the infall velocity and find omega + double bl_Ucon_tmp[NDIM], bl_Ucov_tmp[NDIM]; + double K_con = bl_gcon[0][0] * e * e + 2.0 * bl_gcon[0][3] * e * l + bl_gcon[3][3] * l * l; + double urk_precut = -(1.0 + K_con) / bl_gcon[1][1]; + double urk = -sqrt(fmax(0.0, urk_precut)); + bl_Ucov_tmp[0] = e; + bl_Ucov_tmp[1] = urk; + bl_Ucov_tmp[2] = 0.0; + bl_Ucov_tmp[3] = l; + flip_index(bl_Ucov_tmp, bl_gcon, bl_Ucon_tmp); + omegaK = bl_Ucon_tmp[3] / bl_Ucon_tmp[0]; + + omegaFF = bl_gcon[0][3] / bl_gcon[0][0]; + // Compromise + omega = omegaK + (1 - keplerian_factor)*(omegaFF - omegaK); + + // Then set the infall rate + double urFF = -sqrt(fmax(0.0, -(1.0 + bl_gcon[0][0]) * bl_gcon[1][1])); + ur = bl_Ucon_tmp[1] + infall_factor * (urFF - bl_Ucon_tmp[1]); + +#if DEBUG + if (fabs(ur) < 1e-10) { + fprintf(stderr, "Bad ur: ur is %g\n", ur); + fprintf(stderr, "Ucon BL: %g %g %g %g\n", + bl_Ucon_tmp[0], bl_Ucon_tmp[1], bl_Ucon_tmp[2], bl_Ucon_tmp[3]); + fprintf(stderr, "Ucov BL: %g %g %g %g\n", + bl_Ucov_tmp[0], bl_Ucov_tmp[1], bl_Ucov_tmp[2], bl_Ucov_tmp[3]); + fprintf(stderr, "urk was %g (%g pre-cut), e & l were %g %g\n", urk, urk_precut, e, l); + } +#endif + + // Finally, get Ucon in BL coordinates + K = bl_gcov[0][0] + 2*omega*bl_gcov[0][3] + omega*omega*bl_gcov[3][3]; + ut = sqrt(fmax(0.0, -(1. + ur*ur*bl_gcov[1][1]) / K)); + bl_Ucon[0] = ut; + bl_Ucon[1] = ur; + bl_Ucon[2] = 0.; + bl_Ucon[3] = omega * ut; + } else { + // Outside r_isco, Keplerian + omegaK = 1. / (pow(r, 3./2) + a); + omegaFF = bl_gcon[0][3] / bl_gcon[0][0]; + + // Compromise + omega = omegaK + (1 - keplerian_factor)*(omegaFF - omegaK); + // Set infall rate + ur = infall_factor * -sqrt(fmax(0.0, -(1.0 + bl_gcon[0][0]) * bl_gcon[1][1])); + + // Get the normal observer velocity for Ucon/Ucov, in BL coordinates + K = bl_gcov[0][0] + 2*omega*bl_gcov[0][3] + omega*omega*bl_gcov[3][3]; + ut = sqrt(fmax(0.0, -(1. + ur*ur*bl_gcov[1][1]) / K)); + bl_Ucon[0] = ut; + bl_Ucon[1] = ur; + bl_Ucon[2] = 0.; + bl_Ucon[3] = omega * ut; + } + + // Transform to KS coordinates, + double ks_Ucon[NDIM]; + bl_to_ks(X, bl_Ucon, ks_Ucon); + // then to our coordinates, + vec_from_ks(X, ks_Ucon, Ucon); + + // and grab Ucov + flip_index(Ucon, gcov, Ucov); + + + // Check +#if DEBUG + //if (r < r_isco) { fprintf(stderr, "ur = %g\n", Ucon[1]); } + double bl_Ucov[NDIM]; + double dot_U = Ucon[0]*Ucov[0] + Ucon[1]*Ucov[1] + Ucon[2]*Ucov[2] + Ucon[3]*Ucov[3]; + double sum_U = Ucon[0]+Ucon[1]+Ucon[2]+Ucon[3]; + // Following condition gets handled better above + // (r < r_isco && fabs(Ucon[1]) < 1e-10) || + if (get_fluid_nu(Kcon, Ucov) == 1. || + fabs(fabs(dot_U) - 1.) > 1e-10 || sum_U < 0.1) { + flip_index(bl_Ucon, bl_gcov, bl_Ucov); + fprintf(stderr, "RIAF model problem at r, th, phi = %g %g %g\n", r, th, X[3]); + fprintf(stderr, "Omega K: %g FF: %g Final: %g K: %g ur: %g ut: %g\n", + omegaK, omegaFF, omega, K, ur, ut); + fprintf(stderr, "K1: %g K2: %g K3: %g\n", bl_gcov[0][0], 2*omega*bl_gcov[0][3], omega*omega*bl_gcov[3][3]); + fprintf(stderr, "Ucon BL: %g %g %g %g\n", bl_Ucon[0], bl_Ucon[1], bl_Ucon[2], bl_Ucon[3]); + fprintf(stderr, "Ucon KS: %g %g %g %g\n", ks_Ucon[0], ks_Ucon[1], ks_Ucon[2], ks_Ucon[3]); + fprintf(stderr, "Ucon native: %g %g %g %g\n", Ucon[0], Ucon[1], Ucon[2], Ucon[3]); + fprintf(stderr, "Ucov: %g %g %g %g\n", Ucov[0], Ucov[1], Ucov[2], Ucov[3]); + fprintf(stderr, "Ubl.Ubl: %g\n", bl_Ucov[0]*bl_Ucon[0]+bl_Ucov[1]*bl_Ucon[1]+ + bl_Ucov[2]*bl_Ucon[2]+bl_Ucov[3]*bl_Ucon[3]); + fprintf(stderr, "U.U: %g\n", Ucov[0]*Ucon[0]+Ucov[1]*Ucon[1]+Ucov[2]*Ucon[2]+Ucov[3]*Ucon[3]); + } +#endif + + // Use pure toroidal field, + // See Themis src/VRT2/src/AccretionFlows/mf_toroidal_beta.cpp/h + double bl_Bcon[NDIM]; + bl_Bcon[0] = 0.0; + bl_Bcon[1] = 0.0; + bl_Bcon[2] = 0.0; + bl_Bcon[3] = 1.0; + + // Transform to KS coordinates, + double ks_Bcon[NDIM]; + bl_to_ks(X, bl_Bcon, ks_Bcon); + // then to our coordinates, + vec_from_ks(X, ks_Bcon, Bcon); + normalize(Bcon, gcov); + + // Compute u.b and subtract it, normalize to get_model_b + //project_out(Bcon, Ucon, gcov); ? + double BdotU = 0; + MULOOP BdotU += Bcon[mu] * Ucov[mu]; + MULOOP Bcon[mu] += BdotU * Ucon[mu]; + flip_index(Bcon, gcov, Bcov); + double Bsq = 0; + MULOOP Bsq += Bcon[mu] * Bcov[mu]; + double bmag = fmax(get_model_b(X), 1e-10); + MULOOP Bcon[mu] *= bmag / sqrt(Bsq); + + flip_index(Bcon, gcov, Bcov); +} + +int radiating_region(double X[NDIM]) +{ + // If you don't want conditionals in get_model_jar, + // you can control here where the coefficients are applied + double r, th; + bl_coord(X, &r, &th); + return r > Rh + 0.1 && r > rmin_geo && r < Rout; +} + +//// STUBS: Functions for normal models which we don't use //// +// Define these to specify a fluid model: e- density/temperature for +// synchrotron radiation based on an energy distribution +void update_data(double *tA, double *tB) {return;} +void update_data_until(double *tA, double *tB, double tgt) {return;} +// This is only called for trace file output, and doesn't really apply to analytic models +void get_model_primitives(double X[NDIM], double *p) {return;} +void get_model_jk(double X[NDIM], double Kcon[NDIM], double *jnuinv, double *knuinv) {return;} +void get_model_jar(double X[NDIM], double Kcon[NDIM], + double *jI, double *jQ, double *jU, double *jV, + double *aI, double *aQ, double *aU, double *aV, + double *rQ, double *rU, double *rV) {return;} diff --git a/model/riaf/model_params.h b/model/riaf/model_params.h new file mode 100644 index 0000000..4d43416 --- /dev/null +++ b/model/riaf/model_params.h @@ -0,0 +1,12 @@ + +#ifndef MODEL_PARAMS_H +#define MODEL_PARAMS_H + +#include "decs.h" + +// No sense using slow light with a static model +#define SLOW_LIGHT (0) + +#define THIN_DISK (0) + +#endif /* MODEL_PARAMS_H */ diff --git a/model/riaf/riaf.txt b/model/riaf/riaf.txt new file mode 100644 index 0000000..de3c376 --- /dev/null +++ b/model/riaf/riaf.txt @@ -0,0 +1,44 @@ +# frequency inc Ftot Ftot_unpol nuLnu nuLnu_unpol +3162280000.0 90.0 0.0028489597534122634 0.0028339632545509746 7.425982004760713e+29 7.386892744006865e+29 +5360020000.0 90.0 0.009395550853617164 0.009367009171960488 4.151030407630589e+30 4.1384204616798237e+30 +9085180000.0 90.0 0.028418941300760378 0.028332481384368453 2.1281816205062987e+31 2.1217069808274846e+31 +15399300000.0 90.0 0.08140362809629215 0.08123329920348317 1.0332656651010776e+32 1.031103660767487e+32 +26101600000.0 90.0 0.21372986326771087 0.21402505951085055 4.598324278322123e+32 4.604675323660127e+32 +44241900000.0 90.0 0.4985393092576503 0.5065783945320218 1.8180274423784825e+33 1.847343641460539e+33 +74989400000.0 90.0 0.9693335017865592 1.0290719972737428 5.991566152976433e+33 6.360816928825053e+33 +127106000000.0 90.0 1.6967308037169337 1.8540498052723284 1.7776499837967799e+34 1.9424717221380902e+34 +215443000000.0 90.0 2.68266378175895 2.9538299046403664 4.763935697999135e+34 5.245478700766253e+34 +365174000000.0 90.0 3.630064157190802 3.9575557567899375 1.0926508462497957e+35 1.1912259562055964e+35 +618966000000.0 90.0 4.005393607427599 4.317732383232477 2.0435216873310643e+35 2.2028745811310884e+35 +1049140000000.0 90.0 3.652873635371252 3.8143217620662804 3.1588961702383436e+35 3.2985116948956176e+35 +1778280000000.0 90.0 2.7902571969940904 2.836653376710832 4.089890099296295e+35 4.1578964738602944e+35 +3014160000000.0 90.0 1.8512294055885699 1.8591252216322147 4.599321826236051e+35 4.618938735386181e+35 +5108970000000.0 90.0 1.0907044421523815 1.0908125562614366 4.59311893684354e+35 4.5935742214673565e+35 +8659640000000.0 90.0 0.5688984618077184 0.5682829874773504 4.060708872262758e+35 4.056315712073796e+35 +14678000000000.0 90.0 0.2578378716313442 0.2574897956591044 3.1194702483664345e+35 3.1152590258927796e+35 +24879000000000.0 90.0 0.09878668347435732 0.09865108125223593 2.025809401996887e+35 2.0230286197411848e+35 +42169700000000.0 90.0 0.030913145659099684 0.030871757735873903 1.0745112544752e+35 1.073072650012365e+35 +71477100000000.0 90.0 0.00757933021077172 0.007569505157504826 4.46544772187297e+34 4.459659181130204e+34 +121153000000000.0 90.0 0.0013850575286171222 0.001383317986259005 1.383149875354638e+34 1.3814127288852014e+34 +205353000000000.0 90.0 0.0001776853512653615 0.00017747000043365232 3.0075990158462426e+33 3.003953870397292e+33 +348070000000000.0 90.0 1.4892572562068718e-05 1.4879595743278076e-05 4.272711554195851e+32 4.268988476577636e+32 +589975000000000.0 90.0 7.415244637549087e-07 7.485854340162352e-07 3.606005206585084e+31 3.640342435861315e+31 +1000000000000000.0 90.0 2.503494934729222e-08 2.041599732909495e-08 2.0635451426734356e+30 1.682820745385068e+30 +223005000000.0 5.0 1.864400335340764 1.9744099453676192 3.427054648904674e+34 3.629269236791356e+34 +223005000000.0 10.0 1.8943145448547998 2.013637196869679 3.4820415682052686e+34 3.701374960050258e+34 +223005000000.0 15.0 1.9455349442371501 2.0784656687799745 3.5761925423789325e+34 3.8205396650925143e+34 +223005000000.0 20.0 2.014025566421305 2.165533923411518 3.7020888430356695e+34 3.9805845123020645e+34 +223005000000.0 25.0 2.0974178031963695 2.272477364946569 3.855376603880396e+34 4.177163010779739e+34 +223005000000.0 30.0 2.1902815688664155 2.392769883648396 4.0260744920011666e+34 4.3982791668064985e+34 +223005000000.0 35.0 2.289418662137776 2.523222627282035 4.20830372138629e+34 4.638071379378869e+34 +223005000000.0 40.0 2.387144877008537 2.6542360547782704 4.387939539211589e+34 4.878894215150267e+34 +223005000000.0 45.0 2.4793324106438557 2.780585799872368 4.557394408816205e+34 5.111144485172615e+34 +223005000000.0 50.0 2.557780782466688 2.8923270458402857 4.701594585279541e+34 5.316542086326103e+34 +223005000000.0 55.0 2.6177323156761805 2.9818046832488863 4.81179472668694e+34 5.481015749756392e+34 +223005000000.0 60.0 2.6534246703175217 3.0402553586023138 4.87740276568228e+34 5.588457083521743e+34 +223005000000.0 65.0 2.664715265817202 3.0667273264240706 4.898156617235848e+34 5.637116633013094e+34 +223005000000.0 70.0 2.6523728644264866 3.055358743885895 4.8754693846372925e+34 5.616219429284695e+34 +223005000000.0 75.0 2.6304077917851245 3.021039735088821 4.835094201860172e+34 5.553135811236366e+34 +223005000000.0 80.0 2.641159256586151 2.9925459371937095 4.854857048245929e+34 5.500759827017536e+34 +223005000000.0 85.0 2.701407659466602 3.0067494180801506 4.9656028817810815e+34 5.52686801005088e+34 +223005000000.0 90.0 2.7548546280928115 3.0283057950703323 5.063846632776135e+34 5.566491947344498e+34 diff --git a/model/sphere/model.c b/model/sphere/model.c new file mode 100644 index 0000000..41a40af --- /dev/null +++ b/model/sphere/model.c @@ -0,0 +1,226 @@ +#include "model.h" + +#include "decs.h" +#include "hdf5_utils.h" + +#include "coordinates.h" +#include "geometry.h" +#include "grid.h" +#include "model_radiation.h" // Only for outputting emissivities +#include "par.h" +#include "utils.h" + +#include +#include + +// used by other files +double L_unit; + +// model parameters +static double MODEL_R_0 = 100.; +static double MODEL_TAU_0 = 1.e-5; +static double MODEL_THETAE_0 = 10.; +static double MODEL_BETA_0 = 20.; +static double MODEL_MBH = 4.1e6; +static double MODEL_TP_OVER_TE = 3; +static double MODEL_GAM = 13./9; + +// derived model parameters +double model_Ne_0 = 1.; +double model_B_0 = 1.; +double THETAE_UNIT = 1.; + +void try_set_model_parameter(const char *word, const char *value) +{ + set_by_word_val(word, value, "Thetae0", &MODEL_THETAE_0, TYPE_DBL); + set_by_word_val(word, value, "R0", &MODEL_R_0, TYPE_DBL); + set_by_word_val(word, value, "tau0", &MODEL_TAU_0, TYPE_DBL); + set_by_word_val(word, value, "beta0", &MODEL_BETA_0, TYPE_DBL); + set_by_word_val(word, value, "gam", &MODEL_GAM, TYPE_DBL); + set_by_word_val(word, value, "tp_over_te", &MODEL_TP_OVER_TE, TYPE_DBL); + set_by_word_val(word, value, "MBH", &MODEL_MBH, TYPE_DBL); +} + +// used in slow light with real data +void update_data_until(double *tA, double *tB, double tgt) { } +void update_data(double *tA, double *tB) { } +double get_dump_t(char *fnam, int dumpidx) { return 0.; } + +void init_model(double *tA, double *tB) +{ + // set nice numbers here + *tA = 0.; + *tB = 1.; + + // set metric + use_eKS_internal = 0; + metric = METRIC_EMINKOWSKI; + + // set units + double MBH = MODEL_MBH * MSUN; + L_unit = GNEWT * MBH / (CL * CL); + + // derive model Ne (in cgs) + model_Ne_0 = MODEL_TAU_0 / SIGMA_THOMSON / MODEL_R_0 / L_unit; + + // derive model B (in gauss) + // since B = B(pressure), we need to specify the thermodynamics to + // find pressure = pressure(Thetae) + double gam = MODEL_GAM; + double game = 4./3; + double gamp = 5./3; + + // as implemented in the Illinois suite + THETAE_UNIT = MP/ME * (game-1.) * (gamp-1.) / ( (gamp-1.) + (game-1)*MODEL_TP_OVER_TE ); + + // as implemented in RAPTOR + kmonty + THETAE_UNIT = MP/ME * (gam-1.) / (1. + MODEL_TP_OVER_TE); + + // now we can find B (again, in gauss) + model_B_0 = CL * sqrt(8 * M_PI * (gam-1.) * (MP+ME) / MODEL_BETA_0) * sqrt( model_Ne_0 * MODEL_THETAE_0 ) / sqrt( THETAE_UNIT ); + + fprintf(stderr, "Running with isothermal sphere model.\n"); + fprintf(stderr, "MBH, L_unit: %g [Msun], %g\n",MBH/MSUN, L_unit); + fprintf(stderr, "Ne, Thetae, B: %g %g %g\n", model_Ne_0, MODEL_THETAE_0, model_B_0); + fprintf(stderr, "Rout: %g\n", MODEL_R_0 * L_unit); + + // not really used in ipole other than for coordinates + N1 = 300; + N2 = 100; + N3 = 1; + + // set up coordinates + startx[0] = 0.; + startx[1] = 0.; + startx[2] = 0.; + startx[3] = 0.; + + dx[0] = 0.1; + dx[1] = (MODEL_R_0 - 0) / N1; + dx[2] = M_PI / N2; + dx[3] = 2. * M_PI / N3; + + stopx[0] = 1.; + stopx[1] = startx[1]+N1*dx[1]; + stopx[2] = startx[2]+N2*dx[2]; + stopx[3] = startx[3]+N3*dx[3]; + + MULOOP cstartx[mu] = startx[mu]; + MULOOP cstopx[mu] = stopx[mu]; + + rmin_geo = 0; + rmax_geo = fmax(100., MODEL_R_0); + + fprintf(stderr, "Native coordinate start: %g %g %g stop: %g %g %g\n", + startx[1], startx[2], startx[3], stopx[1], stopx[2], stopx[3]); + +} + +/* + these supply basic model data to ipole +*/ + +// Calculate Ucon,Ucov,Bcon,Bcov from primitives at location X using +// interpolation (on the primitives). This has been all wrapped into +// a single function because some calculations require each other. +void get_model_fourv(double X[NDIM], double Kcon[NDIM], + double Ucon[NDIM], double Ucov[NDIM], + double Bcon[NDIM], double Bcov[NDIM]) +{ + double r, h; + bl_coord(X, &r, &h); + + double gcov[NDIM][NDIM]; + gcov_func(X, gcov); + + Ucon[0] = 1.; + Ucon[1] = 0.; + Ucon[2] = 0.; + Ucon[3] = 0.; + + Bcon[0] = 0.; + Bcon[1] = model_B_0 * cos(h) / r; + Bcon[2] = - model_B_0 * sin(h) / (r + 1.e-8); + Bcon[3] = 0.; + + lower(Ucon, gcov, Ucov); + lower(Bcon, gcov, Bcov); +} + +// used in diagnostics IO. not implemented +void get_model_primitives(double X[NDIM], double *p) { } + +double get_model_thetae(double X[NDIM]) +{ + if (radiating_region(X) == 0) { + return 0; + } + + return MODEL_THETAE_0; +} + +//b field strength in Gauss +double get_model_b(double X[NDIM]) +{ + if (radiating_region(X) == 0) { + return 0.; + } + + return model_B_0; +} + +double get_model_sigma(double X[NDIM]) +{ + if (radiating_region(X) == 0) { + return 0.; + } + + // TODO currently assumes tf==tp + return MODEL_THETAE_0 * MODEL_TP_OVER_TE / MODEL_BETA_0; +} +double get_model_beta(double X[NDIM]) +{ + if (radiating_region(X) == 0) { + return 0.; + } + + return MODEL_BETA_0; +} + +double get_model_ne(double X[NDIM]) +{ + if (radiating_region(X) == 0) { + return 0.; + } + + return model_Ne_0; +} + +void output_hdf5() +{ + hdf5_set_directory("/"); + + // TODO maybe output the model parameters here + + hdf5_set_directory("/header/"); + hdf5_make_directory("units"); + hdf5_set_directory("/header/units/"); + hdf5_write_single_val(&L_unit, "L_unit", H5T_IEEE_F64LE); + + hdf5_set_directory("/"); +} + +int radiating_region(double X[NDIM]) +{ + double r, h; + bl_coord(X, &r, &h); + return (r // Strings and string tools -#define VERSION_STRING "ipole-release-1.4" +#define VERSION_STRING "ipole-beta-1.5" #define xstr(s) str(s) #define str(s) #s #define STRLEN (2048) diff --git a/src/geometry.c b/src/geometry.c index 6d05e12..5373629 100644 --- a/src/geometry.c +++ b/src/geometry.c @@ -18,12 +18,12 @@ MM 11 July 17 */ -int invert_matrix(double Am[][NDIM], double Aminv[][NDIM]); -int LU_decompose(double A[][NDIM], int permute[]); -void LU_substitution(double A[][NDIM], double B[], int permute[]); +int invert_matrix(double Am[NDIM][NDIM], double Aminv[NDIM][NDIM]); +int LU_decompose(double A[NDIM][NDIM], int permute[NDIM]); +void LU_substitution(double A[NDIM][NDIM], double B[NDIM], int permute[NDIM]); /* assumes gcov has been set first; returns sqrt{|g|} */ -double gdet_func(double gcov[][NDIM]) +double gdet_func(double gcov[NDIM][NDIM]) { int i, j; int permute[NDIM]; @@ -51,7 +51,7 @@ double gdet_func(double gcov[][NDIM]) } /* invert gcov to get gcon */ -int gcon_func(double gcov[][NDIM], double gcon[][NDIM]) +int gcon_func(double gcov[NDIM][NDIM], double gcon[NDIM][NDIM]) { int sing = invert_matrix(gcov, gcon); #if DEBUG @@ -165,6 +165,27 @@ void normalize(double vcon[NDIM], double Gcov[NDIM][NDIM]) return; } +/* + * Normalize input vector so that |v . v| = target + * Overwrites input + */ +void normalize_to(double vcon[NDIM], double Gcov[NDIM][NDIM], double target) +{ + int k, l; + double norm; + + norm = 0.; + for (k = 0; k < 4; k++) + for (l = 0; l < 4; l++) + norm += vcon[k] * vcon[l] * Gcov[k][l]; + + norm = sqrt(fabs(norm)); + for (k = 0; k < 4; k++) + vcon[k] *= target/norm; + + return; +} + /* normalize null vector in a tetrad frame */ void null_normalize(double Kcon[NDIM], double fnorm) { @@ -228,7 +249,7 @@ double theta_func(double X[NDIM]) Returns (1) if a singular matrix is found, (0) otherwise. */ -int invert_matrix(double Am[][NDIM], double Aminv[][NDIM]) +int invert_matrix(double Am[NDIM][NDIM], double Aminv[NDIM][NDIM]) { int i, j; @@ -280,7 +301,7 @@ int invert_matrix(double Am[][NDIM], double Aminv[][NDIM]) Returns (1) if a singular matrix is found, (0) otherwise. */ -int LU_decompose(double A[][NDIM], int permute[]) +int LU_decompose(double A[NDIM][NDIM], int permute[NDIM]) { const double absmin = 1.e-30; /* Value used instead of 0 for singular matrices */ @@ -447,7 +468,7 @@ int LU_decompose(double A[][NDIM], int permute[]) Upon exit, B[] contains the solution x[], A[][] is left unchanged. */ -void LU_substitution(double A[][NDIM], double B[], int permute[]) +void LU_substitution(double A[NDIM][NDIM], double B[NDIM], int permute[NDIM]) { int i, j; int n = NDIM; diff --git a/src/geometry.h b/src/geometry.h index a43ffbf..e90013c 100644 --- a/src/geometry.h +++ b/src/geometry.h @@ -10,20 +10,20 @@ #include "decs.h" -int gcon_func(double gcov[][NDIM], double gcon[][NDIM]); -double gdet_func(double gcov[][NDIM]); -void get_connection(double *X, double lconn[][NDIM][NDIM]); +int gcon_func(double gcov[NDIM][NDIM], double gcon[NDIM][NDIM]); +double gdet_func(double gcov[NDIM][NDIM]); +void get_connection(double X[NDIM], double lconn[NDIM][NDIM][NDIM]); -void flip_index(double *ucon, double Gcov[NDIM][NDIM], double *ucov); +void flip_index(double ucon[NDIM], double Gcov[NDIM][NDIM], double ucov[NDIM]); // Old names aliased -inline void lower(double *ucon, double Gcov[NDIM][NDIM], double *ucov) {flip_index(ucon, Gcov, ucov);}; -inline void raise(double *ucov, double Gcon[NDIM][NDIM], double *ucon) {flip_index(ucov, Gcon, ucon);}; +inline void lower(double ucon[NDIM], double Gcov[NDIM][NDIM], double ucov[NDIM]) {flip_index(ucon, Gcov, ucov);}; +inline void raise(double ucov[NDIM], double Gcon[NDIM][NDIM], double ucon[NDIM]) {flip_index(ucov, Gcon, ucon);}; void null_normalize(double Kcon[NDIM], double fnorm); -void normalize(double *vcon, double gcov[][NDIM]); +void normalize(double vcon[NDIM], double gcov[NDIM][NDIM]); +void normalize_to(double vcon[NDIM], double Gcov[NDIM][NDIM], double target); - -int invert_matrix(double Am[][NDIM], double Aminv[][NDIM]); +int invert_matrix(double Am[NDIM][NDIM], double Aminv[NDIM][NDIM]); double theta_func(double X[NDIM]); int levi_civita(int i, int j, int k, int l); diff --git a/src/grid.c b/src/grid.c index be6b11b..6973f66 100644 --- a/src/grid.c +++ b/src/grid.c @@ -1,6 +1,7 @@ #include "grid.h" #include "coordinates.h" +#include "simcoords.h" #include "geometry.h" #include @@ -65,6 +66,13 @@ double interp_scalar_time(double X[NDIM], double ***varA, double ***varB, double */ void ijktoX(int i, int j, int k, double X[NDIM]) { + // possibly deal with simcoords + if (use_simcoords) { + // this call to simcoordsijk assumes i,j,k valid + simcoordijk_to_eks(i, j, k, X); + return; + } + // first do the naive thing X[1] = startx[1] + (i+0.5)*dx[1]; X[2] = startx[2] + (j+0.5)*dx[2]; @@ -144,6 +152,9 @@ void Xtoijk(double X[NDIM], int *i, int *j, int *k, double del[NDIM]) pow(2.,1. + MP0)*MY2)*M_PI); XG[3] = Xks[3]; } + } else if (use_simcoords) { + // the geodesics are in eKS, so invert through simcoords -> zone coordinates + eks_to_simcoord(X, XG); } else { MULOOP XG[mu] = X[mu]; } @@ -186,6 +197,18 @@ int X_in_domain(double X[NDIM]) { // checks different sets of coordinates depending on // specified grid coordinates + if (use_simcoords) { + double gridcoord[NDIM]; + eks_to_simcoord(X, gridcoord); + if (gridcoord[1] < startx[1] || + gridcoord[1] >= stopx[1] || + gridcoord[2] < startx[2] || + gridcoord[2] >= stopx[2]) { + return 0; + } + return 1; + } + if (use_eKS_internal) { double XG[4] = { 0 }; double r, th; @@ -205,14 +228,14 @@ int X_in_domain(double X[NDIM]) { pow(2,1 + MP0)*MY2)*M_PI); XG[3] = Xks[3]; - if (XG[1] < startx[1] || XG[1] > stopx[1]) return 0; + if (XG[1] < startx[1] || XG[1] >= stopx[1]) return 0; } } else { if(X[1] < startx[1] || - X[1] > stopx[1] || + X[1] >= stopx[1] || X[2] < startx[2] || - X[2] > stopx[2]) { + X[2] >= stopx[2]) { return 0; } } @@ -225,6 +248,11 @@ int X_in_domain(double X[NDIM]) { */ double gdet_zone(int i, int j, int k) { + if (use_simcoords) { + // we assume i, j, k is valid here. otherwise, this function returns zero. + return simcoordijk_to_gdet(i, j, k); + } + // get the X for the zone (in geodesic coordinates for bl_coord) // and in zone coordinates (for set_dxdX) double X[NDIM], Xzone[NDIM]; @@ -234,6 +262,12 @@ double gdet_zone(int i, int j, int k) Xzone[2] = startx[2] + (j+0.5)*dx[2]; Xzone[3] = startx[3] + (k+0.5)*dx[3]; + if (metric == METRIC_MINKOWSKI || metric == METRIC_EMINKOWSKI) { + double gcov[NDIM][NDIM]; + gcov_func(Xzone, gcov); + return gdet_func(gcov); + } + // then get gcov for the zone (in zone coordinates) double gcovKS[NDIM][NDIM], gcov[NDIM][NDIM]; double r, th; diff --git a/src/io.c b/src/io.c index b927a55..3ed68db 100644 --- a/src/io.c +++ b/src/io.c @@ -8,15 +8,18 @@ #include "decs.h" #include "coordinates.h" #include "geometry.h" +#include "ipolarray.h" #include "radiation.h" #include "par.h" #include "hdf5_utils.h" #include "model.h" #include "model_radiation.h" +#include "model_tetrads.h" #include #include +#include void write_header(double scale, double cam[NDIM], double fovx, double fovy, Params *params); @@ -271,8 +274,6 @@ void dump(double image[], double imageS[], double taus[], /* * Given a path, dump a variable computed along that path into a file. * Note this is most definitely *not* thread-safe, so it gets called from an 'omp critical' - * - * TODO output of I,Q,U,V, as parallel-transported to camera, along traces */ void dump_var_along(int i, int j, int nstep, struct of_traj *traj, int nx, int ny, double scale, double cam[NDIM], double fovx, double fovy, Params *params) @@ -285,61 +286,150 @@ void dump_var_along(int i, int j, int nstep, struct of_traj *traj, int nx, int n } int nprims = 8; - double *prims = calloc(nprims*nstep, sizeof(double)); - - double *b = calloc(nstep, sizeof(double)); - double *ne = calloc(nstep, sizeof(double)); - double *thetae = calloc(nstep, sizeof(double)); - double *nu = calloc(nstep, sizeof(double)); - double *mu = calloc(nstep, sizeof(double)); - - double *dl = calloc(nstep, sizeof(double)); - double *X = calloc(NDIM*nstep, sizeof(double)); + // Admittedly confusing nomenclature: + // nstep = # of final step + // nsteps = # steps = nstep + 1 + int nsteps = nstep + 1; + double *prims = calloc(nprims*nsteps, sizeof(double)); + + double *b = calloc(nsteps, sizeof(double)); + double *ne = calloc(nsteps, sizeof(double)); + double *thetae = calloc(nsteps, sizeof(double)); + double *nu = calloc(nsteps, sizeof(double)); + double *mu = calloc(nsteps, sizeof(double)); + + double *sigma = calloc(nsteps, sizeof(double)); + double *beta = calloc(nsteps, sizeof(double)); + + double *dl = calloc(nsteps, sizeof(double)); + double *X = calloc(NDIM*nsteps, sizeof(double)); // Vectors aren't really needed. Put back in behind flag if it comes up - double *Kcon = calloc(NDIM*nstep, sizeof(double)); -// double *Kcov = calloc(NDIM*nstep, sizeof(double)); - - double *r = calloc(nstep, sizeof(double)); - double *th = calloc(nstep, sizeof(double)); - double *phi = calloc(nstep, sizeof(double)); - -// double *Ucon = calloc(NDIM*nstep, sizeof(double)); -// double *Ucov = calloc(NDIM*nstep, sizeof(double)); -// double *Bcon = calloc(NDIM*nstep, sizeof(double)); -// double *Bcov = calloc(NDIM*nstep, sizeof(double)); - - // TODO NDIM and NSTOKES are not the same thing - double *j_inv = calloc(NDIM*nstep, sizeof(double)); - double *alpha_inv = calloc(NDIM*nstep, sizeof(double)); - double *rho_inv = calloc(NDIM*nstep, sizeof(double)); - double *unpol_inv = calloc(2*nstep, sizeof(double)); - - for (int i=0; irotcam*M_PI/180.; + + //Plasma basis vectors to be carried along as well + //Could also just carry along contravariant version and metric in case other vectors need to be parallel transported too. + double Ecovt[NDIM][NDIM], Econt[NDIM][NDIM]; + + // Record Stokes parameters + double *stokes = calloc(NDIM*nsteps,sizeof(double)); + double *stokes_coordinate = calloc(NDIM*nsteps,sizeof(double)); + double *ntetrad = calloc(NDIM*NDIM*nsteps,sizeof(double)); + double *ncoord = calloc(NDIM*NDIM*nsteps,sizeof(double)); + double *econ = calloc(NDIM*NDIM*nsteps,sizeof(double)); + double *ecov = calloc(NDIM*NDIM*nsteps,sizeof(double)); + + double *e2Constructed = calloc(NDIM*nsteps,sizeof(double)); + double *e2ConstructedCov = calloc(NDIM*nsteps,sizeof(double)); + double *e2Transported = calloc(NDIM*nsteps,sizeof(double)); + double *e2TransportedCov = calloc(NDIM*nsteps,sizeof(double)); + + for (int i=nstep-1; i > 0; --i) { + // Record ambient variables get_model_primitives(traj[i].X, &(prims[i*nprims])); double Ucont[NDIM], Ucovt[NDIM], Bcont[NDIM], Bcovt[NDIM]; + //variables for parallel transport + double e2Mid[NDIM], e2Final[NDIM], e2Cov[NDIM]; get_model_fourv(traj[i].X, traj[i].Kcon, Ucont, Ucovt, Bcont, Bcovt); - jar_calc(traj[i].X, traj[i].Kcon, &(j_inv[i*NDIM]), &(j_inv[i*NDIM+1]), &(j_inv[i*NDIM+2]), &(j_inv[i*NDIM+3]), - &(alpha_inv[i*NDIM]), &(alpha_inv[i*NDIM+1]), &(alpha_inv[i*NDIM+2]), &(alpha_inv[i*NDIM+3]), - &(rho_inv[i*NDIM+1]), &(rho_inv[i*NDIM+2]), &(rho_inv[i*NDIM+3]), params); - get_jkinv(traj[i].X, traj[i].Kcon, &(unpol_inv[i*2+0]), &(unpol_inv[i*2+1]), params); + // Record scaled values b[i] = get_model_b(traj[i].X); ne[i] = get_model_ne(traj[i].X); thetae[i] = get_model_thetae(traj[i].X); nu[i] = get_fluid_nu(traj[i].Kcon, Ucovt); mu[i] = get_bk_angle(traj[i].X, traj[i].Kcon, Ucovt, Bcont, Bcovt); + sigma[i] = get_model_sigma(traj[i].X); + beta[i] = get_model_beta(traj[i].X); + + // Record emission coefficients + jar_calc(traj[i].X, traj[i].Kcon, &(j_inv[i*NDIM]), &(j_inv[i*NDIM+1]), &(j_inv[i*NDIM+2]), &(j_inv[i*NDIM+3]), + &(alpha_inv[i*NDIM]), &(alpha_inv[i*NDIM+1]), &(alpha_inv[i*NDIM+2]), &(alpha_inv[i*NDIM+3]), + &(rho_inv[i*NDIM+1]), &(rho_inv[i*NDIM+2]), &(rho_inv[i*NDIM+3]), params); + get_jkinv(traj[i].X, traj[i].Kcon, &(j_unpol[i]), &(k_unpol[i]), params); dl[i] = traj[i].dl; MULOOP { X[i*NDIM+mu] = traj[i].X[mu]; Kcon[i*NDIM+mu] = traj[i].Kcon[mu]; } -// double Gcov[NDIM][NDIM]; -// gcov_func(traj[i].X, Gcov); -// lower(traj[i].Kcon, Gcov, &(Kcov[i*NDIM])); + double Gcov[NDIM][NDIM], Gcon[NDIM][NDIM]; + gcov_func(traj[i].X, Gcov); + gcon_func(Gcov, Gcon); +// lower(traj[i].Kcon, Gcov, &(Kcov[i*NDIM])); bl_coord(traj[i].X, &(r[i]), &(th[i])); phi[i] = traj[i].X[3]; + + //make the plasma tetrad to project the coherency tensor + make_plasma_tetrad(Ucont,traj[i].Kcon,Bcont,Gcov,Econt,Ecovt); + + + //parallel transport E2 basis vector by dl to second order accuracy + parallel_transport_vector(traj[i].X,traj[i].X,traj[i].Xhalf,traj[i].Kcon,traj[i].Kcon,traj[i].Kconhalf,Econt[2],Econt[2],e2Mid,traj[i].dl); + parallel_transport_vector(traj[i].X,traj[i].Xhalf,traj[i-1].X,traj[i].Kcon,traj[i].Kconhalf,traj[i-1].Kcon,Econt[2],e2Mid,e2Final,traj[i].dl); + + //copy out transported and constructed vectors + MULOOP{ + e2Constructed[i*NDIM+mu] = Econt[2][mu]; + e2ConstructedCov[i*NDIM+mu] = Ecovt[2][mu]; + //assign parallel transported vector to the right index + e2Transported[(i-1)*NDIM+mu] = e2Final[mu]; + //reset e2Final to be that of the current parallely transported vector to flip index + e2Final[mu] = e2Transported[i*NDIM+mu]; + } + MUNULOOP { + econ[i*NDIM*NDIM + mu*NDIM + nu] = Econt[mu][nu]; + ecov[i*NDIM*NDIM + mu*NDIM + nu] = Ecovt[mu][nu]; + } + + flip_index(e2Final,Gcov,e2Cov); + MULOOP e2TransportedCov[i*NDIM+mu] = e2Cov[mu]; + + // Convert N to Stokes in plasma frame and record the coherency tensor at the current step + complex_coord_to_tetrad_rank2(N_coord,Ecovt,N_tetrad); + // Record N + MUNULOOP { + ntetrad[i*NDIM*NDIM + mu*NDIM + nu] = N_tetrad[mu][nu]; + ncoord[i*NDIM*NDIM + mu*NDIM + nu] = N_coord[mu][nu]; + } + // Record the Stokes parameters in the correct index + tensor_to_stokes(N_tetrad,&(stokes[(i)*NDIM]), &(stokes[(i)*NDIM+1]), &(stokes[(i)*NDIM+2]), &(stokes[(i)*NDIM+3])); + project_N(traj[i-1].X, traj[i-1].Kcon, N_coord, &(stokes_coordinate[i*NDIM]), &(stokes_coordinate[i*NDIM+1]), + &(stokes_coordinate[i*NDIM+2]), &(stokes_coordinate[i*NDIM+3]), rotcam); + + // Integrate and record results + int flag = integrate_emission(&(traj[i]), 1, &Intensity, &Tau, &tauF, N_coord, params, 0); + I_unpol[i] = Intensity; + // project_N(traj[i-1].X, traj[i-1].Kcon, N_coord, &(stokes[i*NDIM]), &(stokes[i*NDIM+1]), &(stokes[i*NDIM+2]), &(stokes[i*NDIM+3]),0; + // any_tensor_to_stokes(N_coord, Gcov, &(stokes_coordinate[i*NDIM]), &(stokes_coordinate[i*NDIM+1]), &(stokes_coordinate[i*NDIM+2]), &(stokes_coordinate[i*NDIM+3])); + + + if (flag) printf("Flagged integration when tracing: %d\n", flag); + } hdf5_set_directory("/"); @@ -358,8 +448,8 @@ void dump_var_along(int i, int j, int nstep, struct of_traj *traj, int nx, int n hsize_t fdims_s[] = { nx, ny, params->maxnstep }; hsize_t chunk_s[] = { 1, 1, 200 }; hsize_t fstart_s[] = { i, j, 0 }; - hsize_t fcount_s[] = { 1, 1, nstep }; - hsize_t mdims_s[] = { 1, 1, nstep }; + hsize_t fcount_s[] = { 1, 1, nsteps }; + hsize_t mdims_s[] = { 1, 1, nsteps }; hsize_t mstart_s[] = { 0, 0, 0 }; hdf5_write_chunked_array(b, "b", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); @@ -367,18 +457,24 @@ void dump_var_along(int i, int j, int nstep, struct of_traj *traj, int nx, int n hdf5_write_chunked_array(thetae, "thetae", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); hdf5_write_chunked_array(nu, "nu", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); hdf5_write_chunked_array(mu, "b_k_angle", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); + hdf5_write_chunked_array(sigma, "sigma", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); + hdf5_write_chunked_array(beta, "beta", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); hdf5_write_chunked_array(dl, "dl", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); hdf5_write_chunked_array(r, "r", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); hdf5_write_chunked_array(th, "th", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); hdf5_write_chunked_array(phi, "phi", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); + hdf5_write_chunked_array(j_unpol, "j_unpol", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); + hdf5_write_chunked_array(k_unpol, "k_unpol", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); + hdf5_write_chunked_array(I_unpol, "I_unpol", 3, fdims_s, fstart_s, fcount_s, mdims_s, mstart_s, chunk_s, H5T_IEEE_F64LE); + // VECTORS: Anything with N values per geodesic step hsize_t fdims_v[] = { nx, ny, params->maxnstep, 4 }; hsize_t chunk_v[] = { 1, 1, 200, 4 }; hsize_t fstart_v[] = { i, j, 0, 0 }; - hsize_t fcount_v[] = { 1, 1, nstep, 4 }; - hsize_t mdims_v[] = { 1, 1, nstep, 4 }; + hsize_t fcount_v[] = { 1, 1, nsteps, 4 }; + hsize_t mdims_v[] = { 1, 1, nsteps, 4 }; hsize_t mstart_v[] = { 0, 0, 0, 0 }; hdf5_write_chunked_array(X, "X", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); @@ -394,25 +490,53 @@ void dump_var_along(int i, int j, int nstep, struct of_traj *traj, int nx, int n hdf5_write_chunked_array(alpha_inv, "alpha_inv", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); hdf5_write_chunked_array(rho_inv, "rho_inv", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); + hdf5_write_chunked_array(stokes, "stokes", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); + // hdf5_write_chunked_array(stokes_coordinate, "stokes_coordinate", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); + + hdf5_write_chunked_array(e2Constructed, "e2Constructed", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); + hdf5_write_chunked_array(e2ConstructedCov, "e2ConstructedCov", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); + hdf5_write_chunked_array(e2Transported, "e2Transported", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); + hdf5_write_chunked_array(e2TransportedCov, "e2TransportedCov", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); + fdims_v[3] = chunk_v[3] = fcount_v[3] = mdims_v[3] = 8; hdf5_write_chunked_array(prims, "prims", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); - // Unpolarized coefficients j and k - fdims_v[3] = chunk_v[3] = fcount_v[3] = mdims_v[3] = 2; - hdf5_write_chunked_array(unpol_inv, "jk", 4, fdims_v, fstart_v, fcount_v, mdims_v, mstart_v, chunk_v, H5T_IEEE_F64LE); - + // TENSORS: Anything with NxN values per geodesic step + hsize_t fdims_t[] = { nx, ny, params->maxnstep, 4, 4 }; + hsize_t chunk_t[] = { 1, 1, 200, 4, 4 }; + hsize_t fstart_t[] = { i, j, 0, 0, 0 }; + hsize_t fcount_t[] = { 1, 1, nsteps, 4, 4 }; + hsize_t mdims_t[] = { 1, 1, nsteps, 4, 4 }; + hsize_t mstart_t[] = { 0, 0, 0, 0, 0 }; + hdf5_write_chunked_array(ntetrad, "ntetrad", 5, fdims_t, fstart_t, fcount_t, mdims_t, mstart_t, chunk_t, H5T_IEEE_F64LE); + hdf5_write_chunked_array(ncoord, "ncoord", 5, fdims_t, fstart_t, fcount_t, mdims_t, mstart_t, chunk_t, H5T_IEEE_F64LE); + hdf5_write_chunked_array(econ, "Econ", 5, fdims_t, fstart_t, fcount_t, mdims_t, mstart_t, chunk_t, H5T_IEEE_F64LE); + hdf5_write_chunked_array(ecov, "Ecov", 5, fdims_t, fstart_t, fcount_t, mdims_t, mstart_t, chunk_t, H5T_IEEE_F64LE); + + // DEALLOCATE + + // Scalars free(b); free(ne); free(thetae); free(nu); free(mu); - - free(X); - //free(Kcon); free(Kcov); + free(dl); free(r); free(th); free(phi); + free(j_unpol); free(k_unpol); free(I_unpol); + free(sigma); free(beta); + // Vectors + free(X); + free(Kcon); //free(Kcov); //free(Ucon); free(Ucov); free(Bcon); free(Bcov); free(j_inv); free(alpha_inv); free(rho_inv); - free(unpol_inv); - + free(stokes); + // free(stokes_coordinate); + free(e2Constructed);free(e2ConstructedCov); + free(e2Transported);free(e2TransportedCov); free(prims); + // Tensors + free(ntetrad); free(ncoord); + free(econ); free(ecov); + hdf5_close(); } diff --git a/src/ipolarray.c b/src/ipolarray.c index 627138b..5392d92 100644 --- a/src/ipolarray.c +++ b/src/ipolarray.c @@ -10,19 +10,30 @@ #include "ipolarray.h" +#include "debug_tools.h" #include "decs.h" + #include "coordinates.h" +#include "debug_tools.h" #include "geometry.h" +#include "radiation.h" + #include "model.h" #include "model_radiation.h" #include "model_tetrads.h" + +#include "simcoords.h" #include "debug_tools.h" #include -// These are related mostly to where exp() and 1/x overflow -// desired accuracy. -#define CUT_HIGH_ABS 500 -#define CUT_LOW_ABS SMALL + +// Define where to switch from 1-exp() to +// Taylor-expanded quantities in the optical +// depth +#define CUT_SMALL_OPTICAL_DEPTH 1e-5 + +// Smallest double that prevents NaNs on inverses +#define CUT_PREVENT_NAN 1e-80 // Sub-functions void push_polar(double Xi[NDIM], double Xm[NDIM], double Xf[NDIM], @@ -31,22 +42,6 @@ void push_polar(double Xi[NDIM], double Xm[NDIM], double Xf[NDIM], complex double Nm[NDIM][NDIM], complex double Nf[NDIM][NDIM], double dlam); -/* tensor tools */ -void complex_lower(double complex N[NDIM][NDIM], double gcov[NDIM][NDIM], - int low1, int low2, double complex Nl[NDIM][NDIM]); -void stokes_to_tensor(double fI, double fQ, double fU, double fV, - double complex f_tetrad[NDIM][NDIM]); -void tensor_to_stokes(double complex f_tetrad[NDIM][NDIM], double *fI, - double *fQ, double *fU, double *fV); -void complex_coord_to_tetrad_rank2(double complex T_coord[NDIM][NDIM], - double Ecov[NDIM][NDIM], - double complex T_tetrad[NDIM][NDIM]); -void complex_tetrad_to_coord_rank2(double complex T_tetrad[NDIM][NDIM], - double Econ[NDIM][NDIM], - double complex T_coord[NDIM][NDIM]); - - - /************************PRIMARY FUNCTION*******************************/ /** * Calculate the emission produced/absorbed/rotated along the given trajectory traj @@ -54,22 +49,28 @@ void complex_tetrad_to_coord_rank2(double complex T_tetrad[NDIM][NDIM], * Return arguments of intensity, total optical depth, total Faraday depth, and complex polarized emission tensor N^alpha^beta * * Returns flag indicating at least one step either used a questionable tetrad, or produced a NaN value + * + * TODO: add stop condidion not based on nsteps, for slow light */ int integrate_emission(struct of_traj *traj, int nsteps, double *Intensity, double *Tau, double *tauF, - double complex N_coord[NDIM][NDIM], Params *params) + double complex N_coord[NDIM][NDIM], Params *params, + int print) { - //fprintf(stderr, "Begin integrate emission\n"); - // Initialize - MUNULOOP N_coord[mu][nu] = 0.0 + I * 0.0; *tauF = 0.; // Unpolarized *Intensity = 0.; *Tau = 0.; + double js = 0.; + double ks; // Error flag int oddflag = 0; - // Integrate the transfer equation (& parallel transport) forwards along trajectory + // Initialize the running cached coefficients js, ks + get_jkinv(traj[nsteps].X, traj[nsteps].Kcon, &js, &ks, params); + + // Integrate the polarized & unpolarized transfer equations (& parallel transport) + // for (int nstep=nsteps; nstep > 0; --nstep) { int sflag = 0; struct of_traj ti = traj[nstep]; @@ -85,7 +86,7 @@ int integrate_emission(struct of_traj *traj, int nsteps, #if THIN_DISK if (thindisk_region(ti.X, tf.X)) { - // The thin disk problem emits nowhere but uses a boundary condition region defined by thindisk_region + // A thin disk problem emits nowhere but uses a boundary condition region defined by thindisk_region // There we just get a starting value for intensity with get_model_i get_model_i(ti.X, ti.Kcon, Intensity); @@ -115,23 +116,46 @@ int integrate_emission(struct of_traj *traj, int nsteps, #endif if (radiating_region(tf.X)) { - - int ZERO_EMISSION = 0; + // Conditions to zero just the emission, not all radiative transport + int zero_emission = 0; if (params->target_nturns >= 0 && ti.nturns != params->target_nturns) { - ZERO_EMISSION = 1; + zero_emission = 1; + } + if (params->isolate_counterjet == 1) { // Allow emission from X[2] > midplane only + if (tf.X[2] < (cstopx[2] - cstartx[2]) / 2) { + zero_emission = 1; + } + } else if (params->isolate_counterjet == 2) { // from X[2] < midplane only + if (tf.X[2] > (cstopx[2] - cstartx[2]) / 2) { + zero_emission = 1; + } } // Solve unpolarized transport - double ji, ki, jf, kf; - get_jkinv(ti.X, ti.Kcon, &ji, &ki, params); + // Cached starting coefficients + double ji = js, ki = ks; + double jf, kf; + //get_jkinv(ti.X, ti.Kcon, &ji, &ki, params); get_jkinv(tf.X, tf.Kcon, &jf, &kf, params); + // End coefficients are next starting coefficients + js = jf; + ks = kf; - if (ZERO_EMISSION) { + if (zero_emission) { *Intensity = approximate_solve(*Intensity, 0, ki, 0, kf, ti.dl, Tau); } else { *Intensity = approximate_solve(*Intensity, ji, ki, jf, kf, ti.dl, Tau); } + //fprintf(stderr, "Unpolarized transport\n"); + + if (print) { + double Xg[4] = { 0., 0., 0., 0. }; + eks_to_simcoord(ti.X, Xg); + fprintf(stderr, "INTEGRATION %d %d %g %g %g %g %g %g %g %g %g \n", + print, nstep, ti.X[1], ti.X[2], ti.X[3], Xg[1], Xg[2], Xg[3], + ji, *Intensity, ti.dl); + } // Solve polarized transport if (!params->only_unpolarized) { @@ -139,17 +163,13 @@ int integrate_emission(struct of_traj *traj, int nsteps, ti.Xhalf, ti.Kconhalf, tf.X, tf.Kcon, ti.dl, N_coord, tauF, - ZERO_EMISSION, params); - //fprintf(stderr, "Polarized transport\n"); + zero_emission, params); } } - // smoosh together all the flags we hit along a geodesic - oddflag |= sflag; - - - - // Cry immediately on bad tetrads, even if we're not debugging +#if DEBUG + // If we're debugging, print errors immediately + // Error for bad tetrads if (sflag & 1) { fprintf(stderr, "that's odd: no orthonormal tetrad found at\n"); fprintf(stderr, "nstep: %d\n", nstep); @@ -181,22 +201,17 @@ int integrate_emission(struct of_traj *traj, int nsteps, bsq, bsq_reported, udotu, udotb, kdotu, kdotb); // exit(-1); } - // Same if there was something in gcov + // Bad gcov if (sflag & 16) { fprintf(stderr, "Matrix inversion failed in tetrad check, step %d:\n", nstep); - // TODO } - - // TODO pull more relevant stuff back out here -#if DEBUG - // Cry on bad tauF + // Bad tauF if (sflag & 2) { printf("tauF = %e dlam = %e\n", *tauF, ti.dl); fprintf(stderr, "nstep: %d\n", nstep); exit(-1); } - - // Cry on bad N + // Bad N if (sflag & 4) { fprintf(stderr, "\nNaN in N00!\n"); fprintf(stderr, "nstep: %d\n", nstep); @@ -210,12 +225,12 @@ int integrate_emission(struct of_traj *traj, int nsteps, //MUNULOOP printf("Econ[%i][%i] = %e Ncoord = %e Ntet = %e\n", mu, nu, Econ[mu][nu], creal(N_coord[mu][nu]), creal(N_tetrad[mu][nu])); exit(-1); } +#else + // Otherwise, smoosh together all the flags we hit along a geodesic + oddflag |= sflag; #endif - //fprintf(stderr, "End Loop\n"); } - - //fprintf(stderr, "End integrate emission\n"); - // Otherwise propagate the full flag so caller can yell or record + // Return the final flag so caller can print return oddflag; } @@ -235,6 +250,12 @@ void push_polar(double Xi[NDIM], double Xm[NDIM], double Xf[NDIM], double dl = dlam / (L_unit * HPL / (ME * CL * CL)); #endif + if (Xm[0] == 0 && Xm[1] == 0 && Xm[2] == 0 && Xm[3] == 0) + { + fprintf(stderr, "Called push_polar at origin! You are probably trying to trace something unsupported.\n"); + exit(-1); + } + /* find the connection */ double lconn[NDIM][NDIM][NDIM]; get_connection(Xm, lconn); @@ -255,6 +276,38 @@ void push_polar(double Xi[NDIM], double Xm[NDIM], double Xf[NDIM], } +/* + +Parallel Transport a real vector over dl +(see push_polar above) +TODO should probably go in io.c +*/ +void parallel_transport_vector(double Xi[NDIM], double Xm[NDIM], double Xf[NDIM], + double Ki[NDIM], double Km[NDIM], double Kf[NDIM], + double Ni[NDIM], + double Nm[NDIM], + double Nf[NDIM], double dl) +{ + + /* find the connection */ + double lconn[NDIM][NDIM][NDIM]; + get_connection(Xm, lconn); + int i, j, k; + + /* push N */ + for (i = 0; i < 4; i++) + Nf[i] = Ni[i]; + + for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++) + for (k = 0; k < 4; k++) + Nf[i] += -(lconn[i][j][k] * Nm[j] * Km[k]) * + dl / (L_unit * HPL / (ME * CL * CL)); + + return; +} + + /* * Updates N for one step on geodesics, using the previous step N * here we compute new right-hand side of the equation @@ -263,11 +316,12 @@ void push_polar(double Xi[NDIM], double Xm[NDIM], double Xf[NDIM], * * Return an error flag indicating any singular matrix, bad tetrad, etc. */ + int evolve_N(double Xi[NDIM], double Kconi[NDIM], double Xhalf[NDIM], double Kconhalf[NDIM], double Xf[NDIM], double Kconf[NDIM], double dlam, double complex N_coord[NDIM][NDIM], double *tauF, - int ZERO_EMISSION, Params *params) + int zero_emission, Params *params) { // TODO might be useful to split this into flat-space S->S portion and transformations to/from N double gcov[NDIM][NDIM]; @@ -275,36 +329,38 @@ int evolve_N(double Xi[NDIM], double Kconi[NDIM], double Ucov[NDIM],Bcov[NDIM]; double Ecov[NDIM][NDIM], Econ[NDIM][NDIM]; double complex N_tetrad[NDIM][NDIM]; - double B; + double jI, jQ, jU, jV; double aI, aQ, aU, aV; double rV, rU, rQ; - double rho2, rho, rdS; - double SI = 0, SQ = 0, SU = 0, SV = 0; + double SI0, SQ0, SU0, SV0; double SI1, SQ1, SU1, SV1; double SI2, SQ2, SU2, SV2; - int oddflag = 0; + double SI, SQ, SU, SV; - // get fluid parameters at Xf - get_model_fourv(Xf, Kconf, Ucon, Ucov, Bcon, Bcov); + int oddflag = 0; // evaluate transport coefficients - jar_calc(Xf, Kconf, &jI, &jQ, &jU, &jV, + jar_calc(Xf, Kconf, + &jI, &jQ, &jU, &jV, &aI, &aQ, &aU, &aV, &rQ, &rU, &rV, params); - if (ZERO_EMISSION) { + if (zero_emission) { jI = 0.; jQ = 0.; jU = 0.; jV = 0.; } + // get fluid parameters at Xf + get_model_fourv(Xf, Kconf, Ucon, Ucov, Bcon, Bcov); + // Guess B if we *absolutely must* // Note get_model_b (rightly) returns 0 outside the domain, // but we can cling to the 4-vectors a bit longer - B = 0.; - MULOOP B += Bcon[mu] * Bcov[mu]; - if (B <= 0.) { + double bsq = 0.; + MULOOP bsq += Bcon[mu] * Bcov[mu]; + if (bsq <= 0.) { Bcon[0] = 0.; Bcon[1] = 1.; Bcon[2] = 1.; @@ -315,154 +371,150 @@ int evolve_N(double Xi[NDIM], double Kconi[NDIM], gcov_func(Xf, gcov); oddflag |= make_plasma_tetrad(Ucon, Kconf, Bcon, gcov, Econ, Ecov); - // TODO If B is 0, just keep guessing - //int exhausted = 0; - //while (oddflag && B <= 0. && !exhausted) { etc } - - /* convert N to Stokes */ + // Convert N to Stokes parameters complex_coord_to_tetrad_rank2(N_coord, Ecov, N_tetrad); tensor_to_stokes(N_tetrad, &SI0, &SQ0, &SU0, &SV0); - /* apply the Faraday rotation solution for a half step */ + // Apply the Faraday rotation solution for a half step double x = dlam * 0.5; - - rdS = rQ * SQ0 + rU * SU0 + rV * SV0; - rho2 = rQ * rQ + rU * rU + rV * rV; - rho = sqrt(rho2); - double c, s, sh; - c = cos(rho * x); - s = sin(rho * x); - sh = sin(0.5 * rho * x); - if (rho2 > 0) { + double rho2 = rQ * rQ + rU * rU + rV * rV; + if (rho2 > CUT_PREVENT_NAN) { + double rho = sqrt(rho2); + double rdS = rQ * SQ0 + rU * SU0 + rV * SV0; + double c = cos(rho * x); + double s = sin(rho * x); + double sh = sin(0.5 * rho * x); SI1 = SI0; SQ1 = SQ0 * c + 2 * rQ * rdS / rho2 * sh * sh + (rU * SV0 - rV * SU0) / rho * s; SU1 = SU0 * c + 2 * rU * rdS / rho2 * sh * sh + (rV * SQ0 - rQ * SV0) / rho * s; SV1 = SV0 * c + 2 * rV * rdS / rho2 * sh * sh + (rQ * SU0 - rU * SQ0) / rho * s; } else { SI1 = SI0; - SQ1 = SQ0; - SU1 = SU0; - SV1 = SV0; + SQ1 = SQ0 + (-rV * SU0 + rU * SV0) * x; + SU1 = SU0 + ( rV * SQ0 - rQ * SV0) * x; + SV1 = SV0 + (-rU * SQ0 + rQ * SU0) * x; } - /* done rotation solution half step */ - /* apply full absorption/emission step */ + // Apply full absorption/emission step x = dlam; - double aI2 = aI * aI; double aP2 = aQ * aQ + aU * aU + aV * aV; - double aP = sqrt(aP2); - double ads0 = aQ * SQ1 + aU * SU1 + aV * SV1; - double adj = aQ * jQ + aU * jU + aV * jV; - - if (aP*x > CUT_HIGH_ABS || aI*x > CUT_HIGH_ABS) { - // Solution assuming aI ~ aP >> 1, the worst case. - // This covers the case aI >> aP by sending exp(aP-aI)->0, which is safe for all terms - double expDiffx = exp((aP-aI) * x)/2; - - SI2 = (SI1 * expDiffx - - (ads0 / aP) * expDiffx - + adj / (aI2 - aP2) * (-1 + (aI * expDiffx + aP * expDiffx) / aP) - + aI * jI / (aI2 - aP2) * (1 - (aI * expDiffx + aP * expDiffx) / aI)); - - SQ2 = (ads0 * aQ / aP2 * expDiffx - - aQ / aP * SI1 * expDiffx - + jQ / aI - + adj * aQ / (aI * (aI2 - aP2)) * - (1 - aI / aP2 * (aI * expDiffx + aP * expDiffx)) - + jI * aQ / (aP * (aI2 - aP2)) * (-aP + (aP * expDiffx + aI * expDiffx))); - - SU2 = (ads0 * aU / aP2 * expDiffx - - aU / aP * SI1 * expDiffx - + jU / aI - + adj * aU / (aI * (aI2 - aP2)) * - (1 - aI / aP2 * (aI * expDiffx + aP * expDiffx)) - + jI * aU / (aP * (aI2 - aP2)) * (-aP + (aP * expDiffx + aI * expDiffx))); - - SV2 = (ads0 * aV / aP2 * expDiffx - - aV / aP * SI1 * expDiffx - + jV / aI - + adj * aV / (aI * (aI2 - aP2)) * (1 - - aI / aP2 * (aI * expDiffx + aP * expDiffx)) - + jI * aV / (aP * (aI2 - aP2)) * - (-aP + (aP * expDiffx + aI * expDiffx))); - } else if (aP*x > CUT_LOW_ABS) { /* full analytic solution has trouble if polarized absorptivity is small */ - double expaIx = exp(-aI * x); - double sinhaPx = sinh(aP * x); - double coshaPx = cosh(aP * x); - - SI2 = (SI1 * coshaPx * expaIx - - (ads0 / aP) * sinhaPx * expaIx - + adj / (aI2 - aP2) * (-1 + (aI * sinhaPx + aP * coshaPx) / aP * expaIx) - + aI * jI / (aI2 - aP2) * (1 - (aI * coshaPx + aP * sinhaPx) / aI * expaIx)); - - SQ2 = (SQ1 * expaIx - + ads0 * aQ / aP2 * (-1 + coshaPx) * expaIx - - aQ / aP * SI1 * sinhaPx * expaIx - + jQ * (1 - expaIx) / aI - + adj * aQ / (aI * (aI2 - aP2)) * (1 - (1 - aI2 / aP2) * expaIx - - aI / aP2 * (aI * coshaPx + aP * sinhaPx) * expaIx) - + jI * aQ / (aP * (aI2 - aP2)) * (-aP + (aP * coshaPx + aI * sinhaPx) * expaIx)); - - SU2 = (SU1 * expaIx - + ads0 * aU / aP2 * (-1 + coshaPx) * expaIx - - aU / aP * SI1 * sinhaPx * expaIx - + jU * (1 - expaIx) / aI - + adj * aU / (aI * (aI2 - aP2)) * - (1 - (1 - aI2 / aP2) * expaIx - - aI / aP2 * (aI * coshaPx + - aP * sinhaPx) * expaIx) - + jI * aU / (aP * (aI2 - aP2)) * - (-aP + (aP * coshaPx + aI * sinhaPx) * expaIx)); - - SV2 = (SV1 * expaIx - + ads0 * aV / aP2 * (-1 + coshaPx) * expaIx - - aV / aP * SI1 * sinhaPx * expaIx - + jV * (1 - expaIx) / aI - + adj * aV / (aI * (aI2 - aP2)) * (1 - - (1 - aI2 / aP2) * expaIx - - aI / aP2 * (aI * coshaPx + - aP * sinhaPx) * expaIx) - + jI * aV / (aP * (aI2 - aP2)) * - (-aP + (aP * coshaPx + aI * sinhaPx) * expaIx)); - + if (aP2 > CUT_PREVENT_NAN) { // If 1/(aP2) will not return NaN... + double aP = sqrt(aP2); + double tauP = aP*x; + double tauI = aI*x; + double ads0 = aQ * SQ1 + aU * SU1 + aV * SV1; + double adj = aQ * jQ + aU * jU + aV * jV; + // appearance of factor 1/(aI2 - aP2) suggests that polarized and unpolarized + // transfer will differ by O(aP2/aI2) + double efacm = exp(-tauI + tauP); + double efacp = exp(-tauI - tauP); + double efac = exp(-tauI); + + /* The formal solution for polarized transfer with emission and absorption + * but no faraday rotation has 3 distinct eigenvalues (the first is degenerate): + * aI, aI+aP, and aI-aP, see Moscibrodzka & Gammie 2017 appendix A2. + * + * The general solution contains terms like (1 - exp(-\lambda/\lambda_i)), + * which can suffer loss of precision for small lambda. + */ + + /* this is the piece that captures effect of absorption on initial stokes vector, + * safe for all optical depths + * + * These expressions assume that aP < aI and all jS, aS > 0 + */ + SI2 = efacm*(SI1/2. - ads0/(2.*aP)) + + efacp*(SI1/2. + ads0/(2.*aP)); + + SQ2 = efacm*(-SI1*aQ*aP + ads0*aQ)/(2.*aP2) + + efacp*(SI1*aQ*aP + ads0*aQ)/(2.*aP2) + + efac*(SQ1 - ads0*aQ/(aP2)); + + SU2 = efacm*(-SI1*aU*aP + ads0*aU)/(2.*aP2) + + efacp*(SI1*aU*aP + ads0*aU)/(2.*aP2) + + efac*(SU1 - ads0*aU/(aP2)); + + SV2 = efacm*(-SI1*aV*aP + ads0*aV)/(2.*aP2) + + efacp*(SI1*aV*aP + ads0*aV)/(2.*aP2) + + efac*(SV1 - ads0*aV/(aP2)); + + /* In the limit of large optical depth Kirchoff's law is satisfied + * for thermal transfer coefficients, i.e. the solution reduces to + * {I,Q,U,V} = {B_\nu, 0, 0, 0}, provided j_S = a_S B_\nu (S = IQUV) + * Notice that there is a potential loss of precision since the Q,U,V + * terms vanish for thermal eDFs. + */ + + // Taylor series expansion at small optical depth to avoid loss of precision + double afacm = 1. - efacm; + double afac = 1. - efac; + double afacp = 1. - efacp; + // Try to be clever by nesting the conditions. Of questionable use. + if (tauI - tauP <= CUT_SMALL_OPTICAL_DEPTH) { + double e = tauI - tauP; + afacm = e*(1. - (e/2.)*(1. - e/3.)); + + if (tauI <= CUT_SMALL_OPTICAL_DEPTH) { + e = tauI; + afac = e*(1. - (e/2.)*(1. - e/3.)); + + if (tauI + tauP <= CUT_SMALL_OPTICAL_DEPTH) { + e = tauI + tauP; + afacp = e*(1. - (e/2.)*(1. - e/3.)); + } + } + } + // piece proportional to afac + SQ2 += afac*(jQ/aI - aQ*adj/(aI*aP2)); + SU2 += afac*(jU/aI - aU*adj/(aI*aP2)); + SV2 += afac*(jV/aI - aV*adj/(aI*aP2)); + // pieces proportional to afacm + SI2 += afacm*(aP*jI - adj)/(2.*aP*(aI - aP)); + SQ2 += afacm*aQ*(-aP*jI + adj)/(2.*aP2*(aI - aP)); + SU2 += afacm*aU*(-aP*jI + adj)/(2.*aP2*(aI - aP)); + SV2 += afacm*aV*(-aP*jI + adj)/(2.*aP2*(aI - aP)); + // pieces proportional to afacp + SI2 += afacp*(aP*jI + adj)/(2.*aP*(aI + aP)); + SQ2 += afacp*aQ*(aP*jI + adj)/(2.*aP2*(aI + aP)); + SU2 += afacp*aU*(aP*jI + adj)/(2.*aP2*(aI + aP)); + SV2 += afacp*aV*(aP*jI + adj)/(2.*aP2*(aI + aP)); } else { - // Still account for aI which may be >> aP, e.g. simulating unpolarized transport - // Should still make this an expansion in aP as well + // Since we can now rely on aP==0, this is less questionable double tau_fake = 0; SI2 = approximate_solve(SI1, jI, aI, jI, aI, x, &tau_fake); SQ2 = approximate_solve(SQ1, jQ, aI, jQ, aI, x, &tau_fake); SU2 = approximate_solve(SU1, jU, aI, jU, aI, x, &tau_fake); SV2 = approximate_solve(SV1, jV, aI, jV, aI, x, &tau_fake); } - /* done absorption/emission full step */ - /* apply second rotation half-step */ + // Apply the second rotation half-step x = dlam * 0.5; - rdS = rQ * SQ2 + rU * SU2 + rV * SV2; rho2 = rQ * rQ + rU * rU + rV * rV; - rho = sqrt(rho2); - c = cos(rho * x); - s = sin(rho * x); - sh = sin(0.5 * rho * x); - if (rho2 > 0) { + if (rho2 > CUT_PREVENT_NAN) { + double rdS = rQ * SQ2 + rU * SU2 + rV * SV2; + double rho = sqrt(rho2); + double c = cos(rho * x); + double s = sin(rho * x); + double sh = sin(0.5 * rho * x); SI = SI2; SQ = SQ2 * c + 2 * rQ * rdS / rho2 * sh * sh + (rU * SV2 - rV * SU2) / rho * s; SU = SU2 * c + 2 * rU * rdS / rho2 * sh * sh + (rV * SQ2 - rQ * SV2) / rho * s; SV = SV2 * c + 2 * rV * rdS / rho2 * sh * sh + (rQ * SU2 - rU * SQ2) / rho * s; } else { SI = SI2; - SQ = SQ2; - SU = SU2; - SV = SV2; + SQ = SQ2 + (-rV * SU2 + rU * SV2) * x; + SU = SU2 + ( rV * SQ2 - rQ * SV2) * x; + SV = SV2 + (-rU * SQ2 + rQ * SU2) * x; } - /* done second rotation half-step */ - *tauF += dlam*fabs(rV); //*sqrt(SQ*SQ + SU*SU); + *tauF += dlam*fabs(rV); if (params->stokes_floors) { - // Correct the resulting Stokes parameters to guarantee: + // Optionally correct the resulting Stokes parameters to guarantee: // 1. I > 0 // 2. sqrt(Q^2 + U^2 + V^2) < I + // This is ensured of the emissivities by default, + // but can be additionally enforced here. if (SI < 0) { SI = 0; SQ = 0; @@ -478,7 +530,7 @@ int evolve_N(double Xi[NDIM], double Kconi[NDIM], } } - /* re-pack the Stokes parameters into N */ + // Re-pack the Stokes parameters into N stokes_to_tensor(SI, SQ, SU, SV, N_tetrad); complex_tetrad_to_coord_rank2(N_tetrad, Econ, N_coord); @@ -492,9 +544,21 @@ int evolve_N(double Xi[NDIM], double Kconi[NDIM], // Flag if something is wrong if (*tauF > 1.e100 || *tauF < -1.e100 || isnan(*tauF)) oddflag |= 2; +#if DEBUG + // Unless it's a NaN and we're debugging, then print the details + if (isnan(creal(N_tetrad[0][0])) || isnan(creal(N_coord[0][0]))) { + oddflag |= 4; + fprintf(stderr, "Stokes S0: [%e %e %e %e]\n", SI0, SQ0, SU0, SV0); + fprintf(stderr, "Stokes S1: [%e %e %e %e]\n", SI0, SQ0, SU0, SV0); + fprintf(stderr, "Stokes S2: [%e %e %e %e]\n", SI0, SQ0, SU0, SV0); + fprintf(stderr, "Stokes S: [%e %e %e %e] dlam: %e\n", SI, SQ, SU, SV, dlam); + fprintf(stderr, "Coefficients: j: [%e %e %e %e] a: [%e %e %e %e] rho: [%e %e %e]\n", jI, jQ, jU, jV, aI, aQ, aU, aV, rQ, rU, rV); + MUNULOOP fprintf(stderr, "Econ[%i][%i] = %e Ncoord = %e Ntet = %e\n", mu, nu, Econ[mu][nu], creal(N_coord[mu][nu]), creal(N_tetrad[mu][nu])); + } +#else if (isnan(creal(N_tetrad[0][0])) || isnan(creal(N_coord[0][0]))) oddflag |= 4; - - /* SOURCE STEP DONE */ +#endif + if (isnan(Ucov[0])) oddflag |= 8; return oddflag; } @@ -617,6 +681,20 @@ void tensor_to_stokes(double complex f_tetrad[NDIM][NDIM], } +void any_tensor_to_stokes(double complex f_any[NDIM][NDIM], double gcov[NDIM][NDIM], + double *fI, double *fQ, double *fU, double *fV) +{ + + // TODO Find the others with a guaranteed coordinate-frame tetrad? + double complex f_lower[NDIM][NDIM]; + complex_lower(f_any, gcov, 0, 1, f_lower); + *fI = creal(f_lower[0][0] + f_lower[1][1] + f_lower[2][2] + f_lower[3][3]) / 2; + *fQ = 0.; + *fU = 0.; + *fV = 0.; + +} + void complex_coord_to_tetrad_rank2(double complex T_coord[NDIM][NDIM], double Ecov[NDIM][NDIM], double complex T_tetrad[NDIM][NDIM]) diff --git a/src/ipolarray.h b/src/ipolarray.h index aca842b..067168e 100644 --- a/src/ipolarray.h +++ b/src/ipolarray.h @@ -14,9 +14,10 @@ #include // Top-level functions for solving emission -int integrate_emission(struct of_traj *traj, int nstep, +int integrate_emission(struct of_traj *traj, int nsteps, double *Intensity, double *Tau, double *tauF, - double complex N_coord[NDIM][NDIM], Params *params); + double complex N_coord[NDIM][NDIM], Params *params, + int print); // Needed for slow light. TODO extend above to use instead int evolve_N(double Xi[NDIM],double Kconi[NDIM], @@ -31,5 +32,26 @@ double approximate_solve (double Ii, double ji, double ki, double jf, double kf, void project_N(double X[NDIM],double Kcon[NDIM], double complex Ncon[NDIM][NDIM], double *Stokes_I, double *Stokes_Q,double *Stokes_U,double *Stokes_V, double rotcam); +void parallel_transport_vector(double Xi[NDIM], double Xm[NDIM], double Xf[NDIM], + double Ki[NDIM], double Km[NDIM], double Kf[NDIM], + double Ni[NDIM], + double Nm[NDIM], + double Nf[NDIM], double dl); + +/* tensor tools */ +void complex_lower(double complex N[NDIM][NDIM], double gcov[NDIM][NDIM], + int low1, int low2, double complex Nl[NDIM][NDIM]); +void stokes_to_tensor(double fI, double fQ, double fU, double fV, + double complex f_tetrad[NDIM][NDIM]); +void tensor_to_stokes(double complex f_tetrad[NDIM][NDIM], double *fI, + double *fQ, double *fU, double *fV); +void any_tensor_to_stokes(double complex f_any[NDIM][NDIM], double gcov[NDIM][NDIM], + double *fI, double *fQ, double *fU, double *fV); +void complex_coord_to_tetrad_rank2(double complex T_coord[NDIM][NDIM], + double Ecov[NDIM][NDIM], + double complex T_tetrad[NDIM][NDIM]); +void complex_tetrad_to_coord_rank2(double complex T_tetrad[NDIM][NDIM], + double Econ[NDIM][NDIM], + double complex T_coord[NDIM][NDIM]); #endif /* IPOLARRAY_H */ diff --git a/src/koral_coords.c b/src/koral_coords.c new file mode 100644 index 0000000..60cd29f --- /dev/null +++ b/src/koral_coords.c @@ -0,0 +1,271 @@ +#include "koral_coords.h" +#include "simcoords.h" +#include "coordinates.h" + +#include +#include + +#define TNY 96 +#define RCYL 30. +#define NCYL 1. +#define MINY (0.0025) +#define MAXY (1.-0.0025) +#define RMIN (0.8*(1.+sqrt(1-a*a))) + +static double x2cyl, rmidcyl, thetaCYL, sinthetaCYL, thetaAX, sinthetaAX; + +double KORAL_theta_diskjet(double r, double x2, void *params); + +int set_cyl_params() +{ + jetcoords_params tpar; + x2cyl = MINY + 0.5*NCYL/((double)TNY); + rmidcyl = 0.5 * (RCYL + RMIN); + + tpar.r0 = mp_koral_jetcoords.mksr0; + tpar.rbrk = mp_koral_jetcoords.rbrk; + tpar.fdisk = mp_koral_jetcoords.fdisk; + tpar.fjet = mp_koral_jetcoords.fjet; + tpar.runi = mp_koral_jetcoords.runi; + tpar.rcoll_jet = mp_koral_jetcoords.rcoll_jet; + tpar.rcoll_disk = mp_koral_jetcoords.rcoll_disk; + tpar.rdecoll_jet = mp_koral_jetcoords.rdecoll_jet; + tpar.rdecoll_disk = mp_koral_jetcoords.rdecoll_disk; + tpar.alpha_1 = mp_koral_jetcoords.alpha_1; + tpar.alpha_2 = mp_koral_jetcoords.alpha_2; + + thetaCYL = KORAL_theta_diskjet(RCYL, x2cyl, &tpar); + sinthetaCYL = sin(thetaCYL); + thetaAX = KORAL_theta_diskjet(RCYL, MAXY, &tpar); + sinthetaAX = sin(thetaAX); + + return 0; +} + +double sinth1(double r, double x2, void* params); +double sinth2(double r, double x2, void* params); + +double sinth0(double r, double x2, void* params) +{ + double sinth0 = RCYL * sin(thetaCYL)/r; + return sinth0; +} + +double minn(double a, double b, double df) +{ + double delta = (b-a)/df; + return b - psi_smooth(delta)*df; +} + +double maxx(double a, double b, double df) +{ + return -1 * minn(-a, -b, df); +} + +double psi_smooth(double x) +{ + if (x < -1) { + return 0.; + } else if(x>=1) { + return x; + } else { + double xout = (-35.*cos(0.5*M_PI*x) - (5./6.)*cos(1.5*M_PI*x) + 0.1*cos(2.5*M_PI*x))/(32.*M_PI); + return xout + 0.5*(x+1.); + } +} + +double theta_smooth(double x) +{ + if (x < -1) { + return 0.; + } else if (x >= 1) { + return 1.; + } else { + return 0.5 + (70.*sin(0.5*M_PI*x) + 5*sin(1.5*M_PI*x) - sin(2.5*M_PI*x))/128.; + } +} + +double theta_disk_or_jet(double r, double x2, double rdecoll, double rcoll, double runi, double a1, double a2) +{ + double r1 = minn(r, rdecoll, 0.5*rdecoll)/runi; + double r2 = minn(r/(r1*runi), rcoll/rdecoll, 0.5*rcoll/rdecoll); + double y = pow(r2, a2)*tan(0.5*x2*M_PI); + double x = pow(r1, a1); //opposite sign convention for alpha1 from ressler 2017! + return 0.5*M_PI + atan2(y,x); +} + +double wjet(double x2, double fdisk, double fjet) +{ + //NOTE! fjet and fdisk must both be positive and sum to < 1. + //NOTE! fjet is NOT defined as in Ressler 2017: their fjet = 1 - (our fjet) + double delta = 2*(fabs(x2) - fdisk)/(1 - fjet - fdisk) - 1.; + return theta_smooth(delta); +} + +double KORAL_theta_diskjet(double r, double x2, void *params) +{ + jetcoords_params *par = (jetcoords_params *)params; + double theta_disk, theta_jet, wfrac, theta; + + theta_disk = theta_disk_or_jet(r, x2, par->rdecoll_disk, par->rcoll_disk, par->runi, + par->alpha_1, par->alpha_2); + theta_jet = theta_disk_or_jet(r, x2, par->rdecoll_jet, par->rcoll_jet, par->runi, + par->alpha_1, par->alpha_2); + wfrac = wjet(x2, par->fdisk, par->fjet); + theta = wfrac*theta_jet + (1-wfrac)*theta_disk; + + return theta; +} + +double to1stquad(double x2) +{ + double ntimes = floor(0.25*(x2+2)); + double x2out = x2 - 4*ntimes; + + if (x2out > 0) { + x2out = -x2out; + } + + if (x2out < -1) { + x2out = - 2 - x2out; + } + + return x2out; +} + +double f2func(double r, double x2, void* params) +{ + jetcoords_params *par = (jetcoords_params *)params; + + double s1in = sinth1(r, x2, par); + double s2in = sinth2(r, x2, par); + + double s1ax = sinth1(r, MAXY, par); + double s2ax = sinth2(r, MAXY, par); + double df = fabs(s2ax - s1ax) + 1.e-16; + + if (r >= RCYL) { + return maxx(s1in, s2in, df); + } else { + return minn(s1in, s2in, df); + } +} + +double sinth1(double r, double x2, void* params) +{ + jetcoords_params *par = (jetcoords_params *) params; + double theta1 = KORAL_theta_diskjet(RCYL, x2, par); + double sinth1 = RCYL*sin(theta1)/r; + return sinth1; +} + +double sinth2(double r, double x2, void* params) +{ + jetcoords_params *par = (jetcoords_params *) params; + + double theta = KORAL_theta_diskjet(r, x2, par); + double theta2 = KORAL_theta_diskjet(r, x2cyl, par); + + double thetamid = 0.5*M_PI; + + double thetaA = asin(sinth0(r,x2,par)); + double thetaB = (theta-theta2)*(thetamid-thetaA)/(thetamid-theta2); + double sinth2 = sin(thetaA + thetaB); + + return sinth2; +} + +double KORAL_cylindrify(double r, double x2, void *params) +{ + jetcoords_params *par = (jetcoords_params *) params; + + double thin = KORAL_theta_diskjet(r, x2, par); + + double x2mir = to1stquad(x2); + double thmir = KORAL_theta_diskjet(r, x2mir, par); + + double f1 = sin(thmir); + double f2 = f2func(r, x2mir, par); + + double thmid = KORAL_theta_diskjet(rmidcyl, x2mir, par); + double f1mid = sin(thmid); + double f2mid = f2func(rmidcyl, x2mir, par); + + double df = f2mid - f1mid; + + double thout = asin(maxx(r*f1, r*f2, r*fabs(df) + 1.e-16)/r); + if (x2 != x2mir) { + thout = thin + thmir - thout; + } + + return thout; +} + + + + +void fwd_KORAL_JETCOORDS(double *xJET, double *xKS) +{ + // get coordinate system parameters + double r0 = mp_koral_jetcoords.mksr0; + double rbrk = mp_koral_jetcoords.rbrk; + double alpha_1 = mp_koral_jetcoords.alpha_1; + double alpha_2 = mp_koral_jetcoords.alpha_2; + double fdisk = mp_koral_jetcoords.fdisk; + double fjet = mp_koral_jetcoords.fjet; + double runi = mp_koral_jetcoords.runi; + double rcoll_jet = mp_koral_jetcoords.rcoll_jet; + double rcoll_disk = mp_koral_jetcoords.rcoll_disk; + double rdecoll_jet = mp_koral_jetcoords.rdecoll_jet; + double rdecoll_disk = mp_koral_jetcoords.rdecoll_disk; + + /* + double x1in = mp_koral_jetcoords.hypx1in; // TODO + double x1out = mp_koral_jetcoords.hypx1out; // TODO + double x1brk = mp_koral_jetcoords.hypx1brk; // TODO + */ + double x1in = 0.; + double x1out = 0.; + double x1brk = 0.; + + // so many parameters, so little time + jetcoords_params tpar; + tpar.r0 = r0; + tpar.rbrk = rbrk; + tpar.runi = runi; + tpar.rcoll_jet = rcoll_jet; + tpar.rcoll_disk = rcoll_disk; + tpar.rdecoll_jet = rdecoll_jet; + tpar.rdecoll_disk = rdecoll_disk; + tpar.alpha_1 = alpha_1; + tpar.alpha_2 = alpha_2; + tpar.fdisk = fdisk; + tpar.fjet = fjet; + + // pass time through + xKS[0] = xJET[0]; + + // get radial coordinate + double x1sc = x1in + xJET[1] * (x1out-x1in); // scale out of 0-1 range + if (x1sc < x1brk) { + xKS[1] = exp(x1sc) + r0; + } else { + // from hyperexp_func + double x1brk = log(rbrk - r0); + xKS[1] = r0 + exp(xJET[1] + 4. * pow(xJET[1] - x1brk, 4)); + } + + // get elevation coordinate + if (mp_koral_jetcoords.cylindrify) { + xKS[2] = KORAL_cylindrify(xKS[1], xJET[2], &tpar); + } else { + // return theta_diskjet(r, x2, par); + assert(1==0); // WARNING, THIS HAS NOT BEEN VALIDATED BY ITSELF! + xKS[2] = KORAL_theta_diskjet(xKS[1], xJET[2], &tpar); + } + + // pass phi through + xKS[3] = xJET[3]; +} + + diff --git a/src/koral_coords.h b/src/koral_coords.h new file mode 100644 index 0000000..65e327d --- /dev/null +++ b/src/koral_coords.h @@ -0,0 +1,21 @@ +#ifndef KORAL_COORDS +#define KORAL_COORDS + +double minn(double a, double b, double df); +double maxx(double a, double b, double df); +double psi_smooth(double x); +double theta_smooth(double x); +double theta_disk_or_jet(double r, double x2, double rdecoll, double rcoll, double runi, double a1, double a2); +double wjet(double x2, double fdisk, double fjet); +double KORAL_theta_diskjet(double r, double x2, void *params); +double KORAL_cylindrify(double r, double x2, void *params); + +typedef struct jetcoords_params_struct { + double r_test, theta_test; + double r0, rbrk, runi; + double rdecoll_jet, rcoll_jet, rdecoll_disk, rcoll_disk; + double alpha_1, alpha_2; + double fdisk, fjet; +} jetcoords_params; + +#endif // KORAL_COORDS diff --git a/src/main.c b/src/main.c index 4c1903b..789e771 100644 --- a/src/main.c +++ b/src/main.c @@ -5,6 +5,7 @@ #include "model_radiation.h" #include "model_tetrads.h" +#include "bremss_fits.h" #include "radiation.h" #include "coordinates.h" #include "debug_tools.h" @@ -88,6 +89,9 @@ int main(int argc, char *argv[]) // them and use init_model to load the first dump init_model(&tA, &tB); + // If we're using Bremsstrahlung emission, precalculate a spline + init_bremss_spline(); + // Adaptive resolution option // nx, ny are the resolution at maximum refinement level // nx_min, ny_min are at coarsest level @@ -118,6 +122,10 @@ int main(int argc, char *argv[]) // normalize frequency to electron rest-mass energy double freqcgs = params.freqcgs; freq = params.freqcgs * HPL / (ME * CL * CL); + if (freq == 0) { + fprintf(stderr, "Frequency cannot be zero. Quitting!\n"); + exit(1); + } // Initialize the camera params.rotcam *= M_PI/180.; @@ -229,7 +237,7 @@ int main(int argc, char *argv[]) int nstep = 0; double dl; double X[NDIM], Xhalf[NDIM], Kcon[NDIM], Kconhalf[NDIM]; - init_XK(i,j, params.nx, params.ny, Xcam, params, fovx,fovy, X, Kcon); + init_XK(i,j, params.nx, params.ny, Xcam, params, fovx, fovy, X, Kcon); MULOOP Kcon[mu] *= freq; @@ -806,8 +814,11 @@ void get_pixel(size_t i, size_t j, int nx, int ny, double Xcam[NDIM], Params par double *Intensity, double *Is, double *Qs, double *Us, double *Vs, double *Tau, double *tauF) { - double X[NDIM], Kcon[NDIM]; - double complex N_coord[NDIM][NDIM]; + double X[NDIM] = {0.}, Kcon[NDIM] = {0.}; + double complex N_coord[NDIM][NDIM] = {{0.}}; + *Intensity = 0.; + *Tau = 0.; + *tauF = 0.; // Integrate backward to find geodesic trajectory init_XK(i,j, params.nx, params.ny, Xcam, params, fovx, fovy, X, Kcon); @@ -824,7 +835,7 @@ void get_pixel(size_t i, size_t j, int nx, int ny, double Xcam[NDIM], Params par } // Integrate emission forward along trajectory - int oddflag = integrate_emission(traj, nstep, Intensity, Tau, tauF, N_coord, ¶ms); + int oddflag = integrate_emission(traj, nstep, Intensity, Tau, tauF, N_coord, ¶ms, 0); if (!only_intensity) { project_N(X, Kcon, N_coord, Is, Qs, Us, Vs, params.rotcam); diff --git a/src/model.h b/src/model.h index e2a4262..3e0ccf5 100644 --- a/src/model.h +++ b/src/model.h @@ -21,16 +21,15 @@ int radiating_region(double X[NDIM]); double get_model_thetae(double X[NDIM]); double get_model_b(double X[NDIM]); +double get_model_sigma(double X[NDIM]); +double get_model_beta(double X[NDIM]); double get_model_ne(double X[NDIM]); // For exotic or custom distributions -void get_model_powerlaw_vals(double X[NDIM], double *p, double *n, - double *gamma_min, double *gamma_max, double *gamma_cut); void get_model_jar(double X[NDIM], double Kcon[NDIM], double *jI, double *jQ, double *jU, double *jV, double *aI, double *aQ, double *aU, double *aV, double *rQ, double *rU, double *rV); -void get_model_jk(double X[NDIM], double Kcon[NDIM], double *jnuinv, double *knuinv); void get_model_fourv(double X[NDIM], double Kcon[NDIM], double Ucon[NDIM], double Ucov[NDIM], diff --git a/src/model_geodesics.c b/src/model_geodesics.c index 93711b7..4ef7374 100644 --- a/src/model_geodesics.c +++ b/src/model_geodesics.c @@ -18,6 +18,7 @@ #include "tetrads.h" #include "debug_tools.h" +#include "simcoords.h" // TODO pick one or runtime #define STEP_STRICT_MIN 0 @@ -252,7 +253,7 @@ int stop_backward_integration(double X[NDIM], double Xhalf[NDIM], double Kcon[ND double stepsize(double X[NDIM], double Kcon[NDIM], double eps) { double dl; - double deh = fmin(fabs(X[1] - startx[1]), 0.1); // TODO coordinate dependent + double deh = fmin(fabs(X[1] - cstartx[1]), 0.1); // TODO coordinate dependent double dlx1 = eps * (10*deh) / (fabs(Kcon[1]) + SMALL*SMALL); // Make the step cautious near the pole, improving accuracy of Stokes U diff --git a/src/model_radiation.c b/src/model_radiation.c index 40fe71d..18ce504 100644 --- a/src/model_radiation.c +++ b/src/model_radiation.c @@ -24,6 +24,7 @@ // Symphony #include "fits.h" #include "params.h" +#include "bremss_fits.h" #include #include @@ -33,123 +34,124 @@ #define E_KAPPA 2 #define E_POWERLAW 3 #define E_DEXTER_THERMAL 4 +#define E_LEUNG 5 #define E_CUSTOM 10 -// Rotation +// Rotation #define ROT_OLD 11 #define ROT_PIECEWISE 12 #define ROT_SHCHERBAKOV 13 -// Debugging +// Debugging/internal #define E_UNPOL 15 -// Declarations of local fitting functions, for Dexter fits and old rotativities -void dexter_j_fit_thermal(double Ne, double nu, double Thetae, double B, double theta, - double *jI, double *jQ, double *jU, double *jV); -void shcherbakov_rho_fit(double Ne, double nu, double Thetae, double B, double theta, - double *rQ, double *rU, double *rV); -void piecewise_rho_fit(double Ne, double nu, double Thetae, double B, double theta, - double *rQ, double *rU, double *rV); -void old_rho_fit(double Ne, double nu, double Thetae, double B, double theta, - double *rQ, double *rU, double *rV); - -// Thermal plasma emissivity, absorptivity and Faraday conversion and rotation -double g(double Xe); -double h(double Xe); -double Je(double Xe); -double jffunc(double Xe); -double I_I(double x); -double I_Q(double x); -double I_V(double x); -double besselk_asym(int n, double x); +#define RAD_OVERFLOW 1e100 + +// Local functions for declaring different kappa/powerlaw distributions +void get_model_powerlaw_vals(double X[NDIM], double *p, double *n, + double *gamma_min, double *gamma_max, double *gamma_cut); +void get_model_kappa(double X[NDIM], double *kappa, double *kappa_width); /* Get coeffs from a specific distribution */ -void jar_calc_dist(int dist, double X[NDIM], double Kcon[NDIM], +void jar_calc_dist(int dist, int pol, double X[NDIM], double Kcon[NDIM], double *jI, double *jQ, double *jU, double *jV, double *aI, double *aQ, double *aU, double *aV, double *rQ, double *rU, double *rV); /** - * Wrapper to call different distributions at different places in the simulation domain - * See jar_calc_dist for distributions - * TODO put the general emission zero criteria here + * Optionally load radiation model parameters + */ +static double model_kappa = 3.5; +static double powerlaw_gamma_cut = 1e10; +static double powerlaw_gamma_min = 1e2; +static double powerlaw_gamma_max = 1e5; +static double powerlaw_p = 3.25; +static double powerlaw_eta = 0.02; +static int variable_kappa = 0; +static double variable_kappa_min = 3.1; +static double variable_kappa_interp_start = 1e20; +static double variable_kappa_max = 7.0; +static double max_pol_frac_e = 0.99; +static double max_pol_frac_a = 0.99; +static int do_bremss = 0; +static int bremss_type = 2; + +void try_set_radiation_parameter(const char *word, const char *value) +{ + set_by_word_val(word, value, "kappa", &model_kappa, TYPE_DBL); + set_by_word_val(word, value, "variable_kappa", &variable_kappa, TYPE_INT); + set_by_word_val(word, value, "variable_kappa_min", &variable_kappa_min, TYPE_DBL); + set_by_word_val(word, value, "variable_kappa_interp_start", &variable_kappa_interp_start, TYPE_DBL); + set_by_word_val(word, value, "variable_kappa_max", &variable_kappa_max, TYPE_DBL); + + set_by_word_val(word, value, "powerlaw_gamma_cut", &powerlaw_gamma_cut, TYPE_DBL); + set_by_word_val(word, value, "powerlaw_gamma_min", &powerlaw_gamma_min, TYPE_DBL); + set_by_word_val(word, value, "powerlaw_gamma_max", &powerlaw_gamma_max, TYPE_DBL); + set_by_word_val(word, value, "powerlaw_p", &powerlaw_p, TYPE_DBL); + set_by_word_val(word, value, "powerlaw_eta", &powerlaw_eta, TYPE_DBL); + + set_by_word_val(word, value, "bremss", &do_bremss, TYPE_INT); + set_by_word_val(word, value, "bremss_type", &bremss_type, TYPE_INT); + + set_by_word_val(word, value, "max_pol_frac_e", &powerlaw_p, TYPE_DBL); + set_by_word_val(word, value, "max_pol_frac_a", &powerlaw_p, TYPE_DBL); +} + +/** + * Get polarized emission, absorption, and rotation coefficients + * + * This is a wrapper to jar_calc_dist, see implementation there + * Also checks for NaN coefficients when built with DEBUG, for quicker debugging */ void jar_calc(double X[NDIM], double Kcon[NDIM], double *jI, double *jQ, double *jU, double *jV, double *aI, double *aQ, double *aU, double *aV, double *rQ, double *rU, double *rV, Params *params) { -#if INTEGRATOR_TEST - jar_calc_dist(E_CUSTOM, X, Kcon, jI, jQ, jU, jV, aI, aQ, aU, aV, rQ, rU, rV); -#else - if (params->emission_type == E_UNPOL) { - get_jkinv(X, Kcon, jI, aI, params); - *jQ = 0.0; *jU = 0.0; *jV = 0.0; - *aQ = 0.0; *aU = 0.0; *aV = 0.0; - *rQ = 0; *rU = 0; *rV = 0; - } else { - jar_calc_dist(params->emission_type, X, Kcon, jI, jQ, jU, jV, aI, aQ, aU, aV, rQ, rU, rV); - } -#endif + jar_calc_dist(params->emission_type, 1, X, Kcon, jI, jQ, jU, jV, aI, aQ, aU, aV, rQ, rU, rV); - // Zero emission to isolate the jet/counterjet portion - if (params->isolate_counterjet == 1) { // Allow emission from X[2] > midplane only - if (X[2] < (cstopx[2] - cstartx[2]) / 2) { - *jI = *jQ = *jU = *jV = 0.; - } - } else if (params->isolate_counterjet == 2) { // from X[2] < midplane only - if (X[2] > (cstopx[2] - cstartx[2]) / 2) { - *jI = *jQ = *jU = *jV = 0.; - } - } + // This wrapper can be used to call jar_calc_dist differently in e.g. funnel vs jet, or + // depending on local fluid parameters, whatever +} + +/** + * Get the emission and absorption coefficients + * This is a wrapper to jar_calc_dist, see implementation there + */ +void get_jkinv(double X[NDIM], double Kcon[NDIM], double *jI, double *aI, Params *params) +{ + double jQ, jU, jV, aQ, aU, aV, rQ, rU, rV; + jar_calc_dist(params->emission_type, 0, X, Kcon, jI, &jQ, &jU, &jV, aI, &aQ, &aU, &aV, &rQ, &rU, &rV); } /** * Get the invariant plasma emissivities, absorptivities, rotativities in tetrad frame - * This is a wrapper to the fitting functions in this file and in the emissivity/ dir + * Calls the appropriate fitting functions from the code in symphony/, + * and ensures that the results obey basic consistency * * The dist argument controls fitting functions/distribution: * 1. Thermal (Pandya+ 2016) - * 2. Kappa (Pandya+ 2016) - * 3. Power-law (Pandya+ 2016) + * 2. Kappa (Pandya+ 2016, no rotativities) + * 3. Power-law (Pandya+ 2016, rhoQ/rhoV Marszewski+ 2021) * 4. Thermal (Dexter 2016) - * Rotativity rhoQ always from Dexter 2016, rhoV from Shcherbakov 2008 (rhoU == 0) + * Thermal rhoQ taken from Dexter 2016, rhoV from Shcherbakov 2008 (rhoU == 0) * * To be implemented? * 5. Power-law (Dexter 2016) - * 6. Thermal (Revised based on Pandya+ 2016 to better match Leung 2011) * * Testing distributions: - * 10. Pass through model-determined values. Currently used for constant-coefficient testing. - * 11. Emulate original ipole, with Dexter 2016 emissivities and rotativities based on Bessel fits - * 12. Emulate the old ipole temporary fix, with Dexter 2016 emissivities and rotativities patched into the constant limit + * 10. Pass through model-determined values -- used in most analytic models + * * */ -void jar_calc_dist(int dist, double X[NDIM], double Kcon[NDIM], +void jar_calc_dist(int dist, int pol, double X[NDIM], double Kcon[NDIM], double *jI, double *jQ, double *jU, double *jV, double *aI, double *aQ, double *aU, double *aV, double *rQ, double *rU, double *rV) { - // Four-vectors needed for most calculations - double Ucon[NDIM], Ucov[NDIM], Bcon[NDIM], Bcov[NDIM]; - // Common parameters - double nu = 0, Ne = 0, B = 0, theta = 0; - // Other parameters are filled as needed - // Thermal: - double Thetae = 0; - // Powerlaw: - double powerlaw_p = 0, gamma_min = 0, gamma_max = 0, gamma_cut = 0; - // Kappa: - double kappa = 0, kappa_width = 0; - // Symphony parameters struct, not to be confused with ipole Params - struct parameters paramsM; int fit = 0; - - // Ignore everything if this isn't our job - if (dist == E_CUSTOM) { - get_model_jar(X, Kcon, jI, jQ, jU, jV, aI, aQ, aU, aV, rQ, rU, rV); - return; - } - // Also ignore everything if we're not in an emitting region - // (or there are actually no e- to emit) - Ne = get_model_ne(X); + // Don't emit where there are no electrons + // This was also used as shorthand in some models to cut off emission + // Please use radiating_region instead for model applicability cutoffs, + // or see integrate_emission for zeroing just jN + double Ne = get_model_ne(X); if (Ne <= 0.) { *jI = 0.0; *jQ = 0.0; *jU = 0.0; *jV = 0.0; *aI = 0.0; *aQ = 0.0; *aU = 0.0; *aV = 0.0; @@ -157,425 +159,227 @@ void jar_calc_dist(int dist, double X[NDIM], double Kcon[NDIM], return; } - // If we must do work, grab the 4-vectors... - get_model_fourv(X, Kcon, Ucon, Ucov, Bcon, Bcov); -#if DEBUG - if (isnan(Ucov[0])) { - int i = 0, j = 0, k = 0; - double del[4] = {0}; - Xtoijk(X, &i,&j,&k, del); - fprintf(stderr, "UCOV[0] (%d,%d,%d) is nan! thread = %i\n", i,j,k, omp_get_thread_num()); - print_vector("Ucon", Ucon); - print_vector("Ucov", Ucov); - fprintf(stderr, "X[] = %e %e %e %e\n", X[0],X[1],X[2],X[3]); - fprintf(stderr, "K[] = %e %e %e %e\n", Kcon[0],Kcon[1],Kcon[2],Kcon[3]); - fprintf(stderr, "Ne = %e\n", Ne); + // Call through to the model if it's responsible for this job + if (dist == E_CUSTOM) { + get_model_jar(X, Kcon, jI, jQ, jU, jV, aI, aQ, aU, aV, rQ, rU, rV); + return; } -#endif - // ...then the parameters needed for all distributions... - theta = get_bk_angle(X, Kcon, Ucov, Bcon, Bcov); // angle between k & b - nu = get_fluid_nu(Kcon, Ucov); // freqcgs in Hz - B = get_model_b(X); // field in G - - //...and the ones for the specific distribution we'll be evaluating. + struct parameters paramsM; + setConstParams(¶msM); + // Set the parameters common to all distributions... + double Ucon[NDIM], Ucov[NDIM], Bcon[NDIM], Bcov[NDIM]; + get_model_fourv(X, Kcon, Ucon, Ucov, Bcon, Bcov); + double nu = get_fluid_nu(Kcon, Ucov); + double nusq = nu*nu; + double theta = get_bk_angle(X, Kcon, Ucov, Bcon, Bcov); + paramsM.electron_density = Ne; + paramsM.nu = nu; + paramsM.observer_angle = theta; + paramsM.magnetic_field = get_model_b(X); + + // ...and then the specific distribution and its parameters. switch (dist) { - case E_THERMAL: - setConstParams(¶msM); - fit = paramsM.MAXWELL_JUETTNER; - Thetae = get_model_thetae(X); - break; - case E_KAPPA: - setConstParams(¶msM); - fit = paramsM.KAPPA_DIST; - // TODO get_model_kappa + case E_KAPPA: // Kappa fits (Pandya + Marszewski) + paramsM.distribution = paramsM.KAPPA_DIST; + // Fall back to Dexter starting at kappa > kappa_interp_start, completely at kappa_max + paramsM.dexter_fit = 1; // This only affects choice of thermal fallback + paramsM.kappa_interp_begin = fmin(variable_kappa_interp_start, variable_kappa_max); + paramsM.kappa_interp_end = variable_kappa_max; + paramsM.theta_e = get_model_thetae(X); + get_model_kappa(X, &(paramsM.kappa), &(paramsM.kappa_width)); break; - case E_POWERLAW: - setConstParams(¶msM); - fit = paramsM.POWER_LAW; + case E_POWERLAW: // Powerlaw fits (Pandya, no rotativities!) + paramsM.distribution = paramsM.POWER_LAW; // NOTE WE REPLACE Ne!! - get_model_powerlaw_vals(X, &powerlaw_p, &Ne, &gamma_min, &gamma_max, &gamma_cut); + get_model_powerlaw_vals(X, &(paramsM.power_law_p), &(paramsM.electron_density), + &(paramsM.gamma_min), &(paramsM.gamma_max), &(paramsM.gamma_cutoff)); + break; + case E_THERMAL: // Pandya thermal fits + paramsM.distribution = paramsM.MAXWELL_JUETTNER; + paramsM.theta_e = get_model_thetae(X); + break; + case E_DEXTER_THERMAL: // Dexter thermal fits (default) + default: + paramsM.dexter_fit = 1; + paramsM.distribution = paramsM.MAXWELL_JUETTNER; + paramsM.theta_e = get_model_thetae(X); break; - case E_DEXTER_THERMAL: - Thetae = get_model_thetae(X); } - // EMISSIVITIES - // Avoid issues directly along field lines - if (theta <= 0 || theta >= M_PI) { + // First, enforce no emission/absorption along field lines, + // but allow Faraday rotation in polarized context + // TODO this skips any rho_V NaN/other checks + if (theta <= 0.0 || theta >= M_PI) { *jI = 0.0; *jQ = 0.0; *jU = 0.0; *jV = 0.0; *aI = 0.0; *aQ = 0.0; *aU = 0.0; *aV = 0.0; - } else { - // EMISSIVITIES - if (dist == E_DEXTER_THERMAL || dist > 10) { - // Use fits from Dexter when called for or emulating old behavior - dexter_j_fit_thermal(Ne, nu, Thetae, B, theta, jI, jQ, jU, jV); + *rQ = 0.0; *rU = 0.0; + if (pol && !(dist == E_UNPOL)) { + *rV = rho_nu_fit(¶msM, paramsM.STOKES_V) * nu; } else { - // Call into bundled Symphony code - // Symphony uses an... interesting coordinate system. Correct it. - // TODO fix Symphony, jV fit is bad - *jI = j_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_I, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); - *jQ = -j_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_Q, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); - *jU = -j_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_U, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); - *jV = j_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_V, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); + *rV = 0.0; } - // Check basic relationships - if (*jI < 0) { - fprintf(stderr, "Negative total emissivity! Exiting!\n"); - exit(-1); + return; + } + + // Then, if performing unpolarized transport, calculate only what we need + if (!pol || dist == E_UNPOL) { + if (paramsM.distribution == paramsM.MAXWELL_JUETTNER) { + paramsM.dexter_fit = 2; // Signal symphony fits to use Leung+ + *jI = j_nu_fit(¶msM, paramsM.STOKES_I); + if(do_bremss) *jI += bremss_I(¶msM, bremss_type); + *jI /= nusq; // Avoids loss of precision in small numbers + double Bnuinv = Bnu_inv(nu, paramsM.theta_e); // Planck function + if (Bnuinv > 0) { + *aI = *jI / Bnuinv; + } else { + *aI = 0; + } + } else { + paramsM.dexter_fit = 2; // Signal symphony fits to use Leung+ as fallback + *jI = j_nu_fit(¶msM, paramsM.STOKES_I) / nusq; + *aI = alpha_nu_fit(¶msM, paramsM.STOKES_I) * nu; } + } else { // Finally, calculate all coefficients normally + // EMISSIVITIES + *jI = j_nu_fit(¶msM, paramsM.STOKES_I); + // Bremsstrahlung is computed only for thermal; + // silently drop Bremss+other dists, as absorptivities will be wrong + if(paramsM.distribution == paramsM.MAXWELL_JUETTNER && do_bremss) + *jI += bremss_I(¶msM, bremss_type); + *jI /= nusq; // Avoids loss of precision in small numbers + + *jQ = -j_nu_fit(¶msM, paramsM.STOKES_Q) / nusq; + *jU = -j_nu_fit(¶msM, paramsM.STOKES_U) / nusq; + *jV = j_nu_fit(¶msM, paramsM.STOKES_V) / nusq; + // Check basic relationships double jP = sqrt(*jQ * *jQ + *jU * *jU + *jV * *jV); - if (*jI < jP) { + if (*jI < jP/max_pol_frac_e) { // Transport does not like 100% polarization... - double pol_frac_e = *jI / jP - 0.01; - //double pol_frac_e = 0.9; + double pol_frac_e = *jI / jP * max_pol_frac_e; *jQ *= pol_frac_e; *jU *= pol_frac_e; *jV *= pol_frac_e; -#if DEBUG - fprintf(stderr, "Polarized emissivities too large:\n %g vs %g, corrected by %g\n", - jP, *jI, pol_frac_e); -#endif } - // Make invariant - double nusq = nu*nu; - *jI /= nusq; - *jQ /= nusq; - *jU /= nusq; - *jV /= nusq; - // ABSORPTIVITIES - if (dist == E_THERMAL || dist == E_DEXTER_THERMAL || dist > 10) { // Thermal distributions + if (paramsM.distribution == paramsM.MAXWELL_JUETTNER) { // Thermal distributions // Get absorptivities via Kirchoff's law // Already invariant, guaranteed to respect aI > aP - double Bnuinv = Bnu_inv(nu, Thetae); // Planck function - *aI = *jI / Bnuinv; - *aQ = *jQ / Bnuinv; - *aU = *jU / Bnuinv; - *aV = *jV / Bnuinv; + // Faster than calling Symphony code since we know jS, Bnu + double Bnuinv = Bnu_inv(nu, paramsM.theta_e); // Planck function + if (Bnuinv > 0) { + *aI = *jI / Bnuinv; + *aQ = *jQ / Bnuinv; + *aU = *jU / Bnuinv; + *aV = *jV / Bnuinv; + } else { + *aI = 0.; + *aQ = 0.; + *aU = 0.; + *aV = 0.; + } } else { - *aI = alpha_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_I, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); - *aQ = -alpha_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_Q, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); - *aU = -alpha_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_U, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); - *aV = alpha_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_V, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); + *aI = alpha_nu_fit(¶msM, paramsM.STOKES_I) * nu; + // Note Bremss emission is available for thermal dists *only* + *aQ = -alpha_nu_fit(¶msM, paramsM.STOKES_Q) * nu; + *aU = -alpha_nu_fit(¶msM, paramsM.STOKES_U) * nu; + *aV = alpha_nu_fit(¶msM, paramsM.STOKES_V) * nu; // Check basic relationships - if (*aI < 0) { - fprintf(stderr, "Negative total absorptivity! Exiting!\n"); - exit(-1); - } double aP = sqrt(*aQ * *aQ + *aU * *aU + *aV * *aV); - if (*aI < aP) { + if (*aI < aP/max_pol_frac_a) { // Transport does not like 100% polarization... - double pol_frac_a = *aI / aP - 0.01; - //double pol_frac_a = 0.9; + double pol_frac_a = *aI / aP * max_pol_frac_a; *aQ *= pol_frac_a; *aU *= pol_frac_a; *aV *= pol_frac_a; -#if DEBUG - fprintf(stderr, "Polarized absorptivities too large:\n %g vs %g, corrected by %g\n", - aP, *aI, pol_frac_a); -#endif } - - // Make invariant - *aI *= nu; - *aQ *= nu; - *aU *= nu; - *aV *= nu; } - } - // ROTATIVITIES - // Fill them by defualt since we will need rV in any case - if (dist == ROT_PIECEWISE) { // Old piecewise distribution, for compatibility - piecewise_rho_fit(Ne, nu, Thetae, B, theta, rQ, rU, rV); - } else if (dist == ROT_OLD) { // Old incorrect distribution, for compatibility - old_rho_fit(Ne, nu, Thetae, B, theta, rQ, rU, rV); - } else if (dist == E_DEXTER_THERMAL) { // Dexter rQ, Shcherbakov rV - // TODO All-Dexter default option w/Taylor series, make this compat - shcherbakov_rho_fit(Ne, nu, Thetae, B, theta, rQ, rU, rV); - } else { // TODO Fix Symphony, these are currently equal to ROT_OLD - *rQ = rho_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_Q, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); - *rU = rho_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_U, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); - *rV = rho_nu_fit(nu, B, Ne, theta, fit, paramsM.STOKES_V, Thetae, powerlaw_p, gamma_min, gamma_max, gamma_cut, kappa, kappa_width); - } - // Make invariant - *rQ *= nu; - *rU *= nu; - *rV *= nu; - - // *Then* handle field lines, to leave rV intact - if (theta <= 0 || theta >= M_PI) { - *rQ = 0; - *rU = 0; + // ROTATIVITIES + paramsM.dexter_fit = 0; // Don't use the Dexter rhoV, as it's unstable at low temperature + *rQ = rho_nu_fit(¶msM, paramsM.STOKES_Q) * nu; + *rU = rho_nu_fit(¶msM, paramsM.STOKES_U) * nu; + *rV = rho_nu_fit(¶msM, paramsM.STOKES_V) * nu; } #if DEBUG - // Spot check for NaN coefficients - if (isnan(*rV) || *rV > 1.e100 || *rV < -1.e100) { - fprintf(stderr, "\nNAN RV! rV = %e nu = %e Ne = %e Thetae = %e\n", *rV, nu, Ne, Thetae); - } - if (isnan(*jV) || *jV > 1.e100 || *jV < -1.e100) { - fprintf(stderr, "\nNAN jV! jV = %e nu = %e Ne = %e Thetae = %e B = %e theta = %e\n", *jV, nu, Ne, Thetae, B, theta); + // Check for NaN coefficients + if (isnan(*jI) || *jI > RAD_OVERFLOW || *jI < -RAD_OVERFLOW || + isnan(*jQ) || *jQ > RAD_OVERFLOW || *jQ < -RAD_OVERFLOW || + isnan(*jU) || *jU > RAD_OVERFLOW || *jU < -RAD_OVERFLOW || + isnan(*jV) || *jV > RAD_OVERFLOW || *jV < -RAD_OVERFLOW || + isnan(*aI) || *aI > RAD_OVERFLOW || *aI < -RAD_OVERFLOW || + isnan(*aQ) || *aQ > RAD_OVERFLOW || *aQ < -RAD_OVERFLOW || + isnan(*aU) || *aU > RAD_OVERFLOW || *aU < -RAD_OVERFLOW || + isnan(*aV) || *aV > RAD_OVERFLOW || *aV < -RAD_OVERFLOW || + isnan(*rQ) || *rQ > RAD_OVERFLOW || *rQ < -RAD_OVERFLOW || + isnan(*rU) || *rU > RAD_OVERFLOW || *rU < -RAD_OVERFLOW || + isnan(*rV) || *rV > RAD_OVERFLOW || *rV < -RAD_OVERFLOW) { +#pragma omp critical + { + fprintf(stderr, "\nNAN in emissivities!\n"); + fprintf(stderr, "j = %g %g %g %g alpha = %g %g %g %g rho = %g %g %g\n", *jI, *jQ, *jU, *jV, *aI, *aQ, *aU, *aV, *rQ, *rU, *rV); + fprintf(stderr, "nu = %g Ne = %g Thetae = %g B = %g theta = %g kappa = %g kappa_width = %g\n", + paramsM.nu, paramsM.electron_density, paramsM.theta_e, paramsM.magnetic_field, paramsM.observer_angle, + paramsM.kappa, paramsM.kappa_width); + // Powerlaw? + exit(-1); + } } #endif } +// SUPPORTING FUNCTIONS + /* - * emissivity functions and functions used for Faraday conversion and rotation - * from J. Dexter PhD thesis (checked with Leung harmony program, and Huang & Shcherbakov 2011 - * Also see Dexter 2016 Appendix A1 + * Get values for the powerlaw distribution: + * power & cutoffs from parameters, plus number density of nonthermals */ -void dexter_j_fit_thermal(double Ne, double nu, double Thetae, double B, double theta, - double *jI, double *jQ, double *jU, double *jV) -{ - // Synchrotron emissivity - double nus = 3.0 * EE * B * sin(theta) / 4.0 / M_PI / ME / CL * Thetae * Thetae + 1.0; - double x = nu / nus; - - *jI = Ne * EE * EE * nu / 2. / sqrt(3) / CL / Thetae / Thetae * I_I(x); // [g/s^2/cm = ergs/s/cm^3] - *jQ = Ne * EE * EE * nu / 2. / sqrt(3) / CL / Thetae / Thetae * I_Q(x); - *jU = 0.0; // convention; depends on tetrad - *jV = 2. * Ne * EE * EE * nu / tan(theta) / 3. / sqrt(3) / CL / Thetae / Thetae / Thetae * I_V(x); -} - -void shcherbakov_rho_fit(double Ne, double nu, double Thetae, double B, double theta, - double *rQ, double *rU, double *rV) +void get_model_powerlaw_vals(double X[NDIM], double *p, double *n, + double *gamma_min, double *gamma_max, double *gamma_cut) { - double Thetaer = 1. / Thetae; - - double omega0 = EE * B / ME / CL; - double wp2 = 4. * M_PI * Ne * EE * EE / ME; - - // Faraday rotativities for thermal plasma - double Xe = Thetae * sqrt(sqrt(2) * sin(theta) * (1.e3 * omega0 / 2. / M_PI / nu)); - - // These are the Dexter (2016) fit actually - *rQ = 2. * M_PI * nu / 2. / CL * wp2 * omega0 * omega0 / pow(2 * M_PI * nu, 4) * - jffunc(Xe) * (gsl_sf_bessel_Kn(1, Thetaer) / gsl_sf_bessel_Kn(2, Thetaer) + - 6. * Thetae) * sin(theta) * sin(theta); - *rU = 0.0; - - // Shcherbakov fit for rV. Possibly questionable at very low frequency - // Note the real bessel functions. Slow? - *rV = 2.0 * M_PI * nu / CL * wp2 * omega0 / pow(2. * M_PI * nu, 3) * - gsl_sf_bessel_Kn(0, Thetaer) / (gsl_sf_bessel_Kn(2, Thetaer)+SMALL) * g(Xe) * cos(theta); -} - -void piecewise_rho_fit(double Ne, double nu, double Thetae, double B, double theta, - double *rQ, double *rU, double *rV) -{ - double Thetaer = 1. / Thetae; - - double omega0 = EE * B / ME / CL; - double wp2 = 4. * M_PI * Ne * EE * EE / ME; - - // Faraday rotativities for thermal plasma - double Xe = Thetae * sqrt(sqrt(2) * sin(theta) * (1.e3 * omega0 / 2. / M_PI / nu)); - - // Approximate bessel functions to match rhoq,v with grtrans - *rQ = 2. * M_PI * nu / 2. / CL * wp2 * omega0 * omega0 / pow(2 * M_PI * nu, 4) * - jffunc(Xe) * (besselk_asym(1, Thetaer) / besselk_asym(2, Thetaer) + - 6. * Thetae) * sin(theta) * sin(theta); - *rU = 0.0; - // Switch between three different fits for rho_V - if (Thetae > 3.0) { - // High temperature: use approximations to bessel - *rV = 2.0 * M_PI * nu / CL * wp2 * omega0 / pow(2. * M_PI * nu, 3) * - (besselk_asym(0, Thetaer) - Je(Xe)) / besselk_asym(2, Thetaer) * cos(theta); - } else if (0.2 < Thetae && Thetae <= 3.0) { - // Mid temperature: use real bessel functions (TODO fit?) - *rV = 2.0 * M_PI * nu / CL * wp2 * omega0 / pow(2. * M_PI * nu, 3) * - (gsl_sf_bessel_Kn(0, Thetaer) - Je(Xe)) / gsl_sf_bessel_Kn(2, Thetaer) * cos(theta); - } else if (Thetae <= 0.2) { - // Use the constant low-temperature limit - *rV = 2.0 * M_PI * nu / CL * wp2 * omega0 / pow(2. * M_PI * nu, 3) * cos(theta); - } -} - -void old_rho_fit(double Ne, double nu, double Thetae, double B, double theta, - double *rQ, double *rU, double *rV) -{ - double Thetaer = 1. / Thetae; - - double omega0 = EE * B / ME / CL; - double wp2 = 4. * M_PI * Ne * EE * EE / ME; - - // Faraday rotativities for thermal plasma - double Xe = Thetae * sqrt(sqrt(2) * sin(theta) * (1.e3 * omega0 / 2. / M_PI / nu)); - - // Approximate bessel functions to match rhoq,v with grtrans - *rQ = 2. * M_PI * nu / 2. / CL * wp2 * omega0 * omega0 / pow(2 * M_PI * nu, 4) * - jffunc(Xe) * (besselk_asym(1, Thetaer) / besselk_asym(2, Thetaer) + - 6. * Thetae) * sin(theta) * sin(theta); - *rU = 0.0; - // Use approximations to Bessel fns for all space. Dangerous at low temp! - *rV = 2.0 * M_PI * nu / CL * wp2 * omega0 / pow(2. * M_PI * nu, 3) * - (besselk_asym(0, Thetaer) - Je(Xe)) / besselk_asym(2, Thetaer) * cos(theta); -} - -double g(double Xe) -{ - return 1. - 0.11 * log(1 + 0.035 * Xe); -} - - -double h(double Xe) -{ - return 2.011 * exp(-pow(Xe, 1.035) / 4.7) - - cos(Xe * 0.5) * exp(-pow(Xe, 1.2) / 2.73) - - 0.011 * exp(-Xe / 47.2); -} - -double Je(double Xe) -{ - return 0.43793091 * log(1. + 0.00185777 * pow(Xe, 1.50316886)); -} - -double jffunc(double Xe) -{ - double extraterm = - (0.011 * exp(-Xe / 47.2) - - pow(2., -1. / 3.) / pow(3., - 23. / 6.) * M_PI * 1e4 * pow(Xe + 1e-16, - -8. / 3.)) * - (0.5 + 0.5 * tanh((log(Xe) - log(120.)) / 0.1)); - - return 2.011 * exp(-pow(Xe, 1.035) / 4.7) - - cos(Xe * 0.5) * exp(-pow(Xe, 1.2) / 2.73) - - 0.011 * exp(-Xe / 47.2) + extraterm; -} - -double I_I(double x) -{ - return 2.5651 * (1 + 1.92 * pow(x, -1. / 3.) + - 0.9977 * pow(x, -2. / 3.)) * exp(-1.8899 * pow(x, - 1. / - 3.)); -} - -double I_Q(double x) -{ - return 2.5651 * (1 + 0.93193 * pow(x, -1. / 3.) + - 0.499873 * pow(x, -2. / 3.)) * exp(-1.8899 * pow(x, - 1. / - 3.)); -} - -double I_V(double x) -{ - return (1.81348 / x + 3.42319 * pow(x, -2. / 3.) + - 0.0292545 * pow(x, -0.5) + 2.03773 * pow(x, - -1. / 3.)) * - exp(-1.8899 * pow(x, 1. / 3.)); + *gamma_min = powerlaw_gamma_min; + *gamma_max = powerlaw_gamma_max; + *gamma_cut = powerlaw_gamma_cut; + *p = powerlaw_p; + + double b = get_model_b(X); + double u_nth = powerlaw_eta*b*b/2; + // Number density of nonthermals + *n = u_nth * (*p - 2)/(*p - 1) * 1/(ME * CL*CL * *gamma_min); } -double besselk_asym(int n, double x) -{ - - if (n == 0) - return -log(x / 2.) - 0.5772; - - if (n == 1) - return 1. / x; - - if (n == 2) - return 2. / x / x; - - fprintf(stderr,"this cannot happen\n"); - exit(1); -} - -// UNPOLARIZED VERSIONS - /* - * get the invariant emissivity and opacity at a given position for a given wavevector + * Get values kappa, kappa_width for the kappa distribution emissivities + * Optionally variable over the domain based on sigma and beta */ -void get_jkinv(double X[NDIM], double Kcon[NDIM], double *jnuinv, double *knuinv, Params *params) -{ - if (params->emission_type == E_CUSTOM) { - get_model_jk(X, Kcon, jnuinv, knuinv); - } else { - double nu, theta, B, Thetae, Ne, Bnuinv; - double Ucov[NDIM], Ucon[NDIM], Bcon[NDIM], Bcov[NDIM]; - - /* get fluid parameters */ - Ne = get_model_ne(X); /* check to see if we're outside fluid model */ - if (Ne == 0.) { - *jnuinv = 0.; - *knuinv = 0.; - return; - } - - /* get covariant four-velocity of fluid for use in get_bk_angle and get_fluid_nu */ - get_model_fourv(X, Kcon, Ucon, Ucov, Bcon, Bcov); - theta = get_bk_angle(X, Kcon, Ucov, Bcon, Bcov); /* angle between k & b */ - // No emission along field - if (theta <= 0. || theta >= M_PI) { /* no emission along field */ - *jnuinv = 0.; - *knuinv = 0.; - return; - } - - // Only compute these if we must - B = get_model_b(X); /* field in G */ - Thetae = get_model_thetae(X); /* temp in e rest-mass units */ - nu = get_fluid_nu(Kcon, Ucov); /* freq in Hz */ +void get_model_kappa(double X[NDIM], double *kappa, double *kappa_width) { - /* assume emission is thermal */ - Bnuinv = Bnu_inv(nu, Thetae); - *jnuinv = jnu_inv(nu, Thetae, Ne, B, theta); + if (variable_kappa) { + double sigma = get_model_sigma(X); + double beta = get_model_beta(X); - if (Bnuinv < SMALL) - *knuinv = SMALL; - else - *knuinv = *jnuinv / Bnuinv; + *kappa = 2.8 + 0.7/sqrt(sigma) + 3.7 * (1.0/pow(sigma, 0.19)) * tanh(23.4 * pow(sigma, 0.26) * beta); -#if DEBUG - if (isnan(*jnuinv) || isnan(*knuinv)) { - fprintf(stderr, "\nisnan get_jkinv\n"); - fprintf(stderr, ">> %g %g %g %g %g %g %g %g\n", *jnuinv, *knuinv, - Ne, theta, nu, B, Thetae, Bnuinv); + if (isnan(*kappa)) { + double B = get_model_b(X); + fprintf(stderr, "kappa, sigma, beta, B: %g %g %g %g \n", *kappa, sigma, beta, B); } -#endif + // Lower limit for kappa. Upper limit switches away from kappa fit entirely -> thermal + if (*kappa < variable_kappa_min) *kappa = variable_kappa_min; - if (params->isolate_counterjet == 1) { // Emission from X[2] > midplane only - if (X[2] < (cstopx[2] - cstartx[2]) / 2) { - *jnuinv = 0.; - } - } else if (params->isolate_counterjet == 2) { // Emission from X[2] < midplane only - if (X[2] > (cstopx[2] - cstartx[2]) / 2) { - *jnuinv = 0.; - } - } + //fprintf(stderr, "sigma, beta -> kappa %g %g -> %g\n", sigma, beta, *kappa); + } else { + *kappa = model_kappa; } -} - -/* - * thermal synchrotron emissivity - * - * Interpolates between Petrosian limit and - * classical thermal synchrotron limit - * Good for Thetae >~ 1 - * See Leung+ 2011, restated Pandya+ 2016 - */ -double jnu_synch(double nu, double Ne, double Thetae, double B, double theta) -{ - double K2,nuc,nus,x,f,j,sth ; - - //K2 = gsl_sf_bessel_Kn(2,1./Thetae) ; - K2 = 2.*Thetae*Thetae ; - - nuc = EE*B/(2.*M_PI*ME*CL) ; - sth = sin(theta) ; - nus = (2./9.)*nuc*Thetae*Thetae*sth ; - if(nu > 1.e12*nus) return(0.) ; - x = nu/nus ; - f = pow( pow(x,1./2.) + pow(2.,11./12.)*pow(x,1./6.), 2 ) ; - j = (sqrt(2.)*M_PI*EE*EE*Ne*nus/(3.*CL*K2)) * f * exp(-pow(x,1./3.)) ; - return(j) ; + double Thetae = get_model_thetae(X); + *kappa_width = (*kappa - 3.) / *kappa * Thetae; +#if DEBUG + if (isnan(*kappa_width) || isnan(*kappa)) { + fprintf(stderr, "NaN kappa val! kappa, kappa_width, Thetae: %g %g %g\n", *kappa, *kappa_width, Thetae); + } +#endif } - diff --git a/src/model_radiation.h b/src/model_radiation.h index a72bee8..c6aee22 100644 --- a/src/model_radiation.h +++ b/src/model_radiation.h @@ -11,12 +11,14 @@ #include "decs.h" #include "par.h" +void try_set_radiation_parameter(const char *word, const char *value); + /* transfer coefficients in tetrad frame */ -void jar_calc(double X[NDIM], double Kcon[NDIM], double *jI, double *jQ, - double *jU, double *jV, double *aI, double *aQ, double *aU, - double *aV, double *rQ, double *rU, double *rV, Params *params); +void jar_calc(double X[NDIM], double Kcon[NDIM], + double *jI, double *jQ, double *jU, double *jV, + double *aI, double *aQ, double *aU, double *aV, + double *rQ, double *rU, double *rV, Params *params); -double jnu_synch(double nu, double Ne, double Thetae, double B, double theta); -void get_jkinv(double X[NDIM], double Kcon[NDIM], double *jnuinv, double *knuinv, Params *params); +void get_jkinv(double X[NDIM], double Kcon[NDIM], double *jI, double *aI, Params *params); #endif /* MODEL_RADIATION_H */ diff --git a/src/model_tetrads.c b/src/model_tetrads.c index 9045df1..a49010a 100644 --- a/src/model_tetrads.c +++ b/src/model_tetrads.c @@ -36,32 +36,51 @@ int make_plasma_tetrad(double Ucon[NDIM], double Kcon[NDIM], double Bcon[NDIM], double Gcov[NDIM][NDIM], double Econ[NDIM][NDIM], double Ecov[NDIM][NDIM]) { - // Modified Gram-Schmidt to produce e^n orthogonal + // Modified Gram-Schmidt method to produce e^n orthogonal + // e^1 is wholly determined by orthonormality + double ones[NDIM] = {1., 1., 1., 1.}; set_Econ_from_trial(Econ[0], 0, Ucon); - normalize(Econ[0], Gcov); + set_Econ_from_trial(Econ[1], 3, ones); + set_Econ_from_trial(Econ[2], 2, Bcon); set_Econ_from_trial(Econ[3], 3, Kcon); + + // (Re)orthogonalize + // Repeating the orthogonalizations below wouldn't be necessary in + // exact math, of course, but what if we have numbers on the order + // of machine accuracy (epsilon)? Consider a matrix A: + // A = [2 1] + // [d 0] + // [0 d] + // where d is O(sqrt(epsilon)), so that generally N + d^2 = N when + // represented in double precision. Say we want an orthogonal form of A, + // the matrix Q. + // Orthonormalized with Gram-Schmidt and throwing away d^2: + // Q2= [1 0 ] + // [d/2 -1/sqrt(5)] + // [0 2/sqrt(5)] + // But if we orthogonalize Q2 again, we get: + // Q = [1 d/(2 sqrt(5))] + // [d/2 -1/sqrt(5) ] + // [0 2/sqrt(5) ] + // While there are more complex & faster reorthogonalization methods, simply + // repeating the orthogonalization exactly once for each vector reaches near + // optimal accuracy, see Giraud and Langou 2003 for background and + // Giraud et al. '05 for the relevant analysis + + // Note we choose the order carefully to preserve ucon == e^0 perfectly, + // and u^3 ~= Kcon as closely as possible. + normalize(Econ[0], Gcov); + project_out(Econ[3], Econ[0], Gcov); project_out(Econ[3], Econ[0], Gcov); normalize(Econ[3], Gcov); - set_Econ_from_trial(Econ[2], 2, Bcon); + project_out(Econ[2], Econ[0], Gcov); + project_out(Econ[2], Econ[3], Gcov); project_out(Econ[2], Econ[0], Gcov); project_out(Econ[2], Econ[3], Gcov); normalize(Econ[2], Gcov); - // whatever is left is econ1 - for (int k = 0; k < 4; k++) /* trial vector */ - Econ[1][k] = 1.; project_out(Econ[1], Econ[0], Gcov); project_out(Econ[1], Econ[2], Gcov); project_out(Econ[1], Econ[3], Gcov); - normalize(Econ[1], Gcov); - - // Reorthogonalize. I swear this is a real thing - // TODO may not require renormalization - normalize(Econ[0], Gcov); - project_out(Econ[3], Econ[0], Gcov); - normalize(Econ[3], Gcov); - project_out(Econ[2], Econ[0], Gcov); - project_out(Econ[2], Econ[3], Gcov); - normalize(Econ[2], Gcov); project_out(Econ[1], Econ[0], Gcov); project_out(Econ[1], Econ[2], Gcov); project_out(Econ[1], Econ[3], Gcov); diff --git a/src/par.c b/src/par.c index 6d1c71f..a953e58 100644 --- a/src/par.c +++ b/src/par.c @@ -2,6 +2,7 @@ #include "par.h" #include "decs.h" #include "model.h" +#include "model_radiation.h" #include #include @@ -188,6 +189,9 @@ void try_set_parameter(const char *word, const char *value, Params *params) { // Let models add/parse their own parameters we don't understand try_set_model_parameter(word, value); + + // Let radiation model load its own parameters + try_set_radiation_parameter(word, value); } // sets default values for elements of params (if desired) and loads from par file 'fname' diff --git a/src/radiation.c b/src/radiation.c index ade8238..309e025 100644 --- a/src/radiation.c +++ b/src/radiation.c @@ -11,7 +11,7 @@ radiation-related utilities. #include "decs.h" -double Bnu_inv (double nu, double Thetae) +double Bnu_inv(double nu, double Thetae) { double x = HPL * nu / (ME * CL * CL * Thetae); @@ -19,14 +19,7 @@ double Bnu_inv (double nu, double Thetae) return ((2. * HPL / (CL * CL)) / (x / 24. * (24. + x * (12. + x * (4. + x))))); else - return ((2. * HPL / (CL * CL)) / (exp (x) - 1.)); -} - -/* get jnu, and convert to jnu/nu^2 = jnu_invariant */ -double jnu_inv(double nu, double Thetae, double Ne, double B, double theta) -{ - double j = jnu_synch(nu, Ne, Thetae, B, theta); - return (j / (nu * nu)); + return ((2. * HPL / (CL * CL)) / (exp(x) - 1.)); } /* get frequency in fluid frame, in Hz */ @@ -39,19 +32,13 @@ double get_fluid_nu(double Kcon[NDIM], double Ucov[NDIM]) Kcon[2] * Ucov[2] + Kcon[3] * Ucov[3]) * ME * CL * CL / HPL; - if (nu < 0.) { - nu = 1.; #if DEBUG - fprintf(stderr, "Fluid nu < 0: %g !", nu); -#endif - } - - if (isnan(nu)) { - fprintf(stderr, "isnan get_fluid_nu, K: %g %g %g %g\n", - Kcon[0], Kcon[1], Kcon[2], Kcon[3]); - fprintf(stderr, "isnan get_fluid_nu, U: %g %g %g %g\n", - Ucov[0], Ucov[1], Ucov[2], Ucov[3]); + if (isnan(nu) || nu < 0) { + fprintf(stderr, "isnan/isnegative get_fluid_nu!\n"); + fprintf(stderr, "K: %g %g %g %g\n", Kcon[0], Kcon[1], Kcon[2], Kcon[3]); + fprintf(stderr, "U: %g %g %g %g\n", Ucov[0], Ucov[1], Ucov[2], Ucov[3]); } +#endif return (nu); } @@ -61,24 +48,25 @@ double get_bk_angle(double X[NDIM], double Kcon[NDIM], double Ucov[NDIM], double { double B, k, mu; - B = sqrt(fabs - (Bcon[0] * Bcov[0] + Bcon[1] * Bcov[1] + Bcon[2] * Bcov[2] + - Bcon[3] * Bcov[3])); + B = sqrt(fabs(Bcon[0]*Bcov[0] + Bcon[1]*Bcov[1] + Bcon[2]*Bcov[2] + Bcon[3]*Bcov[3])); if (B == 0.) return (M_PI / 2.); - k = fabs(Kcon[0] * Ucov[0] + Kcon[1] * Ucov[1] + Kcon[2] * Ucov[2] + - Kcon[3] * Ucov[3]); + k = fabs(Kcon[0]*Ucov[0] + Kcon[1]*Ucov[1] + Kcon[2]*Ucov[2] + Kcon[3] * Ucov[3]); - mu = (Kcon[0] * Bcov[0] + Kcon[1] * Bcov[1] + Kcon[2] * Bcov[2] + - Kcon[3] * Bcov[3]) / (k * B); + mu = (Kcon[0]*Bcov[0] + Kcon[1]*Bcov[1] + Kcon[2]*Bcov[2] + Kcon[3]*Bcov[3]) / (k * B); if (fabs(mu) > 1.) mu /= fabs(mu); - if (isnan(mu)) +#if DEBUG + if (isnan(mu)) { fprintf(stderr, "isnan get_bk_angle\n"); + fprintf(stderr, "B = %g, k = %g, mu = %g\n", B, k, mu); + fprintf(stderr, "Ucov: %g %g %g %g\n", Ucov[0], Ucov[1], Ucov[2], Ucov[3]); + } +#endif return (acos(mu)); } diff --git a/src/simcoords.c b/src/simcoords.c new file mode 100644 index 0000000..821339b --- /dev/null +++ b/src/simcoords.c @@ -0,0 +1,393 @@ +#include "simcoords.h" +#include "decs.h" +#include "coordinates.h" +#include "model_params.h" +#include "koral_coords.h" +#include "debug_tools.h" +#include "hdf5_utils.h" + +#include +#include + +// switches +int use_simcoords = 0; +int simcoords = 0; + +// global metric defintiions +metric_params_koral_mks3 mp_koral_mks3 = { 0 }; +metric_params_koral_jetcoords mp_koral_jetcoords = { 0 }; + +// invisible "members" +static double *simcoords_x1 = NULL; +static double *simcoords_x2 = NULL; +static double *simcoords_gdet = NULL; +static size_t sc_n1 = 1; +static size_t sc_n2 = 1; +static double er0 = 0; +static double h0 = 0; +static double der = 1.e-5; +static double dh = 1.e-5; +static double x1i_oob = 1.e10; + +static double *ks_r = NULL; +static double *ks_h = NULL; +static size_t n1 = 1; +static size_t n2 = 1; +static double startx1, startx2, dx1, dx2; +double minks_r, maxks_r; + +// coordinate function forward declarations +static int rev_saved_rh(double *eKS, double *gridcoord); +static int rev_KORAL_MKS3(double *xKS, double *xMKS); + +#define ij2oned(i,j) ((size_t)(j+sc_n2*(i))) + +// interface functions +void load_simcoord_info_from_file(const char *fname) +{ + if ( hdf5_open(fname) < 0 ) { + fprintf(stderr, "! unable to open file %s. exiting!\n", fname); + exit(-1); + } + + size_t n3; + hdf5_set_directory("/header/"); + hdf5_read_single_val(&n1, "n1", H5T_STD_I32LE); + hdf5_read_single_val(&n2, "n2", H5T_STD_I32LE); + hdf5_read_single_val(&n3, "n3", H5T_STD_I32LE); + + hdf5_set_directory("/header/geom/"); + hdf5_read_single_val(&startx1, "startx1", H5T_IEEE_F64LE); + hdf5_read_single_val(&startx2, "startx2", H5T_IEEE_F64LE); + hdf5_read_single_val(&dx1, "dx1", H5T_IEEE_F64LE); + hdf5_read_single_val(&dx2, "dx2", H5T_IEEE_F64LE); + + ks_r = calloc(n1*n2, sizeof(*ks_r)); + ks_h = calloc(n1*n2, sizeof(*ks_h)); + simcoords_gdet = calloc(n1*n2, sizeof(*simcoords_gdet)); + + hdf5_set_directory("/grid_out/"); + + hsize_t fdims[] = { n1, n2, n3 }; + hsize_t fstart[] = { 0, 0, 0 }; + hsize_t fcount[] = { n1, n2, 1 }; + hsize_t mdims[] = { n1, n2, 1 }; + hsize_t mstart[] = { 0, 0, 0 }; + + hdf5_read_array(ks_r, "r", 3, fdims, fstart, fcount, mdims, mstart, H5T_IEEE_F64LE); + hdf5_read_array(ks_h, "th", 3, fdims, fstart, fcount, mdims, mstart, H5T_IEEE_F64LE); + + hdf5_set_directory("/grid_run/"); + + hdf5_read_array(simcoords_gdet, "gdet", 3, fdims, fstart, fcount, mdims, mstart, H5T_IEEE_F64LE); + + minks_r = ks_r[0]; + maxks_r = ks_r[0]; + for (int i=0; i maxks_r) { + maxks_r = tr; + } + + } + } + + hdf5_close(); +} + +void initialize_simgrid(size_t n1, size_t n2, double x1i, double x1f, double x2i, double x2f) +{ + if (use_simcoords == 0) return; + + // if we're using simgrid, the tracing should be done in eKS. verify here. + assert(metric == METRIC_MKS && hslope==1.); + + if (ks_r == NULL) { + fprintf(stderr, "! must call load_ks_rh_from_file(...) before initialize_simgrid(...)\n"); + exit(-7); + } + + sc_n1 = n1; + sc_n2 = n2; + + simcoords_x1 = calloc(sc_n1*sc_n2, sizeof(*simcoords_x1)); + simcoords_x2 = calloc(sc_n1*sc_n2, sizeof(*simcoords_x2)); + + // note we've made the assumption that x1i,x2i gives the left edge of the grid and + // x1f,x2f gives the right edge. this means the range is (n1+1)*dx1,(n2+1)*dx2, so + // that we cover the full domain. if we have an oob error when trying to determine + // the ii,jj for interpolating, we return x1f+1, so x1f+1 must not be a valid grid + // coordinate in the fluid model! + double Rin = minks_r; // 1.05 * (1. + sqrt(1. - a*a)); + double Rout = maxks_r; + double h_min = 0.; + double h_max = M_PI; + + // set limit for tracking geodesic emission + rmax_geo = fmin(rmax_geo, Rout); + rmin_geo = fmax(rmin_geo, Rin); + + fprintf(stderr, "Rin Rmax %g %g %g %g %g\n", rmin_geo, rmax_geo, Rin, Rout, 1. + sqrt(1.-a*a)); + + x1i_oob = x1f + 1.; + er0 = log(Rin); + der = (log(Rout) - log(Rin)) / sc_n1; + h0 = h_min; + dh = (h_max - h_min) / sc_n2; + + // coordinate system is MKS, so set reasonable values here + cstartx[0] = 0; + cstartx[1] = log(minks_r); + cstartx[2] = 0; + cstartx[3] = 0; + cstopx[0] = 0; + cstopx[1] = log(Rout); + cstopx[2] = 1.0; + cstopx[3] = 2*M_PI; + +#pragma omp parallel for schedule(dynamic,2) collapse(2) shared(simcoords_x1,simcoords_x2) + for (size_t i=0; i= n1 || j >= n2) return 0; + + return simcoords_gdet[ n2*i + j ]; +} + +int simcoordijk_to_eks(int i, int j, int k, double eKS[NDIM]) +{ + // check bounds + if (i < 0 || j < 0 || i >= n1 || j >= n2) return -1; + + // return the eks for the gridzone at i,j,k + eKS[1] = log( ks_r[ n2*i + j ] ); + eKS[2] = ks_h[ n2*i + j ] / M_PI; + + return 0; +} + +#define SIMCOORD_SMALL 1.e-10 +int simcoord_to_eks(double gridcoord[NDIM], double eKS[NDIM]) +{ + // since the ks coordinates are defined at the zone centers but the + // startx1,2 are the left edge, we need to subtract 0.5 in order to + // resolve the locations properly. WARNING: this function will work + // for coordinates that fall WITHIN the "central" part of the zones + // but is strictly bounded by [startx+dx/2, stopx-dx/2] + + double i = (gridcoord[1] - startx1) / dx1 - 0.5; + size_t ii = (size_t) i; + double di = i - ii; + + double j = (gridcoord[2] - startx2) / dx2 - 0.5; + size_t jj = (size_t) j; + double dj = j - jj; + + // before first zone center + if (ii < 0 || jj < 0) { + return -1; + } + + // handle last zone center carefully to avoid segfaults + if (ii == n1-1) { + if (di < SIMCOORD_SMALL) { + ii -= 1; + di = 1.; + } else { + return -1; + } + } + if (jj == n2-1) { + if (dj < SIMCOORD_SMALL) { + jj -= 1; + dj = 1.; + } else { + return -1; + } + } + + // beyond the right-most zone center + if (ii >= n1 || jj >= n2) { + return -1; + } + + eKS[0] = gridcoord[0]; + + eKS[1] = ks_r[ n2*ii + jj ] * (1.-di)*(1.-dj) + + ks_r[ n2*ii + jj+1 ] * (1.-di)*dj + + ks_r[ n2*(ii+1) + jj ] * di*(1.-dj) + + ks_r[ n2*(ii+1) + jj+1 ] * di*dj; + + eKS[2] = ks_h[ n2*ii + jj ] * (1.-di)*(1.-dj) + + ks_h[ n2*ii + jj+1 ] * (1.-di)*dj + + ks_h[ n2*(ii+1) + jj ] * di*(1.-dj) + + ks_h[ n2*(ii+1) + jj+1 ] * di*dj; + + eKS[3] = gridcoord[3]; + + return 0; +} +#undef SIMCOORD_SMALL + +void eks_to_simcoord(double eKS[NDIM], double gridcoord[NDIM]) +{ + // assume that input is in eKS, so r_ks = exp(x1) and theta_ks = Pi * x2 + + // here, (i,j) is distance from left edge (in units of zones). since the + // grid is defined so that simcoord(0.0) == left_edge, we do not need to + // deal with any stray 0.5 (see the for loop in initialize_simgrid(...)) + double i = (eKS[1] - er0) / der; + size_t ii = (size_t) i; + double di = i - ii; + + double j = (M_PI*eKS[2] - h0) / dh; + size_t jj = (size_t) j; + double dj = j - jj; + + if (ii < 0 || ii >= sc_n1 || jj < 0 || jj >= sc_n2) { + gridcoord[1] = x1i_oob; + return; + } + + gridcoord[0] = eKS[0]; + + gridcoord[1] = simcoords_x1[ ij2oned(ii,jj) ] * (1.-di)*(1.-dj) + + simcoords_x1[ ij2oned(ii,jj+1) ] * (1.-di)*dj + + simcoords_x1[ ij2oned(ii+1,jj) ] * di*(1.-dj) + + simcoords_x1[ ij2oned(ii+1,jj+1) ] * di*dj; + + gridcoord[2] = simcoords_x2[ ij2oned(ii,jj) ] * (1.-di)*(1.-dj) + + simcoords_x2[ ij2oned(ii,jj+1) ] * (1.-di)*dj + + simcoords_x2[ ij2oned(ii+1,jj) ] * di*(1.-dj) + + simcoords_x2[ ij2oned(ii+1,jj+1) ] * di*dj; + + gridcoord[3] = eKS[3]; +} + + +////// +// function definitions for populating the interpolation grid +////// +static int rev_saved_rh(double *eKS, double *gridcoord) +{ + int gridi = -2; + double griddx1 = 0.; + double r = exp(eKS[1]); + + // here we assume x1 is independent of x2 so we can find i first + for (int i=0; i= 0.5) testi += 1; + + int gridj = -2; + double griddx2 = 0.; + double h = eKS[2]; + for (int j=0; j + +// // +// !!!!!!!!!! WARNING !!!!!!!!!! // +// // +// We assume phi = x3, so x3 // +// must span 0 -> 2 Pi ! // +// // + +// supported coordinate systems: +// KORAL_MKS3 (forward and reverse available, implements reverse) +// KORAL_JETCOORDS (only forward available) + +#define SIMCOORDS_KORAL_MKS3 3 +#define SIMCOORDS_KORAL_JETCOORDS 4 + +extern int use_simcoords; +extern int simcoords; + +// interface +void load_simcoord_info_from_file(const char *fname); +void initialize_simgrid(size_t n1, size_t n2, double x1i, double x1f, double x2i, double x2f); +int simcoord_to_eks(double gridcoord[NDIM], double eKS[NDIM]); +int simcoordijk_to_eks(int i, int j, int k, double eKS[NDIM]); +double simcoordijk_to_gdet(int i, int j, int k); +void eks_to_simcoord(double eKS[NDIM], double gridcoord[NDIM]); +void finalize_simgrid(); + +// not particularly neat, but the idea is to "namespace" different +// metric parameters in structs, and then define one global struct +// instance for each coordinate system. +typedef struct metric_params_koral_mks3_struct { + double r0; + double h0; + double my1; + double my2; + double mp0; +} metric_params_koral_mks3; +extern metric_params_koral_mks3 mp_koral_mks3; + +typedef struct metric_params_koral_jetcoords_struct { + double mksr0; + double rbrk; + double rmin; + double rphotomax; + double fjet; + double fdisk; + double runi; + double rcoll_jet; + double rcoll_disk; + double rdecoll_jet; + double rdecoll_disk; + double alpha_1; + double alpha_2; + double cylindrify; + double rcyl; + double ncyl; +} metric_params_koral_jetcoords; +extern metric_params_koral_jetcoords mp_koral_jetcoords; + +#endif // SIMCOORDS_H diff --git a/src/symphony/bremss_fits.c b/src/symphony/bremss_fits.c new file mode 100644 index 0000000..7155232 --- /dev/null +++ b/src/symphony/bremss_fits.c @@ -0,0 +1,339 @@ + + +#include "bremss_fits.h" + +#include "decs.h" + +// For planck_func +#include "maxwell_juettner.h" + +#include +#include +#include + +double gffee(double Te, double nu); +double gffei(double Te, double nu); + +#define THETAE_MIN 1.e-3 + +/* Emissivities */ +double bremss_I(struct parameters *params, int bremss_type) +{ + if (params->theta_e < THETAE_MIN) + return 0.; + + double Thetae = params->theta_e; + double Te = Thetae * ME * CL * CL / KBOL; + double nu = params->nu; + double x = HPL * nu / (KBOL * Te); + double Ne = params->electron_density; + double jv; + + double efac = 0.; + if (x < 1.e-3) { + efac = (24. - 24.*x + 12.*x*x - 4.*x*x*x + x*x*x*x) / 24.; + } else { + efac = exp(-x); + } + + // Bremss options + // 0 No bremsstrahlung + // 1 Rybicki and Lightman eq. 5.14a with eq. 5.25 corrective factor + // 2 Piecewise formula + // 3 van Hoof 2015 + Nozawa 2009 + // Bremss is only supported for thermal electrons + if (bremss_type == 1) { + // Method from Rybicki & Lightman, ultimately from Novikov & Thorne + + double rel = (1. + 4.4e-10*Te); + double gff = 1.2; + + jv = 1./(4.*M_PI)*pow(2,5)*M_PI*pow(EE,6)/(3.*ME*pow(CL,3)); + jv *= pow(2.*M_PI/(3.*KBOL*ME),1./2.); + jv *= pow(Te,-1./2.)*Ne*Ne; + jv *= efac*rel*gff; + + } else if (bremss_type == 2) { + // Svensson 1982 as used in e.g. Straub 2012 + double Fei=0., Fee=0., fei=0., fee=0.; + + double SOMMERFELD_ALPHA = 1. / 137.036; + double eta = 0.5616; + double gammaE = 0.577; // = - Log[0.5616] + double gff; + + if (x > 1) { + gff = sqrt(3. / M_PI / x); + } else { + gff = sqrt(3.) / M_PI * log(4 / gammaE / x); + } + + if (Thetae < 1) { + Fei = 4. * sqrt(2.*Thetae/M_PI/M_PI/M_PI) * (1. + 1.781*pow(Thetae,1.34)); + Fee = 20./9./sqrt(M_PI) * (44. - 3.*M_PI*M_PI) * pow(Thetae,1.5); + Fee *= (1. + 1.1*Thetae + Thetae*Thetae - 1.25*pow(Thetae,2.5)); + } else { + Fei = 9.*Thetae/(2.*M_PI) * ( log(1.123 * Thetae + 0.48) + 1.5 ); + Fee = 24. * Thetae * ( log(2.*eta*Thetae) + 1.28 ); + } + + fei = Ne * Ne * SIGMA_THOMSON * SOMMERFELD_ALPHA * ME * CL * CL * CL * Fei; + fee = Ne * Ne * RE * RE * SOMMERFELD_ALPHA * ME * CL * CL * CL * Fee; + + jv = (fei+fee) / (4.*M_PI) * HPL/KBOL/Te * efac * gff; + + } else if (bremss_type == 3) { + // Nozawa 2009 - electron-electron + jv = 7.673889101895528e-44 * Ne * Ne * efac * gffee(Te, nu) * sqrt(Thetae); + // van Hoof 2015 - electron-ion + jv += 7.070090102391322e-44 * Ne * Ne * efac * gffei(Te, nu) / sqrt(Thetae); + } + + return jv; +} + +/* Absorptivities */ +double bremss_I_abs(struct parameters *params, int bremss_type) +{ + double ans = bremss_I(params, bremss_type) / planck_func(params); + return ans; +} + +// Below are bremsstrahlung routines that are used *only* if the van Hoof 2015 + Nozawa 2009 formulae (type 3) are used. +gsl_spline2d *bremss_spline; +gsl_interp_accel *bremss_xacc; +gsl_interp_accel *bremss_yacc; + +/* + * Electron-ion bremsstrahlung. + * This code uses data from: + * van Hoof, P.~A.~M., Ferland, G.~J., Williams, R.~J.~R., et al.\ 2015, \mnras, 449, 2112 + * Please cite their work if you use these electron-ion bremsstrahlung formulae with grmonty. + */ + +// Sets the bremss splines as global variables so they can be used to interpolate the Gaunt factor +void init_bremss_spline() +{ + // This requires gff_ei_bremss.dat in ipole's directory in order to function +#if 0 + const size_t nx = 146; + const size_t ny = 81; + double *z = malloc(nx * ny * sizeof(double*)); + const double xa[] = {-16.,-15.8,-15.6,-15.4,-15.2,-15.,-14.8,-14.6,-14.4,-14.2,-14.,-13.8,-13.6,-13.4,-13.2,-13.,-12.8,-12.6,-12.4,-12.2,-12.,-11.8,-11.6,-11.4,-11.2,-11.,-10.8,-10.6,-10.4,-10.2,-10.,-9.8,-9.6,-9.4,-9.2,-9.,-8.8,-8.6,-8.4,-8.2,-8.,-7.8,-7.6,-7.4,-7.2,-7.,-6.8,-6.6,-6.4,-6.2,-6.,-5.8,-5.6,-5.4,-5.2,-5.,-4.8,-4.6,-4.4,-4.2,-4.,-3.8,-3.6,-3.4,-3.2,-3.,-2.8,-2.6,-2.4,-2.2,-2.,-1.8,-1.6,-1.4,-1.2,-1.,-0.8,-0.6,-0.4,-0.2,0.,0.2,0.4,0.6,0.8,1.,1.2,1.4,1.6,1.8,2.,2.2,2.4,2.6,2.8,3.,3.2,3.4,3.6,3.8,4.,4.2,4.4,4.6,4.8,5.,5.2,5.4,5.6,5.8,6.,6.2,6.4,6.6,6.8,7.,7.2,7.4,7.6,7.8,8.,8.2,8.4,8.6,8.8,9.,9.2,9.4,9.6,9.8,10.,10.2,10.4,10.6,10.8,11.,11.2,11.4,11.6,11.8,12.,12.2,12.4,12.6,12.8,13.}; + const double ya[] = {-6.,-5.8,-5.6,-5.4,-5.2,-5.,-4.8,-4.6,-4.4,-4.2,-4.,-3.8,-3.6,-3.4,-3.2,-3.,-2.8,-2.6,-2.4,-2.2,-2.,-1.8,-1.6,-1.4,-1.2,-1.,-0.8,-0.6,-0.4,-0.2,0.,0.2,0.4,0.6,0.8,1.,1.2,1.4,1.6,1.8,2.,2.2,2.4,2.6,2.8,3.,3.2,3.4,3.6,3.8,4.,4.2,4.4,4.6,4.8,5.,5.2,5.4,5.6,5.8,6.,6.2,6.4,6.6,6.8,7.,7.2,7.4,7.6,7.8,8.,8.2,8.4,8.6,8.8,9.,9.2,9.4,9.6,9.8,10.}; + double val; + + // Set spline up for quadratic interpolation between the tabulated VH15 data. + const gsl_interp2d_type *T = gsl_interp2d_bilinear; + +// gsl_spline2d *bremss_spline; +// gsl_interp_accel *bremss_xacc; +// gsl_interp_accel *bremss_yacc; + + + bremss_spline = gsl_spline2d_alloc(T,nx,ny); + bremss_xacc = gsl_interp_accel_alloc(); + bremss_yacc = gsl_interp_accel_alloc(); + + // Data file where VH15 data is stored. Z01 means hydrogen, merged means combines relativistic and non-relativistic results + FILE *myfile; + myfile = fopen("gff_ei_bremss.dat","r"); + if (myfile == NULL) { + fprintf(stderr, "Cannot find spline2d data: gff_ei_bremss.dat! Exiting.\n"); + exit(-1); + } + + // Read file and assign it to spline. + for (int i = 0; i < nx; i++) + { + for (int j = 0; j < ny; j++) + { + if (fscanf(myfile,"%lf",&val) != 1) exit(5); + gsl_spline2d_set(bremss_spline, z, i, j, val); + } + } + + gsl_spline2d_init(bremss_spline,xa,ya,z,nx,ny); +#endif +} + +// Returns the electron-ion Gaunt factor +double gffei(double Te, double nu) +{ + + double gffeival,loggammasq,logu; + + // Make sure we don't go out of the domain. + if ( Te < 1.579e-5 ) Te = 1.579e-5; + else if ( Te > 1.579e+11 ) Te = 1.579e+11; + + loggammasq = log10(157900. / Te); + + if ( nu < (1.e-16) * KBOL * Te / HPL ) nu = (1.e-16) * KBOL * Te / HPL; + else if ( nu > (1.e+13) * KBOL * Te / HPL ) nu = (1.e+13) * KBOL * Te / HPL; + + logu = log10(HPL * nu / KBOL / Te); + + gffeival = gsl_spline2d_eval(bremss_spline,logu,loggammasq,bremss_xacc,bremss_yacc); + return gffeival; + +} + + +/* + * The following code is a very slightly modified version of the one released in: + * Nozawa, S., Takahashi, K., Kohyama, Y., et al.\ 2009, \aap, 499, 661 + * Please cite their work if you use these electron-electron bremsstrahlung formulae with grmonty. + */ + +double funce(double x) +{ + return gsl_sf_expint_Ei(-x); +} + +double gi(double x, double tau) +{ + const double a[121] = {3.15847E0, 2.46819E-2, -2.11118E-2, 1.24009E-2, -5.41633E-3, 1.70070E-3, -3.05111E-4, -1.21721E-4, 1.77611E-4, -2.05480E-5, -3.58754E-5, -2.52430E0, 1.03924E-1, -8.53821E-2, 4.73623E-2, -1.91406E-2, 5.39773E-3, -7.26681E-4, -7.47266E-4, 8.73517E-4, -6.92284E-5, -1.80305E-4, 4.04877E-1, 1.98935E-1, -1.52444E-1, 7.51656E-2, -2.58034E-2, 4.13361E-3, 4.67015E-3, -2.20675E-3, -2.67582E-3, 2.95254E-5, 1.40751E-3, 6.13466E-1, 2.18843E-1, -1.45660E-1, 5.07201E-2, -2.23048E-3, -1.14273E-2, 1.24789E-2, -2.74351E-3, -4.57871E-3, -1.70374E-4, 2.06757E-3, 6.28867E-1, 1.20482E-1, -4.63705E-2, -2.25247E-2, 5.07325E-2, -3.23280E-2, -1.16976E-2, -1.00402E-3, 2.96622E-2, -5.43191E-4, -1.23098E-2, 3.29441E-1, -4.82390E-2, 8.16592E-2, -8.17151E-2, 5.94414E-2, -2.19399E-2, -1.13488E-2, -2.38863E-3, 1.89850E-2, 2.50978E-3, -8.81767E-3, -1.71486E-1, -1.20811E-1, 9.87296E-2, -4.59297E-2, -2.11247E-2, 1.76310E-2, 6.31446E-2, -2.28987E-3, -8.84093E-2, 4.45570E-3, 3.46210E-2, -3.68685E-1, -4.46133E-4, -3.24743E-2, 5.05096E-2, -5.05387E-2, 2.23352E-2, 1.33830E-2, 7.79323E-3, -2.93629E-2, -2.80083E-3, 1.23727E-2, -7.59200E-2, 8.88749E-2, -8.82637E-2, 5.58818E-2, 9.20453E-3, -4.59817E-3, -8.54735E-2, 7.98332E-3, 1.02966E-1, -5.68093E-3, -4.04801E-2, 1.60187E-1, 2.50320E-2, -7.52221E-3, -9.11885E-3, 1.67321E-2, -8.24286E-3, -6.47349E-3, -3.80435E-3, 1.38957E-2, 1.10618E-3, -5.68689E-3, 8.37729E-2, -1.28900E-2, 1.99419E-2, -1.71348E-2, -3.47663E-3, -3.90032E-4, 3.72266E-2, -4.25035E-3, -4.22093E-2, 2.33625E-3, 1.66733E-2}; + + double logth = log10(tau); + double logu = log10(x ); + double jfit = 0.; + + double the = ( logth + 2.65) / 1.35; + double u = ( logu + 1.50) / 2.50; + double uj; + + for (int j = 0; j < 11; j++){ + uj = pow(u, j); + for (int i = 0; i < 11; i++){ + jfit += a[11*j + i] * pow(the, i) * uj; + } + } + + return sqrt(8. / 3. / M_PI) * jfit; + +} + +double gii(double x, double tau) +{ + const double a2[9] = {0.9217, -13.4988, 76.4539, -217.8301, 320.9753, -188.0667, -82.4161, 163.7191, -60.0248}; + const double a1[9] = {-9.3647, 95.9186, -397.0172, 842.9376, -907.3076, 306.8802, 291.2983, -299.0253, 76.3461}; + const double a0[9] = {-37.3698, 380.3659, -1489.8014, 2861.4150, -2326.3704, -691.6118, 2853.7893, -2040.7952, 492.5981}; + const double b1[9] = {-8.6991, 63.3830, -128.8939, -135.0312, 977.5838, -1649.9529, 1258.6812, -404.7461, 27.3354}; + const double b0[9] = {-11.6281, 125.6066, -532.7489, 1142.3873, -1156.8545, 75.0102, 996.8114, -888.1895, 250.1386}; + + double aa2 = 0.; + double aa1 = 0.; + double aa0 = 0.; + double bb1 = 0.; + double bb0 = 0.; + double powtau; + + double Ei = funce(x); + + for (int i = 0; i < 9; i++){ + powtau = pow(tau, i / 8.); + aa2 += a2[i] * powtau; + aa1 += a1[i] * powtau; + aa0 += a0[i] * powtau; + bb1 += b1[i] * powtau; + bb0 += b0[i] * powtau; + } + + return aa2 * x * x + aa1 * x + aa0 - exp(x) * Ei * ( bb1 * x + bb0 ); + +} + +double giii(double x, double tau) +{ + double const a2[9] = {64.7512, -213.8956, 174.1432, 136.5088, -271.4899, 89.3210, 58.2584, -46.0807, 8.7301}; + double const a1[9] = {49.7139, -189.7746, 271.0298, -269.7807, 420.4812, -576.6247, 432.7790, -160.5365, 23.3925}; + double const a0[9] = {52.1633, -257.0313, 446.8161, -293.0585, 0.0000, 77.0474, -23.8718, 0.0000, 0.1997}; + double const b1[9] = {376.4322, -1223.3635, 628.6787, 2237.3946, -3828.8387, 2121.7933, -55.1667, -349.4321, 92.2059}; + double const b0[9] = {-8.5862, 34.1348, -116.3287, 296.5451, -393.4207, 237.5497, -30.6000, -27.6170, 8.8453}; + + double aa2 = 0.; + double aa1 = 0.; + double aa0 = 0.; + double bb1 = 0.; + double bb0 = 0.; + double powtau; + + double Ei = funce(x); + + for (int i = 0; i < 9; i++){ + powtau = pow(tau, i / 8.); + aa2 += a2[i] * powtau; + aa1 += a1[i] * powtau; + aa0 += a0[i] * powtau; + bb1 += b1[i] * powtau; + bb0 += b0[i] * powtau; + } + + return aa2 * x * x + aa1 * x + aa0 - exp(x) * Ei * ( bb1 * x + bb0 ); + +} + +double giv(double x, double tau) +{ + double gam = exp(0.5772156649); + double con1 = 3. / ( 4. * M_PI * sqrt(tau) ); + double a1 = 28./3. + 2. * x + x * x / 2.; + double a2 = 8./3. + (4./3.) * x + x * x; + double a3 = 8./3. - (4./3.) * x + x * x; + + double Ei = funce(x); + double fx = a1 + 2. * a2 * log(2. * tau / gam) - exp(x) * Ei * a3; + + return con1 * fx; + +} + +double fcc(double x, double tau) +{ + double const p[7] = {-5.7752, 46.2097, -160.7280, 305.0070, -329.5420, 191.0770, -46.2718}; + double const q[7] = {30.5586, -248.2177, 874.1964, -1676.9028, 1828.8677, -1068.9366, 260.5656}; + double const r[7] = {-54.3272, 450.9676, -1616.5987, 3148.1061, -3478.3930, 2055.6693, -505.6789}; + double const s[7] = {36.2625, -310.0972, 1138.0531, -2260.8347, 2541.9361, -1525.2058, 380.0852}; + double const t[7] = {-8.4082, 74.7925, -282.9540, 576.3930, -661.9390, 404.2930, -102.2330}; + + double ptau = 0.; + double qtau = 0.; + double rtau = 0.; + double stau = 0.; + double ttau = 0.; + double powtau; + + for (int i = 0; i < 7; i++){ + powtau = pow(tau, i / 6.); + ptau += p[i] * powtau; + qtau += q[i] * powtau; + rtau += r[i] * powtau; + stau += s[i] * powtau; + ttau += t[i] * powtau; + } + + return 1. + ptau * pow(x, 2./8.) + qtau * pow(x, 3./8.) + rtau * pow(x, 4./8.) + stau * pow(x, 5./8.) + ttau * pow(x, 6./8.); + +} + +double gffee(double Te, double nu) +{ + double emass = 510.998902; + double tau = KBOL * Te / ME / CL / CL; + double x = HPL * nu / KBOL / Te; + double gffeeval; + + if ( tau < ( 0.05 / emass ) ) tau = 0.05 / emass; + + if ( x < 1.e-4 ) x = 1.e-4; + else if ( x > 10 ) x = 10.; + + if ( tau >= ( 0.05 / emass ) && tau < ( 1. / emass ) ) gffeeval = gi(x, tau); + else if ( tau > ( 1. / emass ) && tau < ( 300. / emass ) ) gffeeval = gii(x, tau) * fcc(x, tau); + else if ( tau > ( 300. / emass ) && tau < ( 7000. / emass ) ) gffeeval = giii(x, tau); + else if ( tau > ( 7000. / emass ) ) gffeeval = giv(x, tau); + + return gffeeval; + +} diff --git a/src/symphony/bremss_fits.h b/src/symphony/bremss_fits.h new file mode 100644 index 0000000..931da09 --- /dev/null +++ b/src/symphony/bremss_fits.h @@ -0,0 +1,17 @@ +#ifndef SYMPHONY_BREMSS_FITS_H_ +#define SYMPHONY_BREMSS_FITS_H_ +#include "params.h" +#include + +/* Fits */ + +// Initialize necessary cached values +void init_bremss_spline(); + +/* Emissivities */ +double bremss_I(struct parameters * params, int bremss_type); + +/* Absorptivities */ +double bremss_I_abs(struct parameters * params, int bremss_type); + +#endif /* SYMPHONY_MAXWELL_JUETTNER_H_ */ diff --git a/src/symphony/fits.c b/src/symphony/fits.c index 9a60ba2..98ec3a0 100644 --- a/src/symphony/fits.c +++ b/src/symphony/fits.c @@ -1,17 +1,12 @@ #include "fits.h" -#include #include +#include -/*Wrappers for the fitting formulae*/ +/*Wrappers for the fitting formulae, ipole-specific calling convention*/ /*j_nu_fit: wrapper for the emissivity fitting formulae. Takes in the - * same parameters as j_nu(), populates the struct of - * parameters, and passes them to the fitting formulae. The - * fitting formulae for each distribution function are located - * in their corresponding folders, so for example the - * KAPPA_DIST fitting formulae are located in the kappa folder, - * in the file kappa_fits.c + * same parameters as j_nu(), and passes them to the fitting formulae. * *@params: nu, magnetic_field, electron_density, observer_angle, * distribution, polarization, theta_e, power_law_p, @@ -20,75 +15,72 @@ *@returns: the corresponding fitting formula (based on the distribution * function) evaluated for the input parameters. */ -double j_nu_fit(double nu, - double magnetic_field, - double electron_density, - double observer_angle, - int distribution, - int polarization, - double theta_e, - double power_law_p, - double gamma_min, - double gamma_max, - double gamma_cutoff, - double kappa, - double kappa_width) +double j_nu_fit(struct parameters *params, int polarization) { -/*fill the struct with values*/ - struct parameters params; - setConstParams(¶ms); - params.nu = nu; - params.magnetic_field = magnetic_field; - params.observer_angle = observer_angle; - params.electron_density = electron_density; - params.distribution = distribution; - params.polarization = polarization; - params.mode = params.EMISSIVITY; - params.theta_e = theta_e; - params.power_law_p = power_law_p; - params.gamma_min = gamma_min; - params.gamma_max = gamma_max; - params.gamma_cutoff = gamma_cutoff; - params.kappa = kappa; - params.kappa_width = kappa_width; - + params->polarization = polarization; -// check_for_errors(¶ms); +#if DEBUG + check_for_errors(params); +#endif - if(params.distribution == params.MAXWELL_JUETTNER) + if(params->distribution == params->MAXWELL_JUETTNER) { - if (params.polarization == params.STOKES_I) return maxwell_juettner_I(¶ms); - else if(params.polarization == params.STOKES_Q) return maxwell_juettner_Q(¶ms); - else if(params.polarization == params.STOKES_U) return 0.; - else if(params.polarization == params.STOKES_V) return maxwell_juettner_V(¶ms); + if (params->polarization == params->STOKES_I) return maxwell_juettner_I(params); + else if(params->polarization == params->STOKES_Q) return maxwell_juettner_Q(params); + else if(params->polarization == params->STOKES_U) return 0.; + else if(params->polarization == params->STOKES_V) return maxwell_juettner_V(params); } - else if(params.distribution == params.POWER_LAW) + else if(params->distribution == params->POWER_LAW) { - if (params.polarization == params.STOKES_I) return power_law_I(¶ms); - else if(params.polarization == params.STOKES_Q) return power_law_Q(¶ms); - else if(params.polarization == params.STOKES_U) return 0.; - else if(params.polarization == params.STOKES_V) return power_law_V(¶ms); + if (params->polarization == params->STOKES_I) return power_law_I(params); + else if(params->polarization == params->STOKES_Q) return power_law_Q(params); + else if(params->polarization == params->STOKES_U) return 0.; + else if(params->polarization == params->STOKES_V) return power_law_V(params); } - else if(params.distribution == params.KAPPA_DIST) + else if(params->distribution == params->KAPPA_DIST) { - if (params.polarization == params.STOKES_I) return kappa_I(¶ms); - else if(params.polarization == params.STOKES_Q) return kappa_Q(¶ms); - else if(params.polarization == params.STOKES_U) return 0.; - else if(params.polarization == params.STOKES_V) return kappa_V(¶ms); + if (params->polarization == params->STOKES_I) { + if (params->kappa < params->kappa_interp_begin) { + return kappa_I(params); + } else if (params->kappa >= params->kappa_interp_begin && params->kappa < params->kappa_interp_end) { + return ((params->kappa_interp_end - params->kappa) * maxwell_juettner_I(params) + + (params->kappa - params->kappa_interp_begin) * kappa_I(params)) / + (params->kappa_interp_end - params->kappa_interp_begin); + } else { + return maxwell_juettner_I(params); + } + } else if (params->polarization == params->STOKES_Q) { + if (params->kappa < params->kappa_interp_begin) { + return kappa_Q(params); + } else if (params->kappa >= params->kappa_interp_begin && params->kappa < params->kappa_interp_end) { + return ((params->kappa_interp_end - params->kappa) * maxwell_juettner_Q(params) + + (params->kappa - params->kappa_interp_begin) * kappa_Q(params)) / + (params->kappa_interp_end - params->kappa_interp_begin); + } else { + return maxwell_juettner_Q(params); + } + } else if (params->polarization == params->STOKES_U) { + return 0.; + } else if (params->polarization == params->STOKES_V) { + if (params->kappa < params->kappa_interp_begin) { + return kappa_V(params); + } else if (params->kappa >= params->kappa_interp_begin && params->kappa < params->kappa_interp_end) { + return ((params->kappa_interp_end - params->kappa) * maxwell_juettner_V(params) + + (params->kappa - params->kappa_interp_begin) * kappa_V(params)) / + (params->kappa_interp_end - params->kappa_interp_begin); + } else { + return maxwell_juettner_V(params); + } + } } return 0.; } /*alpha_nu_fit: wrapper for the absorptivity fitting formulae. Takes in the - * same parameters as alpha_nu(), populates the struct of - * parameters, and passes them to the fitting formulae. The - * fitting formulae for each distribution function are located - * in their corresponding folders, so for example the - * KAPPA_DIST fitting formulae are located in the kappa folder, - * in the file kappa_fits.c + * same parameters as alpha_nu(), and passes them to the fitting formulae. * *@params: nu, magnetic_field, electron_density, observer_angle, * distribution, polarization, theta_e, power_law_p, @@ -98,68 +90,73 @@ double j_nu_fit(double nu, * function) evaluated for the input parameters. */ -double alpha_nu_fit(double nu, - double magnetic_field, - double electron_density, - double observer_angle, - int distribution, - int polarization, - double theta_e, - double power_law_p, - double gamma_min, - double gamma_max, - double gamma_cutoff, - double kappa, - double kappa_width) +double alpha_nu_fit(struct parameters *params, int polarization) { -/*fill the struct with values*/ - struct parameters params; - setConstParams(¶ms); - params.nu = nu; - params.magnetic_field = magnetic_field; - params.observer_angle = observer_angle; - params.electron_density = electron_density; - params.distribution = distribution; - params.polarization = polarization; - params.mode = params.ABSORPTIVITY; - params.theta_e = theta_e; - params.power_law_p = power_law_p; - params.gamma_min = gamma_min; - params.gamma_max = gamma_max; - params.gamma_cutoff = gamma_cutoff; - params.kappa = kappa; - params.kappa_width = kappa_width; + params->polarization = polarization; +#if DEBUG + check_for_errors(params); +#endif - if(params.distribution == params.MAXWELL_JUETTNER) + if(params->distribution == params->MAXWELL_JUETTNER) { - if (params.polarization == params.STOKES_I) return maxwell_juettner_I_abs(¶ms); - else if(params.polarization == params.STOKES_Q) return maxwell_juettner_Q_abs(¶ms); - else if(params.polarization == params.STOKES_U) return 0.; - else if(params.polarization == params.STOKES_V) return maxwell_juettner_V_abs(¶ms); + if (params->polarization == params->STOKES_I) return maxwell_juettner_I_abs(params); + else if(params->polarization == params->STOKES_Q) return maxwell_juettner_Q_abs(params); + else if(params->polarization == params->STOKES_U) return 0.; + else if(params->polarization == params->STOKES_V) return maxwell_juettner_V_abs(params); } - else if(params.distribution == params.POWER_LAW) + else if(params->distribution == params->POWER_LAW) { - if (params.polarization == params.STOKES_I) return power_law_I_abs(¶ms); - else if(params.polarization == params.STOKES_Q) return power_law_Q_abs(¶ms); - else if(params.polarization == params.STOKES_U) return 0.; - else if(params.polarization == params.STOKES_V) return power_law_V_abs(¶ms); + if (params->polarization == params->STOKES_I) return power_law_I_abs(params); + else if(params->polarization == params->STOKES_Q) return power_law_Q_abs(params); + else if(params->polarization == params->STOKES_U) return 0.; + else if(params->polarization == params->STOKES_V) return power_law_V_abs(params); } - else if(params.distribution == params.KAPPA_DIST) + else if(params->distribution == params->KAPPA_DIST) { - if (params.polarization == params.STOKES_I) return kappa_I_abs(¶ms); - else if(params.polarization == params.STOKES_Q) return kappa_Q_abs(¶ms); - else if(params.polarization == params.STOKES_U) return 0.; - else if(params.polarization == params.STOKES_V) return kappa_V_abs(¶ms); + if (params->polarization == params->STOKES_I) { + if (params->kappa < params->kappa_interp_begin) { + return kappa_I_abs(params); + } else if (params->kappa >= params->kappa_interp_begin && params->kappa < params->kappa_interp_end) { + return ((15 - params->kappa) * maxwell_juettner_I_abs(params) + + (params->kappa - params->kappa_interp_begin) * kappa_I_abs(params)) / + (params->kappa_interp_end - params->kappa_interp_begin); + } else { + return maxwell_juettner_I_abs(params); + } + } else if(params->polarization == params->STOKES_Q) { + if (params->kappa < params->kappa_interp_begin) { + return kappa_Q_abs(params); + } else if (params->kappa >= params->kappa_interp_begin && params->kappa < params->kappa_interp_end) { + return ((params->kappa_interp_end - params->kappa) * maxwell_juettner_Q_abs(params) + + (params->kappa - params->kappa_interp_begin) * kappa_Q_abs(params)) / + (params->kappa_interp_end - params->kappa_interp_begin); + } else { + return maxwell_juettner_Q_abs(params); + } + } else if(params->polarization == params->STOKES_U) { + return 0.; + } else if(params->polarization == params->STOKES_V) { + if (params->kappa < params->kappa_interp_begin) { + return kappa_V_abs(params); + } else if (params->kappa >= params->kappa_interp_begin && params->kappa < params->kappa_interp_end) { + return ((params->kappa_interp_end - params->kappa) * maxwell_juettner_V_abs(params) + + (params->kappa - params->kappa_interp_begin) * kappa_V_abs(params)) / + (params->kappa_interp_end - params->kappa_interp_begin); + } else { + return maxwell_juettner_V_abs(params); + } + } } return 0.; } /*rho_nu_fit: Fits to Faraday rotation/conversion coefficients; right now only has - * formulae for Maxwell-Juettner distribution, from Dexter (2016) + * formulae for Maxwell-Juettner distribution, from Dexter (2016), + * and the Kappa distribution, from Marszewski+ (2021) * *@params: nu, magnetic_field, electron_density, observer_angle, * distribution, polarization, theta_e, power_law_p, @@ -169,67 +166,68 @@ double alpha_nu_fit(double nu, * (based on the distribution function) evaluated for * the input parameters. */ -double rho_nu_fit(double nu, - double magnetic_field, - double electron_density, - double observer_angle, - int distribution, - int polarization, - double theta_e, - double power_law_p, - double gamma_min, - double gamma_max, - double gamma_cutoff, - double kappa, - double kappa_width) +double rho_nu_fit(struct parameters *params, int polarization) { -/*fill the struct with values*/ - struct parameters params; - setConstParams(¶ms); - params.nu = nu; - params.magnetic_field = magnetic_field; - params.observer_angle = observer_angle; - params.electron_density = electron_density; - params.distribution = distribution; - params.polarization = polarization; - params.mode = params.ABSORPTIVITY; - params.theta_e = theta_e; - params.power_law_p = power_law_p; - params.gamma_min = gamma_min; - params.gamma_max = gamma_max; - params.gamma_cutoff = gamma_cutoff; - params.kappa = kappa; - params.kappa_width = kappa_width; + params->polarization = polarization; -// check_for_errors(¶ms); - - if(params.polarization == params.STOKES_I) +#if DEBUG + check_for_errors(params); + if(params->polarization == params->STOKES_I) { printf("No Faraday rotation of total intensity"); return 0.; } +#endif - if(params.distribution == params.MAXWELL_JUETTNER) + if(params->distribution == params->MAXWELL_JUETTNER) + { + if (params->polarization == params->STOKES_Q) return maxwell_juettner_rho_Q(params); + else if(params->polarization == params->STOKES_U) return 0.; + else if(params->polarization == params->STOKES_V) return maxwell_juettner_rho_V(params); + + } else if(params->distribution == params->KAPPA_DIST) { +#if DEBUG + if(params->nu/(2.8e6 * params->magnetic_field) < 100 || + params->nu/(pow(params->kappa_width * params->kappa, 2.) * (2.8e6 * params->magnetic_field) * sin(params->observer_angle)) < 0.1) + { + printf("\n WARNING: nu and/or X_kappa low; rho kappa fits may be inaccurate \n"); + } +#endif + if (params->polarization == params->STOKES_Q) { - if (params.polarization == params.STOKES_Q) return maxwell_juettner_rho_Q(¶ms); - else if(params.polarization == params.STOKES_U) return 0.; - else if(params.polarization == params.STOKES_V) return maxwell_juettner_rho_V(¶ms); + if (params->kappa < 3.5) + return kappa35_rho_Q(params); + else if (params->kappa >= 3.5 && params->kappa < 4.0) + return ((4.0 - params->kappa) * kappa35_rho_Q(params) + (params->kappa - 3.5) * kappa4_rho_Q(params)) / 0.5; + else if (params->kappa >= 4.0 && params->kappa < 4.5) + return ((4.5 - params->kappa) * kappa4_rho_Q(params) + (params->kappa - 4.0) * kappa45_rho_Q(params)) / 0.5; + else if (params->kappa >= 4.5 && params->kappa < 5.0) + return ((5.0 - params->kappa) * kappa45_rho_Q(params) + (params->kappa - 4.5) * kappa5_rho_Q(params)) / 0.5; + else if (params->kappa >= 5.0 && params->kappa <= 8.0) + return ((8.0 - params->kappa) * kappa5_rho_Q(params) + (params->kappa - 5.0) * maxwell_juettner_rho_Q(params)) / 5.0; + else if (params->kappa > 8.0) + return maxwell_juettner_rho_Q(params); } - -/*Faraday rotation coefficients for Power-law and Kappa distributions will be added later*/ -// else if(params.distribution == params.POWER_LAW) -// { -// if (params.polarization == params.STOKES_Q) return power_law_rho_Q(¶ms); -// else if(params.polarization == params.STOKES_U) return 0.; -// else if(params.polarization == params.STOKES_V) return power_law_rho_V(¶ms); -// } -// -// else if(params.distribution == params.KAPPA_DIST) -// { -// if (params.polarization == params.STOKES_Q) return kappa_rho_Q(¶ms); -// else if(params.polarization == params.STOKES_U) return 0.; -// else if(params.polarization == params.STOKES_V) return kappa_rho_V(¶ms); -// } + else if(params->polarization == params->STOKES_U) return 0.; + else if(params->polarization == params->STOKES_V) + { + if(params->kappa < 3.5) + return kappa35_rho_V(params); + else if(params->kappa >= 3.5 && params->kappa < 4.0) + return ((4.0 - params->kappa) * kappa35_rho_V(params) + (params->kappa - 3.5) * kappa4_rho_V(params)) / 0.5; + else if(params->kappa >= 4.0 && params->kappa < 4.5) + return ((4.5 - params->kappa) * kappa4_rho_V(params) + (params->kappa - 4.0) * kappa45_rho_V(params)) / 0.5; + else if(params->kappa >= 4.5 && params->kappa <= 5.0) + return ((5.0 - params->kappa) * kappa45_rho_V(params) + (params->kappa - 4.5) * kappa5_rho_V(params)) / 0.5; + else if (params->kappa >= 5.0 && params->kappa <= 8.0) + return ((8.0 - params->kappa) * kappa5_rho_V(params) + (params->kappa - 5.0) * maxwell_juettner_rho_V(params)) / 5.0; + else if (params->kappa > 8.0) + return maxwell_juettner_rho_V(params); + } +} else if(params->distribution == params->POWER_LAW) { + fprintf(stderr, "\nNo rotativity fits are implemented for power-law diestributions!!\n"); + exit(-1); +} return 0.; } @@ -243,7 +241,7 @@ double rho_nu_fit(double nu, *@returns: prints error messages or quits if disallowed values * (such as magnitude of magnetic field < 0) are entered. */ -double check_for_errors(struct parameters * params) +void check_for_errors(struct parameters *params) { double nu_c = get_nu_c(*params); @@ -265,24 +263,28 @@ double check_for_errors(struct parameters * params) printf("\n ERROR: cannot have negative electron number density \n"); exit(0); } - if(params->kappa < 2.5 || params->kappa > 7.5) - { - printf("\n WARNING: kappa out of range of fitting formula \n"); - } - if(params->kappa_width < 3 || params->kappa_width > 200) - { - printf("\n WARNING: w out of range; fitting formula may be inaccurate\n"); - } - if(params->gamma_min < 1) - { - printf("\n ERROR: gamma_min < 1\n"); - exit(0); + if(params->distribution == params->KAPPA_DIST) { + // This is taken care of by switching fits, see above + // if(params->kappa < 2.5 || params->kappa > 7.5) + // { + // printf("\n WARNING: kappa out of range of fitting formula \n"); + // } + //if(params->kappa_width < 3 || + if (params->kappa_width > 200) + { + printf("\n WARNING: w out of range; fitting formula may be inaccurate\n"); + } } - if(params->observer_angle < 5.*(params->pi)/180. - || params->observer_angle == 90.*(params->pi)/180.) - { - printf("\n WARNING: theta out of range; fitting formula may be inaccurate \n"); + if(params->distribution == params->POWER_LAW) { + if(params->gamma_min < 1) + { + printf("\n ERROR: gamma_min < 1\n"); + exit(0); + } } - - return 0.; -} + // if(params->observer_angle < 5.*(params->pi)/180. + // || params->observer_angle == 90.*(params->pi)/180.) + // { + // printf("\n WARNING: theta out of range; fitting formula may be inaccurate \n"); + // } +} \ No newline at end of file diff --git a/src/symphony/fits.h b/src/symphony/fits.h index 9f26166..fcf7443 100644 --- a/src/symphony/fits.h +++ b/src/symphony/fits.h @@ -37,49 +37,19 @@ double kappa_I_abs(struct parameters * params); double kappa_Q_abs(struct parameters * params); double kappa_V_abs(struct parameters * params); -double check_for_errors(struct parameters * params); -double j_nu_fit(double nu, - double magnetic_field, - double electron_density, - double observer_angle, - int distribution, - int polarization, - double theta_e, - double power_law_p, - double gamma_min, - double gamma_max, - double gamma_cutoff, - double kappa, - double kappa_width - ); -double alpha_nu_fit(double nu, - double magnetic_field, - double electron_density, - double observer_angle, - int distribution, - int polarization, - double theta_e, - double power_law_p, - double gamma_min, - double gamma_max, - double gamma_cutoff, - double kappa, - double kappa_width - ); - -double rho_nu_fit(double nu, - double magnetic_field, - double electron_density, - double observer_angle, - int distribution, - int polarization, - double theta_e, - double power_law_p, - double gamma_min, - double gamma_max, - double gamma_cutoff, - double kappa, - double kappa_width - ); +/* Kappa Faraday Rotation fits */ +double kappa35_rho_Q(struct parameters * params); +double kappa4_rho_Q(struct parameters * params); +double kappa45_rho_Q(struct parameters * params); +double kappa5_rho_Q(struct parameters * params); +double kappa35_rho_V(struct parameters * params); +double kappa4_rho_V(struct parameters * params); +double kappa45_rho_V(struct parameters * params); +double kappa5_rho_V(struct parameters * params); + +void check_for_errors(struct parameters * params); +double j_nu_fit(struct parameters * params, int polarization); +double alpha_nu_fit(struct parameters * params, int polarization); +double rho_nu_fit(struct parameters * params, int polarization); #endif /* SYMPHONY_FITS_H_ */ diff --git a/src/symphony/kappa.h b/src/symphony/kappa.h index ebed962..e8e3cb0 100644 --- a/src/symphony/kappa.h +++ b/src/symphony/kappa.h @@ -3,6 +3,7 @@ #include "params.h" //#include "distribution_function_common_routines.h" #include "gsl/gsl_sf_hyperg.h" +#include "gsl/gsl_sf_bessel.h" double kappa_to_be_normalized(double gamma, void * paramsInput); double kappa_f(double gamma, struct parameters * params); @@ -16,4 +17,14 @@ double kappa_I_abs(struct parameters * params); double kappa_Q_abs(struct parameters * params); double kappa_V_abs(struct parameters * params); +/* Kappa Faraday Rotation fits */ +double kappa35_rho_Q(struct parameters * params); +double kappa4_rho_Q(struct parameters * params); +double kappa45_rho_Q(struct parameters * params); +double kappa5_rho_Q(struct parameters * params); +double kappa35_rho_V(struct parameters * params); +double kappa4_rho_V(struct parameters * params); +double kappa45_rho_V(struct parameters * params); +double kappa5_rho_V(struct parameters * params); + #endif /* SYMPHONY_KAPPA_H_ */ diff --git a/src/symphony/kappa_fits.c b/src/symphony/kappa_fits.c index 785cd47..86f4de9 100644 --- a/src/symphony/kappa_fits.c +++ b/src/symphony/kappa_fits.c @@ -1,5 +1,36 @@ #include "kappa.h" +#include +#include + +#define SMALL 1e-40 + +/** + * Stabilized version of the hypergeometric fn: + * Use GSL's version on small domain of validity, + * use an extension to maintain stability outside that + * + * Final form courtesy of Angelo Ricarte + */ +static inline double stable_hyp2f1(double a, double b, double c, double z) +{ + if (z > -1 && z < 1) { + //fprintf(stderr, "GSL hyperg_u: %g %g %g %g\n", a, b, c, z); + return gsl_sf_hyperg_2F1(a, b, c, z); + } else { + //fprintf(stderr, "GSL hyperg_p1: %g %g %g %g %g\n", a, c-b, a-b+1., 1./(1.-z), z); + //fprintf(stderr, "GSL hyperg_p2: %g %g %g %g %g\n", b, c-a, b-a+1., 1./(1.-z), z); + /*GSL 2F1 only works for |z| < 1; had to apply a hypergeometric function + identity because in our case z = -kappa*w, so |z| > 1 */ + return pow(1.-z, -a) * tgamma(c) * tgamma(b-a) + / (tgamma(b)*tgamma(c-a)) + * gsl_sf_hyperg_2F1(a, c-b, a-b+1., 1./(1.-z)) + + pow(1.-z, -b) * tgamma(c) * tgamma(a-b) + / (tgamma(a) * tgamma(c-b)) + * gsl_sf_hyperg_2F1(b, c-a, b-a+1., 1./(1.-z)); + } +} + /*kappa_I: fitting formula to the emissivity, in Stokes I, produced by a * kappa distribution of electrons (without any exponential * cutoff). Uses eq. 29, 35, 36, 37, 38 of [1]. @@ -15,6 +46,7 @@ double kappa_I(struct parameters * params) *sin(params->observer_angle); double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } double prefactor = (params->electron_density * pow(params->electron_charge, 2.) * nu_c @@ -26,8 +58,10 @@ double kappa_I(struct parameters * params) double Nhigh = (1./4.) * pow(3., (params->kappa-1.)/2.) * (params->kappa-2.) * (params->kappa-1.) + * tgamma(params->kappa/4.-1./3.) * tgamma(params->kappa/4.-1./3.) - * tgamma(params->kappa/4.+4./3.); + * tgamma(params->kappa/4.-1./3.) + * tgamma(params->kappa/4.+4./3.) + SMALL; double x = 3. * pow(params->kappa, -3./2.); @@ -35,6 +69,13 @@ double kappa_I(struct parameters * params) * pow(1.+pow(X_k, x * (3. * params->kappa-4.)/6.) * pow(Nlow/Nhigh, x), -1./x); +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "NaN in kappa. X_k, prefactor, Nlow, Nhigh, x, ans: %g %g %g %g %g %g\n", + X_k, prefactor, Nlow, Nhigh, x, ans); + } +#endif + return ans; } @@ -54,6 +95,7 @@ double kappa_Q(struct parameters * params) * sin(params->observer_angle); double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } double prefactor = (params->electron_density * pow(params->electron_charge, 2.) @@ -66,7 +108,7 @@ double kappa_Q(struct parameters * params) double Nhigh = -(pow(4./5., 2)+params->kappa/50.) * (1./4.) * pow(3., (params->kappa-1.)/2.) * (params->kappa-2.) * (params->kappa-1.) * tgamma(params->kappa/4.-1./3.) - * tgamma(params->kappa/4.+4./3.); + * tgamma(params->kappa/4.+4./3.) + SMALL; double x = (37./10.)*pow(params->kappa, -8./5.); @@ -74,6 +116,13 @@ double kappa_Q(struct parameters * params) * pow(1. + pow(X_k, x * (3. * params->kappa-4.)/6.) * pow(Nlow/Nhigh, x), -1./x); +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "NaN in kappa. X_k, prefactor, Nlow, Nhigh, x, ans: %g %g %g %g %g %g\n", + X_k, prefactor, Nlow, Nhigh, x, ans); + } +#endif + return ans; } @@ -93,6 +142,7 @@ double kappa_V(struct parameters * params) * nu_c * sin(params->observer_angle); double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } double prefactor = (params->electron_density * pow(params->electron_charge, 2.) @@ -112,13 +162,14 @@ double kappa_V(struct parameters * params) * pow(X_k, -1./2.) * (1./4.) * pow(3., (params->kappa-1.)/2.) * (params->kappa-2.) * (params->kappa-1.) * tgamma(params->kappa/4.-1./3.) - * tgamma(params->kappa/4.+4./3.); + * tgamma(params->kappa/4.+4./3.) + SMALL; double x = 3.*pow(params->kappa, -3./2.); - double ans = prefactor * Nlow * pow(X_k, 1./3.) - * pow(1.+pow(X_k, x * (3.*params->kappa-4.)/6.) - * pow(Nlow/Nhigh, x), -1./x); + double ans = (Nhigh < SMALL*SMALL) ? 0: + prefactor * Nlow * pow(X_k, 1./3.) + * pow(1.+pow(X_k, x * (3.*params->kappa-4.)/6.) + * pow(Nlow/Nhigh, x), -1./x); /*The Stokes V absorption coefficient changes sign at observer_angle equals 90deg, but this formula does not. This discrepancy is a @@ -130,6 +181,14 @@ double kappa_V(struct parameters * params) and Pandya et al. (2016) for Stokes V transfer coefficients does not follow the convention the papers describe (IEEE/IAU); the sign has been corrected here.*/ + +#if DEBUG + if (isnan(ans) || isnan(sign_bug_patch)) { + fprintf(stderr, "NaN in kappa. X_k, prefactor, Nlow, Nhigh, x, ans, sign: %g %g %g %g %g %g %g\n", + X_k, prefactor, Nlow, Nhigh, x, ans, sign_bug_patch); + } +#endif + return -ans * sign_bug_patch; } @@ -149,6 +208,7 @@ double kappa_I_abs(struct parameters * params) * nu_c * sin(params->observer_angle); double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } double prefactor = params->electron_density * params->electron_charge / (params->magnetic_field * sin(params->observer_angle)); @@ -161,14 +221,8 @@ double kappa_I_abs(struct parameters * params) double z = -params->kappa*params->kappa_width; - /*GSL 2F1 only works for |z| < 1; had to apply a hypergeometric function - identity because in our case z = -kappa*w, so |z| > 1 */ - double hyp2f1 = pow(1.-z, -a) * tgamma(c) * tgamma(b-a) - / (tgamma(b)*tgamma(c-a)) - * gsl_sf_hyperg_2F1(a, c-b, a-b+1., 1./(1.-z)) - + pow(1.-z, -b) * tgamma(c) * tgamma(a-b) - / (tgamma(a) * tgamma(c-b)) - * gsl_sf_hyperg_2F1(b, c-a, b-a+1., 1./(1.-z)); + double hyp2f1 = stable_hyp2f1(a, b, c, z); + if (fabs(hyp2f1) < 1e-200) { return 0; } double Nlow = pow(3., 1./6.) * (10./41.) * pow(2. * params->pi, 2.) / pow(params->kappa_width * params->kappa, 16./3.-params->kappa) @@ -180,7 +234,7 @@ double kappa_I_abs(struct parameters * params) / pow(params->kappa_width * params->kappa, 5.) * (2 * tgamma(2. + params->kappa/2.) / (2.+params->kappa)-1.) - * (pow(3./params->kappa, 19./4.) + 3./5.); + * (pow(3./params->kappa, 19./4.) + 3./5.) + SMALL;; double x = pow(-7./4. + 8. * params->kappa/5., -43./50.); @@ -188,6 +242,13 @@ double kappa_I_abs(struct parameters * params) * pow(1. + pow(X_k, x * (3. * params->kappa-1.)/6.) * pow(Nlow/Nhigh, x), -1./x); +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "NaN in kappa. X_k, prefactor: %g %g\na, b, c, z, hyp2f1: %g %g %g %g %g\nNlow, Nhigh, x, ans: %g %g %g %g\n", + X_k, prefactor, a, b, c, z, hyp2f1, Nlow, Nhigh, x, ans); + } +#endif + return ans; } @@ -206,6 +267,7 @@ double kappa_Q_abs(struct parameters * params) * nu_c * sin(params->observer_angle); double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } double prefactor = params->electron_density * params->electron_charge / (params->magnetic_field * sin(params->observer_angle)); @@ -218,13 +280,9 @@ double kappa_Q_abs(struct parameters * params) double z = -params->kappa * params->kappa_width; - /*GSL 2F1 only works for |z| < 1; had to apply a hypergeometric function - identity because in our case z = -kappa*w, so |z| > 1 */ - double hyp2f1 = pow(1.-z, -a) * tgamma(c) * tgamma(b-a) - / (tgamma(b) * tgamma(c-a)) - * gsl_sf_hyperg_2F1(a, c-b, a-b+1., 1./(1.-z))+pow(1.-z, -b) - * tgamma(c) * tgamma(a-b) / (tgamma(a)*tgamma(c-b)) - * gsl_sf_hyperg_2F1(b, c - a, b - a + 1., 1./(1. - z)); + double hyp2f1 = stable_hyp2f1(a, b, c, z); + if (fabs(hyp2f1) < 1e-200) { return 0; } + double Nlow = -(25./48.) * pow(3., 1./6.) * (10./41.) * pow(2. * params->pi, 2.) / pow(params->kappa_width*params->kappa, 16./3.-params->kappa) @@ -236,7 +294,7 @@ double kappa_Q_abs(struct parameters * params) * (params->kappa-1.) * params->kappa / pow(params->kappa_width * params->kappa, 5.) * (2 * tgamma(2. + params->kappa/2.) - / (2. + params->kappa)-1.); + / (2. + params->kappa)-1.) + SMALL; double x = (7./5.) * pow(params->kappa, -23./20.); @@ -244,6 +302,13 @@ double kappa_Q_abs(struct parameters * params) * pow(1. + pow(X_k, x * (3. * params->kappa-1.) / 6.) * pow(Nlow/Nhigh, x), -1./x); +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "NaN in kappa. X_k, prefactor: %g %g\na, b, c, z, hyp2f1: %g %g %g %g %g\nNlow, Nhigh, x, ans: %g %g %g %g\n", + X_k, prefactor, a, b, c, z, hyp2f1, Nlow, Nhigh, x, ans); + } +#endif + return ans; } @@ -262,6 +327,7 @@ double kappa_V_abs(struct parameters * params) * nu_c * sin(params->observer_angle); double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } double prefactor = params->electron_density * params->electron_charge / (params->magnetic_field * sin(params->observer_angle)); @@ -274,14 +340,8 @@ double kappa_V_abs(struct parameters * params) double z = -params->kappa * params->kappa_width; - /*GSL 2F1 only works for |z| < 1; had to apply a hypergeometric function - identity because in our case z = -kappa*w, so |z| > 1 */ - double hyp2f1 = pow(1.-z, -a) * tgamma(c) * tgamma(b-a) - / (tgamma(b) * tgamma(c-a)) - * gsl_sf_hyperg_2F1(a, c-b, a-b+1., 1./(1.-z)) - + pow(1.-z, -b) * tgamma(c) * tgamma(a-b) - / (tgamma(a) * tgamma(c-b)) - * gsl_sf_hyperg_2F1(b, c - a, b - a + 1., 1./(1.-z)); + double hyp2f1 = stable_hyp2f1(a, b, c, z); + if (fabs(hyp2f1) < 1e-200) { return 0; } double Nlow = -(77./(100. * params->kappa_width)) * pow(pow(sin(params->observer_angle), -114./50.) @@ -302,7 +362,7 @@ double kappa_V_abs(struct parameters * params) * (params->kappa-2.) * (params->kappa-1.) * params->kappa / pow(params->kappa_width*params->kappa, 5.) * (2 * tgamma(2. + params->kappa/2.) - / (2. + params->kappa) - 1.); + / (2. + params->kappa) - 1.) + SMALL; double x = (61./50.)*pow(params->kappa, -142./125.)+7./1000.; @@ -316,9 +376,304 @@ double kappa_V_abs(struct parameters * params) double sign_bug_patch = cos(params->observer_angle) / fabs(cos(params->observer_angle)); +#if DEBUG + if (isnan(ans) || isnan(sign_bug_patch)) { + fprintf(stderr, "NaN in kappa. X_k, prefactor: %g %g\na, b, c, z, hyp2f1: %g %g %g %g %g\nNlow, Nhigh, x, ans: %g %g %g %g\n", + X_k, prefactor, a, b, c, z, hyp2f1, Nlow, Nhigh, x, ans); + } +#endif + /*NOTE: Sign corrected; the sign in Leung et al. (2011) and Pandya et al. (2016) for Stokes V transfer coefficients does not follow the convention the papers describe (IEEE/IAU); the sign has been corrected here.*/ return -ans * sign_bug_patch; } + +double kappa35_rho_Q(struct parameters * params) +{ + double nu_c = get_nu_c(*params); + + double nu_w = pow(params->kappa_width*params->kappa, 2.) * nu_c + * sin(params->observer_angle); + + double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } + + double prefactor = -(params->electron_density + * pow(params->electron_charge, 2.) + * pow(nu_c, 2.) + * pow(sin(params->observer_angle), 2.)) + /(params->mass_electron * params->speed_light * pow(params->nu, 3.)); + + double w_term = (17. * params->kappa_width) + - (3. * pow(params->kappa_width, .5)) + + (7. * pow(params->kappa_width, .5) * exp(-5. * params->kappa_width)); + + double f_X = 1. - exp(-pow(X_k, .84) / 30.) + - (sin(X_k / 10.) * exp(-3. * pow(X_k, .471) / 2.)); + + double ans = prefactor * f_X * w_term; + +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "\nNaN in kappa rot. X_k, prefactor, w_term, f_X, ans: %g %g %g %g %g\n", + X_k, prefactor, w_term, f_X, ans); + fprintf(stderr, "kappa, kappa_width: %g %g\n", params->kappa, params->kappa_width); + } +#endif + + return ans; +} + +double kappa4_rho_Q(struct parameters * params) +{ + double nu_c = get_nu_c(*params); + + double nu_w = pow(params->kappa_width*params->kappa, 2.) * nu_c + * sin(params->observer_angle); + + double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } + + double prefactor = -(params->electron_density + * pow(params->electron_charge, 2.) + * pow(nu_c, 2.) + * pow(sin(params->observer_angle), 2.)) + /(params->mass_electron * params->speed_light * pow(params->nu, 3.)); + + double w_term = ((46./3.) * params->kappa_width) + - ((5./3.) * pow(params->kappa_width, .5)) + + ((17./3.) * pow(params->kappa_width, .5) * exp(-5. * params->kappa_width)); + + double f_X = 1. - exp(-pow(X_k, .84) / 18.) + - (sin(X_k / 6.) * exp(-7. * pow(X_k, .5) / 4.)); + + double ans = prefactor * f_X * w_term; + +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "\nNaN in kappa rot. X_k, prefactor, w_term, f_X, ans: %g %g %g %g %g\n", + X_k, prefactor, w_term, f_X, ans); + fprintf(stderr, "kappa, kappa_width: %g %g\n", params->kappa, params->kappa_width); + } +#endif + + return ans; +} + +double kappa45_rho_Q(struct parameters * params) +{ + double nu_c = get_nu_c(*params); + + double nu_w = pow(params->kappa_width*params->kappa, 2.) * nu_c + * sin(params->observer_angle); + + double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } + + double prefactor = -(params->electron_density + * pow(params->electron_charge, 2.) + * pow(nu_c, 2.) + * pow(sin(params->observer_angle), 2.)) + /(params->mass_electron * params->speed_light * pow(params->nu, 3.)); + + double w_term = (14. * params->kappa_width) + - ((13./8.) * pow(params->kappa_width, .5)) + + ((9./2.) * pow(params->kappa_width, .5) * exp(-5. * params->kappa_width)); + + double f_X = 1. - exp(-pow(X_k, .84) / 12.) + - (sin(X_k / 4.) * exp(-2. * pow(X_k, .525))); + + double ans = prefactor * f_X * w_term; + +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "\nNaN in kappa rot. X_k, prefactor, w_term, f_X, ans: %g %g %g %g %g\n", + X_k, prefactor, w_term, f_X, ans); + fprintf(stderr, "kappa, kappa_width: %g %g\n", params->kappa, params->kappa_width); + } +#endif + + return ans; +} + +double kappa5_rho_Q(struct parameters * params) +{ + double nu_c = get_nu_c(*params); + + double nu_w = pow(params->kappa_width*params->kappa, 2.) * nu_c + * sin(params->observer_angle); + + double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } + + double prefactor = -(params->electron_density + * pow(params->electron_charge, 2.) + * pow(nu_c, 2.) + * pow(sin(params->observer_angle), 2.)) + /(params->mass_electron * params->speed_light * pow(params->nu, 3.)); + + double w_term = ((25./2.) * params->kappa_width) + - (pow(params->kappa_width, .5)) + + (5. * pow(params->kappa_width, .5) * exp(-5. * params->kappa_width)); + + double f_X = 1. - exp(-pow(X_k, .84) / 8.) + - (sin(3. * X_k / 8.) * exp(-9. * pow(X_k, .541) / 4.)); + + double ans = prefactor * f_X * w_term; + +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "\nNaN in kappa rot. X_k, prefactor, w_term, f_X, ans: %g %g %g %g %g\n", + X_k, prefactor, w_term, f_X, ans); + fprintf(stderr, "kappa, kappa_width: %g %g\n", params->kappa, params->kappa_width); + } +#endif + + return ans; +} + +double kappa35_rho_V(struct parameters * params) +{ + double nu_c = get_nu_c(*params); + + double nu_w = pow(params->kappa_width*params->kappa, 2.) * nu_c + * sin(params->observer_angle); + + double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } + + double prefactor = 2. * (params->electron_density + * pow(params->electron_charge, 2.) + * nu_c * cos(params->observer_angle)) + /(params->mass_electron * params->speed_light * pow(params->nu, 2.)); + + double bessel_term = (gsl_sf_bessel_Kn(0, 1./params->kappa_width)) / (gsl_sf_bessel_Kn(2, 1./params->kappa_width) + SMALL); + + double w_term = (pow(params->kappa_width, 2.) + (2. * params->kappa_width) + 1.) + / (((25./8.) * pow(params->kappa_width, 2.)) + (4. * params->kappa_width) + 1.); + + double g_X = 1. - .17*log(1. + (.447 * pow(X_k, -.5))); + + double ans = prefactor * bessel_term * g_X * w_term; + +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "\nNaN in kappa rot. X_k, prefactor, bessel_term, w_term, g_X, ans: %g %g %g %g %g %g\n", + X_k, prefactor, bessel_term, w_term, g_X, ans); + fprintf(stderr, "kappa, kappa_width, num, denom: %g %g %g %g", params->kappa, params->kappa_width, + gsl_sf_bessel_Kn(0, 1./params->kappa_width), gsl_sf_bessel_Kn(2, 1./params->kappa_width) + SMALL); + } +#endif + + return ans; +} + +double kappa4_rho_V(struct parameters * params) +{ + double nu_c = get_nu_c(*params); + + double nu_w = pow(params->kappa_width*params->kappa, 2.) * nu_c + * sin(params->observer_angle); + + double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } + + double prefactor = 2. * (params->electron_density + * pow(params->electron_charge, 2.) + * nu_c * cos(params->observer_angle)) + /(params->mass_electron * params->speed_light * pow(params->nu, 2.)); + + double bessel_term = (gsl_sf_bessel_Kn(0, 1./params->kappa_width)) / (gsl_sf_bessel_Kn(2, 1./params->kappa_width) + SMALL); + + double w_term = (pow(params->kappa_width, 2.) + (54. * params->kappa_width) + 50.) + / (((30./11.) * pow(params->kappa_width, 2.)) + (134. * params->kappa_width) + 50.); + + double g_X = 1. - .17*log(1. + (.391 * pow(X_k, -.5))); + + double ans = prefactor * bessel_term * g_X * w_term; + +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "\nNaN in kappa rot. X_k, prefactor, bessel_term, w_term, g_X, ans: %g %g %g %g %g %g\n", + X_k, prefactor, bessel_term, w_term, g_X, ans); + fprintf(stderr, "kappa, kappa_width, num, denom: %g %g %g %g", params->kappa, params->kappa_width, + gsl_sf_bessel_Kn(0, 1./params->kappa_width), gsl_sf_bessel_Kn(2, 1./params->kappa_width) + SMALL); + } +#endif + + return ans; +} + +double kappa45_rho_V(struct parameters * params) +{ + double nu_c = get_nu_c(*params); + + double nu_w = pow(params->kappa_width*params->kappa, 2.) * nu_c + * sin(params->observer_angle); + + double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } + + double prefactor = 2. * (params->electron_density + * pow(params->electron_charge, 2.) + * nu_c * cos(params->observer_angle)) + /(params->mass_electron * params->speed_light * pow(params->nu, 2.)); + + double bessel_term = (gsl_sf_bessel_Kn(0, 1./params->kappa_width)) / (gsl_sf_bessel_Kn(2, 1./params->kappa_width) + SMALL); + + double w_term = (pow(params->kappa_width, 2.) + (43. * params->kappa_width) + 38.) + / (((7./3.) * pow(params->kappa_width, 2.)) + ((185./2.) * params->kappa_width) + 38.); + + double g_X = 1. - .17*log(1. + (.348 * pow(X_k, -.5))); + + double ans = prefactor * bessel_term * g_X * w_term; + +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "\nNaN in kappa rot. X_k, prefactor, bessel_term, w_term, g_X, ans: %g %g %g %g %g %g\n", + X_k, prefactor, bessel_term, w_term, g_X, ans); + fprintf(stderr, "kappa, kappa_width, num, denom: %g %g %g %g", params->kappa, params->kappa_width, + gsl_sf_bessel_Kn(0, 1./params->kappa_width), gsl_sf_bessel_Kn(2, 1./params->kappa_width) + SMALL); + } +#endif + + return ans; +} + +double kappa5_rho_V(struct parameters * params) +{ + double nu_c = get_nu_c(*params); + + double nu_w = pow(params->kappa_width*params->kappa, 2.) * nu_c + * sin(params->observer_angle); + + double X_k = params->nu/nu_w; + if (isinf(X_k)) { return 0; } + + double prefactor = 2. * (params->electron_density + * pow(params->electron_charge, 2.) + * nu_c * cos(params->observer_angle)) + /(params->mass_electron * params->speed_light * pow(params->nu, 2.)); + + double bessel_term = (gsl_sf_bessel_Kn(0, 1./params->kappa_width)) / (gsl_sf_bessel_Kn(2, 1./params->kappa_width) + SMALL); + + double w_term = ((params->kappa_width) + (13./14.)) + / ((2. * params->kappa_width) + (13./14.)); + + double g_X = 1. - .17*log(1. + (.313 * pow(X_k, -.5))); + + double ans = prefactor * bessel_term * g_X * w_term; + +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "\nNaN in kappa rot. X_k, prefactor, bessel_term, w_term, g_X, ans: %g %g %g %g %g %g\n", + X_k, prefactor, bessel_term, w_term, g_X, ans); + fprintf(stderr, "kappa, kappa_width, num, denom: %g %g %g %g", params->kappa, params->kappa_width, + gsl_sf_bessel_Kn(0, 1./params->kappa_width), gsl_sf_bessel_Kn(2, 1./params->kappa_width) + SMALL); + } +#endif + + return ans; +} diff --git a/src/symphony/maxwell_juettner_fits.c b/src/symphony/maxwell_juettner_fits.c index b818ff7..1c77865 100644 --- a/src/symphony/maxwell_juettner_fits.c +++ b/src/symphony/maxwell_juettner_fits.c @@ -1,5 +1,21 @@ #include "maxwell_juettner.h" +#include "constants.h" +#include "radiation.h" + +#include + +// Local functions for dispatched fits +double I_I(double x); +double I_Q(double x); +double I_V(double x); +double maxwell_juettner_dexter_I(struct parameters *params); +double maxwell_juettner_dexter_Q(struct parameters *params); +double maxwell_juettner_dexter_V(struct parameters *params); +double maxwell_juettner_leung_I(struct parameters *params); + +/********* PANDYA FITS *********/ + /*maxwell_juettner_I: fitting formula for the emissivity (polarized in Stokes I) * produced by a Maxwell-Juettner (relativistic thermal) * distribution of electrons. (Eq. 29, 31 of [1]) @@ -8,8 +24,13 @@ *@returns: fit to the emissivity, polarized in Stokes I, for the given * parameters for a Maxwell-Juettner distribution. */ -double maxwell_juettner_I(struct parameters * params) +double maxwell_juettner_I(struct parameters *params) { + if (params->dexter_fit == 2) { + return maxwell_juettner_leung_I(params); + } else if (params->dexter_fit) { + return maxwell_juettner_dexter_I(params); + } double nu_c = get_nu_c(*params); double nu_s = (2./9.)*nu_c*sin(params->observer_angle)*params->theta_e @@ -40,8 +61,11 @@ double maxwell_juettner_I(struct parameters * params) *@returns: fit to the emissivity, polarized in Stokes Q, for the given * parameters for a Maxwell-Juettner distribution. */ -double maxwell_juettner_Q(struct parameters * params) +double maxwell_juettner_Q(struct parameters *params) { + if (params->dexter_fit) { + return maxwell_juettner_dexter_Q(params); + } double nu_c = get_nu_c(*params); double nu_s = (2./9.)*nu_c*sin(params->observer_angle) @@ -73,8 +97,11 @@ double maxwell_juettner_Q(struct parameters * params) *@returns: fit to the emissivity, polarized in Stokes V, for the given * parameters for a Maxwell-Juettner distribution. */ -double maxwell_juettner_V(struct parameters * params) +double maxwell_juettner_V(struct parameters *params) { + if (params->dexter_fit) { + return maxwell_juettner_dexter_V(params); + } double nu_c = get_nu_c(*params); double nu_s = (2./9.)*nu_c*sin(params->observer_angle)*params->theta_e @@ -109,18 +136,9 @@ double maxwell_juettner_V(struct parameters * params) *@params: struct of parameters params *@returns: Planck function evaluated for the supplied parameters */ -double planck_func(struct parameters * params) +double planck_func(struct parameters *params) { - double term1 = (2.*params->plancks_constant*pow(params->nu, 3.)) - /pow(params->speed_light, 2.); - - double term2 = (exp(params->plancks_constant*params->nu - /(params->theta_e*params->mass_electron - *pow(params->speed_light, 2.)))-1.); - - double ans = term1 / term2; - - return ans; + return Bnu_inv(params->nu, params->theta_e) * pow(params->nu, 3); } /*maxwell_juettner_I_abs: Fitting formula for the absorptivity, polarized in @@ -131,9 +149,13 @@ double planck_func(struct parameters * params) *@returns: fitting formula to the absorptivity, in Stokes I, for a Maxwell- * Juettner distribution of electrons. */ -double maxwell_juettner_I_abs(struct parameters * params) +double maxwell_juettner_I_abs(struct parameters *params) { - double ans = maxwell_juettner_I(params)/planck_func(params); + double Bnu = planck_func(params); + double ans = 0.; + if (Bnu > 0.) { + ans = maxwell_juettner_I(params)/Bnu; + } return ans; } @@ -145,9 +167,13 @@ double maxwell_juettner_I_abs(struct parameters * params) *@returns: fitting formula to the absorptivity, in Stokes Q, for a Maxwell- * Juettner distribution of electrons. */ -double maxwell_juettner_Q_abs(struct parameters * params) +double maxwell_juettner_Q_abs(struct parameters *params) { - double ans = maxwell_juettner_Q(params)/planck_func(params); + double Bnu = planck_func(params); + double ans = 0.; + if (Bnu > 0.) { + ans = maxwell_juettner_Q(params)/Bnu; + } return ans; } @@ -159,9 +185,13 @@ double maxwell_juettner_Q_abs(struct parameters * params) *@returns: fitting formula to the absorptivity, in Stokes V, for a Maxwell- * Juettner distribution of electrons. */ -double maxwell_juettner_V_abs(struct parameters * params) +double maxwell_juettner_V_abs(struct parameters *params) { - double ans = maxwell_juettner_V(params)/planck_func(params); + double Bnu = planck_func(params); + double ans = 0.; + if (Bnu > 0.) { + ans = maxwell_juettner_V(params)/Bnu; + } return ans; } @@ -174,7 +204,7 @@ double maxwell_juettner_V_abs(struct parameters * params) *@returns: fitting formula to the Faraday conversion coefficient * for a Maxwell-Juettner distribution of electrons. */ -double maxwell_juettner_rho_Q(struct parameters * params) +double maxwell_juettner_rho_Q(struct parameters *params) { double omega0 = params->electron_charge*params->magnetic_field / (params->mass_electron*params->speed_light); @@ -194,11 +224,13 @@ double maxwell_juettner_rho_Q(struct parameters * params) double jffunc = 2.011 * exp(-pow(x, 1.035)/4.7) - cos(x/2.) * exp(-pow(x, 1.2)/2.73) - .011 * exp(-x / 47.2) + extraterm; + double k1 = gsl_sf_bessel_Kn(1, 1./params->theta_e); + double k2 = gsl_sf_bessel_Kn(2, 1./params->theta_e); + double k_ratio = (k2 > 0) ? k1/k2 : 1; + double eps11m22 = jffunc * wp2 * pow(omega0, 2.) / pow(2.*params->pi * params->nu, 4.) - * (gsl_sf_bessel_Kn(1, 1./params->theta_e) - / gsl_sf_bessel_Kn(2, 1./params->theta_e) - + 6. * params->theta_e) + * (k_ratio + 6. * params->theta_e) * pow(sin(params->observer_angle), 2.); double rhoq = 2. * params->pi * params->nu /(2. * params->speed_light) @@ -221,34 +253,28 @@ double maxwell_juettner_rho_V(struct parameters * params) double omega0 = params->electron_charge*params->magnetic_field / (params->mass_electron*params->speed_light); - double wp2 = 4. * params->pi * params->electron_density + double wp2 = 4. * params->pi * params->electron_density * pow(params->electron_charge, 2.) / params->mass_electron; /* argument for function g(X) (called shgmfunc) below */ double x = params->theta_e * sqrt(sqrt(2.) * sin(params->observer_angle) * (1.e3*omega0 / (2. * params->pi * params->nu))); - /* Approximate the Bessel functions if allowed */ - double k2=0, k0=0; - if (params->approximate && params->theta_e > 5) { - k0 = -log(1 / (2. * params->theta_e)) - 0.5772; - k2 = 2. * params->theta_e * params->theta_e; - } else { - k0 = gsl_sf_bessel_Kn(0, 1./params->theta_e); - k2 = gsl_sf_bessel_Kn(2, 1./params->theta_e); - } + double k0 = gsl_sf_bessel_Kn(0, 1./params->theta_e); + double k2 = gsl_sf_bessel_Kn(2, 1./params->theta_e); /* There are several fits of rho_V phrased as functions of x */ // TODO add straight Bessel-approx -> constant extrapolation? double fit_factor = 0; - if (params->dexter_fit) { + if (params->dexter_fit && k2 > 0) { // TODO Further limit the usage here to match grtrans // Jason Dexter (2016) fits using the modified difference factor g(X) double shgmfunc = 0.43793091 * log(1. + 0.00185777 * pow(x, 1.50316886)); - fit_factor = (k0 - shgmfunc) / k2; + fit_factor = (k0 - shgmfunc) / k2; // TODO might be unstable way to phrase } else { // Shcherbakov fits. Good to the smallest Thetae at high freq but questionable for low frequencies double shgmfunc = 1 - 0.11*log(1 + 0.035*x); - fit_factor = k0 / k2 * shgmfunc; + double k_ratio = (k2 > 0) ? k0/k2 : 1; + fit_factor = k_ratio * shgmfunc; } double eps12 = wp2 * omega0 / pow((2. * params->pi * params->nu), 3.) @@ -256,3 +282,114 @@ double maxwell_juettner_rho_V(struct parameters * params) return 2. * params->pi * params->nu / params->speed_light * eps12; } + +/********* DEXTER FITS *********/ +double maxwell_juettner_dexter_I(struct parameters *params) +{ + double Ne = params->electron_density; + double nu = params->nu; + double Thetae = params->theta_e; + double B = params->magnetic_field; + double theta = params->observer_angle; + // Synchrotron emissivity + double nus = 3.0 * EE * B * sin(theta) / 4.0 / M_PI / ME / CL * Thetae * Thetae + 1.0; + double x = nu / nus; + + return Ne * EE * EE * nu / 2. / sqrt(3) / CL / Thetae / Thetae * I_I(x); // [g/s^2/cm = ergs/s/cm^3] +} + +double maxwell_juettner_dexter_Q(struct parameters *params) +{ + double Ne = params->electron_density; + double nu = params->nu; + double Thetae = params->theta_e; + double B = params->magnetic_field; + double theta = params->observer_angle; + double nus = 3.0 * EE * B * sin(theta) / 4.0 / M_PI / ME / CL * Thetae * Thetae + 1.0; + double x = nu / nus; + return -Ne * EE * EE * nu / 2. / sqrt(3) / CL / Thetae / Thetae * I_Q(x); // Translate to Symphony convention +} + +double maxwell_juettner_dexter_V(struct parameters *params) +{ + double Ne = params->electron_density; + double nu = params->nu; + double Thetae = params->theta_e; + double B = params->magnetic_field; + double theta = params->observer_angle; + double nus = 3.0 * EE * B * sin(theta) / 4.0 / M_PI / ME / CL * Thetae * Thetae + 1.0; + double x = nu / nus; + double ans = 2. * Ne * EE * EE * nu / tan(theta) / 3. / sqrt(3) / CL / Thetae / Thetae / Thetae * I_V(x); +#if DEBUG + if (isnan(ans)) { + fprintf(stderr, "NaN in Dexter jV. Ne, nu, Thetae, B, theta, nus, x: %g %g %g %g %g %g %g\n", + Ne, nu, Thetae, B, theta, nus, x); + } +#endif + return ans; +} + +// Supporting functions + +double I_I(double x) +{ + return 2.5651 * (1 + 1.92 * pow(x, -1. / 3.) + + 0.9977 * pow(x, -2. / 3.)) * exp(-1.8899 * pow(x, + 1. / + 3.)); +} + +double I_Q(double x) +{ + return 2.5651 * (1 + 0.93193 * pow(x, -1. / 3.) + + 0.499873 * pow(x, -2. / 3.)) * exp(-1.8899 * pow(x, + 1. / + 3.)); +} + +double I_V(double x) +{ + return (1.81384 / x + 3.42319 * pow(x, -2. / 3.) + + 0.0292545 * pow(x, -0.5) + 2.03773 * pow(x, + -1. / 3.)) * + exp(-1.8899 * pow(x, 1. / 3.)); +} + +/********* LEUNG FIT *********/ + +/* + * thermal synchrotron emissivity + * + * Interpolates between Petrosian limit and + * classical thermal synchrotron limit + * Good for Thetae >~ 1 + * See Leung+ 2011, restated Pandya+ 2016 + */ +double maxwell_juettner_leung_I(struct parameters *params) +{ + double Ne = params->electron_density; + double nu = params->nu; + double Thetae = params->theta_e; + double B = params->magnetic_field; + double theta = params->observer_angle; + + double K2 = fmax(gsl_sf_bessel_Kn(2,1./Thetae), SMALL); + + double nuc = EE*B/(2.*M_PI*ME*CL); + double nus = (2./9.)*nuc*Thetae*Thetae*sin(theta); + + if(nu > 1.e12*nus) + return 0.; + + double x = nu/nus ; + double f = pow( pow(x,1./2.) + pow(2.,11./12.)*pow(x,1./6.), 2 ); + double j = (sqrt(2.)*M_PI*EE*EE*Ne*nus/(3.*CL*K2)) * f * exp(-pow(x,1./3.)); + +#if DEBUG + if (isnan(j) || isinf(j)) { + fprintf(stderr, "j nan in Leung fit: j %g f %g x %g nu %g nus %g nuc %g K2 %g Thetae %g\n", j, f, x, nu, nus, nuc, K2, Thetae); + } +#endif + + return j; +} diff --git a/src/symphony/params.h b/src/symphony/params.h index 5ae30bc..7b24b45 100644 --- a/src/symphony/params.h +++ b/src/symphony/params.h @@ -54,6 +54,8 @@ struct parameters /*kappa distribution parameters*/ double kappa; double kappa_width; + double kappa_interp_begin; + double kappa_interp_end; /*Choose if n-space peak is known, or if it must be found adaptively */ int use_n_peak; diff --git a/tests/isothermal_sphere/info b/tests/isothermal_sphere/info new file mode 100644 index 0000000..2d64bb2 --- /dev/null +++ b/tests/isothermal_sphere/info @@ -0,0 +1,6 @@ +# note maxnstep is because the tests were done using regular (non-exponential) KS. + +# #thermal performed on commit a2217b5ff432fa5c10080938af9bd14c79151969 +./ipole --freqcgs={freqcgs} --dsource=8000 --thetacam={inc} --maxnstep=1000000 --nx=80 --ny=80 --fov=1200 + +# when doing kappa4, add "--emission_type=2 --kappa=4" diff --git a/tests/isothermal_sphere/kappa4.txt b/tests/isothermal_sphere/kappa4.txt new file mode 100644 index 0000000..9c08e51 --- /dev/null +++ b/tests/isothermal_sphere/kappa4.txt @@ -0,0 +1,39 @@ +# frequency inc Ftot Ftot_unpol nuLnu nuLnu_unpol +11090300000.0 90.0 5.538792042856206 4.643911765020724 4.703805982572336e+33 3.943831032800095e+33 +14180300000.0 90.0 9.821975206285796 8.144802137873944 1.066535458298205e+34 8.844168406479303e+33 +18606400000.0 90.0 18.421721135504548 15.207746122204979 2.6247237631114384e+34 2.1667971378301932e+34 +23790600000.0 90.0 31.61606288657072 26.79942756023756 5.759757886859296e+34 4.882271863109117e+34 +30419100000.0 90.0 50.01200702856 47.027407168533664 1.164961815107029e+35 1.0954396128023123e+35 +39400900000.0 90.0 73.45263362930045 82.29740942799724 2.216177597004166e+35 2.4830379259955193e+35 +49731200000.0 90.0 99.51672031350577 122.89770713632136 3.789797805205784e+35 4.680193029902777e+35 +65253900000.0 90.0 131.4876044521914 157.67158667371078 6.5702566191748866e+35 7.878634570267965e+35 +81304100000.0 90.0 146.031420559943 164.2057931163908 9.091798520316281e+35 1.0223320304346055e+36 +106682000000.0 90.0 143.45740637884492 151.883832433797 1.1719394397651495e+36 1.2407770221482037e+36 +134652000000.0 90.0 129.558559853353 133.28515969725322 1.3358877472006928e+36 1.3743129896225078e+36 +176681000000.0 90.0 108.84312686320173 110.13219429532403 1.4725901957433467e+36 1.4900305993489976e+36 +223005000000.0 90.0 91.37766677162087 91.85475076893294 1.5604351025784984e+36 1.568582154725768e+36 +288851000000.0 90.0 74.11913733253958 74.25463235214907 1.6394390584049396e+36 1.6424360688311478e+36 +369331000000.0 90.0 60.22195567852089 60.24625001723842 1.703184370508939e+36 1.7038714577602466e+36 +478382000000.0 90.0 48.10561448115822 48.09327742303532 1.7622261556992243e+36 1.7617742191271005e+36 +603808000000.0 90.0 39.1450576608002 39.124736604481434 1.8099511192231438e+36 1.809011533977245e+36 +792276000000.0 90.0 30.661907157680677 30.64188898624312 1.860230286302858e+36 1.8590158018745724e+36 +974460000000.0 90.0 25.39627763650709 25.378551039513777 1.8950694443258633e+36 1.8937466862116738e+36 +1295270000000.0 90.0 19.5443471366262 19.530165155682553 1.9385299753907334e+36 1.937123318264891e+36 +223005000000.0 5.0 1.3499179247277533 1.3476141787803724 2.305223354640641e+34 2.301289301418752e+34 +223005000000.0 10.0 3.8317087301078407 3.8278634691389315 6.543319627825836e+34 6.536753165355902e+34 +223005000000.0 15.0 7.727893702930353 7.723151493537718 1.3196743831494916e+35 1.3188645671122162e+35 +223005000000.0 20.0 12.83241875126555 12.829216437728128 2.191362219885461e+35 2.1908153682717354e+35 +223005000000.0 25.0 18.932693758572753 18.93550463236725 3.2330919546329456e+35 3.233571960994764e+35 +223005000000.0 30.0 25.807009693997646 25.822310547118917 4.4070028533059694e+35 4.409615744305856e+35 +223005000000.0 35.0 33.22655541788771 33.26245733389098 5.674021371264006e+35 5.6801522577224375e+35 +223005000000.0 40.0 40.959466824026194 41.02504614279062 6.994552615887085e+35 7.005751443195618e+35 +223005000000.0 45.0 48.7672597076334 48.871594619945164 8.32787119576432e+35 8.345688225389583e+35 +223005000000.0 50.0 56.43913180389175 56.590405240672546 9.637978899806944e+35 9.663811511776607e+35 +223005000000.0 55.0 63.75164079406176 63.956003988659546 1.0886719004399539e+36 1.092161763048533e+36 +223005000000.0 60.0 70.52082809372453 70.78166677679584 1.2042677331144962e+36 1.2087220144674233e+36 +223005000000.0 65.0 76.55444998328952 76.87156751476803 1.3073024868437e+36 1.3127178289644268e+36 +223005000000.0 70.0 81.71688012393665 82.08648548605812 1.3954601022729536e+36 1.401771766835336e+36 +223005000000.0 75.0 85.87014486525646 86.28473548295514 1.4663844355551186e+36 1.4734643028346544e+36 +223005000000.0 80.0 88.90779274026941 89.3566378457653 1.518257639817271e+36 1.5259224629954204e+36 +223005000000.0 85.0 90.76207547391512 91.23218033840185 1.549922793567778e+36 1.557950664691599e+36 +223005000000.0 90.0 91.37766677162087 91.85475076893294 1.5604351025784984e+36 1.568582154725768e+36 diff --git a/tests/isothermal_sphere/powerlaw.txt b/tests/isothermal_sphere/powerlaw.txt new file mode 100644 index 0000000..952156f --- /dev/null +++ b/tests/isothermal_sphere/powerlaw.txt @@ -0,0 +1,39 @@ +# frequency inc Ftot Ftot_unpol nuLnu nuLnu_unpol +11090300000.0 90.0 2.568982102017574 2.357441343714158 2.181701946397708e+33 2.0020514600161365e+33 +14180300000.0 90.0 4.749120983048624 4.358359465407455 5.1569117390235905e+33 4.732596867982162e+33 +18606400000.0 90.0 9.363910657051242 8.596144942832101 1.334168432820633e+34 1.2247773015690956e+34 +23790600000.0 90.0 17.284436731586833 15.892323415931553 3.148847822761782e+34 2.895235104504785e+34 +30419100000.0 90.0 31.688701841381917 29.364728391717602 7.38145293677982e+34 6.840114868376485e+34 +39400900000.0 90.0 57.68759080958187 55.84457598478604 1.7405222938982634e+35 1.6849157354422056e+35 +49731200000.0 90.0 90.14118733264894 98.01403030557918 3.432758563945102e+35 3.732572333184556e+35 +65253900000.0 90.0 136.9497772663922 170.74500673118717 6.843193959824581e+35 8.531895575560063e+35 +81304100000.0 90.0 178.37585737156334 219.27810089772376 1.1105537081625719e+36 1.3652077790076299e+36 +106682000000.0 90.0 204.27928587860166 229.19939196962306 1.6688085884947173e+36 1.8723871691228583e+36 +134652000000.0 90.0 194.10255493538097 205.55943973668553 2.0014055816306153e+36 2.1195383552927838e+36 +176681000000.0 90.0 163.04402316584773 166.8532011619442 2.2058997835512548e+36 2.2574359561379078e+36 +223005000000.0 90.0 133.8548176410341 135.2056120633057 2.2858075006264268e+36 2.308874701916956e+36 +288851000000.0 90.0 105.12378692345416 105.51012629018042 2.3252300074205965e+36 2.333775436717335e+36 +369331000000.0 90.0 82.77282269500205 82.86681602706109 2.3409631309485498e+36 2.3436214301069103e+36 +478382000000.0 90.0 64.09313505309095 64.0944156015178 2.347888083532497e+36 2.3479349931490922e+36 +603808000000.0 90.0 50.83623494547413 50.81570454392648 2.3505164083278736e+36 2.3495671435808072e+36 +792276000000.0 90.0 38.76436144875301 38.740667386685566 2.3517988892643904e+36 2.3503613918629172e+36 +974460000000.0 90.0 31.52259995937203 31.501200381099366 2.3522154247849734e+36 2.3506185889223942e+36 +1295270000000.0 90.0 23.71763351882157 23.700576041359845 2.3524624895454226e+36 2.350770622780421e+36 +223005000000.0 5.0 1.6025398324127995 1.5997673999348212 2.736619894253882e+34 2.731885475975313e+34 +223005000000.0 10.0 4.69671256394097 4.692154003347476 8.020466499557294e+34 8.0126819519554185e+34 +223005000000.0 15.0 9.743870637758837 9.739081139839671 1.6639380622558136e+35 1.663120170867158e+35 +223005000000.0 20.0 16.579924478908957 16.580141993870985 2.8313150323318474e+35 2.8313521768545817e+35 +223005000000.0 25.0 24.983597938854214 24.99892652039947 4.266390748401509e+35 4.2690083745196864e+35 +223005000000.0 30.0 34.68432165474619 34.730315367631704 5.92296071144578e+35 5.930814950519738e+35 +223005000000.0 35.0 45.371712734947145 45.46905543950778 7.74802155899516e+35 7.764644545611349e+35 +223005000000.0 40.0 56.706965729051056 56.88015763097229 9.683716274511914e+35 9.713291851651097e+35 +223005000000.0 45.0 68.32349763939979 68.59851863560343 1.1667444334502287e+36 1.171440902856641e+36 +223005000000.0 50.0 79.88018025539544 80.28184601253058 1.3640952070088177e+36 1.3709543594590977e+36 +223005000000.0 55.0 91.01059515037338 91.55909739825746 1.554166706118053e+36 1.5635333510727844e+36 +223005000000.0 60.0 101.40102375087214 102.10926849831515 1.7316016318704945e+36 1.7436961622319338e+36 +223005000000.0 65.0 110.72684523095532 111.59756472173967 1.890856510136021e+36 1.905725583793975e+36 +223005000000.0 70.0 118.74811066678926 119.77311479850242 2.0278337891078907e+36 2.0453375455937143e+36 +223005000000.0 75.0 125.22774591691295 126.3871431255898 2.1384850089674263e+36 2.1582837646827865e+36 +223005000000.0 80.0 129.98110021859355 131.24445946859115 2.2196569317072896e+36 2.2412310229542304e+36 +223005000000.0 85.0 132.88804315583778 134.21687951385127 2.2692981182327182e+36 2.291990346781399e+36 +223005000000.0 90.0 133.8548176410341 135.2056120633057 2.2858075006264268e+36 2.308874701916956e+36 diff --git a/tests/isothermal_sphere/thermal.txt b/tests/isothermal_sphere/thermal.txt new file mode 100644 index 0000000..23d3c0e --- /dev/null +++ b/tests/isothermal_sphere/thermal.txt @@ -0,0 +1,39 @@ +# frequency inc Ftot Ftot_unpol nuLnu nuLnu_unpol +11090300000.0 90.0 4.275646082651832 4.285143624402521 3.6310822770245304e+33 3.639148041790738e+33 +14180300000.0 90.0 6.988761144348534 7.005656879264287 7.588862131574358e+33 7.607208645389753e+33 +18606400000.0 90.0 12.018731653235056 12.060691823920017 1.712426886753091e+34 1.7184053648926158e+34 +23790600000.0 90.0 19.557375813186006 19.707745503446176 3.5629278063625335e+34 3.5903218880522085e+34 +30419100000.0 90.0 31.3027514002403 32.14457457894139 7.29155102058659e+34 7.487642302765837e+34 +39400900000.0 90.0 48.134475453534684 53.32846962863743 1.4522902838587636e+35 1.6090009824557045e+35 +49731200000.0 90.0 65.02591765835368 81.08815286512608 2.476318343759528e+35 3.0880007177521514e+35 +65253900000.0 90.0 85.1660303167946 111.54531719510815 4.2556306105738254e+35 5.573767669526567e+35 +81304100000.0 90.0 93.63377971845873 114.23569151090516 5.8295636420688124e+35 7.112222061962525e+35 +106682000000.0 90.0 81.52378513139142 90.02139063300528 6.659881945872564e+35 7.354072596762175e+35 +134652000000.0 90.0 59.380761664657314 62.39279382105969 6.122793585931278e+35 6.433366415427598e+35 +176681000000.0 90.0 35.08713213280178 35.914290233003605 4.747104228313359e+35 4.85901436391777e+35 +223005000000.0 90.0 20.307140822627037 20.60142287648152 3.467803074008162e+35 3.518056933962927e+35 +288851000000.0 90.0 10.143348849321 10.249512014947793 2.2436044030025508e+35 2.2670866029520186e+35 +369331000000.0 90.0 4.834533335747593 4.877422581325198 1.3672922978631475e+35 1.3794221418557015e+35 +478382000000.0 90.0 2.026374127557577 2.04255681081707 7.423103368137631e+34 7.482384489513689e+34 +603808000000.0 90.0 0.8517082421646595 0.8580307947514274 3.938045766889259e+34 3.967279370860233e+34 +792276000000.0 90.0 0.2777046563360104 0.2796108501840976 1.6848091337142043e+34 1.696373840076208e+34 +974460000000.0 90.0 0.10852822035681293 0.10923052830847085 8.098372414609783e+33 8.150778612035377e+33 +1295270000000.0 90.0 0.02613317084442504 0.026288997289899124 2.5920505136233558e+33 2.6075063502086575e+33 +223005000000.0 5.0 0.0006700574885164443 0.0006721568882356729 1.144241544752787e+31 1.1478266406870215e+31 +223005000000.0 10.0 0.011184472790924957 0.011241274172991323 1.9099463318988648e+32 1.919646171431084e+32 +223005000000.0 15.0 0.07005176935366612 0.0704908474024495 1.1962577264135406e+33 1.2037557598422733e+33 +223005000000.0 20.0 0.25403965080594987 0.25584153824487565 4.3381758647344004e+33 4.3689462762576273e+33 +223005000000.0 25.0 0.6581404906388175 0.6632466498273145 1.1238911654286154e+34 1.1326108343790146e+34 +223005000000.0 30.0 1.3664644732187523 1.377898567813333 2.333479509567866e+34 2.353005246218791e+34 +223005000000.0 35.0 2.4313978442489854 2.4532154134967525 4.1520413888244715e+34 4.189298742957009e+34 +223005000000.0 40.0 3.8639049623979367 3.900997941745764 6.59830038276435e+34 6.661643198441873e+34 +223005000000.0 45.0 5.631705788001734 5.689450547326058 9.617132620551512e+34 9.715741999213833e+34 +223005000000.0 50.0 7.667214920279675 7.7510045007960615 1.3093124089631188e+35 1.3236209601975114e+35 +223005000000.0 55.0 9.870964769353535 9.985544142162627 1.6856416306745347e+35 1.705208082924798e+35 +223005000000.0 60.0 12.127953921885945 12.276760099817263 2.071062404062487e+35 2.0964736879930467e+35 +223005000000.0 65.0 14.31037962366346 14.494803114785743 2.443750150876439e+35 2.475243720306979e+35 +223005000000.0 70.0 16.29781390375227 16.51677098136076 2.7831396674056094e+35 2.8205304568544384e+35 +223005000000.0 75.0 17.975371205829976 18.224986791745504 3.069612215155309e+35 3.1122384865599823e+35 +223005000000.0 80.0 19.246293840154756 19.520001464076223 3.2866447091254713e+35 3.3333851216682716e+35 +223005000000.0 85.0 20.039343870544908 20.328413756434426 3.422072013109344e+35 3.4714358032975674e+35 +223005000000.0 90.0 20.307140822627037 20.60142287648152 3.467803074008162e+35 3.518056933962927e+35 diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 866400b..4ee875d 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -23,6 +23,9 @@ do make -f ../../makefile -j8 MODEL=analytic elif [[ $folder == *"thin_disk"* ]]; then make -f ../../makefile -j8 MODEL=thin_disk + elif [[ $folder == *"isothermal_sphere"* ]]; then + echo "NOT TESTING ISOTHERMAL SPHERE" + continue else make -f ../../makefile -j8 MODEL=iharm fi