Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
560489e
Added code.
transientlunatic May 5, 2025
1efa1b1
Merge branch 'master' of ssh://git.ligo.org/heron/heron
transientlunatic May 5, 2025
4c7b732
Fixes to the normalisation in inner products.
transientlunatic May 5, 2025
9a6b38d
Update code improved behaviour.
transientlunatic May 5, 2025
c96f433
Added new test
transientlunatic May 21, 2025
ce1d775
Added report.
transientlunatic May 21, 2025
07eca05
Fixed the heron report syntax.
transientlunatic May 21, 2025
979f3dd
Changed location of webdir.
transientlunatic May 21, 2025
1272867
Update the location of the pages directory.
transientlunatic May 21, 2025
bb200b5
Fix the name of the output file.
transientlunatic May 21, 2025
7d13598
Update plotting to fix variance check.
transientlunatic May 21, 2025
09cce57
Add png output of data plot.
transientlunatic May 21, 2025
3ed49e4
Add plotting to the injection code.
transientlunatic May 21, 2025
43f6829
Fix location of webdir.
transientlunatic May 21, 2025
82b2bb3
Add import for os
transientlunatic May 21, 2025
d863c4d
More updates and a better likelihood.
transientlunatic Jun 3, 2025
ff5bac8
Merge branch 'add-plots' into 'master'
transientlunatic Jun 3, 2025
554c259
Housekeeping
transientlunatic Nov 3, 2025
b099a7c
Move to using a pyproject toml file
transientlunatic Nov 3, 2025
236fbca
Housekeeping and various fixes
transientlunatic Nov 3, 2025
0c88c93
Successful bug-hunting
transientlunatic Nov 4, 2025
595f1f9
The bug hunt continues apace.
transientlunatic Nov 5, 2025
3b4d6e0
Update the covariance in the flat PSD
transientlunatic Nov 5, 2025
eef7a4b
The bug hunt continues
transientlunatic Nov 5, 2025
92df507
Add unit test for sine-gaussian without uncertainty
johnveitch Nov 5, 2025
751bd47
Merge branch 'physical_tests_nov25' into 'master'
transientlunatic Nov 5, 2025
f1fb3bc
More work on bug-hunting
transientlunatic Dec 30, 2025
49bea09
Merge branch 'master' of git.ligo.org:daniel-williams/heron
transientlunatic Dec 30, 2025
82a8353
Merge branch 'master' of github.com:transientlunatic/heron
transientlunatic Dec 30, 2025
8956d5d
Implement Cholesky decomposition caching in TimeDomainLikelihood for …
transientlunatic Jan 12, 2026
9bdc8ff
Add comprehensive tests for matrix view behavior, numerical stability…
transientlunatic Jan 12, 2026
c8692e0
Add unit tests for GPyTorch models, focusing on evaluation manifold, …
transientlunatic Jan 12, 2026
bd47a4b
Apply suggestions from code review
transientlunatic Jan 12, 2026
924a622
Initial plan
Copilot Jan 12, 2026
282d3c4
Fix critical test failures and address code review comments
Copilot Jan 12, 2026
5b33b65
Remove verbose performance commentary from test output
Copilot Jan 12, 2026
244cef1
Address final code review feedback
Copilot Jan 12, 2026
d61e4a0
Merge pull request #58 from transientlunatic/copilot/sub-pr-56
transientlunatic Jan 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# Images
*.png
*.jpg
*.jpeg

# Ephemera
.python-version

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
2 changes: 1 addition & 1 deletion AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Credits
Development Lead
----------------

* Daniel Williams <d.williams.2@research.gla.ac.uk>
* Daniel Williams <daniel.williams@glasgow.ac.uk>

Contributors
------------
Expand Down
89 changes: 75 additions & 14 deletions heron/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import logging
import os

import click

Expand All @@ -13,7 +14,7 @@

from heron.detector import KNOWN_IFOS
from heron.models.lalnoise import KNOWN_PSDS
from heron.likelihood import TimeDomainLikelihood, MultiDetector
from heron.likelihood import TimeDomainLikelihood, MultiDetector, TimeDomainLikelihoodModelUncertainty
import heron.priors

from heron.sampling import NessaiSampler
Expand All @@ -26,13 +27,17 @@
)
from heron.utils import load_yaml

import otter

logger = logging.getLogger("heron.inference")

KNOWN_LIKELIHOODS = {
"TimeDomainLikelihood": TimeDomainLikelihood,
"TimeDomainUncertaintyLikelihood": TimeDomainLikelihoodModelUncertainty,
}
KNOWN_WAVEFORMS = {
"IMRPhenomPv2": IMRPhenomPv2,
"IMRPhenomPv2_FakeUncertainty": IMRPhenomPv2_FakeUncertainty,
}


Expand All @@ -57,6 +62,7 @@ def parse_dict(settings):
def heron_inference(settings):

settings = load_yaml(settings)
webdir = settings['pages directory']
settings, other_settings = parse_dict(settings)

if "logging" in other_settings:
Expand All @@ -81,12 +87,21 @@ def heron_inference(settings):

data = {}

report = otter.Otter(os.path.join(
webdir,
"inference.html"),
author="Heron",
title="Heron Inference"
)

if "data files" in settings.get("data", {}):
# Load frame files from disk

start = settings['event time'] - settings['segment length'] + settings['after merger']
with report:
report += "# Data"
start = settings['event time'] - \
settings['segment length'] + settings['after merger']
end = settings['event time'] + settings['after merger']

for ifo in settings["interferometers"]:
print(f"Loading {ifo} data")
logger.info(
Expand All @@ -100,11 +115,22 @@ def heron_inference(settings):
start=start,
end=end,
)
with report:
report += f"## {ifo}"
f = data[ifo].plot()
f.savefig(os.path.join(
other_settings.get('pages directory', 'pages'),
f"{ifo}_data.png"
))
report += f

if data[ifo].sample_rate != settings['likelihood']['sampling rate']:
logger.info("Resampling the data to the likelihood sampling rate")
data[ifo] = data[ifo].resample(settings['likelihood']['sampling rate'])
#elif "injection" in other_settings:
# pass
logger.info(
"Resampling the data to the likelihood sampling rate")
data[ifo] = data[ifo].resample(
settings['likelihood']['sampling rate'])
# elif "injection" in other_settings:
# TODO: implement handling for 'injection' in other_settings if required

# Make Likelihood
if len(settings["interferometers"]) > 1:
Expand All @@ -125,16 +151,47 @@ def heron_inference(settings):
),
)
)
print(likelihoods[-1])
likelihood = MultiDetector(*likelihoods)

priors = heron.priors.PriorDict()
priors.from_dictionary(settings["priors"])

if settings["sampler"]["sampler"] == "nessai":
if settings["sampler"]["sampler"] == "naive":
import numpy as np
import matplotlib.pyplot as plt
# Just draw 100 points across mass ratio space
#prior_points = np.linspace(50, 70, 100)
#prior_points = np.linspace(50, 150, 100)
posterior = []
prior_points = np.linspace(settings["sampler"]["naive range"][0],
settings["sampler"]["naive range"][1],
100)
for mass in prior_points:
parameters = {
"total_mass": 60,#mass * u.solMass,
"mass_ratio": 1.0,
"luminosity_distance": 100,
"gpstime": 4000,
"ra": 1.0,
"dec": 0.3}

parameters[settings["sampler"]["naive parameter"]] = mass
parameters = injection_parameters_add_units(parameters)
posterior.append(likelihood(parameters))

posterior = np.array(posterior)
print(posterior)
f, ax = plt.subplots(1,1, dpi=300)
ax.plot(prior_points, posterior)
f.savefig(os.path.join(webdir, "posterior.png"))

elif settings["sampler"]["sampler"] == "nessai":
nessai_model = NessaiSampler(
likelihood,
priors,
injection_parameters_add_units(other_settings["injection"]["parameters"]),
injection_parameters_add_units(
other_settings["injection"]["parameters"]),
)

fp = FlowSampler(
Expand All @@ -145,14 +202,18 @@ def heron_inference(settings):
),
output=settings["name"],
resume=settings.get("sampler", {}).get("resume", True),
checkpointing=settings.get("sampler", {}).get("checkpointing", True),
checkpointing=settings.get(
"sampler", {}).get("checkpointing", True),
checkpoint_interval=settings.get("sampler", {}).get(
"checkpointing interval", 3600
),
logging_interval=settings.get("sampler", {}).get("logging interval", 10),
log_on_iteration=settings.get("sampler", {}).get("log on iteration", True),
logging_interval=settings.get(
"sampler", {}).get("logging interval", 10),
log_on_iteration=settings.get(
"sampler", {}).get("log on iteration", True),
seed=settings.get("sampler", {}).get("seed", 1234),
flow_class=settings.get("sampler", {}).get("flow class", "GWFlowProposal"),
flow_class=settings.get("sampler", {}).get(
"flow class", "GWFlowProposal"),
signal_handling=True,
)

Expand Down
62 changes: 45 additions & 17 deletions heron/injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Code to create injections using the various models supported by heron.
"""

import os

import logging

import click
Expand All @@ -27,50 +29,59 @@ def make_injection(
times=None,
detectors=None,
framefile=None,
psdfile=None
psdfile=None,
webdir="pages"
):

parameters = {"ra": 0, "dec": 0, "psi": 0, "theta_jn": 0, "phase": 0}
parameters = {"ra": 0, "dec": 0, "psi": 0, "theta_jn": 0, "phase": 0, "gpstime": 4000}
parameters.update(injection_parameters)

waveform = waveform()

if times is None:
times = np.linspace(parameters['gpstime']-duration+2, parameters['gpstime']+2, int(duration * sample_rate))
# The caller is responsible for providing an appropriate 'times' array
# (e.g. a time grid around 'gpstime') before invoking this function.
print(f"Making a waveform from {times[0]} to {times[-1]}")
waveform = waveform.time_domain(
parameters,
times=times,
)

import matplotlib
matplotlib.use("agg")
from gwpy.plot import Plot
f = Plot(waveform['plus'], waveform['cross'], separate=True)
f.savefig(os.path.join(webdir, f"injected_waveform_sep.png"))

injections = {}
for detector, psd_model in detectors.items():
print(f"Making injection for {detector}")
logger.info(f"Making injection for {detector}")
psd_model = KNOWN_PSDS[psd_model]()
detector = KNOWN_IFOS[detector]()
if times is None:
times = waveform['plus'].times.value
data = psd_model.time_series(times)
print(data)

channel = f"{detector.abbreviation}:Injection"

print("Projecting the waveform")
injection = data + waveform.project(detector)
injection.channel = channel
injections[detector.abbreviation] = injection
# likelihood = TimeDomainLikelihood(injection, psd=psd_model)
# snr = likelihood.snr(waveform.project(detector))

#logger.info(f"Optimal Filter SNR: {snr}")
f = Plot(data, waveform.project(detector), injection, separate=True)
f.savefig(os.path.join(webdir, f"{detector.abbreviation}_injected_waveform.png"))

if framefile:
filename = f"{detector.abbreviation}_{framefile}.gwf"
f = Plot(injection)
f.savefig(os.path.join(webdir, f"{detector.abbreviation}_{framefile}.png"))
logger.info(f"Saving framefile to {filename}")
injection.write(filename, format="gwf")

if psdfile:
# Write the PSD file to an ascii file
filename = f"{detector.abbreviation}_{psdfile}.dat"
psd_model.to_file(filename)

return injections


Expand Down Expand Up @@ -102,11 +113,12 @@ def make_injection_zero_noise(
psd_model = KNOWN_PSDS[psd_model]()
#data = psd_model.time_series(times)

# import matplotlib
# matplotlib.use("agg")
# from gwpy.plot import Plot
# f = Plot(data, waveform.project(detector), data+waveform.project(detector), separate=False)
# f.savefig(f"{detector.abbreviation}_injected_waveform.png")
import matplotlib
matplotlib.use("agg")
from gwpy.plot import Plot
webdir = settings['pages directory']
f = Plot(data, waveform.project(detector), data+waveform.project(detector), separate=False)
f.savefig(os.path.join(webdir, f"{detector.abbreviation}_injected_waveform.png"))

injection = waveform.project(detector)
injection.channel = channel
Expand Down Expand Up @@ -134,6 +146,9 @@ def injection_parameters_add_units(parameters):
@click.command()
@click.option("--settings")
def injection(settings):

click.echo("Preparing an injection")

settings = load_yaml(settings)

if "logging" in settings:
Expand All @@ -147,21 +162,34 @@ def injection(settings):
}

logging.basicConfig(level=LOGGER_LEVELS[level])

webdir = settings['pages directory']
settings = settings["injection"]

print(settings)

parameters = injection_parameters_add_units(settings["parameters"])

detector_dict = {
settings["interferometers"][ifo]: settings["psds"][ifo]
for ifo in settings["interferometers"]
}

click.echo("Making injections")
t0 = settings['parameters']['gpstime']
duration = settings["duration"]
srate = settings["sample rate"]

times = np.linspace(t0-(duration-2), t0+2, duration*srate)

injections = make_injection(
waveform=IMRPhenomPv2,
duration=settings["duration"],
times=times,
sample_rate=settings["sample rate"],
injection_parameters=parameters,
detectors=detector_dict,
framefile="injection",
psdfile="psd",
webdir=webdir,
)
data = injections
Loading
Loading