Assumptions and limitations =========================== What this code takes for granted, what it does not model, and which numbers are choices rather than measurements. :doc:`physics` derives the criteria; this page says where they stop being trustworthy. It is deliberately blunt. A site search produces authoritative-looking areas and detector counts, and the only defence against those being over-read is writing down what is behind them. .. contents:: :local: :depth: 2 Numbers that are assumptions, not measurements ---------------------------------------------- These are the ones to check before quoting a result. .. list-table:: :header-rows: 1 :widths: 26 20 54 * - Parameter - Default - What it rests on * - ``beta``, the tau energy-loss constant - :math:`0.6\times10^{-6}\,(E/{\rm EeV})^{0.20}` cm²/g - Estimated from mass scaling, in the range (0.4–1.0)×10⁻⁶. **Does not affect a site search**: it enters tau range and survival through rock, which the search does not model, while the search weights by the decay length :math:`E/m\cdot c\tau`, which carries no beta. Set it with ``physics.set_tau_energy_loss()``. * - Geomagnetic declination - −6.9° (Arequipa IGRF) - **Constant across the DEM unless a model is supplied.** Inclination is derived from the DEM's own coordinates through a centred dipole, but the dipole is unreliable for declination (−0.2° against a measured −6.9°) and is deliberately not used for it. ``physics.set_declination_model()`` takes any callable and ``declination_from_grid()`` builds one from a NOAA export. * - ``decay_weight_by`` - ``flux`` - What weights the spectrum-folded decay term: the flux, the detector acceptance :math:`A(E)`, or their product — the event-rate integrand. Every number here was computed with ``flux``. Weighting by an ``A(E)`` *inferred* from a published integral curve is not safe: the inferred response absorbs everything the geometric model fails to reproduce at high energy. * - ``max_range_km`` - unset (= ``max_dist_km``) - How far each profile is walked. Column depth accumulates over the walk, so leaving it tied to the distance window makes the reported depth a property of where the walk stopped. Measured on TAMBO at Colca: walking to 20 km instead of 5 raised the reported depth **6.4×** and changed the selection not at all. At 60 km the same run kept 6.0% of directions against 17.5% — not a knob to maximise. * - ``grammage_band_fraction`` - 0.1 - How far down the Gaisser–Hillas profile still counts as a usable shower. A choice about detector capability, not a property of the shower. * - ``min_target_slope_deg`` - unset - When set for a canyon search, the floor separating a wall from a hillside. * - ``decay_spectral_index`` - 2.0 - The flux slope the decay term is folded against. May be pinned to a value or given as a ``(low, high)`` pair to marginalise over. Folding replaced a single representative energy, which was far worse: see below. * - ``min_score`` - 0.0 - A cut on a product of six components. See below. The two that dominate a canyon result -------------------------------------- Measured by ``oroscope-sensitivity`` against a Colca TAMBO baseline, varying one parameter at a time: .. list-table:: :header-rows: 1 :widths: 34 22 22 22 * - Parameter - Low - Baseline - High * - ``decay_spectral_index`` (folded) - 1.5 → 6853 - 2.0 → **10 437** - 2.7 → 11 349 * - ``min_score`` - 0.0 → 65 268 - 0.35 → **10 437** - 0.5 → **0** * - ``min_target_slope_deg`` - 0° → 18 622 - 25° → **10 437** - 35° → 2814 All three rows now share one baseline. They did not before — the first read 9717 and the other two 2056, three rows of one table quoting two different runs, which is the kind of thing a table makes invisible by putting the numbers in a column. **The decay term is now folded over the spectrum, and that fixed it.** Evaluated at a single representative energy it *was* the answer rather than an approximation to one: across TAMBO's own 3 PeV – 1 EeV reach the reported capacity ran from 10 878 to zero, because the decay length runs from 147 m to 49 km against a ~3 km crossing. Folded against the flux, the same result varies by 1.46× across a plausible range of spectral index. The remaining exposure is the index itself, and it can be marginalised rather than chosen. The full job is the number of detected events, :math:`\int \Phi(E)\,A(E)\,P_{\rm decay}(E)\,{\rm d}E`. What weights the fold is now selectable — ``decay_weight_by`` takes ``flux``, ``acceptance`` or ``flux_times_acceptance`` — but a *differential* acceptance :math:`A(E)` is still what no available table supplies; what ``data/`` holds are two integral curves, and :ref:`published-curve-scaling` says exactly what they can and cannot carry. **Every number on this page was computed with the flux alone.** ``acceptance`` is worth one caution of its own. It weights by :math:`A(E)\,{\rm d}E`, which is :math:`A(E)` against a *flat differential* flux — :math:`\gamma = 0`, harder than any astrophysical spectrum. It does not remove the spectral assumption, it replaces it: measured against TAMBO's rising :math:`A(E)`, **97.4% of the weight lands above 100 PeV**, against 31.3% for ``flux``. That is a large part of why weighting by acceptance alone empties a search. An :math:`A(E)` can be *inferred* from a published integral curve by dividing out the geometric model (:func:`oroscope.aperture.infer_response`), and it is not a safe weight on its own: for TAMBO the inferred response rises monotonically to the top of the published range, so weighting by it alone puts all the weight where the decay length is hundreds of kilometres against a 3 km canyon and the search returns **zero sites**. That is the model's high-energy shortfall being attributed to "response", not a statement about TAMBO. A real differential table remains the outstanding ask. **A product score has no safe threshold.** Six components each in :math:`[0,1]` multiply to a distribution piled up near zero, so ``min_score`` anywhere in the middle sits on a cliff — 6.3× the baseline at 0.0, zero at 0.5. A sweep across the cut puts the median candidate score near **0.13**, so the shipped 0.35 is already well into the tail: it keeps the top 17.8% by rank. Prefer ranking sites and taking the best :math:`N`; a weighted geometric mean would also spread the distribution. Reported area is not physics-accepted area ------------------------------------------- Three things stand between the accepted pixels and the number in the results file, and only the first is physics. **Morphological closing inflates.** Measured with a stride-1 control run at Colca, closing with a 1 km element more than doubles the area — **2.35×**. So a reported 4569 km² corresponds to about 1945 km² the physics actually accepted. Closing is not wrong: a site has to be a deployable region rather than a scatter of pixels. But the reported figure is an upper bound, and the two should not be conflated. **Candidate striding is unbiased in acceptance, at both element sizes.** ``candidate_stride: 5`` samples one pixel in five. Control runs give 58.414% against 58.415% for GRAND and 75.750% against 75.736% for TAMBO, so which pixels get *tested* is a fair sample either way. Those TAMBO figures used to read 17.491% and 17.494%. The acceptance did not change by a factor of four: the older pair counted candidates surviving *geometry and the score cut together*, because the pre-6.53 funnel recorded the post-cut total under the name ``directions accepted``. The row above is geometry alone. The score cut is a separate row and takes a further 17.8% of it. **But which pixels survive to be measured is another matter, and for TAMBO it costs 1.51×.** The mask is closed morphologically before any area is taken. Marking one pixel in five leaves gaps of five pixels — 154 m at Colca's 30.7 m resolution. GRAND's closing element is 1 km, thirty-two pixels, and bridges that without noticing. TAMBO's element is ``antenna_spacing_km``, and at the published **150 m** it is five pixels — just enough to reconnect most of what striding cut apart. At the 100 m this page used to quote it was three pixels and **could not**, the mask never reconnected, most regions fell below ``min_sub_array_size``, and the same penalty was **4.75×**. Measured with a stride-1 control at TAMBO's own settings on the Colca crop: .. list-table:: :header-rows: 1 :widths: 34 22 22 22 * - - stride 5 - stride 1 - * - directions accepted - 75.750% - 75.736% - unbiased * - **area** - **203.0 km²** - **307.2 km²** - **1.51× low** * - capacity - 10,437 - 15,806 - 1.51× low So **read TAMBO's Colca area as ~307 km² and its capacity as ~15,806**. The full-DEM figure of 1,036.9 km² is low for the same reason and by downsampling on top. The rule generalises: *the closing element must outrun the stride gap.* Every run now warns when it does not. The funnel's own closing factor — closed pixels over stride-corrected accepted pixels — conflates the two effects and should not be read as the inflation alone: for TAMBO it reports 0.97× at stride 5, where the stride-1 control, which has no gap to bridge, puts closing by itself at 1.28×. .. figure:: _static/stride_and_closing.gif :alt: A strided mask closed with elements smaller and larger than the stride gap :align: center :width: 78% The same accepted set, marked one pixel in five, then closed with elements either side of the gap. The transition is **at** the gap and it is abrupt, not gradual: a 3-pixel element recovers 0.04× of the accepted set and a 5-pixel one recovers 0.61×. Regenerate with ``python tools/make_animations.py --only stride_and_closing``. **Not every site in the file is in the result.** ``sites`` lists everything that cleared the area and capacity thresholds. With ``stop_at_target`` the selection stops once the target is met, and only the selection is in ``total_sites``, ``total_capacity`` and the exported raster. Each record carries ``selected``; filter on it before totalling anything, or the area and the site count will both be too large. **Area and capacity are measured on different grids.** Per-site ``area_km2`` comes from the downsampled map, capacity from the full-resolution mask. At ``downsample_factor`` greater than 1 a feature only a few pixels wide loses area it keeps detectors on — for a canyon strip that is a ~30% discrepancy. Use ``downsample_factor: 1`` for thin features, or read the two as measuring different things. .. _published-curve-scaling: Using published effective areas: what is corrected, and what is not ------------------------------------------------------------------- ``data/`` holds two curves supplied by the collaborations and hand-digitized from their figures. **It is in the source repository, not in the installed package** — the wheel carries code only, and the loaders take a path from the caller rather than looking for a bundled file, so an installed user points them at their own copy or at a clone: .. list-table:: :header-rows: 1 :widths: 22 18 14 20 26 * - file - quantity - units - energy range - array simulated * - ``tambo_aperture_fig3`` - aperture (:math:`A\Omega`) - m² sr - 0.35 PeV – 8.8 EeV - 5 000 units at 150 m, **Colca Canyon** * - ``grand_effective_area_fig25`` - effective area - cm² - 0.1 – 100 EeV - 10 000 antennas at 1 km, **HotSpot1** They are different quantities. TAMBO's has the solid angle folded in; GRAND's has been direction-*averaged*, so the solid angle is gone rather than included. They cannot be compared with one another without supplying a solid angle, and the GRAND file says so in its own header. **Each was produced by a simulation of one array at one site.** Oroscope changes both. Only one of those two differences can be corrected by arithmetic, and it is important to be exact about which. What is corrected: the size of the array ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An aperture scales with **instrumented ground**, so :func:`oroscope.aperture.array_scale_factor` applies .. math:: f = \frac{N_{\rm target}\, s_{\rm target}^2}{N_{\rm published}\, s_{\rm published}^2} Both terms are carried deliberately. Adding detectors at a fixed spacing adds ground and scales the aperture roughly linearly; adding them at a fixed footprint only makes the array denser, and past the point where it already samples the Cherenkov cone a denser array collects very little more. Scaling a densified array by its detector count alone would inflate the answer by exactly the factor by which it was densified — which is the error this project would have made, since ``antenna_spacing_km`` for TAMBO was 100 m against the published 150 m, a 2.25× density difference. **TAMBO now runs at 150 m to match the published simulation**, so the factor reduces to a plain ratio of detector counts. GRAND already matched at 1 km. The linearity is not assumed — it is checked against the source. The GRAND paper states that its 200 000-antenna curve is exactly 20× the 10 000-antenna one, and tracing both independently from the figure gives 19.9–20.1× across the resolved range. That is a direct confirmation, in the supplied data, that effective area is linear in array size at fixed spacing. What is **not** corrected: the site ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The published simulation carries its own terrain, and no operation on an integral curve can remove it. Folded into each curve and inseparable from it are that site's * distribution of **column depth** along accepted arrival directions, * distribution of **target distance** and **arrival elevation**, * **trigger geometry** — how the shower presents to that particular array on that particular ground. For TAMBO that terrain is Colca's walls. For GRAND it is "HotSpot1", a prototypical site rather than a surveyed one. So a scaled curve answers: *"what this many detectors would have achieved on the ground the simulation assumed"* — **not** *"what they will achieve on this ground"*. Applying the TAMBO curve to a canyon in Ancash imports Colca's rock, not Ancash's. There is a second, subtler trap in going further. Dividing a published curve by our geometric model to recover a response (:func:`oroscope.aperture.infer_response`) leaves a residual containing the tau exit probability — which oroscope **already scores separately**, through the ``depth`` component and :func:`oroscope.physics.tau_exit_probability`. Multiplying that residual back in would count the same physics twice. Where the scaling does and does not enter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The distinction matters for reading any number in this project: * **In the score, it does not enter at all.** :func:`oroscope.physics.spectrum_weighted_decay_probability` normalises its weights, so any constant multiplying :math:`A(E)` cancels exactly — ``flux_times_acceptance`` with a flat response reproduces ``flux`` to twelve decimal places. Only the *shape* of :math:`A(E)` in energy survives. Array size is therefore irrelevant to site ranking, which is the reassuring half: **ranking does not depend on this scaling being right.** * **In an absolute aperture, it is the whole of the normalisation.** That is the only place the array size, and hence the scaling, changes an answer. This is a workaround, not a simulation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Stated plainly, because it would be easy to read a scaled curve as more than it is. The right calculation is a full detector simulation at the candidate site with the candidate layout, producing :math:`A(E)` — ideally differential in arrival elevation and distance as well as energy — for *that* geometry. Everything above is a stand-in for that, and it is defensible only for the array-size factor, only at fixed spacing, and only while the candidate terrain is not wildly unlike the simulated terrain. What oroscope contributes in the meantime is the part that *is* determined by terrain and needs no simulation: usable area, accepted solid angle, and the analytic decay probability. Section 4.10 of the roadmap is the decision to proceed on that basis, and it rests on a real argument — a factor that depends on energy but not on site cancels when two sites are compared for the same experiment. The rankings stand on that. The absolute apertures wait on a differential table. Physics that is not modelled ---------------------------- * **Neutral-current regeneration.** Only charged-current attenuation is applied to the Earth chord, so the suppression of downgoing directions is somewhat overstated. * **No shower simulation.** Everything about shower development comes from a Gaisser–Hillas profile and an isothermal atmosphere. There is no Monte Carlo, no detector response, and no trigger model. * **No flux, no cross-section spectrum, no exposure.** The search reports geometry and acceptance-shaped quantities. Turning those into an event rate needs a flux and a response table, and :mod:`aperture` only estimates the folding. * **Column depth is bounded by the walk.** The profile is walked out to ``max_dist_km`` and the depth histogram accumulates over that path, so a short-range search reports a depth set by where the walk stopped rather than by the target's thickness. At TAMBO's 5 km this makes the depth band score ~1 for everything; it degrades gracefully, but it is not measuring the far wall. * **Isothermal atmosphere**, single scale height. Adequate for grammage over a few kilometres; not a substitute for a real profile at large zenith angles. * **Standard rock everywhere.** One density for the whole crust. Real geology varies, and column depth scales with it directly. Terrain and data limitations ---------------------------- * **The DEM is the world.** A 30 m SRTM or AW3D30 tile cannot resolve a cliff face, and the search inherits every artefact the DEM has — voids, stripes, vegetation canopy read as ground. Nodata is carried as NaN and excluded rather than interpolated. * **Sub-pixel detector spacing is permitted and is a continuum limit.** At spacings finer than the DEM's pixels, capacity is area divided by area-per-detector; the terrain mask cannot say whether those positions are individually usable. * **The layout is anchored, not fitted.** Detectors are placed from each site's bounding-box corner rather than optimised over placement, so capacity is an estimate for an arbitrarily-placed array, not the best achievable packing. * **No logistics beyond distance-to-road.** No slope stability, land access, power, or cost. * **Geographic coordinates only.** Pixel sizes come from a local flat-Earth conversion at the DEM's centre latitude; the tool does not reproject, and a projected DEM is not handled. Where the results have been checked ----------------------------------- Worth stating, because it bounds how much the rest should be trusted. * Terrain fixtures are **synthetic with closed-form answers** — slope, aspect, target distance and canyon geometry are known analytically, so tests assert against arithmetic rather than against a previous run. The fixtures themselves are verified before the code that uses them. * The far-wall slope measurement **recovers a canyon fixture's own wall slope exactly** at 15°, 25°, 35° and 45°, and at Colca it recovers 34.7–44.3° against a published ~40°. * Capacity **matches analytic lattice density to 2%** from 1000 m down to 60 m spacing, on both square and triangular grids. * Golden-file regression pins whole-pipeline output on synthetic terrain and a real DEM crop. What has *not* happened: no comparison against a collaboration's own simulated acceptance, and no end-to-end validation against a site chosen by other means. Until one of those exists, treat absolute numbers as internally consistent rather than externally verified. A prediction worth falsifying ----------------------------- The Earth-absorption treatment implies the effective arrival window **narrows with energy**: its lower edge climbs from −4.4° at 100 PeV to −0.9° at 10 EeV. If a collaboration's simulated window does not narrow that way, one of the two treatments has the absorption wrong. That is the most useful single check anyone with an independent simulation could run against this code.