Fisher Forecasts

HIcosmo provides professional Fisher matrix analysis tools, supporting 21cm intensity mapping survey forecasts, multi-probe joint analysis, and parameter constraint predictions.

Fisher Matrix Fundamentals

Fisher Information Matrix

The Fisher information matrix quantifies the curvature of the likelihood function in parameter space, defined as:

\[F_{ij} = -\left\langle \frac{\partial^2 \ln \mathcal{L}}{\partial \theta_i \partial \theta_j} \right\rangle\]

For a Gaussian likelihood, the Fisher matrix can be computed from the covariance matrix \(C\) and the theoretical prediction \(\mu\):

\[F_{ij} = \frac{1}{2} \text{Tr}\left[ C^{-1} \frac{\partial C}{\partial \theta_i} C^{-1} \frac{\partial C}{\partial \theta_j} \right] + \frac{\partial \mu^T}{\partial \theta_i} C^{-1} \frac{\partial \mu}{\partial \theta_j}\]

Parameter Constraints

The \(1\sigma\) constraints on parameters are given by the covariance matrix:

\[\text{Cov}(\theta) = F^{-1}\]
\[\sigma(\theta_i) = \sqrt{(F^{-1})_{ii}}\]

Marginalized constraints: When nuisance parameters exist, the constraints on target parameters require integration over nuisance parameters:

\[\text{Cov}_{\text{target}} = (F^{-1})_{\text{target block}}\]

Conditional constraints: Constraints are tighter when other parameters are fixed:

\[\sigma_{\text{cond}}(\theta_i) = \frac{1}{\sqrt{F_{ii}}}\]

Quick Start

3 lines to complete a Fisher forecast:

from hicosmo.models import CPL
from hicosmo.fisher import IntensityMappingFisher

result = IntensityMappingFisher.forecast('ska1_mid_band2', CPL(), ['w0', 'wa'])
print(result)

IntensityMappingFisher Class

IntensityMappingFisher is the core class for 21cm intensity mapping Fisher forecasts, implementing the Bull et al. (2016) method.

Instance Interface

Use the instance interface when multiple forecasts are needed:

from hicosmo.fisher import IntensityMappingFisher, load_survey
from hicosmo.models import CPL

# Load survey configuration
survey = load_survey('ska1_mid_band2')
cosmology = CPL(H0=67.36, Omega_m=0.3153, w0=-1.0, wa=0.0)

# Create Fisher calculator
fisher = IntensityMappingFisher(survey, cosmology)

# Multiple forecasts
result1 = fisher.parameter_forecast(['w0', 'wa'])
result2 = fisher.parameter_forecast(['H0', 'Omega_m'])

With Marginalization

Marginalize over nuisance parameters:

result = IntensityMappingFisher.forecast(
    'ska1_mid_band2',
    cpl_model,
    params=['w0', 'wa'],
    marginalize_over=['H0', 'Omega_m', 'gamma']
)

print(f"σ(w0) = {result.errors['w0']:.4f}")
print(f"σ(wa) = {result.errors['wa']:.4f}")

FisherForecastResult Object

The result object returned by forecast() contains:

Attribute

Type

Description

params

list

List of parameter names

errors

dict

\(1\sigma\) constraints {param: σ}

fisher_matrix

ndarray

Fisher matrix

covariance

ndarray

Covariance matrix

correlation

ndarray

Correlation matrix

survey_name

str

Survey name

n_bins

int

Number of redshift bins

# Access results
print(f"σ(w0) = {result.errors['w0']:.4f}")
print(f"w0-wa correlation coefficient: {result.correlation[0,1]:.3f}")

# Convert to dictionary (compatible with legacy API)
data = result.to_dict()

Survey Configurations

HIcosmo includes built-in configurations for multiple 21cm intensity mapping surveys.

Available Surveys

from hicosmo.fisher import list_available_surveys

surveys = list_available_surveys()
print(surveys)
# ['ska1_mid_band2', 'ska1_wide_band1', 'tianlai', 'bingo', 'meerkat', 'chime', ...]

Survey

Redshift Range

Features

ska1_mid_band2

0.0 - 0.5

SKA1-MID medium-deep, Band 2

ska1_wide_band1

0.4 - 1.0

SKA1-MID wide field, Band 1

tianlai

0.8 - 1.2

Tianlai experiment, cylinder array

bingo

0.13 - 0.45

Brazilian 21cm project

meerkat

0.0 - 0.6

South African MeerKAT array

chime

0.8 - 2.5

Canadian CHIME cylinder array

Loading a Survey

from hicosmo.fisher import load_survey

survey = load_survey('ska1_mid_band2')

# View survey information
print(f"Name: {survey.name}")
print(f"Description: {survey.description}")
print(f"Redshift bins: {len(survey.redshift_bins)}")
print(f"Number of dishes: {survey.instrument.ndish}")
print(f"Survey area: {survey.instrument.survey_area_deg2} deg2")

Survey Configuration Format

Surveys are configured in YAML format (located in hicosmo/fisher/surveys/):

name: ska1_mid_band2
description: "SKA1-MID Medium-Deep Band 2 survey"

# Hardware configuration
instrument:
  telescope_type: single_dish
  ndish: 133
  dish_diameter_m: 15.0
  nbeam: 1

# Observing strategy
observing:
  survey_area_deg2: 5000.0
  total_time_hours: 10000.0
  channel_width_hz: 50000.0

# Noise parameters
noise:
  system_temperature_K: 13.5
  sky_temperature:
    model: power_law
    T_ref_K: 25.0
    nu_ref_MHz: 408.0
    beta: 2.75

# HI tracers
hi_tracers:
  bias:
    model: polynomial
    coefficients: [0.67, 0.18, 0.05]
  density:
    model: polynomial
    coefficients: [0.00048, 0.00039, -0.000065]

# Redshift binning
redshift_bins:
  default_delta_z: 0.1
  centers: [0.05, 0.15, 0.25, 0.35, 0.45]

21cm Physics

Brightness Temperature

21cm brightness temperature formula (Santos et al. 2015):

\[T_b(z) = 27 \, \text{mK} \times h \times \frac{\Omega_{\text{HI}}}{10^{-3}} \times \frac{(1+z)^2}{E(z)}\]

Where \(\Omega_{\text{HI}}\) is the HI density parameter and \(E(z) = H(z)/H_0\).

Power Spectrum

The 21cm power spectrum is:

\[P_{21}(k, \mu, z) = T_b^2 \, b^2 \, (1 + \beta \mu^2)^2 \, P_m(k, z)\]

Where: - \(b(z)\) is the HI bias factor - \(\beta = f/b\) is the redshift-space distortion parameter - \(f = d\ln D/d\ln a\) is the growth rate - \(\mu = \cos\theta\) is the cosine of the angle to the line of sight - \(P_m(k,z)\) is the matter power spectrum

Effective Volume

The Fisher information-weighted effective volume:

\[V_{\text{eff}}(k, \mu, z) = V_{\text{survey}} \left[ \frac{P_{\text{signal}}}{P_{\text{signal}} + P_{\text{noise}}} \right]^2\]

Noise Power Spectrum

Instrumental noise power spectrum:

\[P_{\text{noise}} = \frac{\sigma_{\text{pix}}^2 V_{\text{pix}}}{W^2(k_\perp)}\]

Where \(W(k_\perp)\) is the beam window function and \(\sigma_{\text{pix}}\) is the pixel noise.

FisherMatrix Class

For general Fisher matrix computations (not 21cm-specific), use the FisherMatrix class.

Basic Usage

from hicosmo.fisher import FisherMatrix, FisherMatrixConfig

# Configuration
config = FisherMatrixConfig(
    differentiation_method='automatic',  # Use JAX automatic differentiation
    regularization=True
)

# Create calculator
calculator = FisherMatrix(config)

# Compute Fisher matrix
fisher_matrix, param_names = calculator.compute_fisher_matrix(
    likelihood_func=my_likelihood,
    parameters=params,
    fiducial_values={'H0': 67.36, 'Omega_m': 0.315}
)

Configuration Options

from hicosmo.fisher import FisherMatrixConfig

config = FisherMatrixConfig(
    # Differentiation method
    differentiation_method='automatic',  # 'automatic', 'numerical', 'hybrid'
    step_size=1e-6,
    fallback_to_numerical=True,

    # Regularization
    regularization=True,
    reg_lambda=1e-10,
    svd_threshold=1e-12,

    # Validation
    check_symmetry=True,
    check_positive_definite=True,

    # Performance
    vectorize_derivatives=True,
    cache_derivatives=True
)

Matrix Combination

Combine multi-probe Fisher matrices:

# Compute Fisher matrices for each probe
fisher_bao, params_bao = calculator.compute_fisher_matrix(bao_likelihood, ...)
fisher_sn, params_sn = calculator.compute_fisher_matrix(sn_likelihood, ...)

# Combine
combined_fisher, combined_params = calculator.combine_fisher_matrices(
    [(fisher_bao, params_bao), (fisher_sn, params_sn)],
    probe_names=['BAO', 'SN']
)

Parameter Marginalization

# Marginalize over nuisance parameters
marginalized_fisher, remaining_params = calculator.marginalize_parameters(
    fisher_matrix,
    param_names,
    marginalize_over=['M_B', 'delta_M']
)

Dark Energy Figure of Merit

The constraining power of dark energy experiments is quantified by the Figure of Merit (FoM):

\[\text{FoM} = \frac{1}{\sqrt{\det(\text{Cov}_{w_0, w_a})}}\]

This is equivalent to the inverse of the area enclosed by the \(1\sigma\) contour in the \(w_0\)-\(w_a\) plane.

from hicosmo.models import CPL
from hicosmo.fisher import IntensityMappingFisher
import numpy as np

# Forecast w0-wa constraints
cpl = CPL(H0=67.36, Omega_m=0.3153, w0=-1.0, wa=0.0)
result = IntensityMappingFisher.forecast('ska1_mid_band2', cpl, ['w0', 'wa'])

# Compute FoM
fom = 1.0 / np.sqrt(np.linalg.det(result.covariance))
print(f"Dark Energy FoM: {fom:.1f}")

Growth Parameter Forecasts

HIcosmo supports growth parameter forecasts for modified gravity theories.

Growth Parameterization

The growth rate is parameterized as:

\[f(z) = \Omega_m(z)^{\gamma_0 + \gamma_1 z}\]

The deviation parameters are:

\[\eta(z) = \eta_0 + \eta_1 z\]

In General Relativity: \(\gamma_0 = 0.55\), \(\gamma_1 = \eta_0 = \eta_1 = 0\).

Growth Scenarios

HIcosmo implements the four growth scenarios from Bull et al. (2016):

from hicosmo.fisher.intensity_mapping import GrowthScenario

# Available scenarios
scenarios = [
    'gr',           # General Relativity (no free growth parameters)
    'gamma0_free',  # gamma_0 free, others fixed
    'gamma1_free',  # gamma_1 free, others fixed
    'eta0_free',    # eta_0 free, others fixed
    'eta1_free',    # eta_1 free, others fixed
    'all_free',     # All growth parameters free
]

# Use a growth scenario
scenario = GrowthScenario('gamma0_free')
print(f"Free parameters: {scenario.free_params}")
print(f"Fixed parameters: {scenario.fixed_params}")

Visualization

Visualizing Fisher forecast results:

from hicosmo.visualization import Plotter, plot_fisher_contours
import numpy as np

# Method 1: Using Plotter.from_fisher
plotter = Plotter.from_fisher(
    mean=result.cosmology_summary['fiducial'],
    covariance=result.covariance,
    param_names=result.params
)
plotter.corner(filename='forecast.pdf')

# Method 2: Using standalone function
plot_fisher_contours(
    mean=[-1.0, 0.0],
    covariance=result.covariance,
    param_names=['w0', 'wa'],
    filename='de_forecast.pdf'
)

Multi-Survey Comparison

from hicosmo.models import CPL
from hicosmo.fisher import IntensityMappingFisher
from hicosmo.visualization import Plotter

cpl = CPL()
surveys = ['ska1_mid_band2', 'tianlai', 'bingo']
results = []

for survey in surveys:
    result = IntensityMappingFisher.forecast(survey, cpl, ['w0', 'wa'])
    results.append(result)

# Comparison plot (using Plotter.from_fisher multiple times)
# Or manually generate Gaussian samples and use Plotter

Complete Examples

SKA1 Dark Energy Forecast

from hicosmo.models import CPL
from hicosmo.fisher import IntensityMappingFisher, list_available_surveys
import numpy as np

# Define the cosmological model
fiducial = CPL(
    H0=67.36,
    Omega_m=0.3153,
    Omega_b=0.0493,
    w0=-1.0,
    wa=0.0,
    sigma8=0.811,
    n_s=0.9649
)

# Forecast w0-wa constraints
result = IntensityMappingFisher.forecast(
    'ska1_mid_band2',
    fiducial,
    params=['w0', 'wa'],
    marginalize_over=['H0', 'Omega_m']
)

# Print results
print(result)

# Compute FoM
fom = 1.0 / np.sqrt(np.linalg.det(result.covariance))
print(f"\nDark Energy FoM: {fom:.1f}")

# Correlation coefficient
print(f"w0-wa correlation: {result.correlation[0,1]:.3f}")

Interacting Dark Energy Forecast

from hicosmo.models import ILCDM
from hicosmo.fisher import IntensityMappingFisher

# Interacting dark energy model
ilcdm = ILCDM(
    H0=67.36,
    Omega_m=0.3153,
    beta=0.001  # Interaction strength
)

# Tianlai constraints on beta
result = IntensityMappingFisher.forecast(
    'tianlai',
    ilcdm,
    params=['beta', 'H0', 'Omega_m']
)

print(f"Tianlai constraints on the interaction parameter:")
print(f"sigma(beta) = {result.errors['beta']:.4e}")

Multi-Survey Joint Analysis

from hicosmo.models import CPL
from hicosmo.fisher import IntensityMappingFisher, load_survey, FisherMatrix
import numpy as np

cpl = CPL()
surveys = ['ska1_mid_band2', 'tianlai', 'bingo']

# Collect Fisher matrices from each survey
fisher_matrices = []
for survey_name in surveys:
    result = IntensityMappingFisher.forecast(
        survey_name, cpl, ['w0', 'wa', 'H0', 'Omega_m']
    )
    fisher_matrices.append((result.fisher_matrix, result.params))

# Combine Fisher matrices
calculator = FisherMatrix()
combined_fisher, combined_params = calculator.combine_fisher_matrices(
    fisher_matrices,
    probe_names=surveys
)

# Compute joint constraints
combined_cov = np.linalg.inv(combined_fisher)
combined_errors = np.sqrt(np.diag(combined_cov))

print("Joint constraints:")
for i, param in enumerate(combined_params):
    print(f"  sigma({param}) = {combined_errors[i]:.4e}")

Performance Benchmarks

Operation

Time

Notes

Single-survey Fisher forecast

~0.5s

5 redshift bins

Full parameter forecast (with marginalization)

~1.2s

6 parameters

Automatic differentiation Hessian

~0.1s

JAX JIT

FAQ

Q: How to customize a survey configuration?

Create a YAML file and use IntensityMappingSurvey.from_file():

from hicosmo.fisher import IntensityMappingSurvey

survey = IntensityMappingSurvey.from_file('my_survey.yaml')

Q: What if the Fisher matrix is not positive definite?

Enable regularization:

from hicosmo.fisher import FisherMatrixConfig

config = FisherMatrixConfig(
    regularization=True,
    reg_lambda=1e-8
)

Q: How to compare different cosmological models?

Use the same survey configuration with different models:

from hicosmo.models import LCDM, wCDM, CPL

survey = 'ska1_mid_band2'

result_lcdm = IntensityMappingFisher.forecast(survey, LCDM(), ['H0', 'Omega_m'])
result_wcdm = IntensityMappingFisher.forecast(survey, wCDM(), ['H0', 'Omega_m', 'w0'])
result_cpl = IntensityMappingFisher.forecast(survey, CPL(), ['H0', 'Omega_m', 'w0', 'wa'])

Next Steps