diff --git a/src/server/backends/SIM/rt_sim.c b/src/server/backends/SIM/rt_sim.c index d6d95374cd4bd02993889882e092c5dcd0d2e4f1..7ebe31d662e6cbbd91efcfd306bd225210eae6f1 100644 --- a/src/server/backends/SIM/rt_sim.c +++ b/src/server/backends/SIM/rt_sim.c @@ -66,14 +66,21 @@ #define SIM_V_RED_KMS 400.0 #define SIM_V_BLU_KMS -400.0 #define SIM_V_RES_KMS 1.0 +#define SIM_HI_BINS 801 +#define SIM_HI_AMP_CAL 10.0 /* conversion from cK to mK */ + +#define SIM_V_REF_HZ (SIM_V_REF_MHZ * 1e6) /* default allowed HW ranges */ -#define SIM_FREQ_MIN_HZ DOPPLER_FREQ(SIM_V_RED_KMS, SIM_V_REF_MHZ * 1e6) -#define SIM_FREQ_MAX_HZ DOPPLER_FREQ(SIM_V_BLU_KMS, SIM_V_REF_MHZ * 1e6) -#define SIM_FREQ_STP_HZ DOPPLER_FREQ_REL(SIM_V_RES_KMS, SIM_V_REF_MHZ * 1e6) +#define SIM_FREQ_MIN_HZ DOPPLER_FREQ(SIM_V_RED_KMS, SIM_V_REF_HZ) +#define SIM_FREQ_MAX_HZ DOPPLER_FREQ(SIM_V_BLU_KMS, SIM_V_REF_HZ) +#define SIM_FREQ_STP_HZ DOPPLER_FREQ_REL(SIM_V_RES_KMS, SIM_V_REF_HZ) + #define SIM_IF_BW_HZ (SIM_FREQ_MAX_HZ - SIM_FREQ_MIN_HZ) -#define SIM_BINS ((SIM_V_RED_KMS - SIM_V_BLU_KMS) / SIM_V_RES_KMS) +//#define SIM_BINS (SIM_IF_BW_HZ / SIM_V_RES_KMS) +#define SIM_BINS ((SIM_V_RED_KMS - SIM_V_BLU_KMS) / SIM_V_RES_KMS) + #define SIM_BW_DIV_MAX 0 /* other properties */ @@ -82,6 +89,9 @@ #define SIM_TSYS 100. /* default system temperature */ #define SIM_EFF 0.6 /* default efficiency */ +#define SIM_SIG_NOISE 12. /* default noise */ +#define SIM_READOUT_HZ 1 /* default readout frequency */ +#define SIM_SUN_SFU 48. /* Sun @1415 as of Jun 11 2019 12 UTC */ #define SKY_GAUSS_INTG_STP 0.10 /* integration step for gaussian */ #define SKY_BASE_RES 0.5 /* resolution of the underlying data */ @@ -139,6 +149,9 @@ static struct { gdouble r_beam; /* the beam radius */ gdouble tsys; /* system temperature */ gdouble eff; /* system efficiency (eta) */ + gdouble sig_n; /* noise sigma */ + gdouble readout_hz; /* spectrum readout frequency */ + gdouble sun_sfu; /* sun solar flux units */ struct { GdkPixbuf *pb_sky; @@ -177,6 +190,9 @@ static struct { .r_beam = SIM_BEAM_RADIUS, .tsys = SIM_TSYS, .eff = SIM_EFF, + .sig_n = SIM_SIG_NOISE, + .readout_hz = SIM_READOUT_HZ, + .sun_sfu = SIM_SUN_SFU, }; /** @@ -661,32 +677,37 @@ static gboolean sim_sky_draw(GtkWidget *w, cairo_t *cr, gpointer data) -#define UNOISE (rand()/((double)RAND_MAX + 1.)) + + /** - * @brief gaussian noise macro + * @brief get the half width of the gaussian scaled by a beam radius + * (== 1/2 FWHM) at given sigma */ -#define GNOISE (UNOISE + UNOISE + UNOISE + UNOISE + UNOISE + UNOISE + UNOISE + UNOISE + UNOISE + UNOISE + UNOISE + UNOISE - 6.) +static double gauss_half_width_sigma_r(double sigma, double r) +{ + return r * sigma * sqrt(2.0 * log(2.0)); +} /** - * @brief returns the value of the width-normalised gaussian for a given FWHM + * @brief returns the value of the width-normalised gaussian for a given + * beam radius (== 1/2 FWHM) */ -static double gauss_norm(double x, double fwhm) +static double gauss_norm(double sigma, double r) { /* The FWHM auf a gaussian with sigma == 1 is 2*sqrt(2*ln(2)) ~2.35482 * To normalise x, we multiply by the latter and divide by two, since * we moved the factor from the divisor to the dividend. * We need only half of the FWHM, i.e. 1 / (fwhm / 2) which == 2 / fwhm, - * so our final scale factor is 2.35482/fwhm + * so our final scale factor is 2.35482/fwhm where fwhm == r */ + sigma = sigma / r * 2.0 * sqrt(2.0 * log(2.0)); - x = x / fwhm * 2.*sqrt(2.*log(2.)); - - return 1.0 / sqrt(2.0 * M_PI) * exp( -0.5 * x * x); + return 1.0 / sqrt(2.0 * M_PI) * exp(-0.5 * sigma * sigma); } /** @@ -730,208 +751,900 @@ static gpointer sim_spec_extract_HI_survey(gdouble glat, gdouble glon) if (!data) data = fopen("../data/sky_vel.dat", "rb"); - if (!data) { - GtkWidget *dia; - GtkWindow * win; + if (!data) { + GtkWidget *dia; + GtkWindow * win; + + dia = gtk_message_dialog_new(NULL, + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Please place sky_vel.dat in the " + "directory path this program is executed in."); + + gtk_dialog_run(GTK_DIALOG(dia)); + gtk_widget_destroy(dia); + + + exit(0); + /** XXX **/ + g_error("%s: error opening file", __func__); + return 0; + } + + + + + map = g_malloc(VEL * SKY_WIDTH * SKY_HEIGHT * sizeof(guint16)); + + fread(map, sizeof(guint16), VEL * SKY_WIDTH * SKY_HEIGHT, data); + fclose(data); + } + + as = g_malloc(VEL * sizeof(guint16)); + offset = get_offset(glat, glon); + + memcpy(as, &map[offset], sizeof(guint16) * VEL); + + return as; +} + + +/** + * @brief order two frequencies so f0 < f1 + * + * @param f0 the first frequency + * @param f1 the second frequency + */ + +static void sim_order_freq(gdouble *f0, gdouble *f1) +{ + double tmp; + + if (!f0) + return; + + if (!f1) + return; + + if ((*f0) < (*f1)) + return; + + tmp = (*f1); + (*f1) = (*f0); + (*f0) = tmp; +} + + +/** + * @brief clamp a frequency to the simulator settings + * + * @param f the frequency in Hz to clamp into range + * + * @returns the clamped frequency in Hz + */ + +static gdouble sim_clamp_freq_range(gdouble f) +{ + if (f < sim.radio.freq_min_hz) + return sim.radio.freq_min_hz; + + if (f > sim.radio.freq_max_hz) + return sim.radio.freq_max_hz; + + return f; +} + + +/** + * @brief round off a frequency to the simulator spectral resolution setting + * + * @param f the frequency in Hz to round off + * + * @returns the rounded off frequency in Hz + */ + +static gdouble sim_round_freq_to_res(gdouble f) +{ + return round(f / sim.radio.freq_inc_hz) * sim.radio.freq_inc_hz; +} + + +/** + * @brief get the number of data bins for a frequency range given the + * simulator configuration + * + * @param f0 the lower bound frequency in Hz + * @param f1 the upper bound frequency in Hz + * + * @returns the number of bins needed + * @note f0, f1 must be in order, clamped and rounded + */ + +static gsize sim_get_spec_bins(gdouble f0, gdouble f1) +{ + return (gsize) ((f1 - f0) / sim.radio.freq_inc_hz) + 1; +} + + +/** + * @brief create empty spectral data for a frequency range + * + * @param f0 the lower bound frequency in Hz + * @param f1 the upper bound frequency in Hz + * + * @returns the zeroed struct spec_data or NULL on error + * + * @note f0, f1 will be arranged so that always: f0 < f1 + * @note the range will be clamped to the configured simulator parameters + * @note the step size will be fixed to the configured simulator parameters + * @note since claming may happen, refer to s->freq_* for further processing + */ + + +static struct spec_data *sim_create_empty_spectrum(gdouble f0, gdouble f1) +{ + gsize bins; + + struct spec_data *s; + + const gsize data_size = sizeof(typeof(*((struct spec_data *) 0)->spec)); + + + sim_order_freq(&f0, &f1); + + f0 = sim_clamp_freq_range(f0); + f1 = sim_clamp_freq_range(f1); + + f0 = sim_round_freq_to_res(f0); + f1 = sim_round_freq_to_res(f1); + + bins = sim_get_spec_bins(f0, f1); + + if (bins > sim.radio.max_bins) { + g_warning(MSG "Computed bins %u > sim.radio.max_bins (%u)", + bins, sim.radio.max_bins); + } + + s = g_malloc0(sizeof(struct spec_data) + bins * data_size); + if (!s) + return NULL; + + + s->n = (typeof(s->n)) bins; + s->freq_min_hz = (typeof(s->freq_min_hz)) f0; + s->freq_max_hz = (typeof(s->freq_max_hz)) f1; + s->freq_inc_hz = (typeof(s->freq_inc_hz)) sim.radio.freq_inc_hz; + + return s; +} + + +/** + * @brief round off a velocity to the HI data resolution + * + * @param v the velocity (in km/s) to round off + * + * @returns the rounded off velocity in km/s + */ + +static gdouble HI_round_vel_to_res(gdouble v) +{ + v = v / SIM_V_RES_KMS; + + if (v > 0.0) + v = ceil(v); + else + v = floor(v); + + return v * SIM_V_RES_KMS; +} + + +/** + * @brief clamp a velocity into the available HI data range + * + * @param vel the input velocity (in km/s relative to HI data rest freq) + * + * @returns vel the range-clamped velocituy + */ + +static gdouble HI_clamp_vel_range(gdouble v) +{ + if (v > SIM_V_RED_KMS) + return SIM_V_RED_KMS; + + if (v < SIM_V_BLU_KMS) + return SIM_V_BLU_KMS; + + return v; +} + + +/** + * @brief get the vlsr-shifted velocity of the spectrum + * + * @param f the frequency + * @param gal the galactic coodrinates + * + * @returns the doppler velocity clamped into available HI data range + * + */ + +static gdouble HI_get_vel(double f, struct coord_galactic gal) +{ + gdouble v; + + v = -doppler_vel(f, SIM_V_REF_HZ); + v = v - vlsr(galactic_to_equatorial(gal), 0.0); + + v = HI_round_vel_to_res(v); + + return HI_clamp_vel_range(v); +} + + +/** + * @brief geht the bin (array) offset for a given velocity + * + * @param v the velocity in km/s + * + * @returns the bin offset in the HI data array for the given velocity + * + * @note v must be rounded and clamped to HI data range + */ + +static gsize HI_get_vel_bin(gdouble v) +{ + gsize bins; + + + bins = (gsize) (SIM_V_RED_KMS - v); + +#if 1 + /* account for zero-km/s bin */ + if (v >= 0.0) + bins += 1; +#endif + if (bins > SIM_HI_BINS) { + g_warning(MSG "something went wrong bins %d > %d max bins", + bins, SIM_HI_BINS); + + bins = SIM_HI_BINS; + } + + return bins; +} + + +/** + * @brief get the frequency given a velocity for the frequency reference + * + * @param v a velocity (in km/s) + * + * @returns the doppler frequency (in Hz) + * + * @note v must be in the valid HI data range + */ + +static gdouble HI_get_freq(double v) +{ + return doppler_freq(v, SIM_V_REF_HZ); +} + + +/** + * @brief fold GLAT into a circular range and round to HI base resolution + * + * @param lat a galactic latitude + * + * @returns the galactic latitude folded in range -90.0 to +90.0 + */ + +static gdouble HI_fold_glat_round(gdouble lat) +{ + lat = fmod(lat + 90.0, 180.0) - 90.0; + + /* the sky is a sphere :) */ + if (lat < -90.0) + lat = lat + 180.0; + + if (lat > 90.0) + lat = lat - 180.0; + + /* map to HI data grid */ + lat = round(( 1.0 / SKY_BASE_RES ) * lat) * SKY_BASE_RES; + + + return lat; +} + + +/** + * @brief fold GLON into a circular range and round to HI base resolution + * + * @param lon galactic longitude + * + * @returns the galactic longitude folded in range 0.0 to +360.0 + */ + +static gdouble HI_fold_glon_round(gdouble lon) +{ + lon = fmod(lon, 360.0); + + if (lon < 0.0) + lon = lon + 360.0; + if (lon > 360.0) + lon = lon - 360.0; + + /* map to HI data grid */ + lon = round(( 1.0 / SKY_BASE_RES ) * lon) * SKY_BASE_RES; + + + return lon; +} + + +/** + * @brief apply scale calibration to HI data for use with spec data + * + * @param val the value to calibrate + * + * @returns the calibrated value + * + * @note spectra data amplitude unit is milliKelvins + */ + +static gdouble HI_raw_amp_to_mKelvins(gdouble val) +{ + return val * SIM_HI_AMP_CAL; +} + + +/** + * @brief extract and compute get the spectrum for a given galatic lat/lon + * center for a given beam + * + * @param gal the galactic lat/lon + * @param red the red velocity + * @param blue the blue velocity + * @param beam an nxn array which describes the shape of the beam + * @param n the shape of the beam in data bins + * @param w the width of the area in degrees (== width of the beam) + * @param[out] n_elem the number of elements in the returned array + * + * @returns the requested spectrum or NULL on error + * + * @note all parameters must be valid for the given Hi data + */ + +static gdouble *HI_gen_conv_spec(struct coord_galactic gal, + gdouble red, gdouble blue, + const gdouble *beam, gsize n, gdouble w, + gsize *n_elem) +{ + gsize i; + gsize r, b; + gsize bw, bh; + gsize bins; + + gdouble x, y; + gdouble off, res; + gdouble lat, lon; + gdouble amp; + + gdouble *spec; + + gint16 *raw; + + + r = HI_get_vel_bin(red); + b = HI_get_vel_bin(blue); + + + bins = b - r + 1; + + spec = g_malloc0(bins * sizeof(double)); + + if (!spec) + return NULL; + + + off = 0.5 * w; + res = w / ((gdouble) n - 1.0); + + + bh = 0; + + for (x = -off; x <= off; x += res) { + + bw = 0; + + for (y = -off; y <= off; y += res) { + + lon = HI_fold_glon_round(gal.lon + x); + lat = HI_fold_glat_round(gal.lat + y); + + raw = sim_spec_extract_HI_survey(lat, lon); + + for (i = r; i <= b; i++) { + amp = (gdouble) raw[SIM_HI_BINS - i - 1]; + amp = amp * beam[bh * n + bw]; + spec[i - r] += amp; + } + + g_free(raw); + + bw++; /* beam width index */ + } + + bh++; /* beam height index */ + } + + for (i = 0; i < bins; i++) + spec[i] = HI_raw_amp_to_mKelvins(spec[i]); + + (*n_elem) = bins; + + + return spec; +} + +/** + * @brief get the index for a given frequency within the target spectrum + * + * @param s the target spec_data + * @param gal the galactic lat/lon + * @parma f the frequency in Hz + * + * @returns the index + * + * @note all inputs must be valid for the particular configuration + */ + +static gsize HI_get_spec_idx(struct spec_data *s, struct coord_galactic gal, + gdouble f) +{ + gsize idx; + + + f = f - (gdouble) s->freq_min_hz + vlsr(galactic_to_equatorial(gal), 0.0);; + f = f / (gdouble) s->freq_inc_hz; + + idx = (gsize) f; + + + /* adjust for actual spectrum */ + if (idx < 0) + idx = 0; + + if (idx > s->n) + idx = s->n; + + return (gsize) idx; +} + + +/** + * @brief stack a HI spectrum for a give GLAT/GLON and a beam on a base spectrum + * + * @param s the target spec_data + * + * @param gal the galactic lat/lon + * @param beam an nxn array which describes the shape of the beam + * @param n the shape of the beam in data bins + * @param w the width of the area in degrees (== width of the beam) + */ + +static void HI_stack_spec(struct spec_data *s, + struct coord_galactic gal, + const gdouble *beam, gint n, gdouble w) +{ + gsize bins; + gsize i, i0, i1; + + gdouble f0, f1; + gdouble red, blue; + + gdouble *spec; + + + + red = HI_get_vel((gdouble) s->freq_min_hz, gal); + blue = HI_get_vel((gdouble) s->freq_max_hz, gal); + + spec = HI_gen_conv_spec(gal, red, blue, beam, n, w, &bins); + + if (!spec) { + g_warning(MSG "could not extract HI spectrum"); + return; + } + + /* we need to know where we'll have to put the corresponding velocity + * bins within the spectrum + */ + + f0 = HI_get_freq(red); + f1 = HI_get_freq(blue); + + f0 = sim_round_freq_to_res(f0); + f1 = sim_round_freq_to_res(f1); + + /* find index offsets */ + i0 = HI_get_spec_idx(s, gal, f0); + i1 = HI_get_spec_idx(s, gal, f1); + + + for (i = i0; i <= i1; i++) + s->spec[i] = (typeof(*s->spec)) ((gdouble) s->spec[i] + + spec[i - i0]); + + + g_free(spec); +} + + +static gdouble *gauss_2d(gdouble sigma, gdouble r, gdouble res, gint *n); + + +/** + * @brief stack the CMB for a give GLAT/GLON and a beam on a base spectrum + * + * @param s the target spec_data + * + * @param gal the galactic lat/lon + * @param beam an nxn array which describes the shape of the beam + * @param n the shape of the beam in data bins + * @param w the width of the area in degrees (== width of the beam) + * + * @todo maybe add a data source (e.g. from Planck, such as found at + * https://irsa.ipac.caltech.edu/data/Planck/release_2/all-sky-maps/previews/COM_CMB_IQU-100-fgsub-sevem-field-Int_2048_R2.01_full/index.html + * for now, we just use a uniform CMB + */ + +static void sim_stack_cmb(struct spec_data *s, + struct coord_galactic gal, + const gdouble *beam, gint n, gdouble w) +{ + gsize i; + + + /* add CMB in mK */ + for (i = 0; i < s->n; i++) + s->spec[i] = (typeof(*s->spec)) ((double) s->spec[i] + 2725.); +} + + +/** + * @brief stack the system temperature on a base spectrum + * + * @param tsys a system temperature in Kelvins + * + * @param s the target spec_data + */ + +static void sim_stack_tsys(struct spec_data *s, gdouble tsys) +{ + gsize i; + + + tsys = tsys * 1000.0; /* to mK */ + + for (i = 0; i < s->n; i++) + s->spec[i] = (typeof(*s->spec)) ((gdouble) s->spec[i] + tsys); +} + + +/** + * @brief simulate the sun for a given GLAT/GLON and a beam + * + * @param gal the galactic lat/lon + * @param freq a frequency in Hz + * @param beam an nxn array which describes the shape of the beam + * @param n the shape of the beam in data bins + * @param w the width of the area in degrees (== width of the beam) + * + * @param returns the solar radio temperature + * + * @note this is a very simple solar model, where we assume that the + * smallest beam is 0.5 deg, so we can use the beam itself as the + * sun, ignoring any frequency-radius dependency and limb brightening. + * If this simulator is ever to be operated for higher resolutions, or + * frequency ranges significantly outside the neutral hydrogen range, + * this generator must be updated to do an actual convolution on a + * better solar base emission model. + * + * @warn this assumes that the beam's coordinate system's basis vector is in + * the same rotation as the horizon coordinate system + * + * @todo maybe also add an online data source for up-to-date flux values, e.g. + * ftp://ftp.swpc.noaa.gov/pub/lists/radio/rad.txt + * + */ + +static gdouble sim_sun(struct coord_galactic gal, gdouble freq, + const gdouble *beam, gint n, gdouble w) +{ + gsize i; + + gssize x, y; + + gdouble res; + + gdouble D; + gdouble A; + gdouble l; + gdouble T; + + struct coord_galactic sun; + + + sun = equatorial_to_galactic(sun_ra_dec(0.0)); - dia = gtk_message_dialog_new(NULL, - GTK_DIALOG_MODAL, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - "Please place sky_vel.dat in the " - "directory path this program is executed in."); + /* force onto grid */ + sun.lat = round(( 1.0 / SKY_BASE_RES ) * sun.lat) * SKY_BASE_RES; + sun.lon = round(( 1.0 / SKY_BASE_RES ) * sun.lon) * SKY_BASE_RES; - gtk_dialog_run(GTK_DIALOG(dia)); - gtk_widget_destroy(dia); + res = w / (gdouble) n; + x = (gssize) ((sun.lat - gal.lat) / res + 0.5 * (gdouble) n); + y = (gssize) ((sun.lon - gal.lon) / res + 0.5 * (gdouble) n); - exit(0); - /** XXX **/ - g_error("%s: error opening file", __func__); - return 0; - } + if ((x < 0) || (y < 0)) + return 0.0; + if ((x >= n) || (y >= n)) + return 0.0; + l = 299792458.0 / freq; - map = g_malloc(VEL * SKY_WIDTH * SKY_HEIGHT * sizeof(guint16)); + D = 1.22 * l / atan(2.0 * sim.r_beam / 180.0 * M_PI); - fread(map, sizeof(guint16), VEL * SKY_WIDTH * SKY_HEIGHT, data); - fclose(data); - } + A = 0.25 * D * D * M_PI; - as = g_malloc(VEL * sizeof(guint16)); - offset = get_offset(glat, glon); + /* convert flux to temperature + * sun is in solar flux units, i.e. 10^-22 W/m^2/Hz + */ - memcpy(as, &map[offset], sizeof(guint16) * VEL); + T = A * 0.5 * 1e-22 * sim.sun_sfu / 1.38064852e-23; - return as; + + /* beam is normalised to integral 1, scale temperature to center bin */ + T = T * beam[y * n + x] / beam[n/2 * n + n/2]; + + return T; } /** - * @brief build a gaussian convolution kernel for a beam of radius r + * @brief stack the sun for a given GLAT/GLON and a beam on a base spectrum + * + * @param s the target spec_data * - * @param r the beam radius in degrees + * @param gal the galactic lat/lon + * @param beam an nxn array which describes the shape of the beam + * @param n the shape of the beam in data bins + * @param w the width of the area in degrees (== width of the beam) */ -static gpointer gauss_kernel_spec(double r, double glat, double glon) + +static void sim_stack_sun(struct spec_data *s, struct coord_galactic gal, + const gdouble *beam, gint n, gdouble w) { - static double x, x0, x1; -static double bins; + gsize i; + + gdouble T; + + + T = sim_sun(gal, s->freq_min_hz, beam, n, w); - double binw; + /* convert to mK */ + T = 1000. * T; - int i, n; + for (i = 0; i < s->n; i++) + s->spec[i] = (typeof(*s->spec)) ((double) s->spec[i] + T); +} - double s; -static double intg, prev, tmp, total; +/** + * @brief simulate moon for a given GLAT/GLON and a beam + * + * @param gal the galactic lat/lon + * @param beam an nxn array which describes the shape of the beam + * @param n the shape of the beam in data bins + * @param w the width of the area in degrees (== width of the beam) + * + * @param returns the moon's radio temperature + * + * @note this is a very simple moon model, where we assume that the + * smallest beam is 0.5 deg, so we can use the beam itself as the + * moon + * + * @warn this assumes that the beam's coordinate system's basis vector is in + * the same rotation as the horizon coordinate system + * + */ -static double *conv; -static double *conv2; +static gdouble sim_moon(struct coord_galactic gal, const gdouble *beam, + gint n, gdouble w) +{ + gsize i; - /* our width-normalised kernel runs from -3 to +3 sigma */ + gssize x, y; - x1 = 3.0 / 2.0 * sqrt(2.0 * log(2.0)) * r; + gdouble res; - x0 = - x1; + gdouble E; + gdouble T; - /* need an extra center bin, does not count towards stepsize */ - bins = round((x1 - x0) / SKY_BASE_RES) + 1.0; - binw = (x1 - x0) / (bins - 1.0); + gdouble a, b; + gdouble costheta; + struct coord_galactic moon; + struct coord_galactic sun; - if(conv) - g_free(conv); - conv = g_malloc(bins * sizeof(double)); + moon = equatorial_to_galactic(moon_ra_dec(server_cfg_get_station_lat(), + server_cfg_get_station_lon(), + 0.0)); + /* force onto grid */ + moon.lat = round(( 1.0 / SKY_BASE_RES ) * moon.lat) * SKY_BASE_RES; + moon.lon = round(( 1.0 / SKY_BASE_RES ) * moon.lon) * SKY_BASE_RES; - x = x0; - total = 0.0; - for (i = 0; i < bins; i++) { - n = 0; + res = w / (gdouble) n; - s = x - binw * 0.5; + x = (gssize) ((moon.lat - gal.lat) / res + 0.5 * (gdouble) n); + y = (gssize) ((moon.lon - gal.lon) / res + 0.5 * (gdouble) n); - tmp = 0.0; - intg = 0.0; - prev = gauss_norm(s, r); - do { - s += SKY_GAUSS_INTG_STP; - /* simple numerical integration */ - intg += SKY_GAUSS_INTG_STP * 0.5 * ((gauss_norm(s, r) + prev)); - prev = intg; - } while (s < (x + binw * 0.5)); + if ((x < 0) || (y < 0)) + return 0.0; - conv[i] = prev; + if ((x >= n) || (y >= n)) + return 0.0; - total += intg; + /* approximate the reflected solar energy per unit area via the + * solar constant in Earth's neighbourhood, reduced by the angle + * between the surface of the moon facing the earth and the sun + * (varname I is reserved due to complex.h) + */ - x += binw; + sun = equatorial_to_galactic(sun_ra_dec(0.0)); - } + a = sun.lat - moon.lat; + b = sun.lon - moon.lon; - /* oder conv[i] dividieren! */ - gdouble norm = 2.0 * bins * total; + costheta = (sun.lon * sun.lon + sun.lat * sun.lat) / euclid_norm(a, b); + costheta = fmod(costheta - 180.0, 360.) / 180. * M_PI; - if (conv2) - g_free(conv2); - conv2 = g_malloc(bins * bins * sizeof(double)); - int j; - for (i = 0; i < (int) bins; i++) - for (j = 0; j < (int) bins; j++) { - conv2[i* (int) bins + j] = (conv[i] + conv[j]) / norm; - } + /* average the moon temperature across all degrees of latitude by + * divding by pi + */ + E = 1366.0 * costheta / M_PI; - /* strategy: allocate columns * rows in flat array, fill with - * convolution kernel rows (2d gaussian has radial symmetry and can be convolve - * by application to rows+columns; add data in same fashion, 2d-indexed ragged type: - * compute once, apply continuously; - * once, convolved, sum along 2d direction for final spectrum - * + /* for emissivity ~ reflectance, we get the Moon's surface temperature + * via the Stefan-Boltzmann equation */ + T = pow((E / 5.67e-8), 0.25); - gdouble *res; - static gint16 *raw; + /* scale to beam/moon ratio (apparent diameter ~0.5 deg) */ - double l, b; - double dl, db; - gdouble lat, lon; + T = T * 0.25 * 0.25 / (sim.r_beam * sim.r_beam); + /* beam is normalised to integral 1, scale temperature to center bin */ + T = T * beam[y * n + x] / beam[n/2 * n + n/2]; - res = g_malloc0(VEL * sizeof(double)); + return T; +} - gint idxl=0; - gint idxb=0; - /* coordinates needed */ - for (l = x0; l <= x1; l+=binw) { - idxb = 0; - for (b = x0; b <= x1; b+=binw) { +/** + * @brief stack the moon for a given GLAT/GLON and a beam on a base spectrum + * + * @param s the target spec_data + * + * @param gal the galactic lat/lon + * @param beam an nxn array which describes the shape of the beam + * @param n the shape of the beam in data bins + * @param w the width of the area in degrees (== width of the beam) + * + * @note this is a very simple moon model, where we assume that the + * smallest beam is 0.5 deg, so we can use the beam itself as the + * moon + * + * @warn this assumes that the beam's coordinate system's basis vector is in + * the same rotation as the horizon coordinate system + * + */ - /* get needed coordinate */ - lat = glat + b; - lon = glon + l; +static void sim_stack_moon(struct spec_data *s, struct coord_galactic gal, + const gdouble *beam, gint n, gdouble w) +{ + gsize i; + gdouble T; - /* fold lat into 180 deg range */ - lat = fmod(lat + 90.0, 180.0) - 90.0; - /* same for lon */ - lon = fmod(lon, 360.0); + T = sim_moon(gal, beam, n, w); - /* the sky is a sphere :) */ - if (lat < -90.0) - lat = lat + 180.0; - if (lat > 90.0) - lat = lat - 180.0; + /* convert to mK */ + T = 1000. * T; - if (lon < 0.0) - lon = lon + 360.0; - if (lon > 360.0) - lon = lon - 360.0; + for (i = 0; i < s->n; i++) + s->spec[i] = (typeof(*s->spec)) ((double) s->spec[i] + T); +} - /* data is reverse? */ - //lon = 360.0 - lon; +/** + * @brief stack system efficiency on a base spectrum + * + * @param s the target spec_data + * @param eff a efficiency multiplier + */ - /*** - * strategy for image creation: sum up vel range first, - * then store. Apply convolution to stored data - */ +static void sim_stack_eff(struct spec_data *s, gdouble eff) +{ + gsize i; + for (i = 0; i < s->n; i++) + s->spec[i] = (typeof(*s->spec)) ((gdouble) s->spec[i] * eff); +} - /* map to data grid */ - lat = round(( 1.0 / SKY_BASE_RES ) * lat) * SKY_BASE_RES; - lon = round(( 1.0 / SKY_BASE_RES ) * lon) * SKY_BASE_RES; +/** + * @brief generate approximate gaussian noise + * + * @note we want something decently fast, this Box-Muller transform + * appears to work quite well + */ - raw = sim_spec_extract_HI_survey(lat, lon); - /* get the corresponding coordinate */ +static gdouble sim_rand_gauss(void) +{ + static gboolean gen; + static gdouble u, v; - for (i = 0; i < VEL; i++) - res[i] += ((gdouble) raw[i]) * conv2[idxl * (int) bins + idxb]; + gen = !gen; - g_free(raw); - idxb++; - } - idxl++; - } + if (!gen) + return sqrt(- 2.0 * log(u)) * cos(2.0 * M_PI * v); + + u = (rand() + 1.0) / (RAND_MAX + 2.0); + v = rand() / (RAND_MAX + 1.0); + + return sqrt(-2.0 * log(u)) * sin(2.0 * M_PI * v); +} + + + +/** + * @brief stack system noise on a base spectrum + * + * @param s the target spec_data + * @param sig a sigma mutliplier for the noise + * + * @note the noise is scaled by the sqrt() of the signal amplitude + */ + +static void sim_stack_gnoise(struct spec_data *s, gdouble sig) +{ + gsize i; + + gdouble amp; -#if 0 - for (i = 0; i < VEL; i++) - res[i] /= norm; -#endif - return res; + for (i = 0; i < s->n; i++) { + amp = (gdouble) s->spec[i]; + amp = amp + sqrt(amp) * sig * sim_rand_gauss(); + s->spec[i] = (typeof(*s->spec)) amp; + } } + /** * @brief transpose a two-dimensional array of complex doubles * @@ -1188,100 +1901,6 @@ static gdouble *sim_rt_get_HI_img(gint vmin, gint vmax) } -/** - * @brief returns an NxN normalised gaussian convolution kernel for a given - * sigma cutoff and resolution in degrees - * - * @note bins is always uneven (need center bin) - */ - -static gdouble *gauss_conv_kernel(gdouble r, gdouble sigma, gdouble res, - gint *n) -{ - gint i, j; - gint bins; - - gdouble x; - gdouble x0; - gdouble x1; - - gdouble binw; - - gdouble tot; - gdouble norm; - - double *conv; - - double *conv2; - - - - /* our width-normalised kernel runs from -sigma to +sigma */ - x1 = sigma / 2.0 * sqrt(2.0 * log(2.0)) * r; - x0 = - x1; - x = x0; - - /* need an extra center bin, does not count towards stepsize */ - bins = (gint) (round((x1 - x0) / res)); - - if (!(bins & 0x1)) - bins++; - - - binw = (x1 - x0) / ((gdouble) (bins - 1)); - - - /* 1d convolution profile */ - conv = g_malloc(bins * sizeof(gdouble)); - - - tot = 0.0; - - for (i = 0; i < bins; i++) { - - gdouble s; - gdouble intg; - gdouble prev; - - s = x - binw * 0.5; - - intg = 0.0; - prev = gauss_norm(s, r); - - /* simple numerical integration */ - do { - s += SKY_GAUSS_INTG_STP; - intg += SKY_GAUSS_INTG_STP * 0.5 * ((gauss_norm(s, r) + prev)); - prev = intg; - } while (s < (x + binw * 0.5)); - - conv[i] = prev; - - tot += intg; - - x += binw; - } - - - /* 2d kernel */ - conv2 = g_malloc(bins * bins * sizeof(double)); - - norm = 2.0 * tot * bins; - - for (i = 0; i < bins; i++) - for (j = 0; j < bins; j++) - conv2[i * bins + j] = (conv[i] + conv[j]) / norm; - - - g_free(conv); - - - (*n) = bins; - - return conv2; -} - - /** * @brief returns an NxN normalised gaussian convolution kernel for a given * sigma cutoff and resolution in degrees @@ -1295,7 +1914,7 @@ static gdouble *gauss_conv_kernel(gdouble r, gdouble sigma, gdouble res, * (for no added visual benefit) */ -static gdouble *gauss_2d(gdouble r, gdouble sigma, gdouble res, gint *n) +static gdouble *gauss_2d(gdouble sigma, gdouble r, gdouble res, gint *n) { gint i, j; gint bins; @@ -1311,7 +1930,7 @@ static gdouble *gauss_2d(gdouble r, gdouble sigma, gdouble res, gint *n) /* our width-normalised kernel runs from -sigma to +sigma */ - x = sigma / 2.0 * sqrt(2.0 * log(2.0)) * r; + x = gauss_half_width_sigma_r(sigma, r); /* need an extra center bin, does not count towards stepsize */ bins = (gint) (round((2.0 * x) / res)); @@ -1340,7 +1959,7 @@ static gdouble *gauss_2d(gdouble r, gdouble sigma, gdouble res, gint *n) } /* normalise kernel */ - for (i = 0; i < bins; i++) + for (i = 0; i < bins * bins; i++) ker[i] /= sum; @@ -1483,10 +2102,18 @@ static void sim_gen_sky(void) vmax = v2; } + kernel = gauss_2d(3.0, sim.r_beam, SKY_BASE_RES, &n); + if (!conv) conv = g_malloc(SKY1_WIDTH * SKY1_HEIGHT * sizeof(double complex)); if (!raw_sky) { +#if 0 + gdouble T; + struct coord_galactic gal; + + gdouble w = 2.0 * gauss_half_width_sigma_r(3.0, sim.r_beam); +#endif raw_sky = sim_rt_get_HI_img(vmin, vmax); if (csky) { @@ -1494,6 +2121,45 @@ static void sim_gen_sky(void) csky =NULL; } +#if 0 + gal = equatorial_to_galactic(moon_ra_dec(server_cfg_get_station_lat(), + server_cfg_get_station_lon(), + 0.0)); + + T = sim_moon(gal, kernel, n, w); + + T = T * VEL * 100.; + + /* force onto grid */ + gal.lat = round(( 1.0 / SKY_BASE_RES ) * gal.lat) * SKY_BASE_RES; + gal.lon = round(( 1.0 / SKY_BASE_RES ) * gal.lon) * SKY_BASE_RES; + + gal.lat = 90. - gal.lat; + gal.lon = gal.lon - 90.; + + raw_sky[SKY1_WIDTH * ((int)gal.lat) * 2 + (int)gal.lon * 2] += T; + + + + gal = equatorial_to_galactic(sun_ra_dec(0.0)); + + T = sim_sun(gal, SIM_V_REF_HZ, kernel, n, w); + + T = T * VEL * 100.; + g_message("at %g %g T %g", gal.lat, gal.lon, T); + + /* force onto grid */ + gal.lat = round(( 1.0 / SKY_BASE_RES ) * gal.lat) * SKY_BASE_RES; + gal.lon = round(( 1.0 / SKY_BASE_RES ) * gal.lon) * SKY_BASE_RES; + + gal.lat = gal.lat + 90.; + gal.lon = gal.lon - 90.; + g_message("at %g %g T %g", gal.lat, gal.lon, T); + + raw_sky[SKY1_WIDTH * ((int)gal.lat) * 2 + (int)gal.lon * 2] += T; +#endif + + csky = g_malloc0(SKY1_WIDTH * SKY1_HEIGHT * sizeof(double complex)); double complex *tmp = g_malloc0(SKY1_WIDTH * SKY1_HEIGHT * sizeof(double complex)); @@ -1505,7 +2171,6 @@ static void sim_gen_sky(void) #if 1 - kernel = gauss_2d(sim.r_beam, 3.0, SKY_BASE_RES, &n); #else kernel = gauss_conv_kernel(sim.r_beam, 3.0, SKY_BASE_RES, &n); #endif @@ -1635,6 +2300,23 @@ static gboolean sim_spb_eff_value_changed_cb(GtkSpinButton *sb, gpointer data) sim.eff = gtk_spin_button_get_value(sb); } +static gboolean sim_spb_sig_n_value_changed_cb(GtkSpinButton *sb, gpointer data) +{ + sim.sig_n = gtk_spin_button_get_value(sb); +} + +static gboolean sim_spb_readout_hz_value_changed_cb(GtkSpinButton *sb, gpointer data) +{ + sim.readout_hz = gtk_spin_button_get_value(sb); +} + +static gboolean sim_sun_sfu_value_changed_cb(GtkSpinButton *sb, gpointer data) +{ + sim.sun_sfu = gtk_spin_button_get_value(sb); +} + + + static void sim_rt_gui_defaults(void) { @@ -1699,12 +2381,30 @@ static GtkWidget *sim_rt_par_gui(void) g_signal_connect(GTK_SPIN_BUTTON(w), "value-changed", G_CALLBACK(sim_spb_tsys_value_changed_cb), NULL); + w = gtk_label_new("SIG"); + gtk_widget_set_halign(w, GTK_ALIGN_START); + gtk_widget_set_halign(w, GTK_ALIGN_START); + gtk_label_set_xalign(GTK_LABEL(w), 0.0); + gtk_grid_attach(GTK_GRID(grid), w, 0, 2, 1, 1); + + w = gtk_spin_button_new_with_range(0., 20.0, 0.1); + gtk_entry_set_alignment(GTK_ENTRY(w), 1.0); + gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(w), TRUE); + gtk_spin_button_set_digits(GTK_SPIN_BUTTON(w), 1); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), SIM_SIG_NOISE); + gtk_widget_set_halign(w, GTK_ALIGN_FILL); + gtk_widget_set_hexpand(w, FALSE); + gtk_grid_attach(GTK_GRID(grid), w, 1, 2, 1, 1); + + g_signal_connect(GTK_SPIN_BUTTON(w), "value-changed", + G_CALLBACK(sim_spb_sig_n_value_changed_cb), NULL); + w = gtk_label_new("EFF"); gtk_widget_set_halign(w, GTK_ALIGN_START); gtk_widget_set_halign(w, GTK_ALIGN_START); gtk_label_set_xalign(GTK_LABEL(w), 0.0); - gtk_grid_attach(GTK_GRID(grid), w, 0, 2, 1, 1); + gtk_grid_attach(GTK_GRID(grid), w, 0, 3, 1, 1); w = gtk_spin_button_new_with_range(0., 1.0, 0.1); gtk_entry_set_alignment(GTK_ENTRY(w), 1.0); @@ -1713,7 +2413,7 @@ static GtkWidget *sim_rt_par_gui(void) gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), SIM_EFF); gtk_widget_set_halign(w, GTK_ALIGN_FILL); gtk_widget_set_hexpand(w, FALSE); - gtk_grid_attach(GTK_GRID(grid), w, 1, 2, 1, 1); + gtk_grid_attach(GTK_GRID(grid), w, 1, 3, 1, 1); g_signal_connect(GTK_SPIN_BUTTON(w), "value-changed", G_CALLBACK(sim_spb_eff_value_changed_cb), NULL); @@ -1724,7 +2424,7 @@ static GtkWidget *sim_rt_par_gui(void) gtk_widget_set_halign(w, GTK_ALIGN_START); gtk_widget_set_halign(w, GTK_ALIGN_START); gtk_label_set_xalign(GTK_LABEL(w), 0.0); - gtk_grid_attach(GTK_GRID(grid), w, 0, 3, 1, 1); + gtk_grid_attach(GTK_GRID(grid), w, 0, 4, 1, 1); w = gtk_spin_button_new_with_range(-90., 90., 0.01); gtk_entry_set_alignment(GTK_ENTRY(w), 1.0); @@ -1733,7 +2433,7 @@ static GtkWidget *sim_rt_par_gui(void) gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), server_cfg_get_station_lat()); gtk_widget_set_halign(w, GTK_ALIGN_FILL); gtk_widget_set_hexpand(w, FALSE); - gtk_grid_attach(GTK_GRID(grid), w, 1, 3, 1, 1); + gtk_grid_attach(GTK_GRID(grid), w, 1, 4, 1, 1); g_signal_connect(GTK_SPIN_BUTTON(w), "value-changed", @@ -1743,7 +2443,7 @@ static GtkWidget *sim_rt_par_gui(void) gtk_widget_set_halign(w, GTK_ALIGN_START); gtk_widget_set_halign(w, GTK_ALIGN_START); gtk_label_set_xalign(GTK_LABEL(w), 0.0); - gtk_grid_attach(GTK_GRID(grid), w, 0, 4, 1, 1); + gtk_grid_attach(GTK_GRID(grid), w, 0, 5, 1, 1); w = gtk_spin_button_new_with_range(-180., 180., 0.01); gtk_entry_set_alignment(GTK_ENTRY(w), 1.0); @@ -1752,12 +2452,49 @@ static GtkWidget *sim_rt_par_gui(void) gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), server_cfg_get_station_lon()); gtk_widget_set_halign(w, GTK_ALIGN_FILL); gtk_widget_set_hexpand(w, FALSE); - gtk_grid_attach(GTK_GRID(grid), w, 1, 4, 1, 1); + gtk_grid_attach(GTK_GRID(grid), w, 1, 5, 1, 1); g_signal_connect(GTK_SPIN_BUTTON(w), "value-changed", G_CALLBACK(sim_spb_lon_value_changed_cb), NULL); + w = gtk_label_new("Rate [Hz]"); + gtk_widget_set_halign(w, GTK_ALIGN_START); + gtk_widget_set_halign(w, GTK_ALIGN_START); + gtk_label_set_xalign(GTK_LABEL(w), 0.0); + gtk_grid_attach(GTK_GRID(grid), w, 0, 6, 1, 1); + + w = gtk_spin_button_new_with_range(0.1, 32.0, 1.0); + gtk_entry_set_alignment(GTK_ENTRY(w), 1.0); + gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(w), TRUE); + gtk_spin_button_set_digits(GTK_SPIN_BUTTON(w), 1); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), SIM_READOUT_HZ); + gtk_widget_set_halign(w, GTK_ALIGN_FILL); + gtk_widget_set_hexpand(w, FALSE); + gtk_grid_attach(GTK_GRID(grid), w, 1, 6, 1, 1); + + g_signal_connect(GTK_SPIN_BUTTON(w), "value-changed", + G_CALLBACK(sim_spb_readout_hz_value_changed_cb), NULL); + + w = gtk_label_new("Sun [SFU]"); + gtk_widget_set_halign(w, GTK_ALIGN_START); + gtk_widget_set_halign(w, GTK_ALIGN_START); + gtk_label_set_xalign(GTK_LABEL(w), 0.0); + gtk_grid_attach(GTK_GRID(grid), w, 0, 7, 1, 1); + + w = gtk_spin_button_new_with_range(1., 200., 1.0); + gtk_entry_set_alignment(GTK_ENTRY(w), 1.0); + gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(w), TRUE); + gtk_spin_button_set_digits(GTK_SPIN_BUTTON(w), 1); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), SIM_SUN_SFU); + gtk_widget_set_halign(w, GTK_ALIGN_FILL); + gtk_widget_set_hexpand(w, FALSE); + gtk_grid_attach(GTK_GRID(grid), w, 1, 7, 1, 1); + + g_signal_connect(GTK_SPIN_BUTTON(w), "value-changed", + G_CALLBACK(sim_sun_sfu_value_changed_cb), NULL); + + return frame; } @@ -1981,11 +2718,9 @@ static uint32_t sim_spec_acquire(struct observation *obs) struct coord_horizontal hor; struct coord_galactic gal; - gdouble *rawspec; gint16 *rawu; - //sim_gen_sky(); #if 0 if (!obs->acq.acq_max) return 0; @@ -1994,100 +2729,75 @@ static uint32_t sim_spec_acquire(struct observation *obs) hor.el = sim.el.cur; gal = horizontal_to_galactic(hor, server_cfg_get_station_lat(), server_cfg_get_station_lon()); -#if 0 - gal.lat = 0; - gal.lon = 180.0; -#endif + st.busy = 1; st.eta_msec = (typeof(st.eta_msec)) 20.0; /* whatever */ ack_status_rec(PKT_TRANS_ID_UNDEF, &st); -#if 1 /* conv */ + static gdouble *beam; + static gdouble r; + static gdouble sky_deg; + static gsize n_vel; + static gint n_beam; + static gdouble glat; + static gdouble glon; - int v1 = (int) DOPPLER_VEL(g_obs.acq.freq_stop_hz, SIM_V_REF_MHZ * 1e6) + SIM_V_RED_KMS +2 + vlsr(galactic_to_equatorial(gal), 0.0); - int v0 = (int) DOPPLER_VEL(g_obs.acq.freq_start_hz, SIM_V_REF_MHZ * 1e6) + SIM_V_RED_KMS + vlsr(galactic_to_equatorial(gal), 0.0); - -// g_message("vmin: %d vmax %d", v0, v1); - if (v0 < 0) - v0 = 0; - if (v1 > VEL) - v1 = VEL; -// g_message("vmin: %d vmax %d\n", v0, v1); + gal.lat = round(( 1.0 / SKY_BASE_RES ) * gal.lat) * SKY_BASE_RES; + gal.lon = round(( 1.0 / SKY_BASE_RES ) * gal.lon) * SKY_BASE_RES; + if (r != sim.r_beam || !beam) { + r = sim.r_beam; - /* prepare and send: allocate full length */ - s = g_malloc0(sizeof(struct spec_data) + (v1 - v0) * sizeof(uint32_t)); - - rawspec = gauss_kernel_spec(sim.r_beam, gal.lat, gal.lon); -#if 0 - s->freq_min_hz = (typeof(s->freq_min_hz)) DOPPLER_FREQ((v0 - SIM_V_RED_KMS ), SIM_V_REF_MHZ * 1e6); - s->freq_max_hz = (typeof(s->freq_max_hz)) DOPPLER_FREQ((v1 - SIM_V_RED_KMS - 2 ), SIM_V_REF_MHZ * 1e6); -#endif - s->freq_min_hz = (typeof(s->freq_min_hz)) g_obs.acq.freq_start_hz; - s->freq_max_hz = (typeof(s->freq_max_hz)) g_obs.acq.freq_stop_hz; - s->freq_inc_hz = (typeof(s->freq_inc_hz)) SIM_FREQ_STP_HZ; -#if 1 - s->freq_min_hz = round(s->freq_min_hz / s->freq_inc_hz) * s->freq_inc_hz; - s->freq_max_hz = round(s->freq_max_hz / s->freq_inc_hz) * s->freq_inc_hz; -#endif - - //srand(time(0)); - for (i = v0; i < v1; i++) { - /* reverse or not? */ - s->spec[i- v0] = (uint32_t) ((double) rawspec[VEL - i - 1] * 10. * sim.eff + sim.tsys * 1000.); /* to mK fom cK + tsys*/ - // s->spec[i] = (uint32_t) rawspec[i] *10 + 200000; /* to mK fom cK + tsys*/ - s->spec[i-v0] += (int) (GNOISE * sqrt((double) s->spec[i-v0] * 15.)); + if (beam) { + g_free(beam); + beam = NULL; + } - s->n++; + sky_deg = 2.0 * gauss_half_width_sigma_r(3.0, sim.r_beam); + beam = gauss_2d(3.0, sim.r_beam, SKY_BASE_RES, &n_beam); } - g_free(rawspec); + if ((glat != gal.lat) || (glon != gal.lon)) + glat = gal.lat; - /* handover for transmission */ - ack_spec_data(PKT_TRANS_ID_UNDEF, s); + s = sim_create_empty_spectrum(g_obs.acq.freq_start_hz, + g_obs.acq.freq_stop_hz); -#else - /* noconv */ + if (!s) { + g_warning(MSG "could not create spectral data"); + return 0; + } + /* NOTE: values in s->spec must ALWAYS be >0, since it is (usually) + * a uin32_t. Adding the CMB temperature is a good place to start + */ + sim_stack_cmb(s, gal, beam, n_beam, sky_deg); - /* prepare and send: allocate full length */ - s = g_malloc0(sizeof(struct spec_data) + VEL * sizeof(uint32_t)); - rawu = sim_spec_extract_HI_survey(gal.lat, gal.lon); + HI_stack_spec(s, gal, beam, n_beam, sky_deg); + sim_stack_moon(s, gal, beam, n_beam, sky_deg); - s->freq_min_hz = (typeof(s->freq_min_hz)) DOPPLER_FREQ((SIM_V_RED_KMS + vlsr(galactic_to_equatorial(gal), 0.0)), SIM_V_REF_MHZ * 1e6); - s->freq_max_hz = (typeof(s->freq_max_hz)) DOPPLER_FREQ((SIM_V_BLU_KMS + vlsr(galactic_to_equatorial(gal), 0.0)), SIM_V_REF_MHZ * 1e6); - s->freq_inc_hz = (typeof(s->freq_inc_hz)) SIM_FREQ_STP_HZ; + sim_stack_sun(s, gal, beam, n_beam, sky_deg); - //srand(time(0)); - for (i = 0; i < VEL; i++) { - s->spec[i] = (uint32_t) rawu[VEL - i - 1] * 10 + 300000; /* to mK from cK + tsys*/ - // s->spec[i] += (int) (GNOISE * 1000.); + sim_stack_eff(s, sim.eff); - s->n++; - } + sim_stack_tsys(s, sim.tsys); - g_free(rawu); + sim_stack_gnoise(s, sim.sig_n); /* handover for transmission */ ack_spec_data(PKT_TRANS_ID_UNDEF, s); -#endif - - - - - st.busy = 0; st.eta_msec = 0; @@ -2098,7 +2808,7 @@ static uint32_t sim_spec_acquire(struct observation *obs) cleanup: g_free(s); - g_usleep(G_USEC_PER_SEC / 32); + g_usleep(G_USEC_PER_SEC / sim.readout_hz); return obs->acq.acq_max; }