From 71f8e952673df38bc303454d415a64d0656773cb Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:14:01 +0800 Subject: [PATCH 001/248] Update utils.py --- src/jimgw/single_event/utils.py | 153 ++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index c0b14b7a..b149bcf4 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -143,6 +143,159 @@ def ra_dec_to_theta_phi(ra: Float, dec: Float, gmst: Float) -> tuple[Float, Floa theta = jnp.pi / 2 - dec return theta, phi +def rotate_y(angle: Float, x: Float, y: Float, z: Float) -> tuple[Float, Float, Float]: + """ + Rotate the vector (x, y, z) about y-axis + """ + x_new = x * jnp.cos(angle) + z * jnp.sin(angle) + z_new = - (x * jnp.sin(angle)) + z * jnp.cos(angle) + return x_new, y, z_new + + +def rotate_z(angle: Float, x: Float, y: Float, z: Float) -> tuple[Float, Float, Float]: + """ + Rotate the vector (x, y, z) about z-axis + """ + x_new = x * jnp.cos(angle) - y * jnp.sin(angle) + y_new = x * jnp.sin(angle) + y * jnp.cos(angle) + return x_new, y_new, z + + +def spin_to_spin( + thetaJN: Float, + phiJL: Float, + theta1: Float, + theta2: Float, + phi12: Float, + chi1: Float, + chi2: Float, + M_c: Float, + eta: Float, + fRef: Float, + phiRef: Float +) -> tuple[Float, Float, Float, Float, Float, Float, Float]: +""" + Transforming the spin parameters + + Parameters: + ------- + thetaJN: Float + Zenith angle between the total angular momentum and the line of sight + phiJL: Float + Difference between total and orbital angular momentum azimuthal angles + theta1: Float + Zenith angle between the spin and orbital angular momenta for the primary object + theta2: Float + Zenith angle between the spin and orbital angular momenta for the secondary object + phi12: Float + Difference between the azimuthal angles of the individual spin vector projections + onto the orbital plane + chi1: Float + Primary object aligned spin: + chi2: Float + Secondary object aligned spin: + M_c: Float + The chirp mass + eta: Float + The symmetric mass ratio + fRef: Float + The reference frequency + phiRef: Float + Binary phase at a reference frequency + + Returns: + ------- + iota: Float + Zenith angle between the orbital angular momentum and the line of sight + S1x: Float + The x-component of the primary spin + S1y: Float + The y-component of the primary spin + S1z: Float + The z-component of the primary spin + S2x: Float + The x-component of the secondary spin + S2y: Float + The y-component of the secondary spin + S2z: Float + The z-component of the secondary spin +""" + + LNhx = 0. + LNhy = 0. + LNhz = 1. + + s1hatx = jnp.sin(theta1)*jnp.cos(phiRef) + s1haty = jnp.sin(theta1)*jnp.sin(phiRef) + s1hatz = jnp.cos(theta1) + s2hatx = jnp.sin(theta2) * jnp.cos(phi12+phiRef) + s2haty = jnp.sin(theta2) * jnp.sin(phi12+phiRef) + s2hatz = jnp.cos(theta2) + + temp = (1 / eta / 2 - 1) + q = temp - (temp ** 2 - 1) ** 0.5 + m1, m2 = Mc_q_to_m1m2(M_c, q) + MTsun_SI = 4.925490947641266978197229498498379006e-6 + v0 = jnp.cbrt((m1+m2) * MTsun_SI * jnp.pi * fRef) + + Lmag = ((m1+m2)*(m1+m2)*eta/v0) * (1.0 + v0*v0*(1.5 + eta/6.0)) + s1x = m1 * m1 * chi1 * s1hatx + s1y = m1 * m1 * chi1 * s1haty + s1z = m1 * m1 * chi1 * s1hatz + s2x = m2 * m2 * chi2 * s2hatx + s2y = m2 * m2 * chi2 * s2haty + s2z = m2 * m2 * chi2 * s2hatz + Jx = s1x + s2x + Jy = s1y + s2y + Jz = Lmag + s1z + s2z + + + Jnorm = jnp.sqrt( Jx*Jx + Jy*Jy + Jz*Jz) + Jhatx = Jx / Jnorm + Jhaty = Jy / Jnorm + Jhatz = Jz / Jnorm + theta0 = jnp.arccos(Jhatz) + phi0 = jnp.arctan2(Jhaty, Jhatx) + + s1hatx, s1haty, s1hatz = rotate_z(-phi0, s1hatx, s1haty, s1hatz) + s2hatx, s2haty, s2hatz = rotate_z(-phi0, s2hatx, s2haty, s2hatz) + + LNhx, LNhy, LNhz = rotate_y(-theta0, LNhx, LNhy, LNhz) + s1hatx, s1haty, s1hatz = rotate_y(-theta0, s1hatx, s1haty, s1hatz) + s2hatx, s2haty, s2hatz = rotate_y(-theta0, s2hatx, s2haty, s2hatz) + + LNhx, LNhy, LNhz = rotate_z(phiJL - jnp.pi, LNhx, LNhy, LNhz) + s1hatx, s1haty, s1hatz = rotate_z(phiJL - jnp.pi, s1hatx, s1haty, s1hatz) + s2hatx, s2haty, s2hatz = rotate_z(phiJL - jnp.pi, s2hatx, s2haty, s2hatz) + + Nx=0.0 + Ny=jnp.sin(thetaJN) + Nz=jnp.cos(thetaJN) + iota=jnp.arccos(Nx*LNhx+Ny*LNhy+Nz*LNhz) + + thetaLJ = jnp.arccos(LNhz) + phiL = jnp.arctan2(LNhy, LNhx) + + s1hatx, s1haty, s1hatz = rotate_z(-phiL, s1hatx, s1haty, s1hatz) + s2hatx, s2haty, s2hatz = rotate_z(-phiL, s2hatx, s2haty, s2hatz) + Nx, Ny, Nz = rotate_z(-phiL, Nx, Ny, Nz) + + s1hatx, s1haty, s1hatz = rotate_y(-thetaLJ, s1hatx, s1haty, s1hatz) + s2hatx, s2haty, s2hatz = rotate_y(-thetaLJ, s2hatx, s2haty, s2hatz) + Nx, Ny, Nz = rotate_y(-thetaLJ, Nx, Ny, Nz) + + phiN = jnp.arctan2(Ny, Nx) + s1hatx, s1haty, s1hatz = rotate_z(jnp.pi/2.-phiN-phiRef, s1hatx, s1haty, s1hatz) + s2hatx, s2haty, s2hatz = rotate_z(jnp.pi/2.-phiN-phiRef, s2hatx, s2haty, s2hatz) + + S1x = s1hatx*chi1 + S1y = s1haty*chi1 + S1z = s1hatz*chi1 + S2x = s2hatx*chi2 + S2y = s2haty*chi2 + S2z = s2hatz*chi2 + + return iota, S1x, S1y, S1z, S2x, S2y, S2z def log_i0(x): """ From 77e0247c512d0c5ed6c9d736e6015b3fb3160490 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Tue, 23 Jul 2024 13:55:23 -0400 Subject: [PATCH 002/248] Update Single_event_runManager.py --- example/Single_event_runManager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/example/Single_event_runManager.py b/example/Single_event_runManager.py index c0878afc..88ffe52b 100644 --- a/example/Single_event_runManager.py +++ b/example/Single_event_runManager.py @@ -31,7 +31,6 @@ run = SingleEventRun( seed=0, - path="test_data/GW150914/", detectors=["H1", "L1"], priors={ "M_c": {"name": "Uniform", "xmin": 10.0, "xmax": 80.0}, From 722cff0355474da394a02341b56d4017e4bf275b Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 07:38:02 -0400 Subject: [PATCH 003/248] Update utils.py --- src/jimgw/single_event/utils.py | 34 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index b149bcf4..82ee357c 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -142,23 +142,6 @@ def ra_dec_to_theta_phi(ra: Float, dec: Float, gmst: Float) -> tuple[Float, Floa phi = ra - gmst theta = jnp.pi / 2 - dec return theta, phi - -def rotate_y(angle: Float, x: Float, y: Float, z: Float) -> tuple[Float, Float, Float]: - """ - Rotate the vector (x, y, z) about y-axis - """ - x_new = x * jnp.cos(angle) + z * jnp.sin(angle) - z_new = - (x * jnp.sin(angle)) + z * jnp.cos(angle) - return x_new, y, z_new - - -def rotate_z(angle: Float, x: Float, y: Float, z: Float) -> tuple[Float, Float, Float]: - """ - Rotate the vector (x, y, z) about z-axis - """ - x_new = x * jnp.cos(angle) - y * jnp.sin(angle) - y_new = x * jnp.sin(angle) + y * jnp.cos(angle) - return x_new, y_new, z def spin_to_spin( @@ -220,7 +203,22 @@ def spin_to_spin( S2z: Float The z-component of the secondary spin """ - + def rotate_y(angle: Float, x: Float, y: Float, z: Float) -> tuple[Float, Float, Float]: + """ + Rotate the vector (x, y, z) about y-axis + """ + x_new = x * jnp.cos(angle) + z * jnp.sin(angle) + z_new = - (x * jnp.sin(angle)) + z * jnp.cos(angle) + return x_new, y, z_new + + def rotate_z(angle: Float, x: Float, y: Float, z: Float) -> tuple[Float, Float, Float]: + """ + Rotate the vector (x, y, z) about z-axis + """ + x_new = x * jnp.cos(angle) - y * jnp.sin(angle) + y_new = x * jnp.sin(angle) + y * jnp.cos(angle) + return x_new, y_new, z + LNhx = 0. LNhy = 0. LNhz = 1. From 75c74a36517147f17e785cdb8dd17e2984279fac Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 10:16:11 -0400 Subject: [PATCH 004/248] scaffolding jim to handle naming. Also isort some import in prior.py --- src/jimgw/jim.py | 8 +++++++- src/jimgw/prior.py | 7 ++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 0ab1105a..15acda7c 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -13,9 +13,15 @@ class Jim(object): """ Master class for interfacing with flowMC - """ + likelihood: LikelihoodBase + prior: Prior + + seed: int + + parameter_names: list[str] + def __init__(self, likelihood: LikelihoodBase, prior: Prior, **kwargs): self.Likelihood = likelihood self.Prior = prior diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index f1827753..702dcd31 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -3,12 +3,13 @@ import jax import jax.numpy as jnp +from astropy.time import Time +from beartype import beartype as typechecker from flowMC.nfmodel.base import Distribution from jaxtyping import Array, Float, Int, PRNGKeyArray, jaxtyped -from beartype import beartype as typechecker -from jimgw.single_event.utils import zenith_azimuth_to_ra_dec + from jimgw.single_event.detector import GroundBased2G, detector_preset -from astropy.time import Time +from jimgw.single_event.utils import zenith_azimuth_to_ra_dec class Prior(Distribution): From c749dbd06b512a5c9285889f570012a229522a2e Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 10:19:32 -0400 Subject: [PATCH 005/248] Rename variables in jim --- src/jimgw/jim.py | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 15acda7c..39c348fd 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -18,13 +18,19 @@ class Jim(object): likelihood: LikelihoodBase prior: Prior - seed: int - parameter_names: list[str] + sampler: Sampler - def __init__(self, likelihood: LikelihoodBase, prior: Prior, **kwargs): - self.Likelihood = likelihood - self.Prior = prior + def __init__( + self, + likelihood: LikelihoodBase, + prior: Prior, + parameter_names: list[str], + **kwargs, + ): + self.likelihood = likelihood + self.prior = prior + self.parameter_names = parameter_names seed = kwargs.get("seed", 0) @@ -39,11 +45,11 @@ def __init__(self, likelihood: LikelihoodBase, prior: Prior, **kwargs): rng_key, subkey = jax.random.split(rng_key) model = MaskedCouplingRQSpline( - self.Prior.n_dim, num_layers, hidden_size, num_bins, subkey + self.prior.n_dim, num_layers, hidden_size, num_bins, subkey ) self.Sampler = Sampler( - self.Prior.n_dim, + self.prior.n_dim, rng_key, None, # type: ignore local_sampler, @@ -52,15 +58,15 @@ def __init__(self, likelihood: LikelihoodBase, prior: Prior, **kwargs): ) def posterior(self, params: Float[Array, " n_dim"], data: dict): - prior_params = self.Prior.add_name(params.T) - prior = self.Prior.log_prob(prior_params) + prior_params = self.prior.add_name(params.T) + prior = self.prior.log_prob(prior_params) return ( - self.Likelihood.evaluate(self.Prior.transform(prior_params), data) + prior + self.likelihood.evaluate(self.prior.transform(prior_params), data) + prior ) def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): if initial_guess.size == 0: - initial_guess_named = self.Prior.sample(key, self.Sampler.n_chains) + initial_guess_named = self.prior.sample(key, self.Sampler.n_chains) initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T self.Sampler.sample(initial_guess, None) # type: ignore @@ -73,7 +79,7 @@ def maximize_likelihood( ): key = jax.random.PRNGKey(seed) set_nwalkers = set_nwalkers - initial_guess = self.Prior.sample(key, set_nwalkers) + initial_guess = self.prior.sample(key, set_nwalkers) def negative_posterior(x: Float[Array, " n_dim"]): return -self.posterior(x, None) # type: ignore since flowMC does not have typing info, yet @@ -84,7 +90,7 @@ def negative_posterior(x: Float[Array, " n_dim"]): print("Done compiling") print("Starting the optimizer") - optimizer = EvolutionaryOptimizer(self.Prior.n_dim, verbose=True) + optimizer = EvolutionaryOptimizer(self.prior.n_dim, verbose=True) _ = optimizer.optimize(negative_posterior, bounds, n_loops=n_loops) best_fit = optimizer.get_result()[0] return best_fit @@ -98,19 +104,19 @@ def print_summary(self, transform: bool = True): train_summary = self.Sampler.get_sampler_state(training=True) production_summary = self.Sampler.get_sampler_state(training=False) - training_chain = train_summary["chains"].reshape(-1, self.Prior.n_dim).T - training_chain = self.Prior.add_name(training_chain) + training_chain = train_summary["chains"].reshape(-1, self.prior.n_dim).T + training_chain = self.prior.add_name(training_chain) if transform: - training_chain = self.Prior.transform(training_chain) + training_chain = self.prior.transform(training_chain) training_log_prob = train_summary["log_prob"] training_local_acceptance = train_summary["local_accs"] training_global_acceptance = train_summary["global_accs"] training_loss = train_summary["loss_vals"] - production_chain = production_summary["chains"].reshape(-1, self.Prior.n_dim).T - production_chain = self.Prior.add_name(production_chain) + production_chain = production_summary["chains"].reshape(-1, self.prior.n_dim).T + production_chain = self.prior.add_name(production_chain) if transform: - production_chain = self.Prior.transform(production_chain) + production_chain = self.prior.transform(production_chain) production_log_prob = production_summary["log_prob"] production_local_acceptance = production_summary["local_accs"] production_global_acceptance = production_summary["global_accs"] @@ -166,7 +172,7 @@ def get_samples(self, training: bool = False) -> dict: else: chains = self.Sampler.get_sampler_state(training=False)["chains"] - chains = self.Prior.transform(self.Prior.add_name(chains.transpose(2, 0, 1))) + chains = self.prior.transform(self.prior.add_name(chains.transpose(2, 0, 1))) return chains def plot(self): From f45b41e83822888adba6ea358794e832c7db0952 Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 11:15:58 -0400 Subject: [PATCH 006/248] Starting tracking names in jim --- src/jimgw/jim.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 39c348fd..08b82b05 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -18,6 +18,7 @@ class Jim(object): likelihood: LikelihoodBase prior: Prior + # Name of parameters to sample from parameter_names: list[str] sampler: Sampler @@ -57,12 +58,22 @@ def __init__( **kwargs, ) + def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: + """ + Turn an array into a dictionary + + Parameters + ---------- + x : Array + An array of parameters. Shape (n_dim,). + """ + + return dict(zip(self.parameter_names, x)) + def posterior(self, params: Float[Array, " n_dim"], data: dict): - prior_params = self.prior.add_name(params.T) - prior = self.prior.log_prob(prior_params) - return ( - self.likelihood.evaluate(self.prior.transform(prior_params), data) + prior - ) + named_params = self.add_name(params) + prior = self.prior.log_prob(named_params) + return self.likelihood.evaluate(named_params, data) + prior def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): if initial_guess.size == 0: From dcd4662e6e380b734cf83910a3cc6c5d8095cba8 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:22:16 -0400 Subject: [PATCH 007/248] Update utils.py --- src/jimgw/single_event/utils.py | 158 ++++++++++++++++---------------- 1 file changed, 81 insertions(+), 77 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 9e756070..c37c8408 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -198,97 +198,101 @@ def spin_to_spin( S2z: Float The z-component of the secondary spin """ - def rotate_y(angle: Float, x: Float, y: Float, z: Float) -> tuple[Float, Float, Float]: + def rotate_y(angle, vec): """ Rotate the vector (x, y, z) about y-axis """ - x_new = x * jnp.cos(angle) + z * jnp.sin(angle) - z_new = - (x * jnp.sin(angle)) + z * jnp.cos(angle) - return x_new, y, z_new - - def rotate_z(angle: Float, x: Float, y: Float, z: Float) -> tuple[Float, Float, Float]: + cos_angle = jnp.cos(angle) + sin_angle = jnp.sin(angle) + rotation_matrix = np.array([ + [cos_angle, 0, sin_angle], + [0, 1, 0], + [-sin_angle, 0, cos_angle] + ]) + rotated_vec = jnp.dot(rotation_matrix, vec) + return rotated_vec + + def rotate_z(angle, vec): """ Rotate the vector (x, y, z) about z-axis """ - x_new = x * jnp.cos(angle) - y * jnp.sin(angle) - y_new = x * jnp.sin(angle) + y * jnp.cos(angle) - return x_new, y_new, z - - LNhx = 0. - LNhy = 0. - LNhz = 1. - - s1hatx = jnp.sin(theta1)*jnp.cos(phiRef) - s1haty = jnp.sin(theta1)*jnp.sin(phiRef) - s1hatz = jnp.cos(theta1) - s2hatx = jnp.sin(theta2) * jnp.cos(phi12+phiRef) - s2haty = jnp.sin(theta2) * jnp.sin(phi12+phiRef) - s2hatz = jnp.cos(theta2) - + cos_angle = jnp.cos(angle) + sin_angle = jnp.sin(angle) + rotation_matrix = jnp.array([ + [cos_angle, -sin_angle, 0], + [sin_angle, cos_angle, 0], + [0, 0, 1] + ]) + rotated_vec = jnp.dot(rotation_matrix, vec) + return rotated_vec + + LNh = jnp.array([0., 0., 1.]) + + s1hat = jnp.array([ + jnp.sin(theta1)*jnp.cos(phiRef), + jnp.sin(theta1)*jnp.sin(phiRef), + jnp.cos(theta1) + ]) + s2hat = jnp.array([ + jnp.sin(theta2) * jnp.cos(phi12+phiRef), + jnp.sin(theta2) * jnp.sin(phi12+phiRef), + jnp.cos(theta2) + ]) + temp = (1 / eta / 2 - 1) q = temp - (temp ** 2 - 1) ** 0.5 m1, m2 = Mc_q_to_m1m2(M_c, q) - MTsun_SI = 4.925490947641266978197229498498379006e-6 v0 = jnp.cbrt((m1+m2) * MTsun_SI * jnp.pi * fRef) - + Lmag = ((m1+m2)*(m1+m2)*eta/v0) * (1.0 + v0*v0*(1.5 + eta/6.0)) - s1x = m1 * m1 * chi1 * s1hatx - s1y = m1 * m1 * chi1 * s1haty - s1z = m1 * m1 * chi1 * s1hatz - s2x = m2 * m2 * chi2 * s2hatx - s2y = m2 * m2 * chi2 * s2haty - s2z = m2 * m2 * chi2 * s2hatz - Jx = s1x + s2x - Jy = s1y + s2y - Jz = Lmag + s1z + s2z - - - Jnorm = jnp.sqrt( Jx*Jx + Jy*Jy + Jz*Jz) - Jhatx = Jx / Jnorm - Jhaty = Jy / Jnorm - Jhatz = Jz / Jnorm - theta0 = jnp.arccos(Jhatz) - phi0 = jnp.arctan2(Jhaty, Jhatx) - - s1hatx, s1haty, s1hatz = rotate_z(-phi0, s1hatx, s1haty, s1hatz) - s2hatx, s2haty, s2hatz = rotate_z(-phi0, s2hatx, s2haty, s2hatz) - - LNhx, LNhy, LNhz = rotate_y(-theta0, LNhx, LNhy, LNhz) - s1hatx, s1haty, s1hatz = rotate_y(-theta0, s1hatx, s1haty, s1hatz) - s2hatx, s2haty, s2hatz = rotate_y(-theta0, s2hatx, s2haty, s2hatz) + s1 = m1 * m1 * chi1 * s1hat + s2 = m2 * m2 * chi2 * s2hat + J = s1 + s2 + jnp.array([0., 0., Lmag]) + + Jhat = J / jnp.linalg.norm(J) + theta0 = jnp.arccos(Jhat[2]) + phi0 = jnp.arctan2(Jhat[1], Jhat[0]) + + # Rotation 1: + s1hat = rotate_z(-phi0, s1hat) + s2hat = rotate_z(-phi0, s2hat) + + # Rotation 2: + LNh = rotate_y(-theta0, LNh) + s1hat = rotate_y(-theta0, s1hat) + s2hat = rotate_y(-theta0, s2hat) + + # Rotation 3: + LNh = rotate_z(phiJL - jnp.pi, LNh) + s1hat = rotate_z(phiJL - jnp.pi, s1hat) + s2hat = rotate_z(phiJL - jnp.pi, s2hat) + + # Compute iota + N = jnp.array([0.0, jnp.sin(thetaJN), jnp.cos(thetaJN)]) + iota = jnp.arccos(jnp.dot(N, LNh)) - LNhx, LNhy, LNhz = rotate_z(phiJL - jnp.pi, LNhx, LNhy, LNhz) - s1hatx, s1haty, s1hatz = rotate_z(phiJL - jnp.pi, s1hatx, s1haty, s1hatz) - s2hatx, s2haty, s2hatz = rotate_z(phiJL - jnp.pi, s2hatx, s2haty, s2hatz) - - Nx=0.0 - Ny=jnp.sin(thetaJN) - Nz=jnp.cos(thetaJN) - iota=jnp.arccos(Nx*LNhx+Ny*LNhy+Nz*LNhz) - - thetaLJ = jnp.arccos(LNhz) - phiL = jnp.arctan2(LNhy, LNhx) - - s1hatx, s1haty, s1hatz = rotate_z(-phiL, s1hatx, s1haty, s1hatz) - s2hatx, s2haty, s2hatz = rotate_z(-phiL, s2hatx, s2haty, s2hatz) - Nx, Ny, Nz = rotate_z(-phiL, Nx, Ny, Nz) + thetaLJ = jnp.arccos(LNh[2]) + phiL = jnp.arctan2(LNh[1], LNh[0]) - s1hatx, s1haty, s1hatz = rotate_y(-thetaLJ, s1hatx, s1haty, s1hatz) - s2hatx, s2haty, s2hatz = rotate_y(-thetaLJ, s2hatx, s2haty, s2hatz) - Nx, Ny, Nz = rotate_y(-thetaLJ, Nx, Ny, Nz) - - phiN = jnp.arctan2(Ny, Nx) - s1hatx, s1haty, s1hatz = rotate_z(jnp.pi/2.-phiN-phiRef, s1hatx, s1haty, s1hatz) - s2hatx, s2haty, s2hatz = rotate_z(jnp.pi/2.-phiN-phiRef, s2hatx, s2haty, s2hatz) - - S1x = s1hatx*chi1 - S1y = s1haty*chi1 - S1z = s1hatz*chi1 - S2x = s2hatx*chi2 - S2y = s2haty*chi2 - S2z = s2hatz*chi2 + # Rotation 4: + s1hat = rotate_z(-phiL, s1hat) + s2hat = rotate_z(-phiL, s2hat) + N = rotate_z(-phiL, N) - return iota, S1x, S1y, S1z, S2x, S2y, S2z + # Rotation 5: + s1hat = rotate_y(-thetaLJ, s1hat) + s2hat = rotate_y(-thetaLJ, s2hat) + N = rotate_y(-thetaLJ, N) + + # Rotation 6: + phiN = jnp.arctan2(N[1], N[0]) + s1hat = rotate_z(jnp.pi/2.-phiN-phiRef, s1hat) + s2hat = rotate_z(jnp.pi/2.-phiN-phiRef, s2hat) + + S1 = s1hat * chi1 + S2 = s2hat * chi2 + return iota, S1[0], S1[1], S1[2], S2[0], S2[1], S2[2] + def euler_rotation(delta_x: Float[Array, " 3"]): """ From 3a92fd62a5ffbaa0e6e1da5dd630cd01d9686cb5 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:26:59 -0400 Subject: [PATCH 008/248] Update utils.py --- src/jimgw/single_event/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index c37c8408..29db462b 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -3,6 +3,8 @@ from jax.scipy.special import i0e from jaxtyping import Array, Float +from jimgw.constants import Msun + def inner_product( h1: Float[Array, " n_sample"], @@ -139,7 +141,7 @@ def ra_dec_to_theta_phi(ra: Float, dec: Float, gmst: Float) -> tuple[Float, Floa return theta, phi -def spin_to_spin( +def spin_to_cartesian_spin( thetaJN: Float, phiJL: Float, theta1: Float, @@ -154,6 +156,9 @@ def spin_to_spin( ) -> tuple[Float, Float, Float, Float, Float, Float, Float]: """ Transforming the spin parameters + + The code is based on the approach used in LALsimulation: + https://lscsoft.docs.ligo.org/lalsuite/lalsimulation/group__lalsimulation__inference.html Parameters: ------- @@ -242,7 +247,7 @@ def rotate_z(angle, vec): temp = (1 / eta / 2 - 1) q = temp - (temp ** 2 - 1) ** 0.5 m1, m2 = Mc_q_to_m1m2(M_c, q) - v0 = jnp.cbrt((m1+m2) * MTsun_SI * jnp.pi * fRef) + v0 = jnp.cbrt((m1+m2) * Msun * jnp.pi * fRef) Lmag = ((m1+m2)*(m1+m2)*eta/v0) * (1.0 + v0*v0*(1.5 + eta/6.0)) s1 = m1 * m1 * chi1 * s1hat From b1be7079cf5fcccee6a5bf1833a0aa227ed3a685 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:29:16 -0400 Subject: [PATCH 009/248] Update utils.py --- src/jimgw/single_event/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 29db462b..43aa133e 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -154,7 +154,7 @@ def spin_to_cartesian_spin( fRef: Float, phiRef: Float ) -> tuple[Float, Float, Float, Float, Float, Float, Float]: -""" + """ Transforming the spin parameters The code is based on the approach used in LALsimulation: @@ -202,7 +202,7 @@ def spin_to_cartesian_spin( The y-component of the secondary spin S2z: Float The z-component of the secondary spin -""" + """ def rotate_y(angle, vec): """ Rotate the vector (x, y, z) about y-axis From f4cfd7faa79bd030df4f4efe987f71feba7acd6d Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:32:03 -0400 Subject: [PATCH 010/248] Update utils.py --- src/jimgw/single_event/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 43aa133e..9b9dea87 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -209,7 +209,7 @@ def rotate_y(angle, vec): """ cos_angle = jnp.cos(angle) sin_angle = jnp.sin(angle) - rotation_matrix = np.array([ + rotation_matrix = jnp.array([ [cos_angle, 0, sin_angle], [0, 1, 0], [-sin_angle, 0, cos_angle] From f7e287f61053f1965cc999d928bcfe38cc32b2e2 Mon Sep 17 00:00:00 2001 From: "Yu On (Ben) Xu" Date: Wed, 24 Jul 2024 11:59:17 -0400 Subject: [PATCH 011/248] Updated utils.py --- src/jimgw/single_event/utils.py | 103 ++++++++++++++++---------------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 9b9dea87..557a79ea 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -139,27 +139,27 @@ def ra_dec_to_theta_phi(ra: Float, dec: Float, gmst: Float) -> tuple[Float, Floa phi = ra - gmst theta = jnp.pi / 2 - dec return theta, phi - + def spin_to_cartesian_spin( - thetaJN: Float, - phiJL: Float, - theta1: Float, - theta2: Float, - phi12: Float, - chi1: Float, - chi2: Float, - M_c: Float, + thetaJN: Float, + phiJL: Float, + theta1: Float, + theta2: Float, + phi12: Float, + chi1: Float, + chi2: Float, + M_c: Float, eta: Float, - fRef: Float, - phiRef: Float + fRef: Float, + phiRef: Float, ) -> tuple[Float, Float, Float, Float, Float, Float, Float]: """ Transforming the spin parameters The code is based on the approach used in LALsimulation: https://lscsoft.docs.ligo.org/lalsuite/lalsimulation/group__lalsimulation__inference.html - + Parameters: ------- thetaJN: Float @@ -185,7 +185,7 @@ def spin_to_cartesian_spin( The reference frequency phiRef: Float Binary phase at a reference frequency - + Returns: ------- iota: Float @@ -203,56 +203,57 @@ def spin_to_cartesian_spin( S2z: Float The z-component of the secondary spin """ + def rotate_y(angle, vec): """ Rotate the vector (x, y, z) about y-axis """ cos_angle = jnp.cos(angle) sin_angle = jnp.sin(angle) - rotation_matrix = jnp.array([ - [cos_angle, 0, sin_angle], - [0, 1, 0], - [-sin_angle, 0, cos_angle] - ]) + rotation_matrix = jnp.array( + [[cos_angle, 0, sin_angle], [0, 1, 0], [-sin_angle, 0, cos_angle]] + ) rotated_vec = jnp.dot(rotation_matrix, vec) return rotated_vec - + def rotate_z(angle, vec): """ Rotate the vector (x, y, z) about z-axis """ cos_angle = jnp.cos(angle) sin_angle = jnp.sin(angle) - rotation_matrix = jnp.array([ - [cos_angle, -sin_angle, 0], - [sin_angle, cos_angle, 0], - [0, 0, 1] - ]) + rotation_matrix = jnp.array( + [[cos_angle, -sin_angle, 0], [sin_angle, cos_angle, 0], [0, 0, 1]] + ) rotated_vec = jnp.dot(rotation_matrix, vec) return rotated_vec - - LNh = jnp.array([0., 0., 1.]) - - s1hat = jnp.array([ - jnp.sin(theta1)*jnp.cos(phiRef), - jnp.sin(theta1)*jnp.sin(phiRef), - jnp.cos(theta1) - ]) - s2hat = jnp.array([ - jnp.sin(theta2) * jnp.cos(phi12+phiRef), - jnp.sin(theta2) * jnp.sin(phi12+phiRef), - jnp.cos(theta2) - ]) - - temp = (1 / eta / 2 - 1) - q = temp - (temp ** 2 - 1) ** 0.5 + + LNh = jnp.array([0.0, 0.0, 1.0]) + + s1hat = jnp.array( + [ + jnp.sin(theta1) * jnp.cos(phiRef), + jnp.sin(theta1) * jnp.sin(phiRef), + jnp.cos(theta1), + ] + ) + s2hat = jnp.array( + [ + jnp.sin(theta2) * jnp.cos(phi12 + phiRef), + jnp.sin(theta2) * jnp.sin(phi12 + phiRef), + jnp.cos(theta2), + ] + ) + + temp = 1 / eta / 2 - 1 + q = temp - (temp**2 - 1) ** 0.5 m1, m2 = Mc_q_to_m1m2(M_c, q) - v0 = jnp.cbrt((m1+m2) * Msun * jnp.pi * fRef) - - Lmag = ((m1+m2)*(m1+m2)*eta/v0) * (1.0 + v0*v0*(1.5 + eta/6.0)) + v0 = jnp.cbrt((m1 + m2) * Msun * jnp.pi * fRef) + + Lmag = ((m1 + m2) * (m1 + m2) * eta / v0) * (1.0 + v0 * v0 * (1.5 + eta / 6.0)) s1 = m1 * m1 * chi1 * s1hat s2 = m2 * m2 * chi2 * s2hat - J = s1 + s2 + jnp.array([0., 0., Lmag]) + J = s1 + s2 + jnp.array([0.0, 0.0, Lmag]) Jhat = J / jnp.linalg.norm(J) theta0 = jnp.arccos(Jhat[2]) @@ -266,24 +267,24 @@ def rotate_z(angle, vec): LNh = rotate_y(-theta0, LNh) s1hat = rotate_y(-theta0, s1hat) s2hat = rotate_y(-theta0, s2hat) - + # Rotation 3: LNh = rotate_z(phiJL - jnp.pi, LNh) s1hat = rotate_z(phiJL - jnp.pi, s1hat) s2hat = rotate_z(phiJL - jnp.pi, s2hat) - + # Compute iota N = jnp.array([0.0, jnp.sin(thetaJN), jnp.cos(thetaJN)]) iota = jnp.arccos(jnp.dot(N, LNh)) - + thetaLJ = jnp.arccos(LNh[2]) phiL = jnp.arctan2(LNh[1], LNh[0]) - + # Rotation 4: s1hat = rotate_z(-phiL, s1hat) s2hat = rotate_z(-phiL, s2hat) N = rotate_z(-phiL, N) - + # Rotation 5: s1hat = rotate_y(-thetaLJ, s1hat) s2hat = rotate_y(-thetaLJ, s2hat) @@ -291,8 +292,8 @@ def rotate_z(angle, vec): # Rotation 6: phiN = jnp.arctan2(N[1], N[0]) - s1hat = rotate_z(jnp.pi/2.-phiN-phiRef, s1hat) - s2hat = rotate_z(jnp.pi/2.-phiN-phiRef, s2hat) + s1hat = rotate_z(jnp.pi / 2.0 - phiN - phiRef, s1hat) + s2hat = rotate_z(jnp.pi / 2.0 - phiN - phiRef, s2hat) S1 = s1hat * chi1 S2 = s2hat * chi2 From dd6e0de37c6d4ca63aa55bd77b7108e8ca51cef0 Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 13:53:00 -0400 Subject: [PATCH 012/248] Add Transform class --- src/jimgw/transforms.py | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/jimgw/transforms.py diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py new file mode 100644 index 00000000..0795d81d --- /dev/null +++ b/src/jimgw/transforms.py @@ -0,0 +1,63 @@ +from dataclasses import field +from typing import Callable, Union + +import jax +import jax.numpy as jnp +from beartype import beartype as typechecker +from jaxtyping import Float, Array, jaxtyped + + +class Transform: + """ + Base class for transform. + + The idea of transform should be used on distribtuion, + """ + + transform_func: Callable[[Float[Array, " N"]], Float[Array, " M"]] + jacobian_func: Callable[[Float[Array, " N"]], Float] + name_mapping: tuple[list[str], list[str]] + + def __init__( + self, + transform_func: Callable[[Float[Array, " N"]], Float[Array, " M"]], + name_mapping: tuple[list[str], list[str]], + ): + self.transform_func = transform_func + self.jacobian_func = jax.jacfwd(transform_func) + self.name_mapping = name_mapping + + def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: + """ + Transform the input x to transformed coordinate y and return the log Jacobian determinant. + This only works if the transform is a N -> N transform. + + Parameters + ---------- + x : dict[str, Float] + The input dictionary. + + Returns + ------- + y : dict[str, Float] + The transformed dictionary. + log_det : Float + The log Jacobian determinant. + """ + return self.transform_func(x), jnp.log(jnp.linalg.det(self.jacobian_func(x))) + + def push_forward(self, x: dict[str, Float]) -> dict[str, Float]: + """ + Push forward the input x to transformed coordinate y. + + Parameters + ---------- + x : dict[str, Float] + The input dictionary. + + Returns + ------- + y : dict[str, Float] + The transformed dictionary. + """ + return self.transform_func(x) \ No newline at end of file From 4f3be7043e5df159336b5427f80bf50b2e657828 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:29:05 -0400 Subject: [PATCH 013/248] Updated runManager.py --- src/jimgw/single_event/runManager.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index 3f65166d..aa8d0dc7 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -71,7 +71,9 @@ class SingleEventRun: str, dict[str, Union[str, float, int, bool]] ] # Transform cannot be included in this way, add it to preset if used often. jim_parameters: dict[str, Union[str, float, int, bool, dict]] - injection_parameters: dict[str, float] + injection_parameters: dict[str, float] = field( + default_factory=lambda: {} + ) injection: bool = False likelihood_parameters: dict[str, Union[str, float, int, bool, PyTree]] = field( default_factory=lambda: {"name": "TransientLikelihoodFD"} @@ -123,6 +125,9 @@ def __init__(self, **kwargs): print("Neither run instance nor path provided.") raise ValueError + if self.run.injection and not self.run.injection_parameters: + raise ValueError("Injection mode requires injection parameters.") + local_prior = self.initialize_prior() local_likelihood = self.initialize_likelihood(local_prior) self.jim = Jim(local_likelihood, local_prior, **self.run.jim_parameters) From 6b09d72086230889363d12ad68641caa9b2e56e4 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:42:12 -0400 Subject: [PATCH 014/248] updated SingleEventRun --- src/jimgw/single_event/runManager.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index 3f65166d..bb687814 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -71,7 +71,9 @@ class SingleEventRun: str, dict[str, Union[str, float, int, bool]] ] # Transform cannot be included in this way, add it to preset if used often. jim_parameters: dict[str, Union[str, float, int, bool, dict]] - injection_parameters: dict[str, float] + injection_parameters: dict[str, float] = field( + default_factory=lambda: {} + ) injection: bool = False likelihood_parameters: dict[str, Union[str, float, int, bool, PyTree]] = field( default_factory=lambda: {"name": "TransientLikelihoodFD"} @@ -122,6 +124,9 @@ def __init__(self, **kwargs): else: print("Neither run instance nor path provided.") raise ValueError + + if self.run.injection and not self.run.injection_parameters: + raise ValueError("Injection mode requires injection parameters.") local_prior = self.initialize_prior() local_likelihood = self.initialize_likelihood(local_prior) From f11f8c889d69caa2dc0b5a1dd2bcc58857bb32e0 Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 14:42:34 -0400 Subject: [PATCH 015/248] Add LogitToUniform Transform --- src/jimgw/transforms.py | 64 ++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 0795d81d..a261a7fe 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -1,33 +1,35 @@ +from abc import ABC, abstractmethod from dataclasses import field from typing import Callable, Union +from chex import assert_rank import jax import jax.numpy as jnp from beartype import beartype as typechecker -from jaxtyping import Float, Array, jaxtyped +from jaxtyping import Array, Float, jaxtyped -class Transform: +class Transform(ABC): """ Base class for transform. The idea of transform should be used on distribtuion, """ - transform_func: Callable[[Float[Array, " N"]], Float[Array, " M"]] - jacobian_func: Callable[[Float[Array, " N"]], Float] name_mapping: tuple[list[str], list[str]] + transform_func: Callable[[dict[str, Float]], dict[str, Float]] def __init__( self, - transform_func: Callable[[Float[Array, " N"]], Float[Array, " M"]], name_mapping: tuple[list[str], list[str]], ): - self.transform_func = transform_func - self.jacobian_func = jax.jacfwd(transform_func) self.name_mapping = name_mapping def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: + return self.transform(x) + + @abstractmethod + def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ Transform the input x to transformed coordinate y and return the log Jacobian determinant. This only works if the transform is a N -> N transform. @@ -44,9 +46,10 @@ def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: log_det : Float The log Jacobian determinant. """ - return self.transform_func(x), jnp.log(jnp.linalg.det(self.jacobian_func(x))) - - def push_forward(self, x: dict[str, Float]) -> dict[str, Float]: + raise NotImplementedError + + @abstractmethod + def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ Push forward the input x to transformed coordinate y. @@ -60,4 +63,43 @@ def push_forward(self, x: dict[str, Float]) -> dict[str, Float]: y : dict[str, Float] The transformed dictionary. """ - return self.transform_func(x) \ No newline at end of file + raise NotImplementedError + +class LogitToUniform(Transform): + """ + Transform from unconstrained space to uniform space. + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + bounds : tuple[Float, Float] + The lower and upper bounds of the uniform distribution. + + """ + + bounds: tuple[Float, Float] + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + bounds: tuple[Float, Float], + ): + super().__init__(name_mapping) + self.bounds = bounds + self.transform_func = lambda x: (self.bounds[1] - self.bounds[0]) / (1 + jnp.exp(-x)) + self.bounds[0] + + def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: + input_params = x.pop(self.name_mapping[0][0]) + assert_rank(input_params, 0) + output_params = self.transform_func(input_params) + jacobian = jax.jacfwd(self.transform_func)(input_params) + x[self.name_mapping[1][0]] = output_params + return x, jnp.log(jacobian) + + def forward(self, x: dict[str, Float]) -> dict[str, Float]: + input_params = x.pop(self.name_mapping[0][0]) + assert_rank(input_params, 0) + output_params = self.transform_func(input_params) + x[self.name_mapping[1][0]] = output_params + return x \ No newline at end of file From c1192f2c3a1031ea0b6a68533c27394b03a96732 Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 15:24:11 -0400 Subject: [PATCH 016/248] Add logit distribution --- src/jimgw/prior.py | 79 +++++++++++++++++++++-------------------- src/jimgw/transforms.py | 2 +- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 702dcd31..e7b1acda 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -23,57 +23,19 @@ class Prior(Distribution): """ naming: list[str] - transforms: dict[str, tuple[str, Callable]] = field(default_factory=dict) @property def n_dim(self): return len(self.naming) - def __init__( - self, naming: list[str], transforms: dict[str, tuple[str, Callable]] = {} - ): + def __init__(self, naming: list[str]): """ Parameters ---------- naming : list[str] A list of names for the parameters of the prior. - transforms : dict[tuple[str,Callable]] - A dictionary of transforms to apply to the parameters. The keys are - the names of the parameters and the values are a tuple of the name - of the transform and the transform itself. """ self.naming = naming - self.transforms = {} - - def make_lambda(name): - return lambda x: x[name] - - for name in naming: - if name in transforms: - self.transforms[name] = transforms[name] - else: - # Without the function, the lambda will refer to the variable name instead of its value, - # which will make lambda reference the last value of the variable name - self.transforms[name] = (name, make_lambda(name)) - - def transform(self, x: dict[str, Float]) -> dict[str, Float]: - """ - Apply the transforms to the parameters. - - Parameters - ---------- - x : dict - A dictionary of parameters. Names should match the ones in the prior. - - Returns - ------- - x : dict - A dictionary of parameters with the transforms applied. - """ - output = {} - for value in self.transforms.values(): - output[value[0]] = value[1](x) - return output def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: """ @@ -96,6 +58,45 @@ def log_prob(self, x: dict[str, Array]) -> Float: raise NotImplementedError +@jaxtyped(typechecker=typechecker) +class Logit(Prior): + + def __repr__(self): + return f"Logit(naming={self.naming})" + + def __init__(self, naming: list[str], **kwargs): + super().__init__(naming) + assert self.n_dim == 1, "Logit needs to be 1D distributions" + + def sample( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + """ + Sample from a logit distribution. + + Parameters + ---------- + rng_key : PRNGKeyArray + A random key to use for sampling. + n_samples : int + The number of samples to draw. + + Returns + ------- + samples : dict + Samples from the distribution. The keys are the names of the parameters. + + """ + samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) + samples = jnp.log(samples / (1 - samples)) + return self.add_name(samples[None]) + + def log_prob(self, x: dict[str, Float]) -> Float: + variable = x[self.naming[0]] + return -variable - 2 * jnp.log(1 + jnp.exp(-variable)) + + +# ====================== Things below may need rework ====================== @jaxtyped(typechecker=typechecker) class Uniform(Prior): xmin: float = 0.0 diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index a261a7fe..d8402cfe 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -2,10 +2,10 @@ from dataclasses import field from typing import Callable, Union -from chex import assert_rank import jax import jax.numpy as jnp from beartype import beartype as typechecker +from chex import assert_rank from jaxtyping import Array, Float, jaxtyped From 79b07fc563452071cbbaae3b580f584678ad947d Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:36:48 -0400 Subject: [PATCH 017/248] Update the runManager --- example/Single_event_runManager.py | 19 ++++++++++--------- src/jimgw/single_event/runManager.py | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/example/Single_event_runManager.py b/example/Single_event_runManager.py index c0878afc..80bffda5 100644 --- a/example/Single_event_runManager.py +++ b/example/Single_event_runManager.py @@ -31,19 +31,18 @@ run = SingleEventRun( seed=0, - path="test_data/GW150914/", detectors=["H1", "L1"], priors={ - "M_c": {"name": "Uniform", "xmin": 10.0, "xmax": 80.0}, + "M_c": {"name": "Unconstrained_Uniform", "xmin": 10.0, "xmax": 80.0}, "q": {"name": "MassRatio"}, - "s1_z": {"name": "Uniform", "xmin": -1.0, "xmax": 1.0}, - "s2_z": {"name": "Uniform", "xmin": -1.0, "xmax": 1.0}, - "d_L": {"name": "Uniform", "xmin": 0.0, "xmax": 2000.0}, - "t_c": {"name": "Uniform", "xmin": -0.05, "xmax": 0.05}, - "phase_c": {"name": "Uniform", "xmin": 0.0, "xmax": 2 * jnp.pi}, + "s1_z": {"name": "Unconstrained_Uniform", "xmin": -1.0, "xmax": 1.0}, + "s2_z": {"name": "Unconstrained_Uniform", "xmin": -1.0, "xmax": 1.0}, + "d_L": {"name": "Unconstrained_Uniform", "xmin": 0.0, "xmax": 2000.0}, + "t_c": {"name": "Unconstrained_Uniform", "xmin": -0.05, "xmax": 0.05}, + "phase_c": {"name": "Unconstrained_Uniform", "xmin": 0.0, "xmax": 2 * jnp.pi}, "cos_iota": {"name": "CosIota"}, - "psi": {"name": "Uniform", "xmin": 0.0, "xmax": jnp.pi}, - "ra": {"name": "Uniform", "xmin": 0.0, "xmax": 2 * jnp.pi}, + "psi": {"name": "Unconstrained_Uniform", "xmin": 0.0, "xmax": jnp.pi}, + "ra": {"name": "Unconstrained_Uniform", "xmin": 0.0, "xmax": 2 * jnp.pi}, "sin_dec": {"name": "SinDec"}, }, waveform_parameters={"name": "RippleIMRPhenomD", "f_ref": 20.0}, @@ -91,3 +90,5 @@ ) run_manager = SingleEventPERunManager(run=run) +run_manager.jim.sample(jax.random.PRNGKey(42)) +run_manager.jim.get_samples() diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index bb687814..f079dee3 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -65,7 +65,8 @@ def jaxarray_representer(dumper: yaml.Dumper, data: ArrayImpl): @dataclass class SingleEventRun: seed: int - + path: str = "./experiment" + detectors: list[str] priors: dict[ str, dict[str, Union[str, float, int, bool]] @@ -356,3 +357,20 @@ def plot_data(self, path: str): plt.ylabel("Amplitude") plt.legend() plt.savefig(path) + + def sample(self): + self.jim.sample(jax.random.PRNGKey(self.run.seed)) + + def get_samples(self): + return self.jim.get_samples() + + def plot_samples(self, figure_name: str="corner.png"): + import corner + import matplotlib.pyplot as plt + samples = self.jim.get_samples() + param_names = list(samples.keys()) + samples = jnp.array(list(samples.values())).reshape(int(len(param_names)), -1).T + corner.corner(samples, labels=param_names, plot_datapoints=False, title_quantiles=[0.16, 0.5, 0.84], show_titles=True, title_fmt='g', use_math_text=True) + plt.savefig(figure_name) + plt.close() + From b3ebc5efd21f958aa4d931a4954f48d2828adcab Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 15:42:35 -0400 Subject: [PATCH 018/248] Add propagate)name method in transform --- src/jimgw/transforms.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index d8402cfe..0ee32927 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -27,13 +27,13 @@ def __init__( def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: return self.transform(x) - + @abstractmethod def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ Transform the input x to transformed coordinate y and return the log Jacobian determinant. This only works if the transform is a N -> N transform. - + Parameters ---------- x : dict[str, Float] @@ -63,7 +63,14 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: y : dict[str, Float] The transformed dictionary. """ - raise NotImplementedError + raise NotImplementedError + + def propagate_name(self, x: list[str]) -> list[str]: + input_set = set(x) + from_set = set(self.name_mapping[0]) + to_set = set(self.name_mapping[1]) + return list(input_set - from_set | to_set) + class LogitToUniform(Transform): """ @@ -87,7 +94,10 @@ def __init__( ): super().__init__(name_mapping) self.bounds = bounds - self.transform_func = lambda x: (self.bounds[1] - self.bounds[0]) / (1 + jnp.exp(-x)) + self.bounds[0] + self.transform_func = ( + lambda x: (self.bounds[1] - self.bounds[0]) / (1 + jnp.exp(-x)) + + self.bounds[0] + ) def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: input_params = x.pop(self.name_mapping[0][0]) @@ -102,4 +112,4 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: assert_rank(input_params, 0) output_params = self.transform_func(input_params) x[self.name_mapping[1][0]] = output_params - return x \ No newline at end of file + return x From 95ef89b0fc466e1efdf15f9a4f0a8284a434343d Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 15:43:00 -0400 Subject: [PATCH 019/248] Rename composite to Combine for combining priors --- src/jimgw/prior.py | 118 +++++++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index e7b1acda..b257ef38 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -10,6 +10,7 @@ from jimgw.single_event.detector import GroundBased2G, detector_preset from jimgw.single_event.utils import zenith_azimuth_to_ra_dec +from jimgw.transforms import Transform class Prior(Distribution): @@ -22,20 +23,20 @@ class Prior(Distribution): the names of the parameters and the transforms that are applied to them. """ - naming: list[str] + parameter_names: list[str] @property def n_dim(self): - return len(self.naming) + return len(self.parameter_names) - def __init__(self, naming: list[str]): + def __init__(self, parameter_names: list[str]): """ Parameters ---------- - naming : list[str] + parameter_names : list[str] A list of names for the parameters of the prior. """ - self.naming = naming + self.parameter_names = parameter_names def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: """ @@ -47,8 +48,8 @@ def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: An array of parameters. Shape (n_dim,). """ - return dict(zip(self.naming, x)) - + return dict(zip(self.parameter_names, x)) + def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: @@ -62,10 +63,10 @@ def log_prob(self, x: dict[str, Array]) -> Float: class Logit(Prior): def __repr__(self): - return f"Logit(naming={self.naming})" + return f"Logit(parameter_names={self.parameter_names})" - def __init__(self, naming: list[str], **kwargs): - super().__init__(naming) + def __init__(self, parameter_names: list[str], **kwargs): + super().__init__(parameter_names) assert self.n_dim == 1, "Logit needs to be 1D distributions" def sample( @@ -92,10 +93,68 @@ def sample( return self.add_name(samples[None]) def log_prob(self, x: dict[str, Float]) -> Float: - variable = x[self.naming[0]] + variable = x[self.parameter_names[0]] return -variable - 2 * jnp.log(1 + jnp.exp(-variable)) +class Sequential(Prior): + """ + A prior class constructed + """ + + members: list[Prior | Transform] = field(default_factory=list) + + def __repr__(self): + return ( + f"Sequential(priors={self.members}, parameter_names={self.parameter_names})" + ) + + def __init( + self, + members: list[Prior | Transform], + ): + self.members = members + +class Combine(Prior): + """ + A prior class constructed by joinning multiple priors together to form a multivariate prior. + This assumes the priors composing the Combine class are independent. + """ + + priors: list[Prior] = field(default_factory=list) + + def __repr__(self): + return ( + f"Composite(priors={self.priors}, parameter_names={self.parameter_names})" + ) + + def __init__( + self, + priors: list[Prior], + **kwargs, + ): + parameter_names = [] + for prior in priors: + parameter_names += prior.parameter_names + self.priors = priors + self.parameter_names = parameter_names + + def sample( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + output = {} + for prior in self.priors: + rng_key, subkey = jax.random.split(rng_key) + output.update(prior.sample(subkey, n_samples)) + return output + + def log_prob(self, x: dict[str, Float]) -> Float: + output = 0.0 + for prior in self.priors: + output += prior.log_prob(x) + return output + + # ====================== Things below may need rework ====================== @jaxtyped(typechecker=typechecker) class Uniform(Prior): @@ -686,40 +745,3 @@ def log_prob(self, x: dict[str, Array]) -> Float: - 0.5 * ((variable - self.mean) / self.std) ** 2 ) return output - - -class Composite(Prior): - priors: list[Prior] = field(default_factory=list) - - def __repr__(self): - return f"Composite(priors={self.priors}, naming={self.naming})" - - def __init__( - self, - priors: list[Prior], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - naming = [] - self.transforms = {} - for prior in priors: - naming += prior.naming - self.transforms.update(prior.transforms) - self.priors = priors - self.naming = naming - self.transforms.update(transforms) - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - output = {} - for prior in self.priors: - rng_key, subkey = jax.random.split(rng_key) - output.update(prior.sample(subkey, n_samples)) - return output - - def log_prob(self, x: dict[str, Float]) -> Float: - output = 0.0 - for prior in self.priors: - output += prior.log_prob(x) - return output From 9e282b798f5d7ada64af3cf4a26d934d8c085854 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:43:00 -0400 Subject: [PATCH 020/248] Updated runManager.py --- src/jimgw/single_event/runManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index f079dee3..0575dc92 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -65,13 +65,13 @@ def jaxarray_representer(dumper: yaml.Dumper, data: ArrayImpl): @dataclass class SingleEventRun: seed: int - path: str = "./experiment" detectors: list[str] priors: dict[ str, dict[str, Union[str, float, int, bool]] ] # Transform cannot be included in this way, add it to preset if used often. jim_parameters: dict[str, Union[str, float, int, bool, dict]] + path: str = "./experiment" injection_parameters: dict[str, float] = field( default_factory=lambda: {} ) From e6e45efc00b1790a4e0d8ef087aab51bf627472d Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 15:46:19 -0400 Subject: [PATCH 021/248] scaffold prior test --- test/test_prior.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/test_prior.py b/test/test_prior.py index 53b065da..98b8b1c4 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -1 +1,16 @@ -from jimgw.prior import Composite, Unconstrained_Uniform, Uniform +from jimgw.prior import * + + +class TestUnivariatePrior: + def test_logit(self): + p = Logit() + +class TestPriorOperations: + def test_combine(self): + raise NotImplementedError + + def test_sequence(self): + raise NotImplementedError + + def test_factor(self): + raise NotImplementedError \ No newline at end of file From 182824337b59131b510a13d3cec4224cf0aea7d9 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 24 Jul 2024 15:47:31 -0400 Subject: [PATCH 022/248] Create verify_transform.py --- test/verify_transform.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/verify_transform.py diff --git a/test/verify_transform.py b/test/verify_transform.py new file mode 100644 index 00000000..8531c985 --- /dev/null +++ b/test/verify_transform.py @@ -0,0 +1,21 @@ +import numpy as np + +class VerifyTransform: + def verify_sky_location_transform(self): + from bilby.gw.utils import zenith_azimuth_to_ra_dec as bilby_earth_to_sky + from bilby.gw.detector.networks import InterferometerList + + from jimgw.single_event.utils import zenith_azimuth_to_ra_dec as jimgw_earth_to_sky + from jimgw.single_event.detector import detector_preset + from astropy.time import Time + + ifos = ["H1", "L1"] + geocent_time = 1000000000 + + import matplotlib.pyplot as plt + + for zenith in np.linspace(0, np.pi, 5): + for azimuth in np.linspace(0, 2*np.pi, 5): + bilby_sky_location = np.array(bilby_earth_to_sky(zenith, azimuth, geocent_time, InterferometerList(ifos))) + jimgw_sky_location = np.array(jimgw_earth_to_sky(zenith, azimuth, Time(geocent_time, format="gps").sidereal_time("apparent", "greenwich").rad, detector_preset[ifos[0]].vertex - detector_preset[ifos[1]].vertex)) + assert np.allclose(bilby_sky_location, jimgw_sky_location, atol=1e-4) From d503a37e6089f395ffdfa934ee6cd9f107e66aa8 Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 15:49:02 -0400 Subject: [PATCH 023/248] Add Sequential Transform --- src/jimgw/prior.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index b257ef38..3dbd6491 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -97,12 +97,13 @@ def log_prob(self, x: dict[str, Float]) -> Float: return -variable - 2 * jnp.log(1 + jnp.exp(-variable)) -class Sequential(Prior): +class SequentialTransform(Prior): """ - A prior class constructed + Transform a prior distribution by applying a sequence of transforms. """ - members: list[Prior | Transform] = field(default_factory=list) + base_prior: Prior + transforms: list[Transform] def __repr__(self): return ( @@ -111,9 +112,30 @@ def __repr__(self): def __init( self, - members: list[Prior | Transform], + base_prior: Prior, + transforms: list[Transform], ): - self.members = members + + self.base_prior = base_prior + self.transforms = transforms + self.parameter_names = base_prior.parameter_names + for transform in transforms: + self.parameter_names = transform.propagate_name(self.parameter_names) + + def sample( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + output = self.base_prior.sample(rng_key, n_samples) + for transform in self.transforms: + output, _ = transform.forward(output) + return output + + def log_prob(self, x: dict[str, Float]) -> Float: + output = self.base_prior.log_prob(x) + for transform in self.transforms: + _, log_jacobian = transform.transform(x) + output += log_jacobian + return output class Combine(Prior): """ From 61b3b9ec885fac94f4545cb748c64f5e07deb311 Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 16:03:26 -0400 Subject: [PATCH 024/248] Separate logit and scaling. Also rename prior --- src/jimgw/prior.py | 2 +- src/jimgw/transforms.py | 75 ++++++++++++++++++++++++++++++----------- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 3dbd6491..30442662 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -60,7 +60,7 @@ def log_prob(self, x: dict[str, Array]) -> Float: @jaxtyped(typechecker=typechecker) -class Logit(Prior): +class LogisticDistribution(Prior): def __repr__(self): return f"Logit(parameter_names={self.parameter_names})" diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 0ee32927..a8d0a908 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -72,32 +72,13 @@ def propagate_name(self, x: list[str]) -> list[str]: return list(input_set - from_set | to_set) -class LogitToUniform(Transform): - """ - Transform from unconstrained space to uniform space. - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - bounds : tuple[Float, Float] - The lower and upper bounds of the uniform distribution. - - """ - - bounds: tuple[Float, Float] +class UnivariateTransform(Transform): def __init__( self, name_mapping: tuple[list[str], list[str]], - bounds: tuple[Float, Float], ): super().__init__(name_mapping) - self.bounds = bounds - self.transform_func = ( - lambda x: (self.bounds[1] - self.bounds[0]) / (1 + jnp.exp(-x)) - + self.bounds[0] - ) def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: input_params = x.pop(self.name_mapping[0][0]) @@ -113,3 +94,57 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: output_params = self.transform_func(input_params) x[self.name_mapping[1][0]] = output_params return x + + +class ScaleToRange(UnivariateTransform): + + range: tuple[Float, Float] + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + range: tuple[Float, Float], + ): + super().__init__(name_mapping) + self.range = range + self.transform_func = ( + lambda x: (self.range[1] - self.range[0]) * x + self.range[0] + ) + + +class Logit(Transform): + """ + Logit transform following + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) + + +class Sine(Transform): + """ + Transform from unconstrained space to uniform space. + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + self.transform_func = lambda x: jnp.sin(x) From b47e6aed93c2abe1777aa5a6c8a150164d14e64d Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 16:10:08 -0400 Subject: [PATCH 025/248] Univeriate Transform seems working --- src/jimgw/prior.py | 8 ++++---- src/jimgw/transforms.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 30442662..a9274259 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -63,7 +63,7 @@ def log_prob(self, x: dict[str, Array]) -> Float: class LogisticDistribution(Prior): def __repr__(self): - return f"Logit(parameter_names={self.parameter_names})" + return f"Logistic(parameter_names={self.parameter_names})" def __init__(self, parameter_names: list[str], **kwargs): super().__init__(parameter_names) @@ -107,10 +107,10 @@ class SequentialTransform(Prior): def __repr__(self): return ( - f"Sequential(priors={self.members}, parameter_names={self.parameter_names})" + f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" ) - def __init( + def __init__( self, base_prior: Prior, transforms: list[Transform], @@ -127,7 +127,7 @@ def sample( ) -> dict[str, Float[Array, " n_samples"]]: output = self.base_prior.sample(rng_key, n_samples) for transform in self.transforms: - output, _ = transform.forward(output) + output = jax.vmap(transform.forward)(output) return output def log_prob(self, x: dict[str, Float]) -> Float: diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index a8d0a908..52ec43be 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -112,7 +112,7 @@ def __init__( ) -class Logit(Transform): +class Logit(UnivariateTransform): """ Logit transform following @@ -131,7 +131,7 @@ def __init__( self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) -class Sine(Transform): +class Sine(UnivariateTransform): """ Transform from unconstrained space to uniform space. From c66c872691977bfa7e2db1434a110f50ec63e487 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:12:26 -0400 Subject: [PATCH 026/248] Updated runManager.py --- src/jimgw/single_event/runManager.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index 0575dc92..c63bc2de 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -364,13 +364,18 @@ def sample(self): def get_samples(self): return self.jim.get_samples() - def plot_samples(self, figure_name: str="corner.png"): + def plot_samples(self, figure_name: str="corner.png", **kwargs): import corner import matplotlib.pyplot as plt + import numpy as np + + title_quantiles = kwargs.get("title_quantiles", [0.16, 0.5, 0.84]) + title_fmt = kwargs.get("title_fmt", "g") + samples = self.jim.get_samples() param_names = list(samples.keys()) - samples = jnp.array(list(samples.values())).reshape(int(len(param_names)), -1).T - corner.corner(samples, labels=param_names, plot_datapoints=False, title_quantiles=[0.16, 0.5, 0.84], show_titles=True, title_fmt='g', use_math_text=True) + samples = np.array(list(samples.values())).reshape(int(len(param_names)), -1).T + corner.corner(samples, labels=param_names, plot_datapoints=False, title_quantiles=title_quantiles, show_titles=True, title_fmt=title_fmt, use_math_text=True, **kwargs) plt.savefig(figure_name) plt.close() From c3446b75389e94efeb0a57b2effba24d4f834358 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 24 Jul 2024 16:29:46 -0400 Subject: [PATCH 027/248] Add test for spin transform --- test/verify_transform.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/test/verify_transform.py b/test/verify_transform.py index 8531c985..8da7f40d 100644 --- a/test/verify_transform.py +++ b/test/verify_transform.py @@ -1,4 +1,5 @@ import numpy as np +import jax.numpy as jnp class VerifyTransform: def verify_sky_location_transform(self): @@ -14,8 +15,36 @@ def verify_sky_location_transform(self): import matplotlib.pyplot as plt - for zenith in np.linspace(0, np.pi, 5): - for azimuth in np.linspace(0, 2*np.pi, 5): + for zenith in np.linspace(0, np.pi, 10): + for azimuth in np.linspace(0, 2*np.pi, 10): bilby_sky_location = np.array(bilby_earth_to_sky(zenith, azimuth, geocent_time, InterferometerList(ifos))) jimgw_sky_location = np.array(jimgw_earth_to_sky(zenith, azimuth, Time(geocent_time, format="gps").sidereal_time("apparent", "greenwich").rad, detector_preset[ifos[0]].vertex - detector_preset[ifos[1]].vertex)) assert np.allclose(bilby_sky_location, jimgw_sky_location, atol=1e-4) + + def verify_spin_transform(self): + from bilby.gw.conversion import bilby_to_lalsimulation_spins as bilby_spin_transform + from bilby.gw.conversion import symmetric_mass_ratio_to_mass_ratio, chirp_mass_and_mass_ratio_to_component_masses + from jimgw.constants import Msun + + from jimgw.single_event.utils import spin_to_cartesian_spin as jimgw_spin_transform + + for _ in range(100): + thetaJN = jnp.array(np.random.uniform(0, np.pi)) + phiJL = jnp.array(np.random.uniform(0, np.pi)) + theta1 = jnp.array(np.random.uniform(0, np.pi)) + theta2 = jnp.array(np.random.uniform(0, np.pi)) + phi12 = jnp.array(np.random.uniform(0, np.pi)) + chi1 = jnp.array(np.random.uniform(0, 1)) + chi2 = jnp.array(np.random.uniform(0, 1)) + M_c = jnp.array(np.random.uniform(1, 100)) + eta = jnp.array(np.random.uniform(0.1, 0.25)) + fRef = jnp.array(np.random.uniform(10, 1000)) + phiRef = jnp.array(np.random.uniform(0, 2*np.pi)) + + q = symmetric_mass_ratio_to_mass_ratio(eta) + m1, m2 = chirp_mass_and_mass_ratio_to_component_masses(M_c, q) + bilby_spin = jnp.array(bilby_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, m1*Msun, m2*Msun, fRef, phiRef)) + jimgw_spin = jnp.array(jimgw_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, M_c, eta, fRef, phiRef)) + assert np.allclose(bilby_spin, jimgw_spin) + +# VerifyTransform().verify_spin_transform() From 0c9028e7f49315fa8e7f0801f99207e9cc5357ec Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 24 Jul 2024 16:35:34 -0400 Subject: [PATCH 028/248] Finish spin transform test --- test/verify_transform.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/verify_transform.py b/test/verify_transform.py index 8da7f40d..08cd6ff2 100644 --- a/test/verify_transform.py +++ b/test/verify_transform.py @@ -24,7 +24,6 @@ def verify_sky_location_transform(self): def verify_spin_transform(self): from bilby.gw.conversion import bilby_to_lalsimulation_spins as bilby_spin_transform from bilby.gw.conversion import symmetric_mass_ratio_to_mass_ratio, chirp_mass_and_mass_ratio_to_component_masses - from jimgw.constants import Msun from jimgw.single_event.utils import spin_to_cartesian_spin as jimgw_spin_transform @@ -43,8 +42,7 @@ def verify_spin_transform(self): q = symmetric_mass_ratio_to_mass_ratio(eta) m1, m2 = chirp_mass_and_mass_ratio_to_component_masses(M_c, q) - bilby_spin = jnp.array(bilby_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, m1*Msun, m2*Msun, fRef, phiRef)) + MsunInkg = 1.9884e30 + bilby_spin = jnp.array(bilby_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, m1*MsunInkg, m2*MsunInkg, fRef, phiRef)) jimgw_spin = jnp.array(jimgw_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, M_c, eta, fRef, phiRef)) assert np.allclose(bilby_spin, jimgw_spin) - -# VerifyTransform().verify_spin_transform() From e1cc408c2400807d35492a875e5ba1d10823e382 Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 16:37:18 -0400 Subject: [PATCH 029/248] Uniform prior now working. Also add prior test --- src/jimgw/prior.py | 133 ++++++++++++++++++---------------------- src/jimgw/transforms.py | 22 ++++--- test/test_prior.py | 8 ++- 3 files changed, 80 insertions(+), 83 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index a9274259..a7a81acc 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -10,7 +10,7 @@ from jimgw.single_event.detector import GroundBased2G, detector_preset from jimgw.single_event.utils import zenith_azimuth_to_ra_dec -from jimgw.transforms import Transform +from jimgw.transforms import Transform, Logit, Scale, Offset class Prior(Distribution): @@ -134,104 +134,87 @@ def log_prob(self, x: dict[str, Float]) -> Float: output = self.base_prior.log_prob(x) for transform in self.transforms: _, log_jacobian = transform.transform(x) - output += log_jacobian + output -= log_jacobian return output -class Combine(Prior): - """ - A prior class constructed by joinning multiple priors together to form a multivariate prior. - This assumes the priors composing the Combine class are independent. - """ +# class Combine(Prior): +# """ +# A prior class constructed by joinning multiple priors together to form a multivariate prior. +# This assumes the priors composing the Combine class are independent. +# """ + +# priors: list[Prior] = field(default_factory=list) + +# def __repr__(self): +# return ( +# f"Composite(priors={self.priors}, parameter_names={self.parameter_names})" +# ) + +# def __init__( +# self, +# priors: list[Prior], +# **kwargs, +# ): +# parameter_names = [] +# for prior in priors: +# parameter_names += prior.parameter_names +# self.priors = priors +# self.parameter_names = parameter_names + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# output = {} +# for prior in self.priors: +# rng_key, subkey = jax.random.split(rng_key) +# output.update(prior.sample(subkey, n_samples)) +# return output + +# def log_prob(self, x: dict[str, Float]) -> Float: +# output = 0.0 +# for prior in self.priors: +# output -= prior.log_prob(x) +# return output - priors: list[Prior] = field(default_factory=list) - def __repr__(self): - return ( - f"Composite(priors={self.priors}, parameter_names={self.parameter_names})" - ) - def __init__( - self, - priors: list[Prior], - **kwargs, - ): - parameter_names = [] - for prior in priors: - parameter_names += prior.parameter_names - self.priors = priors - self.parameter_names = parameter_names - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - output = {} - for prior in self.priors: - rng_key, subkey = jax.random.split(rng_key) - output.update(prior.sample(subkey, n_samples)) - return output - - def log_prob(self, x: dict[str, Float]) -> Float: - output = 0.0 - for prior in self.priors: - output += prior.log_prob(x) - return output - - -# ====================== Things below may need rework ====================== @jaxtyped(typechecker=typechecker) class Uniform(Prior): - xmin: float = 0.0 - xmax: float = 1.0 + _dist: Prior + + xmin: float + xmax: float def __repr__(self): return f"Uniform(xmin={self.xmin}, xmax={self.xmax})" def __init__( self, - xmin: Float, - xmax: Float, - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, + xmin: float, + xmax: float, + parameter_names: list[str], ): - super().__init__(naming, transforms) + super().__init__(parameter_names) assert self.n_dim == 1, "Uniform needs to be 1D distributions" self.xmax = xmax self.xmin = xmin + self._dist = SequentialTransform( + LogisticDistribution(parameter_names), + [ + Logit((parameter_names, parameter_names)), + Scale((parameter_names, parameter_names), xmax - xmin), + Offset((parameter_names, parameter_names), xmin), + ]) def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from a uniform distribution. - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : dict - Samples from the distribution. The keys are the names of the parameters. - - """ - samples = jax.random.uniform( - rng_key, (n_samples,), minval=self.xmin, maxval=self.xmax - ) - return self.add_name(samples[None]) + return self._dist.sample(rng_key, n_samples) def log_prob(self, x: dict[str, Array]) -> Float: - variable = x[self.naming[0]] - output = jnp.where( - (variable >= self.xmax) | (variable <= self.xmin), - jnp.zeros_like(variable) - jnp.inf, - jnp.zeros_like(variable), - ) - return output + jnp.log(1.0 / (self.xmax - self.xmin)) + return self._dist.log_prob(x) +# ====================== Things below may need rework ====================== @jaxtyped(typechecker=typechecker) class Unconstrained_Uniform(Prior): diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 52ec43be..203f6bf1 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -95,21 +95,29 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: x[self.name_mapping[1][0]] = output_params return x +class Scale(UnivariateTransform): + scale: Float -class ScaleToRange(UnivariateTransform): + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + scale: Float, + ): + super().__init__(name_mapping) + self.scale = scale + self.transform_func = lambda x: x * self.scale - range: tuple[Float, Float] +class Offset(UnivariateTransform): + offset: Float def __init__( self, name_mapping: tuple[list[str], list[str]], - range: tuple[Float, Float], + offset: Float, ): super().__init__(name_mapping) - self.range = range - self.transform_func = ( - lambda x: (self.range[1] - self.range[0]) * x + self.range[0] - ) + self.offset = offset + self.transform_func = lambda x: x + self.offset class Logit(UnivariateTransform): diff --git a/test/test_prior.py b/test/test_prior.py index 98b8b1c4..6890f9b9 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -2,9 +2,15 @@ class TestUnivariatePrior: - def test_logit(self): + def test_logistic(self): p = Logit() + def test_uniform(self): + p = Uniform(0.0, 10.0, ['x']) + samples = p.sample(jax.random.PRNGKey(0), 10000) + log_prob = jax.vmap(p.log_prob)(samples) + assert jnp.allclose(log_prob, -jnp.log(10.0)) + class TestPriorOperations: def test_combine(self): raise NotImplementedError From 374f2483a4d36cc37bdf41b693177cfcdcf39818 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 24 Jul 2024 16:45:36 -0400 Subject: [PATCH 030/248] Add likelihood name check --- src/jimgw/single_event/runManager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index 3f65166d..50a591cd 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -150,6 +150,7 @@ def initialize_likelihood(self, prior: prior.Prior) -> SingleEventLiklihood: waveform = self.initialize_waveform() name = self.run.likelihood_parameters["name"] assert isinstance(name, str), "Likelihood name must be a string." + assert name in likelihood_presets, f"Likelihood {name} not recognized." if self.run.injection: freqs = jnp.linspace( self.run.data_parameters["f_min"], From 0c49b23cebac8bb43b1b0dfe102f4292a7cdabf1 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 24 Jul 2024 16:47:06 -0400 Subject: [PATCH 031/248] Rename test file --- test/{verify_transform.py => test_transform.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{verify_transform.py => test_transform.py} (100%) diff --git a/test/verify_transform.py b/test/test_transform.py similarity index 100% rename from test/verify_transform.py rename to test/test_transform.py From d866fae021aa332033156a47bb74106dfb933468 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:04:07 -0400 Subject: [PATCH 032/248] Updated runManager.py --- src/jimgw/single_event/runManager.py | 52 ++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index c63bc2de..a3080e68 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -4,6 +4,8 @@ import jax import jax.numpy as jnp import matplotlib.pyplot as plt +import corner +import numpy as np import yaml from astropy.time import Time from jaxlib.xla_extension import ArrayImpl @@ -364,18 +366,56 @@ def sample(self): def get_samples(self): return self.jim.get_samples() - def plot_samples(self, figure_name: str="corner.png", **kwargs): - import corner - import matplotlib.pyplot as plt - import numpy as np - + def plot_corner(self, figure_name: str="corner.png", **kwargs): + """ + plot corner plot of the samples. + """ + plot_datapoint = kwargs.get("plot_datapoints", False) title_quantiles = kwargs.get("title_quantiles", [0.16, 0.5, 0.84]) + show_titles = kwargs.get("show_titles", True) title_fmt = kwargs.get("title_fmt", "g") + use_math_text = kwargs.get("use_math_text", True) samples = self.jim.get_samples() param_names = list(samples.keys()) samples = np.array(list(samples.values())).reshape(int(len(param_names)), -1).T - corner.corner(samples, labels=param_names, plot_datapoints=False, title_quantiles=title_quantiles, show_titles=True, title_fmt=title_fmt, use_math_text=True, **kwargs) + corner.corner(samples, labels=param_names, plot_datapoints=plot_datapoint, title_quantiles=title_quantiles, show_titles=show_titles, title_fmt=title_fmt, use_math_text=use_math_text, **kwargs) + plt.savefig(figure_name) + plt.close() + + def plot_diagnostic(self, figure_name: str="diagnostic.png", **kwargs): + """ + plot diagnostic plot of the samples. + """ + summary = self.jim.Sampler.get_sampler_state(training=True) + chains, log_prob, local_accs, global_accs, loss_vals = summary.values() + log_prob = np.array(log_prob) + + plt.figure(figsize=(6, 6)) + axs = [plt.subplot(2, 2, i + 1) for i in range(4)] + plt.sca(axs[0]) + plt.title("log probability") + for i in range(log_prob.shape[0]): + axs[0].plot(log_prob[i], linewidth=0.05) + plt.xlabel("iteration") + + plt.sca(axs[1]) + plt.title("NF loss") + plt.plot(loss_vals.reshape(-1)) + plt.xlabel("iteration") + + plt.sca(axs[2]) + plt.title("Local Acceptance") + plt.plot(local_accs.mean(0)) + plt.xlabel("iteration") + + plt.sca(axs[3]) + plt.title("Global Acceptance") + plt.plot(global_accs.mean(0)) + plt.xlabel("iteration") + plt.tight_layout() + plt.savefig(figure_name) plt.close() + From d68cb366e3d8b3ce22aa0dc8f2bfb1f412eb747a Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 17:10:29 -0400 Subject: [PATCH 033/248] Added inverse transform and uniform now perform correct --- src/jimgw/prior.py | 83 +++-------------------------------------- src/jimgw/transforms.py | 56 ++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 80 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index a7a81acc..35722707 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -131,10 +131,11 @@ def sample( return output def log_prob(self, x: dict[str, Float]) -> Float: - output = self.base_prior.log_prob(x) - for transform in self.transforms: - _, log_jacobian = transform.transform(x) - output -= log_jacobian + output = 0.0 + for transform in reversed(self.transforms): + x, log_jacobian = transform.inverse_transform(x) + output += log_jacobian + output += self.base_prior.log_prob(x) return output # class Combine(Prior): @@ -216,80 +217,6 @@ def log_prob(self, x: dict[str, Array]) -> Float: # ====================== Things below may need rework ====================== -@jaxtyped(typechecker=typechecker) -class Unconstrained_Uniform(Prior): - xmin: float = 0.0 - xmax: float = 1.0 - - def __repr__(self): - return f"Unconstrained_Uniform(xmin={self.xmin}, xmax={self.xmax})" - - def __init__( - self, - xmin: Float, - xmax: Float, - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - super().__init__(naming, transforms) - assert self.n_dim == 1, "Unconstrained_Uniform needs to be 1D distributions" - self.xmax = xmax - self.xmin = xmin - local_transform = self.transforms - - def new_transform(param): - param[self.naming[0]] = self.to_range(param[self.naming[0]]) - return local_transform[self.naming[0]][1](param) - - self.transforms = { - self.naming[0]: (local_transform[self.naming[0]][0], new_transform) - } - - def to_range(self, x: Float) -> Float: - """ - Transform the parameters to the range of the prior. - - Parameters - ---------- - x : Float - The parameters to transform. - - Returns - ------- - x : dict - A dictionary of parameters with the transforms applied. - """ - return (self.xmax - self.xmin) / (1 + jnp.exp(-x)) + self.xmin - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from a uniform distribution. - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : - An array of shape (n_samples, n_dim) containing the samples. - - """ - samples = jax.random.uniform(rng_key, (n_samples,), minval=0, maxval=1) - samples = jnp.log(samples / (1 - samples)) - return self.add_name(samples[None]) - - def log_prob(self, x: dict[str, Float]) -> Float: - variable = x[self.naming[0]] - return jnp.log(jnp.exp(-variable) / (1 + jnp.exp(-variable)) ** 2) - - class Sphere(Prior): """ A prior on a sphere represented by Cartesian coordinates. diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 203f6bf1..c5e9120a 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -18,6 +18,7 @@ class Transform(ABC): name_mapping: tuple[list[str], list[str]] transform_func: Callable[[dict[str, Float]], dict[str, Float]] + inverse_func: Callable[[dict[str, Float]], dict[str, Float]] def __init__( self, @@ -47,6 +48,23 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: The log Jacobian determinant. """ raise NotImplementedError + + @abstractmethod + def inverse_transform(self, x: dict[str, Float]) -> dict[str, Float]: + """ + Inverse transform the input x to transformed coordinate y. + + Parameters + ---------- + x : dict[str, Float] + The input dictionary. + + Returns + ------- + y : dict[str, Float] + The transformed dictionary. + """ + raise NotImplementedError @abstractmethod def forward(self, x: dict[str, Float]) -> dict[str, Float]: @@ -64,6 +82,23 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: The transformed dictionary. """ raise NotImplementedError + + @abstractmethod + def backward(self, x: dict[str, Float]) -> dict[str, Float]: + """ + Pull back the input x to transformed coordinate y. + + Parameters + ---------- + x : dict[str, Float] + The input dictionary. + + Returns + ------- + y : dict[str, Float] + The transformed dictionary. + """ + raise NotImplementedError def propagate_name(self, x: list[str]) -> list[str]: input_set = set(x) @@ -88,12 +123,28 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: x[self.name_mapping[1][0]] = output_params return x, jnp.log(jacobian) + def inverse_transform(self, x: dict[str, Float]) -> dict[str, Float]: + output_params = x.pop(self.name_mapping[1][0]) + assert_rank(output_params, 0) + input_params = self.inverse_func(output_params) + jacobian = jax.jacfwd(self.inverse_func)(output_params) + x[self.name_mapping[0][0]] = input_params + return x, jnp.log(jacobian) + def forward(self, x: dict[str, Float]) -> dict[str, Float]: input_params = x.pop(self.name_mapping[0][0]) assert_rank(input_params, 0) output_params = self.transform_func(input_params) x[self.name_mapping[1][0]] = output_params return x + + def backward(self, x: dict[str, Float]) -> dict[str, Float]: + output_params = x.pop(self.name_mapping[1][0]) + assert_rank(output_params, 0) + input_params = self.inverse_func(output_params) + x[self.name_mapping[0][0]] = input_params + return x + class Scale(UnivariateTransform): scale: Float @@ -106,6 +157,7 @@ def __init__( super().__init__(name_mapping) self.scale = scale self.transform_func = lambda x: x * self.scale + self.inverse_func = lambda x: x / self.scale class Offset(UnivariateTransform): offset: Float @@ -118,7 +170,7 @@ def __init__( super().__init__(name_mapping) self.offset = offset self.transform_func = lambda x: x + self.offset - + self.inverse_func = lambda x: x - self.offset class Logit(UnivariateTransform): """ @@ -137,7 +189,7 @@ def __init__( ): super().__init__(name_mapping) self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) - + self.inverse_func = lambda x: jnp.log(x / (1 - x)) class Sine(UnivariateTransform): """ From c16eef5bec5b04aa7b2bddc8788dabfd6f801c06 Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 24 Jul 2024 17:13:11 -0400 Subject: [PATCH 034/248] Add comments to current sequential transform prior class --- src/jimgw/prior.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 35722707..9fee29d9 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -131,6 +131,10 @@ def sample( return output def log_prob(self, x: dict[str, Float]) -> Float: + """ + Requiring inverse transform in log_prob may not be the best option, + may need alternative + """ output = 0.0 for transform in reversed(self.transforms): x, log_jacobian = transform.inverse_transform(x) From 1ca24210c941d8bfa4da5b2f58dd52201627f94d Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 25 Jul 2024 08:38:28 -0400 Subject: [PATCH 035/248] Updated runManager.py and Single_event_runManager.py --- example/Single_event_runManager.py | 2 ++ src/jimgw/single_event/runManager.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/example/Single_event_runManager.py b/example/Single_event_runManager.py index 80bffda5..3a89b561 100644 --- a/example/Single_event_runManager.py +++ b/example/Single_event_runManager.py @@ -91,4 +91,6 @@ run_manager = SingleEventPERunManager(run=run) run_manager.jim.sample(jax.random.PRNGKey(42)) +run_manager.plot_corner() +run_manager.plot_diagnostic() run_manager.jim.get_samples() diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index a3080e68..db90dd77 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -366,7 +366,7 @@ def sample(self): def get_samples(self): return self.jim.get_samples() - def plot_corner(self, figure_name: str="corner.png", **kwargs): + def plot_corner(self, path: str="corner.png", **kwargs): """ plot corner plot of the samples. """ @@ -380,10 +380,10 @@ def plot_corner(self, figure_name: str="corner.png", **kwargs): param_names = list(samples.keys()) samples = np.array(list(samples.values())).reshape(int(len(param_names)), -1).T corner.corner(samples, labels=param_names, plot_datapoints=plot_datapoint, title_quantiles=title_quantiles, show_titles=show_titles, title_fmt=title_fmt, use_math_text=use_math_text, **kwargs) - plt.savefig(figure_name) + plt.savefig(path) plt.close() - def plot_diagnostic(self, figure_name: str="diagnostic.png", **kwargs): + def plot_diagnostic(self, path: str="diagnostic.png", **kwargs): """ plot diagnostic plot of the samples. """ @@ -415,7 +415,7 @@ def plot_diagnostic(self, figure_name: str="diagnostic.png", **kwargs): plt.xlabel("iteration") plt.tight_layout() - plt.savefig(figure_name) + plt.savefig(path) plt.close() From 227fc983c53db406499cf82ddd3d49b4ac668322 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 25 Jul 2024 08:59:32 -0400 Subject: [PATCH 036/248] Updated runManager.py --- src/jimgw/single_event/runManager.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index db90dd77..124e9de5 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -366,7 +366,7 @@ def sample(self): def get_samples(self): return self.jim.get_samples() - def plot_corner(self, path: str="corner.png", **kwargs): + def plot_corner(self, path: str="corner.jpeg", **kwargs): """ plot corner plot of the samples. """ @@ -383,7 +383,7 @@ def plot_corner(self, path: str="corner.png", **kwargs): plt.savefig(path) plt.close() - def plot_diagnostic(self, path: str="diagnostic.png", **kwargs): + def plot_diagnostic(self, path: str="diagnostic.jpeg", **kwargs): """ plot diagnostic plot of the samples. """ @@ -395,8 +395,7 @@ def plot_diagnostic(self, path: str="diagnostic.png", **kwargs): axs = [plt.subplot(2, 2, i + 1) for i in range(4)] plt.sca(axs[0]) plt.title("log probability") - for i in range(log_prob.shape[0]): - axs[0].plot(log_prob[i], linewidth=0.05) + plt.plot(log_prob.mean(0)) plt.xlabel("iteration") plt.sca(axs[1]) From 27998caceb4f77b5bb85d9bba2f5a8e6d0862ab7 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:05:09 -0400 Subject: [PATCH 037/248] Reformatted files --- example/Single_event_runManager.py | 5 ++-- src/jimgw/single_event/runManager.py | 41 ++++++++++++++++------------ 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/example/Single_event_runManager.py b/example/Single_event_runManager.py index 3a89b561..5c678b22 100644 --- a/example/Single_event_runManager.py +++ b/example/Single_event_runManager.py @@ -28,7 +28,6 @@ ] ) - run = SingleEventRun( seed=0, detectors=["H1", "L1"], @@ -91,6 +90,8 @@ run_manager = SingleEventPERunManager(run=run) run_manager.jim.sample(jax.random.PRNGKey(42)) + +# plot the corner plot and diagnostic plot run_manager.plot_corner() run_manager.plot_diagnostic() -run_manager.jim.get_samples() + diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index 124e9de5..66790c1d 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -67,16 +67,14 @@ def jaxarray_representer(dumper: yaml.Dumper, data: ArrayImpl): @dataclass class SingleEventRun: seed: int - + detectors: list[str] priors: dict[ str, dict[str, Union[str, float, int, bool]] ] # Transform cannot be included in this way, add it to preset if used often. jim_parameters: dict[str, Union[str, float, int, bool, dict]] path: str = "./experiment" - injection_parameters: dict[str, float] = field( - default_factory=lambda: {} - ) + injection_parameters: dict[str, float] = field(default_factory=lambda: {}) injection: bool = False likelihood_parameters: dict[str, Union[str, float, int, bool, PyTree]] = field( default_factory=lambda: {"name": "TransientLikelihoodFD"} @@ -127,7 +125,7 @@ def __init__(self, **kwargs): else: print("Neither run instance nor path provided.") raise ValueError - + if self.run.injection and not self.run.injection_parameters: raise ValueError("Injection mode requires injection parameters.") @@ -359,14 +357,14 @@ def plot_data(self, path: str): plt.ylabel("Amplitude") plt.legend() plt.savefig(path) - + def sample(self): self.jim.sample(jax.random.PRNGKey(self.run.seed)) - + def get_samples(self): return self.jim.get_samples() - - def plot_corner(self, path: str="corner.jpeg", **kwargs): + + def plot_corner(self, path: str = "corner.jpeg", **kwargs): """ plot corner plot of the samples. """ @@ -375,29 +373,38 @@ def plot_corner(self, path: str="corner.jpeg", **kwargs): show_titles = kwargs.get("show_titles", True) title_fmt = kwargs.get("title_fmt", "g") use_math_text = kwargs.get("use_math_text", True) - + samples = self.jim.get_samples() param_names = list(samples.keys()) samples = np.array(list(samples.values())).reshape(int(len(param_names)), -1).T - corner.corner(samples, labels=param_names, plot_datapoints=plot_datapoint, title_quantiles=title_quantiles, show_titles=show_titles, title_fmt=title_fmt, use_math_text=use_math_text, **kwargs) + corner.corner( + samples, + labels=param_names, + plot_datapoints=plot_datapoint, + title_quantiles=title_quantiles, + show_titles=show_titles, + title_fmt=title_fmt, + use_math_text=use_math_text, + **kwargs, + ) plt.savefig(path) plt.close() - - def plot_diagnostic(self, path: str="diagnostic.jpeg", **kwargs): + + def plot_diagnostic(self, path: str = "diagnostic.jpeg", **kwargs): """ plot diagnostic plot of the samples. """ summary = self.jim.Sampler.get_sampler_state(training=True) chains, log_prob, local_accs, global_accs, loss_vals = summary.values() log_prob = np.array(log_prob) - + plt.figure(figsize=(6, 6)) axs = [plt.subplot(2, 2, i + 1) for i in range(4)] plt.sca(axs[0]) plt.title("log probability") plt.plot(log_prob.mean(0)) plt.xlabel("iteration") - + plt.sca(axs[1]) plt.title("NF loss") plt.plot(loss_vals.reshape(-1)) @@ -413,8 +420,6 @@ def plot_diagnostic(self, path: str="diagnostic.jpeg", **kwargs): plt.plot(global_accs.mean(0)) plt.xlabel("iteration") plt.tight_layout() - + plt.savefig(path) plt.close() - - From 94e5dfac77c80f2de9d30fbfb6dd08ed4f45c5f0 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:05:53 -0400 Subject: [PATCH 038/248] Updated runManager.py --- src/jimgw/single_event/runManager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index 66790c1d..6a3b3091 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -398,27 +398,31 @@ def plot_diagnostic(self, path: str = "diagnostic.jpeg", **kwargs): chains, log_prob, local_accs, global_accs, loss_vals = summary.values() log_prob = np.array(log_prob) - plt.figure(figsize=(6, 6)) + plt.figure(figsize=(10, 10)) axs = [plt.subplot(2, 2, i + 1) for i in range(4)] plt.sca(axs[0]) plt.title("log probability") plt.plot(log_prob.mean(0)) plt.xlabel("iteration") + plt.xlim(0, None) plt.sca(axs[1]) plt.title("NF loss") plt.plot(loss_vals.reshape(-1)) plt.xlabel("iteration") + plt.xlim(0, None) plt.sca(axs[2]) plt.title("Local Acceptance") plt.plot(local_accs.mean(0)) plt.xlabel("iteration") + plt.xlim(0, None) plt.sca(axs[3]) plt.title("Global Acceptance") plt.plot(global_accs.mean(0)) plt.xlabel("iteration") + plt.xlim(0, None) plt.tight_layout() plt.savefig(path) From 4267d97835242a90a16d5afeb58e88aa801b7e80 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:11:04 -0400 Subject: [PATCH 039/248] Changed corner plot default setting --- src/jimgw/single_event/runManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index 6a3b3091..36312498 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -371,7 +371,7 @@ def plot_corner(self, path: str = "corner.jpeg", **kwargs): plot_datapoint = kwargs.get("plot_datapoints", False) title_quantiles = kwargs.get("title_quantiles", [0.16, 0.5, 0.84]) show_titles = kwargs.get("show_titles", True) - title_fmt = kwargs.get("title_fmt", "g") + title_fmt = kwargs.get("title_fmt", ".2E") use_math_text = kwargs.get("use_math_text", True) samples = self.jim.get_samples() From 8ab92a481aac134ecef29883ea04eae725242fb3 Mon Sep 17 00:00:00 2001 From: kazewong Date: Thu, 25 Jul 2024 13:13:34 -0400 Subject: [PATCH 040/248] Removing inverse. Inverse limts the type of transform one can use, And it doesn't seem to have case that will require log_prob on target space --- src/jimgw/prior.py | 14 +++++------ src/jimgw/transforms.py | 56 ----------------------------------------- 2 files changed, 7 insertions(+), 63 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 9fee29d9..61202609 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -132,14 +132,14 @@ def sample( def log_prob(self, x: dict[str, Float]) -> Float: """ - Requiring inverse transform in log_prob may not be the best option, - may need alternative + log_prob has to be evaluated in the space of the base_prior. + + """ - output = 0.0 - for transform in reversed(self.transforms): - x, log_jacobian = transform.inverse_transform(x) - output += log_jacobian - output += self.base_prior.log_prob(x) + output = self.base_prior.log_prob(x) + for transform in self.transforms: + x, log_jacobian = transform.transform(x) + output -= log_jacobian return output # class Combine(Prior): diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index c5e9120a..7ef8560a 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -8,7 +8,6 @@ from chex import assert_rank from jaxtyping import Array, Float, jaxtyped - class Transform(ABC): """ Base class for transform. @@ -18,8 +17,6 @@ class Transform(ABC): name_mapping: tuple[list[str], list[str]] transform_func: Callable[[dict[str, Float]], dict[str, Float]] - inverse_func: Callable[[dict[str, Float]], dict[str, Float]] - def __init__( self, name_mapping: tuple[list[str], list[str]], @@ -49,23 +46,6 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ raise NotImplementedError - @abstractmethod - def inverse_transform(self, x: dict[str, Float]) -> dict[str, Float]: - """ - Inverse transform the input x to transformed coordinate y. - - Parameters - ---------- - x : dict[str, Float] - The input dictionary. - - Returns - ------- - y : dict[str, Float] - The transformed dictionary. - """ - raise NotImplementedError - @abstractmethod def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ @@ -82,23 +62,6 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: The transformed dictionary. """ raise NotImplementedError - - @abstractmethod - def backward(self, x: dict[str, Float]) -> dict[str, Float]: - """ - Pull back the input x to transformed coordinate y. - - Parameters - ---------- - x : dict[str, Float] - The input dictionary. - - Returns - ------- - y : dict[str, Float] - The transformed dictionary. - """ - raise NotImplementedError def propagate_name(self, x: list[str]) -> list[str]: input_set = set(x) @@ -123,14 +86,6 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: x[self.name_mapping[1][0]] = output_params return x, jnp.log(jacobian) - def inverse_transform(self, x: dict[str, Float]) -> dict[str, Float]: - output_params = x.pop(self.name_mapping[1][0]) - assert_rank(output_params, 0) - input_params = self.inverse_func(output_params) - jacobian = jax.jacfwd(self.inverse_func)(output_params) - x[self.name_mapping[0][0]] = input_params - return x, jnp.log(jacobian) - def forward(self, x: dict[str, Float]) -> dict[str, Float]: input_params = x.pop(self.name_mapping[0][0]) assert_rank(input_params, 0) @@ -138,14 +93,6 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: x[self.name_mapping[1][0]] = output_params return x - def backward(self, x: dict[str, Float]) -> dict[str, Float]: - output_params = x.pop(self.name_mapping[1][0]) - assert_rank(output_params, 0) - input_params = self.inverse_func(output_params) - x[self.name_mapping[0][0]] = input_params - return x - - class Scale(UnivariateTransform): scale: Float @@ -157,7 +104,6 @@ def __init__( super().__init__(name_mapping) self.scale = scale self.transform_func = lambda x: x * self.scale - self.inverse_func = lambda x: x / self.scale class Offset(UnivariateTransform): offset: Float @@ -170,7 +116,6 @@ def __init__( super().__init__(name_mapping) self.offset = offset self.transform_func = lambda x: x + self.offset - self.inverse_func = lambda x: x - self.offset class Logit(UnivariateTransform): """ @@ -189,7 +134,6 @@ def __init__( ): super().__init__(name_mapping) self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) - self.inverse_func = lambda x: jnp.log(x / (1 - x)) class Sine(UnivariateTransform): """ From d8b2d1feb0bdae6b4fd59bef8cc345eca2bb8c33 Mon Sep 17 00:00:00 2001 From: kazewong Date: Thu, 25 Jul 2024 13:48:17 -0400 Subject: [PATCH 041/248] Add transformation function --- src/jimgw/prior.py | 19 +++++++++++++++---- test/test_prior.py | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 61202609..232b4a0f 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -126,9 +126,7 @@ def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: output = self.base_prior.sample(rng_key, n_samples) - for transform in self.transforms: - output = jax.vmap(transform.forward)(output) - return output + return jax.vmap(self.transform)(output) def log_prob(self, x: dict[str, Float]) -> Float: """ @@ -141,6 +139,11 @@ def log_prob(self, x: dict[str, Float]) -> Float: x, log_jacobian = transform.transform(x) output -= log_jacobian return output + + def transform(self, x: dict[str, Float]) -> dict[str, Float]: + for transform in self.transforms: + x = transform.forward(x) + return x # class Combine(Prior): # """ @@ -185,7 +188,7 @@ def log_prob(self, x: dict[str, Float]) -> Float: @jaxtyped(typechecker=typechecker) class Uniform(Prior): - _dist: Prior + _dist: SequentialTransform xmin: float xmax: float @@ -218,6 +221,14 @@ def sample( def log_prob(self, x: dict[str, Array]) -> Float: return self._dist.log_prob(x) + + def sample_base( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + return self._dist.base_prior.sample(rng_key, n_samples) + + def transform(self, x: dict[str, Float]) -> dict[str, Float]: + return self._dist.transform(x) # ====================== Things below may need rework ====================== diff --git a/test/test_prior.py b/test/test_prior.py index 6890f9b9..b6eb7c87 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -7,7 +7,7 @@ def test_logistic(self): def test_uniform(self): p = Uniform(0.0, 10.0, ['x']) - samples = p.sample(jax.random.PRNGKey(0), 10000) + samples = p._dist.base_prior.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.allclose(log_prob, -jnp.log(10.0)) From ca1d6b6c1fa2ac7cae0d9a313693ea97af070fe4 Mon Sep 17 00:00:00 2001 From: kazewong Date: Thu, 25 Jul 2024 14:07:16 -0400 Subject: [PATCH 042/248] Combine should be working now --- src/jimgw/prior.py | 75 +++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 232b4a0f..85414064 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -145,44 +145,43 @@ def transform(self, x: dict[str, Float]) -> dict[str, Float]: x = transform.forward(x) return x -# class Combine(Prior): -# """ -# A prior class constructed by joinning multiple priors together to form a multivariate prior. -# This assumes the priors composing the Combine class are independent. -# """ - -# priors: list[Prior] = field(default_factory=list) - -# def __repr__(self): -# return ( -# f"Composite(priors={self.priors}, parameter_names={self.parameter_names})" -# ) - -# def __init__( -# self, -# priors: list[Prior], -# **kwargs, -# ): -# parameter_names = [] -# for prior in priors: -# parameter_names += prior.parameter_names -# self.priors = priors -# self.parameter_names = parameter_names - -# def sample( -# self, rng_key: PRNGKeyArray, n_samples: int -# ) -> dict[str, Float[Array, " n_samples"]]: -# output = {} -# for prior in self.priors: -# rng_key, subkey = jax.random.split(rng_key) -# output.update(prior.sample(subkey, n_samples)) -# return output - -# def log_prob(self, x: dict[str, Float]) -> Float: -# output = 0.0 -# for prior in self.priors: -# output -= prior.log_prob(x) -# return output +class Combine(Prior): + """ + A prior class constructed by joinning multiple priors together to form a multivariate prior. + This assumes the priors composing the Combine class are independent. + """ + + priors: list[Prior] = field(default_factory=list) + + def __repr__(self): + return ( + f"Composite(priors={self.priors}, parameter_names={self.parameter_names})" + ) + + def __init__( + self, + priors: list[Prior], + ): + parameter_names = [] + for prior in priors: + parameter_names += prior.parameter_names + self.priors = priors + self.parameter_names = parameter_names + + def sample( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + output = {} + for prior in self.priors: + rng_key, subkey = jax.random.split(rng_key) + output.update(prior.sample(subkey, n_samples)) + return output + + def log_prob(self, x: dict[str, Float]) -> Float: + output = 0.0 + for prior in self.priors: + output += prior.log_prob(x) + return output From 2f3f412446ec346a8dbfe367dc7acfee7bf4c7d1 Mon Sep 17 00:00:00 2001 From: kazewong Date: Thu, 25 Jul 2024 14:52:02 -0400 Subject: [PATCH 043/248] Sine is an illegal transform since its Jacobian could be negative --- src/jimgw/prior.py | 2 +- src/jimgw/transforms.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 85414064..1e7cb960 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -193,7 +193,7 @@ class Uniform(Prior): xmax: float def __repr__(self): - return f"Uniform(xmin={self.xmin}, xmax={self.xmax})" + return f"Uniform(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" def __init__( self, diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 7ef8560a..55a5d0ce 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -135,10 +135,10 @@ def __init__( super().__init__(name_mapping) self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) -class Sine(UnivariateTransform): +class ArcSine(UnivariateTransform): """ - Transform from unconstrained space to uniform space. - + ArcSine transformation + Parameters ---------- name_mapping : tuple[list[str], list[str]] @@ -151,4 +151,4 @@ def __init__( name_mapping: tuple[list[str], list[str]], ): super().__init__(name_mapping) - self.transform_func = lambda x: jnp.sin(x) + self.transform_func = lambda x: jnp.arcsin(x) From 4978cb1bd8fbf7e1083d0552ebe198ab44de1040 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 25 Jul 2024 18:24:33 -0400 Subject: [PATCH 044/248] Modify Uniform and add UniformSphere --- src/jimgw/prior.py | 143 +++++++++++++++------------------------- src/jimgw/transforms.py | 37 +++++++++-- 2 files changed, 82 insertions(+), 98 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 1e7cb960..effbf388 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -10,7 +10,7 @@ from jimgw.single_event.detector import GroundBased2G, detector_preset from jimgw.single_event.utils import zenith_azimuth_to_ra_dec -from jimgw.transforms import Transform, Logit, Scale, Offset +from jimgw.transforms import Transform, Logit, Scale, Offset, ArcCosine class Prior(Distribution): @@ -49,7 +49,7 @@ def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: """ return dict(zip(self.parameter_names, x)) - + def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: @@ -106,9 +106,7 @@ class SequentialTransform(Prior): transforms: list[Transform] def __repr__(self): - return ( - f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" - ) + return f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" def __init__( self, @@ -127,24 +125,28 @@ def sample( ) -> dict[str, Float[Array, " n_samples"]]: output = self.base_prior.sample(rng_key, n_samples) return jax.vmap(self.transform)(output) - + def log_prob(self, x: dict[str, Float]) -> Float: """ log_prob has to be evaluated in the space of the base_prior. - - """ output = self.base_prior.log_prob(x) for transform in self.transforms: x, log_jacobian = transform.transform(x) output -= log_jacobian return output - + + def sample_base( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + return self.base_prior.sample(rng_key, n_samples) + def transform(self, x: dict[str, Float]) -> dict[str, Float]: for transform in self.transforms: x = transform.forward(x) return x + class Combine(Prior): """ A prior class constructed by joinning multiple priors together to form a multivariate prior. @@ -184,16 +186,13 @@ def log_prob(self, x: dict[str, Float]) -> Float: return output - @jaxtyped(typechecker=typechecker) -class Uniform(Prior): - _dist: SequentialTransform - +class Uniform(SequentialTransform): xmin: float xmax: float def __repr__(self): - return f"Uniform(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" + return f"Uniform(xmin={self.xmin}, xmax={self.xmax}, parameter_names={self.parameter_names})" def __init__( self, @@ -201,94 +200,56 @@ def __init__( xmax: float, parameter_names: list[str], ): - super().__init__(parameter_names) + self.parameter_names = parameter_names assert self.n_dim == 1, "Uniform needs to be 1D distributions" self.xmax = xmax self.xmin = xmin - self._dist = SequentialTransform( - LogisticDistribution(parameter_names), + super().__init__( + LogisticDistribution(self.parameter_names), [ - Logit((parameter_names, parameter_names)), - Scale((parameter_names, parameter_names), xmax - xmin), - Offset((parameter_names, parameter_names), xmin), - ]) - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - return self._dist.sample(rng_key, n_samples) - - def log_prob(self, x: dict[str, Array]) -> Float: - return self._dist.log_prob(x) - - def sample_base( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - return self._dist.base_prior.sample(rng_key, n_samples) - - def transform(self, x: dict[str, Float]) -> dict[str, Float]: - return self._dist.transform(x) - -# ====================== Things below may need rework ====================== + Logit((self.parameter_names, self.parameter_names)), + Scale((self.parameter_names, self.parameter_names), xmax - xmin), + Offset((self.parameter_names, self.parameter_names), xmin), + ], + ) -class Sphere(Prior): - """ - A prior on a sphere represented by Cartesian coordinates. - Magnitude is sampled from a uniform distribution. - """ +@jaxtyped(typechecker=typechecker) +class UniformSphere(Combine): def __repr__(self): - return f"Sphere(naming={self.naming})" + return f"UniformSphere(parameter_names={self.parameter_names})" - def __init__(self, naming: list[str], **kwargs): - name = naming[0] - self.naming = [f"{name}_theta", f"{name}_phi", f"{name}_mag"] - self.transforms = { - self.naming[0]: ( - f"{naming}_x", - lambda params: jnp.sin(params[self.naming[0]]) - * jnp.cos(params[self.naming[1]]) - * params[self.naming[2]], - ), - self.naming[1]: ( - f"{naming}_y", - lambda params: jnp.sin(params[self.naming[0]]) - * jnp.sin(params[self.naming[1]]) - * params[self.naming[2]], - ), - self.naming[2]: ( - f"{naming}_z", - lambda params: jnp.cos(params[self.naming[0]]) * params[self.naming[2]], - ), - } - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - rng_keys = jax.random.split(rng_key, 3) - theta = jnp.arccos( - jax.random.uniform(rng_keys[0], (n_samples,), minval=-1.0, maxval=1.0) + def __init__(self, parameter_names: list[str], **kwargs): + assert ( + len(parameter_names) == 1 + ), "UniformSphere only takes the name of the vector" + parameter_names = parameter_names[0] + self.parameter_names = [ + f"{parameter_names}_mag", + f"{parameter_names}_theta", + f"{parameter_names}_phi", + ] + super().__init__( + [ + Uniform(0.0, 1.0, [self.parameter_names[0]]), + SequentialTransform( + Uniform(-1.0, 1.0, [f"cos_{self.parameter_names[1]}"]), + [ + ArcCosine( + ( + [f"cos_{self.parameter_names[1]}"], + [self.parameter_names[1]], + ) + ) + ], + ), + Uniform(0.0, 2 * jnp.pi, [self.parameter_names[2]]), + ] ) - phi = jax.random.uniform(rng_keys[1], (n_samples,), minval=0, maxval=2 * jnp.pi) - mag = jax.random.uniform(rng_keys[2], (n_samples,), minval=0, maxval=1) - return self.add_name(jnp.stack([theta, phi, mag], axis=1).T) - def log_prob(self, x: dict[str, Float]) -> Float: - theta = x[self.naming[0]] - phi = x[self.naming[1]] - mag = x[self.naming[2]] - output = jnp.where( - (mag > 1) - | (mag < 0) - | (phi > 2 * jnp.pi) - | (phi < 0) - | (theta > jnp.pi) - | (theta < 0), - jnp.zeros_like(0) - jnp.inf, - jnp.log(mag**2 * jnp.sin(x[self.naming[0]])), - ) - return output + +# ====================== Things below may need rework ====================== @jaxtyped(typechecker=typechecker) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 55a5d0ce..d8ee9536 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -1,12 +1,11 @@ from abc import ABC, abstractmethod -from dataclasses import field -from typing import Callable, Union +from typing import Callable import jax import jax.numpy as jnp -from beartype import beartype as typechecker from chex import assert_rank -from jaxtyping import Array, Float, jaxtyped +from jaxtyping import Float + class Transform(ABC): """ @@ -17,6 +16,7 @@ class Transform(ABC): name_mapping: tuple[list[str], list[str]] transform_func: Callable[[dict[str, Float]], dict[str, Float]] + def __init__( self, name_mapping: tuple[list[str], list[str]], @@ -45,7 +45,7 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: The log Jacobian determinant. """ raise NotImplementedError - + @abstractmethod def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ @@ -92,7 +92,8 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: output_params = self.transform_func(input_params) x[self.name_mapping[1][0]] = output_params return x - + + class Scale(UnivariateTransform): scale: Float @@ -105,6 +106,7 @@ def __init__( self.scale = scale self.transform_func = lambda x: x * self.scale + class Offset(UnivariateTransform): offset: Float @@ -117,6 +119,7 @@ def __init__( self.offset = offset self.transform_func = lambda x: x + self.offset + class Logit(UnivariateTransform): """ Logit transform following @@ -135,10 +138,11 @@ def __init__( super().__init__(name_mapping) self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) + class ArcSine(UnivariateTransform): """ ArcSine transformation - + Parameters ---------- name_mapping : tuple[list[str], list[str]] @@ -152,3 +156,22 @@ def __init__( ): super().__init__(name_mapping) self.transform_func = lambda x: jnp.arcsin(x) + + +class ArcCosine(UnivariateTransform): + """ + ArcCosine transformation + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + self.transform_func = lambda x: jnp.arccos(x) From c1115bd5b669bc8a3bcc7e30fe5e7405beb4ba97 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 06:27:36 -0400 Subject: [PATCH 045/248] Add Sine and Cosine Prior --- src/jimgw/prior.py | 50 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index effbf388..894ea9f7 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -10,7 +10,7 @@ from jimgw.single_event.detector import GroundBased2G, detector_preset from jimgw.single_event.utils import zenith_azimuth_to_ra_dec -from jimgw.transforms import Transform, Logit, Scale, Offset, ArcCosine +from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine class Prior(Distribution): @@ -214,6 +214,42 @@ def __init__( ) +@jaxtyped(typechecker=typechecker) +class Sine(SequentialTransform): + """ + A prior distribution where the pdf is proportional to sin(x) in the range [0, pi]. + """ + + def __repr__(self): + return f"Sine(parameter_names={self.parameter_names})" + + def __init__(self, parameter_names: list[str]): + self.parameter_names = parameter_names + assert self.n_dim == 1, "Sine needs to be 1D distributions" + super().__init__( + Uniform(-1.0, 1.0, f"cos_{self.parameter_names}"), + [ArcCosine(([f"cos_{self.parameter_names}"], [self.parameter_names]))], + ) + + +@jaxtyped(typechecker=typechecker) +class Cosine(SequentialTransform): + """ + A prior distribution where the pdf is proportional to cos(x) in the range [-pi/2, pi/2]. + """ + + def __repr__(self): + return f"Cosine(parameter_names={self.parameter_names})" + + def __init__(self, parameter_names: list[str]): + self.parameter_names = parameter_names + assert self.n_dim == 1, "Cosine needs to be 1D distributions" + super().__init__( + Uniform(-1.0, 1.0, f"sin_{self.parameter_names}"), + [ArcSine(([f"sin_{self.parameter_names}"], [self.parameter_names]))], + ) + + @jaxtyped(typechecker=typechecker) class UniformSphere(Combine): @@ -233,17 +269,7 @@ def __init__(self, parameter_names: list[str], **kwargs): super().__init__( [ Uniform(0.0, 1.0, [self.parameter_names[0]]), - SequentialTransform( - Uniform(-1.0, 1.0, [f"cos_{self.parameter_names[1]}"]), - [ - ArcCosine( - ( - [f"cos_{self.parameter_names[1]}"], - [self.parameter_names[1]], - ) - ) - ], - ), + Sine([self.parameter_names[1]]), Uniform(0.0, 2 * jnp.pi, [self.parameter_names[2]]), ] ) From 01a6c1e65ebc4bdc53df48bad87afe1f08d47c58 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 06:57:11 -0400 Subject: [PATCH 046/248] Revert "Modify Uniform and add UniformSphere" This reverts commit 4978cb1bd8fbf7e1083d0552ebe198ab44de1040. --- src/jimgw/prior.py | 143 +++++++++++++++++++++++++--------------- src/jimgw/transforms.py | 37 ++--------- 2 files changed, 98 insertions(+), 82 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index effbf388..1e7cb960 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -10,7 +10,7 @@ from jimgw.single_event.detector import GroundBased2G, detector_preset from jimgw.single_event.utils import zenith_azimuth_to_ra_dec -from jimgw.transforms import Transform, Logit, Scale, Offset, ArcCosine +from jimgw.transforms import Transform, Logit, Scale, Offset class Prior(Distribution): @@ -49,7 +49,7 @@ def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: """ return dict(zip(self.parameter_names, x)) - + def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: @@ -106,7 +106,9 @@ class SequentialTransform(Prior): transforms: list[Transform] def __repr__(self): - return f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" + return ( + f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" + ) def __init__( self, @@ -125,28 +127,24 @@ def sample( ) -> dict[str, Float[Array, " n_samples"]]: output = self.base_prior.sample(rng_key, n_samples) return jax.vmap(self.transform)(output) - + def log_prob(self, x: dict[str, Float]) -> Float: """ log_prob has to be evaluated in the space of the base_prior. + + """ output = self.base_prior.log_prob(x) for transform in self.transforms: x, log_jacobian = transform.transform(x) output -= log_jacobian return output - - def sample_base( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - return self.base_prior.sample(rng_key, n_samples) - + def transform(self, x: dict[str, Float]) -> dict[str, Float]: for transform in self.transforms: x = transform.forward(x) return x - class Combine(Prior): """ A prior class constructed by joinning multiple priors together to form a multivariate prior. @@ -186,13 +184,16 @@ def log_prob(self, x: dict[str, Float]) -> Float: return output + @jaxtyped(typechecker=typechecker) -class Uniform(SequentialTransform): +class Uniform(Prior): + _dist: SequentialTransform + xmin: float xmax: float def __repr__(self): - return f"Uniform(xmin={self.xmin}, xmax={self.xmax}, parameter_names={self.parameter_names})" + return f"Uniform(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" def __init__( self, @@ -200,56 +201,94 @@ def __init__( xmax: float, parameter_names: list[str], ): - self.parameter_names = parameter_names + super().__init__(parameter_names) assert self.n_dim == 1, "Uniform needs to be 1D distributions" self.xmax = xmax self.xmin = xmin - super().__init__( - LogisticDistribution(self.parameter_names), + self._dist = SequentialTransform( + LogisticDistribution(parameter_names), [ - Logit((self.parameter_names, self.parameter_names)), - Scale((self.parameter_names, self.parameter_names), xmax - xmin), - Offset((self.parameter_names, self.parameter_names), xmin), - ], - ) + Logit((parameter_names, parameter_names)), + Scale((parameter_names, parameter_names), xmax - xmin), + Offset((parameter_names, parameter_names), xmin), + ]) + def sample( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + return self._dist.sample(rng_key, n_samples) -@jaxtyped(typechecker=typechecker) -class UniformSphere(Combine): + def log_prob(self, x: dict[str, Array]) -> Float: + return self._dist.log_prob(x) + + def sample_base( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + return self._dist.base_prior.sample(rng_key, n_samples) + + def transform(self, x: dict[str, Float]) -> dict[str, Float]: + return self._dist.transform(x) + +# ====================== Things below may need rework ====================== + +class Sphere(Prior): + """ + A prior on a sphere represented by Cartesian coordinates. + + Magnitude is sampled from a uniform distribution. + """ def __repr__(self): - return f"UniformSphere(parameter_names={self.parameter_names})" + return f"Sphere(naming={self.naming})" - def __init__(self, parameter_names: list[str], **kwargs): - assert ( - len(parameter_names) == 1 - ), "UniformSphere only takes the name of the vector" - parameter_names = parameter_names[0] - self.parameter_names = [ - f"{parameter_names}_mag", - f"{parameter_names}_theta", - f"{parameter_names}_phi", - ] - super().__init__( - [ - Uniform(0.0, 1.0, [self.parameter_names[0]]), - SequentialTransform( - Uniform(-1.0, 1.0, [f"cos_{self.parameter_names[1]}"]), - [ - ArcCosine( - ( - [f"cos_{self.parameter_names[1]}"], - [self.parameter_names[1]], - ) - ) - ], - ), - Uniform(0.0, 2 * jnp.pi, [self.parameter_names[2]]), - ] - ) + def __init__(self, naming: list[str], **kwargs): + name = naming[0] + self.naming = [f"{name}_theta", f"{name}_phi", f"{name}_mag"] + self.transforms = { + self.naming[0]: ( + f"{naming}_x", + lambda params: jnp.sin(params[self.naming[0]]) + * jnp.cos(params[self.naming[1]]) + * params[self.naming[2]], + ), + self.naming[1]: ( + f"{naming}_y", + lambda params: jnp.sin(params[self.naming[0]]) + * jnp.sin(params[self.naming[1]]) + * params[self.naming[2]], + ), + self.naming[2]: ( + f"{naming}_z", + lambda params: jnp.cos(params[self.naming[0]]) * params[self.naming[2]], + ), + } + def sample( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + rng_keys = jax.random.split(rng_key, 3) + theta = jnp.arccos( + jax.random.uniform(rng_keys[0], (n_samples,), minval=-1.0, maxval=1.0) + ) + phi = jax.random.uniform(rng_keys[1], (n_samples,), minval=0, maxval=2 * jnp.pi) + mag = jax.random.uniform(rng_keys[2], (n_samples,), minval=0, maxval=1) + return self.add_name(jnp.stack([theta, phi, mag], axis=1).T) -# ====================== Things below may need rework ====================== + def log_prob(self, x: dict[str, Float]) -> Float: + theta = x[self.naming[0]] + phi = x[self.naming[1]] + mag = x[self.naming[2]] + output = jnp.where( + (mag > 1) + | (mag < 0) + | (phi > 2 * jnp.pi) + | (phi < 0) + | (theta > jnp.pi) + | (theta < 0), + jnp.zeros_like(0) - jnp.inf, + jnp.log(mag**2 * jnp.sin(x[self.naming[0]])), + ) + return output @jaxtyped(typechecker=typechecker) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index d8ee9536..55a5d0ce 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -1,11 +1,12 @@ from abc import ABC, abstractmethod -from typing import Callable +from dataclasses import field +from typing import Callable, Union import jax import jax.numpy as jnp +from beartype import beartype as typechecker from chex import assert_rank -from jaxtyping import Float - +from jaxtyping import Array, Float, jaxtyped class Transform(ABC): """ @@ -16,7 +17,6 @@ class Transform(ABC): name_mapping: tuple[list[str], list[str]] transform_func: Callable[[dict[str, Float]], dict[str, Float]] - def __init__( self, name_mapping: tuple[list[str], list[str]], @@ -45,7 +45,7 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: The log Jacobian determinant. """ raise NotImplementedError - + @abstractmethod def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ @@ -92,8 +92,7 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: output_params = self.transform_func(input_params) x[self.name_mapping[1][0]] = output_params return x - - + class Scale(UnivariateTransform): scale: Float @@ -106,7 +105,6 @@ def __init__( self.scale = scale self.transform_func = lambda x: x * self.scale - class Offset(UnivariateTransform): offset: Float @@ -119,7 +117,6 @@ def __init__( self.offset = offset self.transform_func = lambda x: x + self.offset - class Logit(UnivariateTransform): """ Logit transform following @@ -138,11 +135,10 @@ def __init__( super().__init__(name_mapping) self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) - class ArcSine(UnivariateTransform): """ ArcSine transformation - + Parameters ---------- name_mapping : tuple[list[str], list[str]] @@ -156,22 +152,3 @@ def __init__( ): super().__init__(name_mapping) self.transform_func = lambda x: jnp.arcsin(x) - - -class ArcCosine(UnivariateTransform): - """ - ArcCosine transformation - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): - super().__init__(name_mapping) - self.transform_func = lambda x: jnp.arccos(x) From 7ee41335d7820f21f1b777d0482c39a989a5f65a Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 07:12:10 -0400 Subject: [PATCH 047/248] Add standard normal distribution --- src/jimgw/prior.py | 96 +++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 57 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 894ea9f7..3c411c42 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -63,17 +63,17 @@ def log_prob(self, x: dict[str, Array]) -> Float: class LogisticDistribution(Prior): def __repr__(self): - return f"Logistic(parameter_names={self.parameter_names})" + return f"LogisticDistribution(parameter_names={self.parameter_names})" def __init__(self, parameter_names: list[str], **kwargs): super().__init__(parameter_names) - assert self.n_dim == 1, "Logit needs to be 1D distributions" + assert self.n_dim == 1, "LogisticDistribution needs to be 1D distributions" def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: """ - Sample from a logit distribution. + Sample from a logistic distribution. Parameters ---------- @@ -97,6 +97,42 @@ def log_prob(self, x: dict[str, Float]) -> Float: return -variable - 2 * jnp.log(1 + jnp.exp(-variable)) +@jaxtyped(typechecker=typechecker) +class StandardNormalDistribution(Prior): + + def __repr__(self): + return f"StandardNormalDistribution(parameter_names={self.parameter_names})" + + def __init__(self, parameter_names: list[str], **kwargs): + super().__init__(parameter_names) + assert self.n_dim == 1, "StandardNormalDistribution needs to be 1D distributions" + + def sample( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + """ + Sample from a standard normal distribution. + + Parameters + ---------- + rng_key : PRNGKeyArray + A random key to use for sampling. + n_samples : int + The number of samples to draw. + + Returns + ------- + samples : dict + Samples from the distribution. The keys are the names of the parameters. + + """ + samples = jax.random.normal(rng_key, (n_samples,)) + return self.add_name(samples[None]) + + def log_prob(self, x: dict[str, Float]) -> Float: + variable = x[self.parameter_names[0]] + return -0.5 * variable ** 2 - 0.5 * jnp.log(2 * jnp.pi) + class SequentialTransform(Prior): """ Transform a prior distribution by applying a sequence of transforms. @@ -624,57 +660,3 @@ def log_prob(self, x: dict[str, Float]) -> Float: ) log_p = self.alpha * variable + jnp.log(self.normalization) return log_p + log_in_range - - -@jaxtyped(typechecker=typechecker) -class Normal(Prior): - mean: Float = 0.0 - std: Float = 1.0 - - def __repr__(self): - return f"Normal(mean={self.mean}, std={self.std})" - - def __init__( - self, - mean: Float, - std: Float, - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - super().__init__(naming, transforms) - assert self.n_dim == 1, "Normal needs to be 1D distributions" - self.mean = mean - self.std = std - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from a normal distribution. - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : dict - Samples from the distribution. The keys are the names of the parameters. - - """ - samples = jax.random.normal(rng_key, (n_samples,)) - samples = self.mean + samples * self.std - return self.add_name(samples[None]) - - def log_prob(self, x: dict[str, Array]) -> Float: - variable = x[self.naming[0]] - output = ( - -0.5 * jnp.log(2 * jnp.pi) - - jnp.log(self.std) - - 0.5 * ((variable - self.mean) / self.std) ** 2 - ) - return output From 6a3579296b9cd646d815f49897454af2fc57d7e2 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 07:18:22 -0400 Subject: [PATCH 048/248] Add periodic uniform prior --- src/jimgw/prior.py | 31 +++++++++++++++++++++++++++++-- src/jimgw/transforms.py | 19 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 3c411c42..e2d98f71 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -10,7 +10,7 @@ from jimgw.single_event.detector import GroundBased2G, detector_preset from jimgw.single_event.utils import zenith_azimuth_to_ra_dec -from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine +from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine, Modulo class Prior(Distribution): @@ -249,6 +249,33 @@ def __init__( ], ) +@jaxtyped(typechecker=typechecker) +class PeriodicUniform(SequentialTransform): + xmin: float + xmax: float + + def __repr__(self): + return f"PeriodicUniform(xmin={self.xmin}, xmax={self.xmax}, parameter_names={self.parameter_names})" + + def __init__( + self, + xmin: float, + xmax: float, + parameter_names: list[str], + ): + self.parameter_names = parameter_names + assert self.n_dim == 1, "PeriodicUniform needs to be 1D distributions" + self.xmax = xmax + self.xmin = xmin + super().__init__( + LogisticDistribution(self.parameter_names), + [ + Logit((self.parameter_names, self.parameter_names)), + Modulo((self.parameter_names, self.parameter_names), xmax - xmin), + Offset((self.parameter_names, self.parameter_names), xmin), + ], + ) + @jaxtyped(typechecker=typechecker) class Sine(SequentialTransform): @@ -306,7 +333,7 @@ def __init__(self, parameter_names: list[str], **kwargs): [ Uniform(0.0, 1.0, [self.parameter_names[0]]), Sine([self.parameter_names[1]]), - Uniform(0.0, 2 * jnp.pi, [self.parameter_names[2]]), + PeriodicUniform(0.0, 2 * jnp.pi, [self.parameter_names[2]]), ] ) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index d8ee9536..05dacdb1 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -138,6 +138,25 @@ def __init__( super().__init__(name_mapping) self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) +class Modulo(UnivariateTransform): + """ + Modulo transform following + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + modulo: Float, + ): + super().__init__(name_mapping) + self.modulo = modulo + self.transform_func = lambda x: jnp.mod(x, self.modulo) class ArcSine(UnivariateTransform): """ From 1bcf32c239c73ef904bda3b1aff86e761dc66bf9 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 07:20:53 -0400 Subject: [PATCH 049/248] Reformat --- src/jimgw/prior.py | 8 ++++++-- src/jimgw/transforms.py | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index e2d98f71..7dd50018 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -105,7 +105,9 @@ def __repr__(self): def __init__(self, parameter_names: list[str], **kwargs): super().__init__(parameter_names) - assert self.n_dim == 1, "StandardNormalDistribution needs to be 1D distributions" + assert ( + self.n_dim == 1 + ), "StandardNormalDistribution needs to be 1D distributions" def sample( self, rng_key: PRNGKeyArray, n_samples: int @@ -131,7 +133,8 @@ def sample( def log_prob(self, x: dict[str, Float]) -> Float: variable = x[self.parameter_names[0]] - return -0.5 * variable ** 2 - 0.5 * jnp.log(2 * jnp.pi) + return -0.5 * variable**2 - 0.5 * jnp.log(2 * jnp.pi) + class SequentialTransform(Prior): """ @@ -249,6 +252,7 @@ def __init__( ], ) + @jaxtyped(typechecker=typechecker) class PeriodicUniform(SequentialTransform): xmin: float diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 05dacdb1..8a4787c5 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -138,6 +138,7 @@ def __init__( super().__init__(name_mapping) self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) + class Modulo(UnivariateTransform): """ Modulo transform following @@ -158,6 +159,7 @@ def __init__( self.modulo = modulo self.transform_func = lambda x: jnp.mod(x, self.modulo) + class ArcSine(UnivariateTransform): """ ArcSine transformation From 5d98aeb529c165fccde0d14400e6ccb0408fdfd8 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 07:44:30 -0400 Subject: [PATCH 050/248] Revert "Merge branch '98-moving-naming-tracking-into-jim-class-from-prior-class' into sphere_prior" This reverts commit 17450be4adc3c03d24d23c8ccbb6f9af418980e7, reversing changes made to 1bcf32c239c73ef904bda3b1aff86e761dc66bf9. --- src/jimgw/prior.py | 72 +++++++++++++---------------------------- src/jimgw/transforms.py | 36 +++++++++++++++++---- 2 files changed, 51 insertions(+), 57 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 74cc39dd..7dd50018 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -49,7 +49,7 @@ def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: """ return dict(zip(self.parameter_names, x)) - + def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: @@ -145,9 +145,7 @@ class SequentialTransform(Prior): transforms: list[Transform] def __repr__(self): - return ( - f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" - ) + return f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" def __init__( self, @@ -166,24 +164,28 @@ def sample( ) -> dict[str, Float[Array, " n_samples"]]: output = self.base_prior.sample(rng_key, n_samples) return jax.vmap(self.transform)(output) - + def log_prob(self, x: dict[str, Float]) -> Float: """ log_prob has to be evaluated in the space of the base_prior. - - """ output = self.base_prior.log_prob(x) for transform in self.transforms: x, log_jacobian = transform.transform(x) output -= log_jacobian return output - + + def sample_base( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + return self.base_prior.sample(rng_key, n_samples) + def transform(self, x: dict[str, Float]) -> dict[str, Float]: for transform in self.transforms: x = transform.forward(x) return x + class Combine(Prior): """ A prior class constructed by joinning multiple priors together to form a multivariate prior. @@ -223,16 +225,13 @@ def log_prob(self, x: dict[str, Float]) -> Float: return output - @jaxtyped(typechecker=typechecker) -class Uniform(Prior): - _dist: SequentialTransform - +class Uniform(SequentialTransform): xmin: float xmax: float def __repr__(self): - return f"Uniform(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" + return f"Uniform(xmin={self.xmin}, xmax={self.xmax}, parameter_names={self.parameter_names})" def __init__( self, @@ -240,22 +239,19 @@ def __init__( xmax: float, parameter_names: list[str], ): - super().__init__(parameter_names) + self.parameter_names = parameter_names assert self.n_dim == 1, "Uniform needs to be 1D distributions" self.xmax = xmax self.xmin = xmin - self._dist = SequentialTransform( - LogisticDistribution(parameter_names), + super().__init__( + LogisticDistribution(self.parameter_names), [ - Logit((parameter_names, parameter_names)), - Scale((parameter_names, parameter_names), xmax - xmin), - Offset((parameter_names, parameter_names), xmin), - ]) + Logit((self.parameter_names, self.parameter_names)), + Scale((self.parameter_names, self.parameter_names), xmax - xmin), + Offset((self.parameter_names, self.parameter_names), xmin), + ], + ) - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - return self._dist.sample(rng_key, n_samples) @jaxtyped(typechecker=typechecker) class PeriodicUniform(SequentialTransform): @@ -325,7 +321,7 @@ def __init__(self, parameter_names: list[str]): class UniformSphere(Combine): def __repr__(self): - return f"Sphere(naming={self.naming})" + return f"UniformSphere(parameter_names={self.parameter_names})" def __init__(self, parameter_names: list[str], **kwargs): assert ( @@ -345,32 +341,8 @@ def __init__(self, parameter_names: list[str], **kwargs): ] ) - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - rng_keys = jax.random.split(rng_key, 3) - theta = jnp.arccos( - jax.random.uniform(rng_keys[0], (n_samples,), minval=-1.0, maxval=1.0) - ) - phi = jax.random.uniform(rng_keys[1], (n_samples,), minval=0, maxval=2 * jnp.pi) - mag = jax.random.uniform(rng_keys[2], (n_samples,), minval=0, maxval=1) - return self.add_name(jnp.stack([theta, phi, mag], axis=1).T) - def log_prob(self, x: dict[str, Float]) -> Float: - theta = x[self.naming[0]] - phi = x[self.naming[1]] - mag = x[self.naming[2]] - output = jnp.where( - (mag > 1) - | (mag < 0) - | (phi > 2 * jnp.pi) - | (phi < 0) - | (theta > jnp.pi) - | (theta < 0), - jnp.zeros_like(0) - jnp.inf, - jnp.log(mag**2 * jnp.sin(x[self.naming[0]])), - ) - return output +# ====================== Things below may need rework ====================== @jaxtyped(typechecker=typechecker) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index da8c1591..8a4787c5 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -1,12 +1,11 @@ from abc import ABC, abstractmethod -from dataclasses import field -from typing import Callable, Union +from typing import Callable import jax import jax.numpy as jnp -from beartype import beartype as typechecker from chex import assert_rank -from jaxtyping import Array, Float, jaxtyped +from jaxtyping import Float + class Transform(ABC): """ @@ -17,6 +16,7 @@ class Transform(ABC): name_mapping: tuple[list[str], list[str]] transform_func: Callable[[dict[str, Float]], dict[str, Float]] + def __init__( self, name_mapping: tuple[list[str], list[str]], @@ -45,7 +45,7 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: The log Jacobian determinant. """ raise NotImplementedError - + @abstractmethod def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ @@ -92,7 +92,8 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: output_params = self.transform_func(input_params) x[self.name_mapping[1][0]] = output_params return x - + + class Scale(UnivariateTransform): scale: Float @@ -105,6 +106,7 @@ def __init__( self.scale = scale self.transform_func = lambda x: x * self.scale + class Offset(UnivariateTransform): offset: Float @@ -117,6 +119,7 @@ def __init__( self.offset = offset self.transform_func = lambda x: x + self.offset + class Logit(UnivariateTransform): """ Logit transform following @@ -160,7 +163,7 @@ def __init__( class ArcSine(UnivariateTransform): """ ArcSine transformation - + Parameters ---------- name_mapping : tuple[list[str], list[str]] @@ -174,3 +177,22 @@ def __init__( ): super().__init__(name_mapping) self.transform_func = lambda x: jnp.arcsin(x) + + +class ArcCosine(UnivariateTransform): + """ + ArcCosine transformation + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + self.transform_func = lambda x: jnp.arccos(x) From 87ee212ac44d5afa09c40312c4d5570b735ae2de Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 07:47:00 -0400 Subject: [PATCH 051/248] Minor text change --- src/jimgw/prior.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 7dd50018..94363f4c 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -195,9 +195,7 @@ class Combine(Prior): priors: list[Prior] = field(default_factory=list) def __repr__(self): - return ( - f"Composite(priors={self.priors}, parameter_names={self.parameter_names})" - ) + return f"Combine(priors={self.priors}, parameter_names={self.parameter_names})" def __init__( self, From cc1448e2ec120828e5bc1b715cd2ae4c6e01563b Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 08:28:48 -0400 Subject: [PATCH 052/248] Remove PeriodicUniform --- src/jimgw/prior.py | 30 +----------------------------- src/jimgw/transforms.py | 21 --------------------- 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 94363f4c..c571c7dc 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -10,7 +10,7 @@ from jimgw.single_event.detector import GroundBased2G, detector_preset from jimgw.single_event.utils import zenith_azimuth_to_ra_dec -from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine, Modulo +from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine class Prior(Distribution): @@ -251,34 +251,6 @@ def __init__( ) -@jaxtyped(typechecker=typechecker) -class PeriodicUniform(SequentialTransform): - xmin: float - xmax: float - - def __repr__(self): - return f"PeriodicUniform(xmin={self.xmin}, xmax={self.xmax}, parameter_names={self.parameter_names})" - - def __init__( - self, - xmin: float, - xmax: float, - parameter_names: list[str], - ): - self.parameter_names = parameter_names - assert self.n_dim == 1, "PeriodicUniform needs to be 1D distributions" - self.xmax = xmax - self.xmin = xmin - super().__init__( - LogisticDistribution(self.parameter_names), - [ - Logit((self.parameter_names, self.parameter_names)), - Modulo((self.parameter_names, self.parameter_names), xmax - xmin), - Offset((self.parameter_names, self.parameter_names), xmin), - ], - ) - - @jaxtyped(typechecker=typechecker) class Sine(SequentialTransform): """ diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 8a4787c5..d8ee9536 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -139,27 +139,6 @@ def __init__( self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) -class Modulo(UnivariateTransform): - """ - Modulo transform following - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - modulo: Float, - ): - super().__init__(name_mapping) - self.modulo = modulo - self.transform_func = lambda x: jnp.mod(x, self.modulo) - - class ArcSine(UnivariateTransform): """ ArcSine transformation From 6070f130511caf40fde0a4b00ea87f430d80d653 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 08:41:51 -0400 Subject: [PATCH 053/248] Use self.sample_base --- src/jimgw/prior.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index c571c7dc..53271680 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -162,7 +162,7 @@ def __init__( def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: - output = self.base_prior.sample(rng_key, n_samples) + output = self.sample_base(rng_key, n_samples) return jax.vmap(self.transform)(output) def log_prob(self, x: dict[str, Float]) -> Float: From 8a301f2759b7ddc3bb765d4ca8a00de3dd88994c Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 12:27:25 -0400 Subject: [PATCH 054/248] Update prior.py --- src/jimgw/prior.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 53271680..de378973 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -307,7 +307,7 @@ def __init__(self, parameter_names: list[str], **kwargs): [ Uniform(0.0, 1.0, [self.parameter_names[0]]), Sine([self.parameter_names[1]]), - PeriodicUniform(0.0, 2 * jnp.pi, [self.parameter_names[2]]), + Uniform(0.0, 2 * jnp.pi, [self.parameter_names[2]]), ] ) From d761f6bfb83da27d83813f4cb1fce44a2882ebac Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 26 Jul 2024 12:47:01 -0400 Subject: [PATCH 055/248] Update prior.py to include powerLaw --- src/jimgw/prior.py | 191 +++++++++++++++++++++++++++------------------ 1 file changed, 113 insertions(+), 78 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index de378973..637bbc2b 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -10,7 +10,7 @@ from jimgw.single_event.detector import GroundBased2G, detector_preset from jimgw.single_event.utils import zenith_azimuth_to_ra_dec -from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine +from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine, PowerLawTransform, ParetoTransform class Prior(Distribution): @@ -311,6 +311,41 @@ def __init__(self, parameter_names: list[str], **kwargs): ] ) +@jaxtyped(typechecker=typechecker) +class PowerLaw(SequentialTransform): + xmin: float + xmax: float + alpha: float + + def __repr__(self): + return f"PowerLaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.parameter_names})" + + def __init__( + self, + xmin: float, + xmax: float, + alpha: float, + parameter_names: list[str], + ): + self.parameter_names = parameter_names + assert self.n_dim == 1, "Power law needs to be 1D distributions" + self.xmax = xmax + self.xmin = xmin + self.alpha = alpha + if self.alpha == -1.0: + transform = ParetoTransform((self.parameter_names, self.parameter_names), xmin, xmax) + else: + transform = PowerLawTransform( + (self.parameter_names, self.parameter_names), xmin, xmax, alpha + ) + super().__init__( + LogisticDistribution(self.parameter_names), + [ + Logit((self.parameter_names, self.parameter_names)), + transform, + ], + ) + # ====================== Things below may need rework ====================== @@ -504,83 +539,83 @@ def log_prob(self, x: dict[str, Float]) -> Float: return output + jnp.log(jnp.sin(zenith)) -@jaxtyped(typechecker=typechecker) -class PowerLaw(Prior): - """ - A prior following the power-law with alpha in the range [xmin, xmax). - p(x) ~ x^{\alpha} - """ - - xmin: float = 0.0 - xmax: float = 1.0 - alpha: float = 0.0 - normalization: float = 1.0 - - def __repr__(self): - return f"Powerlaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" - - def __init__( - self, - xmin: float, - xmax: float, - alpha: Union[Int, float], - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - super().__init__(naming, transforms) - if alpha < 0.0: - assert xmin > 0.0, "With negative alpha, xmin must > 0" - assert self.n_dim == 1, "Powerlaw needs to be 1D distributions" - self.xmax = xmax - self.xmin = xmin - self.alpha = alpha - if alpha == -1: - self.normalization = float(1.0 / jnp.log(self.xmax / self.xmin)) - else: - self.normalization = (1 + self.alpha) / ( - self.xmax ** (1 + self.alpha) - self.xmin ** (1 + self.alpha) - ) - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from a power-law distribution. - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : dict - Samples from the distribution. The keys are the names of the parameters. - - """ - q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) - if self.alpha == -1: - samples = self.xmin * jnp.exp(q_samples * jnp.log(self.xmax / self.xmin)) - else: - samples = ( - self.xmin ** (1.0 + self.alpha) - + q_samples - * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - ) ** (1.0 / (1.0 + self.alpha)) - return self.add_name(samples[None]) - - def log_prob(self, x: dict[str, Float]) -> Float: - variable = x[self.naming[0]] - log_in_range = jnp.where( - (variable >= self.xmax) | (variable <= self.xmin), - jnp.zeros_like(variable) - jnp.inf, - jnp.zeros_like(variable), - ) - log_p = self.alpha * jnp.log(variable) + jnp.log(self.normalization) - return log_p + log_in_range +# @jaxtyped(typechecker=typechecker) +# class PowerLaw(Prior): +# """ +# A prior following the power-law with alpha in the range [xmin, xmax). +# p(x) ~ x^{\alpha} +# """ + +# xmin: float = 0.0 +# xmax: float = 1.0 +# alpha: float = 0.0 +# normalization: float = 1.0 + +# def __repr__(self): +# return f"Powerlaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" + +# def __init__( +# self, +# xmin: float, +# xmax: float, +# alpha: Union[Int, float], +# naming: list[str], +# transforms: dict[str, tuple[str, Callable]] = {}, +# **kwargs, +# ): +# super().__init__(naming, transforms) +# if alpha < 0.0: +# assert xmin > 0.0, "With negative alpha, xmin must > 0" +# assert self.n_dim == 1, "Powerlaw needs to be 1D distributions" +# self.xmax = xmax +# self.xmin = xmin +# self.alpha = alpha +# if alpha == -1: +# self.normalization = float(1.0 / jnp.log(self.xmax / self.xmin)) +# else: +# self.normalization = (1 + self.alpha) / ( +# self.xmax ** (1 + self.alpha) - self.xmin ** (1 + self.alpha) +# ) + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# """ +# Sample from a power-law distribution. + +# Parameters +# ---------- +# rng_key : PRNGKeyArray +# A random key to use for sampling. +# n_samples : int +# The number of samples to draw. + +# Returns +# ------- +# samples : dict +# Samples from the distribution. The keys are the names of the parameters. + +# """ +# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) +# if self.alpha == -1: +# samples = self.xmin * jnp.exp(q_samples * jnp.log(self.xmax / self.xmin)) +# else: +# samples = ( +# self.xmin ** (1.0 + self.alpha) +# + q_samples +# * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) +# ) ** (1.0 / (1.0 + self.alpha)) +# return self.add_name(samples[None]) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# variable = x[self.naming[0]] +# log_in_range = jnp.where( +# (variable >= self.xmax) | (variable <= self.xmin), +# jnp.zeros_like(variable) - jnp.inf, +# jnp.zeros_like(variable), +# ) +# log_p = self.alpha * jnp.log(variable) + jnp.log(self.normalization) +# return log_p + log_in_range @jaxtyped(typechecker=typechecker) From b9a725506012f80db3d297fe66f8e73fc632d37e Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 26 Jul 2024 12:47:51 -0400 Subject: [PATCH 056/248] Update transforms.py --- src/jimgw/transforms.py | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index d8ee9536..2feac5a2 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -175,3 +175,58 @@ def __init__( ): super().__init__(name_mapping) self.transform_func = lambda x: jnp.arccos(x) + + +class PowerLawTransform(UnivariateTransform): + """ + PowerLaw transformation + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + xmin: Float + xmax: Float + alpha: Float + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + xmin: Float, + xmax: Float, + alpha: Float, + ): + super().__init__(name_mapping) + self.xmin = xmin + self.xmax = xmax + self.alpha = alpha + self.transform_func = lambda x: (self.xmin ** (1.0 + self.alpha)+ x* (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)))** (1.0 / (1.0 + self.alpha)), + + + +class ParetoTransform(UnivariateTransform): + """ + Pareto transformation: Power law when alpha = -1 + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + xmin: Float, + xmax: Float, + ): + super().__init__(name_mapping) + self.xmin = xmin + self.xmax = xmax + self.transform_func = lambda x: self.xmin * jnp.exp( + x * jnp.log(self.xmax / self.xmin) + ) From 2f6e12ac73915ccf0e2081ef241f0dc3cf9eb53e Mon Sep 17 00:00:00 2001 From: kazewong Date: Thu, 25 Jul 2024 16:15:41 -0400 Subject: [PATCH 057/248] format and updating typing hint --- src/jimgw/prior.py | 945 ++++++++++++++++++++-------------------- src/jimgw/transforms.py | 20 +- 2 files changed, 483 insertions(+), 482 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 1e7cb960..77d5713b 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -1,15 +1,11 @@ from dataclasses import field -from typing import Callable, Union import jax import jax.numpy as jnp -from astropy.time import Time from beartype import beartype as typechecker from flowMC.nfmodel.base import Distribution -from jaxtyping import Array, Float, Int, PRNGKeyArray, jaxtyped +from jaxtyping import Array, Float, PRNGKeyArray, jaxtyped -from jimgw.single_event.detector import GroundBased2G, detector_preset -from jimgw.single_event.utils import zenith_azimuth_to_ra_dec from jimgw.transforms import Transform, Logit, Scale, Offset @@ -49,7 +45,7 @@ def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: """ return dict(zip(self.parameter_names, x)) - + def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: @@ -106,9 +102,7 @@ class SequentialTransform(Prior): transforms: list[Transform] def __repr__(self): - return ( - f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" - ) + return f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" def __init__( self, @@ -127,7 +121,7 @@ def sample( ) -> dict[str, Float[Array, " n_samples"]]: output = self.base_prior.sample(rng_key, n_samples) return jax.vmap(self.transform)(output) - + def log_prob(self, x: dict[str, Float]) -> Float: """ log_prob has to be evaluated in the space of the base_prior. @@ -139,12 +133,13 @@ def log_prob(self, x: dict[str, Float]) -> Float: x, log_jacobian = transform.transform(x) output -= log_jacobian return output - + def transform(self, x: dict[str, Float]) -> dict[str, Float]: for transform in self.transforms: x = transform.forward(x) return x + class Combine(Prior): """ A prior class constructed by joinning multiple priors together to form a multivariate prior. @@ -184,7 +179,6 @@ def log_prob(self, x: dict[str, Float]) -> Float: return output - @jaxtyped(typechecker=typechecker) class Uniform(Prior): _dist: SequentialTransform @@ -211,7 +205,8 @@ def __init__( Logit((parameter_names, parameter_names)), Scale((parameter_names, parameter_names), xmax - xmin), Offset((parameter_names, parameter_names), xmin), - ]) + ], + ) def sample( self, rng_key: PRNGKeyArray, n_samples: int @@ -220,474 +215,476 @@ def sample( def log_prob(self, x: dict[str, Array]) -> Float: return self._dist.log_prob(x) - + def sample_base( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: return self._dist.base_prior.sample(rng_key, n_samples) - + def transform(self, x: dict[str, Float]) -> dict[str, Float]: return self._dist.transform(x) -# ====================== Things below may need rework ====================== - -class Sphere(Prior): - """ - A prior on a sphere represented by Cartesian coordinates. - - Magnitude is sampled from a uniform distribution. - """ - - def __repr__(self): - return f"Sphere(naming={self.naming})" - - def __init__(self, naming: list[str], **kwargs): - name = naming[0] - self.naming = [f"{name}_theta", f"{name}_phi", f"{name}_mag"] - self.transforms = { - self.naming[0]: ( - f"{naming}_x", - lambda params: jnp.sin(params[self.naming[0]]) - * jnp.cos(params[self.naming[1]]) - * params[self.naming[2]], - ), - self.naming[1]: ( - f"{naming}_y", - lambda params: jnp.sin(params[self.naming[0]]) - * jnp.sin(params[self.naming[1]]) - * params[self.naming[2]], - ), - self.naming[2]: ( - f"{naming}_z", - lambda params: jnp.cos(params[self.naming[0]]) * params[self.naming[2]], - ), - } - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - rng_keys = jax.random.split(rng_key, 3) - theta = jnp.arccos( - jax.random.uniform(rng_keys[0], (n_samples,), minval=-1.0, maxval=1.0) - ) - phi = jax.random.uniform(rng_keys[1], (n_samples,), minval=0, maxval=2 * jnp.pi) - mag = jax.random.uniform(rng_keys[2], (n_samples,), minval=0, maxval=1) - return self.add_name(jnp.stack([theta, phi, mag], axis=1).T) - - def log_prob(self, x: dict[str, Float]) -> Float: - theta = x[self.naming[0]] - phi = x[self.naming[1]] - mag = x[self.naming[2]] - output = jnp.where( - (mag > 1) - | (mag < 0) - | (phi > 2 * jnp.pi) - | (phi < 0) - | (theta > jnp.pi) - | (theta < 0), - jnp.zeros_like(0) - jnp.inf, - jnp.log(mag**2 * jnp.sin(x[self.naming[0]])), - ) - return output - - -@jaxtyped(typechecker=typechecker) -class AlignedSpin(Prior): - """ - Prior distribution for the aligned (z) component of the spin. - - This assume the prior distribution on the spin magnitude to be uniform in [0, amax] - with its orientation uniform on a sphere - - p(chi) = -log(|chi| / amax) / 2 / amax - - This is useful when comparing results between an aligned-spin run and - a precessing spin run. - - See (A7) of https://arxiv.org/abs/1805.10457. - """ - - amax: Float = 0.99 - chi_axis: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) - cdf_vals: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) - - def __repr__(self): - return f"Alignedspin(amax={self.amax}, naming={self.naming})" - - def __init__( - self, - amax: Float, - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - super().__init__(naming, transforms) - assert self.n_dim == 1, "Alignedspin needs to be 1D distributions" - self.amax = amax - - # build the interpolation table for the ppf of the one-sided distribution - chi_axis = jnp.linspace(1e-31, self.amax, num=1000) - cdf_vals = -chi_axis * (jnp.log(chi_axis / self.amax) - 1.0) / self.amax - self.chi_axis = chi_axis - self.cdf_vals = cdf_vals - - @property - def xmin(self): - return -self.amax - - @property - def xmax(self): - return self.amax - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from the Alignedspin distribution. - for chi > 0; - p(chi) = -log(chi / amax) / amax # halved normalization constant - cdf(chi) = -chi * (log(chi / amax) - 1) / amax - - Since there is a pole at chi=0, we will sample with the following steps - 1. Map the samples with quantile > 0.5 to positive chi and negative otherwise - 2a. For negative chi, map the quantile back to [0, 1] via q -> 2(0.5 - q) - 2b. For positive chi, map the quantile back to [0, 1] via q -> 2(q - 0.5) - 3. Map the quantile to chi via the ppf by checking against the table - built during the initialization - 4. add back the sign - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : dict - Samples from the distribution. The keys are the names of the parameters. - - """ - q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) - # 1. calculate the sign of chi from the q_samples - sign_samples = jnp.where( - q_samples >= 0.5, - jnp.zeros_like(q_samples) + 1.0, - jnp.zeros_like(q_samples) - 1.0, - ) - # 2. remap q_samples - q_samples = jnp.where( - q_samples >= 0.5, - 2 * (q_samples - 0.5), - 2 * (0.5 - q_samples), - ) - # 3. map the quantile to chi via interpolation - samples = jnp.interp( - q_samples, - self.cdf_vals, - self.chi_axis, - ) - # 4. add back the sign - samples *= sign_samples - - return self.add_name(samples[None]) - - def log_prob(self, x: dict[str, Float]) -> Float: - variable = x[self.naming[0]] - log_p = jnp.where( - (variable >= self.amax) | (variable <= -self.amax), - jnp.zeros_like(variable) - jnp.inf, - jnp.log(-jnp.log(jnp.absolute(variable) / self.amax) / 2.0 / self.amax), - ) - return log_p - - -@jaxtyped(typechecker=typechecker) -class EarthFrame(Prior): - """ - Prior distribution for sky location in Earth frame. - """ - - ifos: list = field(default_factory=list) - gmst: float = 0.0 - delta_x: Float[Array, " 3"] = field(default_factory=lambda: jnp.zeros(3)) - - def __repr__(self): - return f"EarthFrame(naming={self.naming})" - - def __init__(self, gps: Float, ifos: list, **kwargs): - self.naming = ["zenith", "azimuth"] - if len(ifos) < 2: - return ValueError( - "At least two detectors are needed to define the Earth frame" - ) - elif isinstance(ifos[0], str): - self.ifos = [detector_preset[ifos[0]], detector_preset[ifos[1]]] - elif isinstance(ifos[0], GroundBased2G): - self.ifos = ifos[:1] - else: - return ValueError( - "ifos should be a list of detector names or GroundBased2G objects" - ) - self.gmst = float( - Time(gps, format="gps").sidereal_time("apparent", "greenwich").rad - ) - self.delta_x = self.ifos[1].vertex - self.ifos[0].vertex - - self.transforms = { - "azimuth": ( - "ra", - lambda params: zenith_azimuth_to_ra_dec( - params["zenith"], - params["azimuth"], - gmst=self.gmst, - delta_x=self.delta_x, - )[0], - ), - "zenith": ( - "dec", - lambda params: zenith_azimuth_to_ra_dec( - params["zenith"], - params["azimuth"], - gmst=self.gmst, - delta_x=self.delta_x, - )[1], - ), - } - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - rng_keys = jax.random.split(rng_key, 2) - zenith = jnp.arccos( - jax.random.uniform(rng_keys[0], (n_samples,), minval=-1.0, maxval=1.0) - ) - azimuth = jax.random.uniform( - rng_keys[1], (n_samples,), minval=0, maxval=2 * jnp.pi - ) - return self.add_name(jnp.stack([zenith, azimuth], axis=1).T) - - def log_prob(self, x: dict[str, Float]) -> Float: - zenith = x["zenith"] - azimuth = x["azimuth"] - output = jnp.where( - (zenith > jnp.pi) | (zenith < 0) | (azimuth > 2 * jnp.pi) | (azimuth < 0), - jnp.zeros_like(0) - jnp.inf, - jnp.zeros_like(0), - ) - return output + jnp.log(jnp.sin(zenith)) - - -@jaxtyped(typechecker=typechecker) -class PowerLaw(Prior): - """ - A prior following the power-law with alpha in the range [xmin, xmax). - p(x) ~ x^{\alpha} - """ - - xmin: float = 0.0 - xmax: float = 1.0 - alpha: float = 0.0 - normalization: float = 1.0 - - def __repr__(self): - return f"Powerlaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" - - def __init__( - self, - xmin: float, - xmax: float, - alpha: Union[Int, float], - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - super().__init__(naming, transforms) - if alpha < 0.0: - assert xmin > 0.0, "With negative alpha, xmin must > 0" - assert self.n_dim == 1, "Powerlaw needs to be 1D distributions" - self.xmax = xmax - self.xmin = xmin - self.alpha = alpha - if alpha == -1: - self.normalization = float(1.0 / jnp.log(self.xmax / self.xmin)) - else: - self.normalization = (1 + self.alpha) / ( - self.xmax ** (1 + self.alpha) - self.xmin ** (1 + self.alpha) - ) - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from a power-law distribution. - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : dict - Samples from the distribution. The keys are the names of the parameters. - - """ - q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) - if self.alpha == -1: - samples = self.xmin * jnp.exp(q_samples * jnp.log(self.xmax / self.xmin)) - else: - samples = ( - self.xmin ** (1.0 + self.alpha) - + q_samples - * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - ) ** (1.0 / (1.0 + self.alpha)) - return self.add_name(samples[None]) - - def log_prob(self, x: dict[str, Float]) -> Float: - variable = x[self.naming[0]] - log_in_range = jnp.where( - (variable >= self.xmax) | (variable <= self.xmin), - jnp.zeros_like(variable) - jnp.inf, - jnp.zeros_like(variable), - ) - log_p = self.alpha * jnp.log(variable) + jnp.log(self.normalization) - return log_p + log_in_range - - -@jaxtyped(typechecker=typechecker) -class Exponential(Prior): - """ - A prior following the power-law with alpha in the range [xmin, xmax). - p(x) ~ exp(\alpha x) - """ - - xmin: float = 0.0 - xmax: float = jnp.inf - alpha: float = -1.0 - normalization: float = 1.0 - - def __repr__(self): - return f"Exponential(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" - - def __init__( - self, - xmin: Float, - xmax: Float, - alpha: Union[Int, Float], - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - super().__init__(naming, transforms) - if alpha < 0.0: - assert xmin != -jnp.inf, "With negative alpha, xmin must finite" - if alpha > 0.0: - assert xmax != jnp.inf, "With positive alpha, xmax must finite" - assert not jnp.isclose(alpha, 0.0), "alpha=zero is given, use Uniform instead" - assert self.n_dim == 1, "Exponential needs to be 1D distributions" - - self.xmax = xmax - self.xmin = xmin - self.alpha = alpha - - self.normalization = self.alpha / ( - jnp.exp(self.alpha * self.xmax) - jnp.exp(self.alpha * self.xmin) - ) - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from a exponential distribution. - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : dict - Samples from the distribution. The keys are the names of the parameters. - - """ - q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) - samples = ( - self.xmin - + jnp.log1p( - q_samples * (jnp.exp(self.alpha * (self.xmax - self.xmin)) - 1.0) - ) - / self.alpha - ) - return self.add_name(samples[None]) - - def log_prob(self, x: dict[str, Float]) -> Float: - variable = x[self.naming[0]] - log_in_range = jnp.where( - (variable >= self.xmax) | (variable <= self.xmin), - jnp.zeros_like(variable) - jnp.inf, - jnp.zeros_like(variable), - ) - log_p = self.alpha * variable + jnp.log(self.normalization) - return log_p + log_in_range - - -@jaxtyped(typechecker=typechecker) -class Normal(Prior): - mean: Float = 0.0 - std: Float = 1.0 - - def __repr__(self): - return f"Normal(mean={self.mean}, std={self.std})" - - def __init__( - self, - mean: Float, - std: Float, - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - super().__init__(naming, transforms) - assert self.n_dim == 1, "Normal needs to be 1D distributions" - self.mean = mean - self.std = std - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from a normal distribution. - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : dict - Samples from the distribution. The keys are the names of the parameters. +# ====================== Things below may need rework ====================== - """ - samples = jax.random.normal(rng_key, (n_samples,)) - samples = self.mean + samples * self.std - return self.add_name(samples[None]) - def log_prob(self, x: dict[str, Array]) -> Float: - variable = x[self.naming[0]] - output = ( - -0.5 * jnp.log(2 * jnp.pi) - - jnp.log(self.std) - - 0.5 * ((variable - self.mean) / self.std) ** 2 - ) - return output +# class Sphere(Prior): +# """ +# A prior on a sphere represented by Cartesian coordinates. + +# Magnitude is sampled from a uniform distribution. +# """ + +# def __repr__(self): +# return f"Sphere(naming={self.naming})" + +# def __init__(self, naming: list[str], **kwargs): +# name = naming[0] +# self.naming = [f"{name}_theta", f"{name}_phi", f"{name}_mag"] +# self.transforms = { +# self.naming[0]: ( +# f"{naming}_x", +# lambda params: jnp.sin(params[self.naming[0]]) +# * jnp.cos(params[self.naming[1]]) +# * params[self.naming[2]], +# ), +# self.naming[1]: ( +# f"{naming}_y", +# lambda params: jnp.sin(params[self.naming[0]]) +# * jnp.sin(params[self.naming[1]]) +# * params[self.naming[2]], +# ), +# self.naming[2]: ( +# f"{naming}_z", +# lambda params: jnp.cos(params[self.naming[0]]) * params[self.naming[2]], +# ), +# } + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# rng_keys = jax.random.split(rng_key, 3) +# theta = jnp.arccos( +# jax.random.uniform(rng_keys[0], (n_samples,), minval=-1.0, maxval=1.0) +# ) +# phi = jax.random.uniform(rng_keys[1], (n_samples,), minval=0, maxval=2 * jnp.pi) +# mag = jax.random.uniform(rng_keys[2], (n_samples,), minval=0, maxval=1) +# return self.add_name(jnp.stack([theta, phi, mag], axis=1).T) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# theta = x[self.naming[0]] +# phi = x[self.naming[1]] +# mag = x[self.naming[2]] +# output = jnp.where( +# (mag > 1) +# | (mag < 0) +# | (phi > 2 * jnp.pi) +# | (phi < 0) +# | (theta > jnp.pi) +# | (theta < 0), +# jnp.zeros_like(0) - jnp.inf, +# jnp.log(mag**2 * jnp.sin(x[self.naming[0]])), +# ) +# return output + + +# @jaxtyped(typechecker=typechecker) +# class AlignedSpin(Prior): +# """ +# Prior distribution for the aligned (z) component of the spin. + +# This assume the prior distribution on the spin magnitude to be uniform in [0, amax] +# with its orientation uniform on a sphere + +# p(chi) = -log(|chi| / amax) / 2 / amax + +# This is useful when comparing results between an aligned-spin run and +# a precessing spin run. + +# See (A7) of https://arxiv.org/abs/1805.10457. +# """ + +# amax: Float = 0.99 +# chi_axis: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) +# cdf_vals: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) + +# def __repr__(self): +# return f"Alignedspin(amax={self.amax}, naming={self.naming})" + +# def __init__( +# self, +# amax: Float, +# naming: list[str], +# transforms: dict[str, tuple[str, Callable]] = {}, +# **kwargs, +# ): +# super().__init__(naming, transforms) +# assert self.n_dim == 1, "Alignedspin needs to be 1D distributions" +# self.amax = amax + +# # build the interpolation table for the ppf of the one-sided distribution +# chi_axis = jnp.linspace(1e-31, self.amax, num=1000) +# cdf_vals = -chi_axis * (jnp.log(chi_axis / self.amax) - 1.0) / self.amax +# self.chi_axis = chi_axis +# self.cdf_vals = cdf_vals + +# @property +# def xmin(self): +# return -self.amax + +# @property +# def xmax(self): +# return self.amax + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# """ +# Sample from the Alignedspin distribution. + +# for chi > 0; +# p(chi) = -log(chi / amax) / amax # halved normalization constant +# cdf(chi) = -chi * (log(chi / amax) - 1) / amax + +# Since there is a pole at chi=0, we will sample with the following steps +# 1. Map the samples with quantile > 0.5 to positive chi and negative otherwise +# 2a. For negative chi, map the quantile back to [0, 1] via q -> 2(0.5 - q) +# 2b. For positive chi, map the quantile back to [0, 1] via q -> 2(q - 0.5) +# 3. Map the quantile to chi via the ppf by checking against the table +# built during the initialization +# 4. add back the sign + +# Parameters +# ---------- +# rng_key : PRNGKeyArray +# A random key to use for sampling. +# n_samples : int +# The number of samples to draw. + +# Returns +# ------- +# samples : dict +# Samples from the distribution. The keys are the names of the parameters. + +# """ +# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) +# # 1. calculate the sign of chi from the q_samples +# sign_samples = jnp.where( +# q_samples >= 0.5, +# jnp.zeros_like(q_samples) + 1.0, +# jnp.zeros_like(q_samples) - 1.0, +# ) +# # 2. remap q_samples +# q_samples = jnp.where( +# q_samples >= 0.5, +# 2 * (q_samples - 0.5), +# 2 * (0.5 - q_samples), +# ) +# # 3. map the quantile to chi via interpolation +# samples = jnp.interp( +# q_samples, +# self.cdf_vals, +# self.chi_axis, +# ) +# # 4. add back the sign +# samples *= sign_samples + +# return self.add_name(samples[None]) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# variable = x[self.naming[0]] +# log_p = jnp.where( +# (variable >= self.amax) | (variable <= -self.amax), +# jnp.zeros_like(variable) - jnp.inf, +# jnp.log(-jnp.log(jnp.absolute(variable) / self.amax) / 2.0 / self.amax), +# ) +# return log_p + + +# @jaxtyped(typechecker=typechecker) +# class EarthFrame(Prior): +# """ +# Prior distribution for sky location in Earth frame. +# """ + +# ifos: list = field(default_factory=list) +# gmst: float = 0.0 +# delta_x: Float[Array, " 3"] = field(default_factory=lambda: jnp.zeros(3)) + +# def __repr__(self): +# return f"EarthFrame(naming={self.naming})" + +# def __init__(self, gps: Float, ifos: list, **kwargs): +# self.naming = ["zenith", "azimuth"] +# if len(ifos) < 2: +# return ValueError( +# "At least two detectors are needed to define the Earth frame" +# ) +# elif isinstance(ifos[0], str): +# self.ifos = [detector_preset[ifos[0]], detector_preset[ifos[1]]] +# elif isinstance(ifos[0], GroundBased2G): +# self.ifos = ifos[:1] +# else: +# return ValueError( +# "ifos should be a list of detector names or GroundBased2G objects" +# ) +# self.gmst = float( +# Time(gps, format="gps").sidereal_time("apparent", "greenwich").rad +# ) +# self.delta_x = self.ifos[1].vertex - self.ifos[0].vertex + +# self.transforms = { +# "azimuth": ( +# "ra", +# lambda params: zenith_azimuth_to_ra_dec( +# params["zenith"], +# params["azimuth"], +# gmst=self.gmst, +# delta_x=self.delta_x, +# )[0], +# ), +# "zenith": ( +# "dec", +# lambda params: zenith_azimuth_to_ra_dec( +# params["zenith"], +# params["azimuth"], +# gmst=self.gmst, +# delta_x=self.delta_x, +# )[1], +# ), +# } + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# rng_keys = jax.random.split(rng_key, 2) +# zenith = jnp.arccos( +# jax.random.uniform(rng_keys[0], (n_samples,), minval=-1.0, maxval=1.0) +# ) +# azimuth = jax.random.uniform( +# rng_keys[1], (n_samples,), minval=0, maxval=2 * jnp.pi +# ) +# return self.add_name(jnp.stack([zenith, azimuth], axis=1).T) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# zenith = x["zenith"] +# azimuth = x["azimuth"] +# output = jnp.where( +# (zenith > jnp.pi) | (zenith < 0) | (azimuth > 2 * jnp.pi) | (azimuth < 0), +# jnp.zeros_like(0) - jnp.inf, +# jnp.zeros_like(0), +# ) +# return output + jnp.log(jnp.sin(zenith)) + + +# @jaxtyped(typechecker=typechecker) +# class PowerLaw(Prior): +# """ +# A prior following the power-law with alpha in the range [xmin, xmax). +# p(x) ~ x^{\alpha} +# """ + +# xmin: float = 0.0 +# xmax: float = 1.0 +# alpha: float = 0.0 +# normalization: float = 1.0 + +# def __repr__(self): +# return f"Powerlaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" + +# def __init__( +# self, +# xmin: float, +# xmax: float, +# alpha: Union[Int, float], +# naming: list[str], +# transforms: dict[str, tuple[str, Callable]] = {}, +# **kwargs, +# ): +# super().__init__(naming, transforms) +# if alpha < 0.0: +# assert xmin > 0.0, "With negative alpha, xmin must > 0" +# assert self.n_dim == 1, "Powerlaw needs to be 1D distributions" +# self.xmax = xmax +# self.xmin = xmin +# self.alpha = alpha +# if alpha == -1: +# self.normalization = float(1.0 / jnp.log(self.xmax / self.xmin)) +# else: +# self.normalization = (1 + self.alpha) / ( +# self.xmax ** (1 + self.alpha) - self.xmin ** (1 + self.alpha) +# ) + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# """ +# Sample from a power-law distribution. + +# Parameters +# ---------- +# rng_key : PRNGKeyArray +# A random key to use for sampling. +# n_samples : int +# The number of samples to draw. + +# Returns +# ------- +# samples : dict +# Samples from the distribution. The keys are the names of the parameters. + +# """ +# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) +# if self.alpha == -1: +# samples = self.xmin * jnp.exp(q_samples * jnp.log(self.xmax / self.xmin)) +# else: +# samples = ( +# self.xmin ** (1.0 + self.alpha) +# + q_samples +# * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) +# ) ** (1.0 / (1.0 + self.alpha)) +# return self.add_name(samples[None]) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# variable = x[self.naming[0]] +# log_in_range = jnp.where( +# (variable >= self.xmax) | (variable <= self.xmin), +# jnp.zeros_like(variable) - jnp.inf, +# jnp.zeros_like(variable), +# ) +# log_p = self.alpha * jnp.log(variable) + jnp.log(self.normalization) +# return log_p + log_in_range + + +# @jaxtyped(typechecker=typechecker) +# class Exponential(Prior): +# """ +# A prior following the power-law with alpha in the range [xmin, xmax). +# p(x) ~ exp(\alpha x) +# """ + +# xmin: float = 0.0 +# xmax: float = jnp.inf +# alpha: float = -1.0 +# normalization: float = 1.0 + +# def __repr__(self): +# return f"Exponential(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" + +# def __init__( +# self, +# xmin: Float, +# xmax: Float, +# alpha: Union[Int, Float], +# naming: list[str], +# transforms: dict[str, tuple[str, Callable]] = {}, +# **kwargs, +# ): +# super().__init__(naming, transforms) +# if alpha < 0.0: +# assert xmin != -jnp.inf, "With negative alpha, xmin must finite" +# if alpha > 0.0: +# assert xmax != jnp.inf, "With positive alpha, xmax must finite" +# assert not jnp.isclose(alpha, 0.0), "alpha=zero is given, use Uniform instead" +# assert self.n_dim == 1, "Exponential needs to be 1D distributions" + +# self.xmax = xmax +# self.xmin = xmin +# self.alpha = alpha + +# self.normalization = self.alpha / ( +# jnp.exp(self.alpha * self.xmax) - jnp.exp(self.alpha * self.xmin) +# ) + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# """ +# Sample from a exponential distribution. + +# Parameters +# ---------- +# rng_key : PRNGKeyArray +# A random key to use for sampling. +# n_samples : int +# The number of samples to draw. + +# Returns +# ------- +# samples : dict +# Samples from the distribution. The keys are the names of the parameters. + +# """ +# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) +# samples = ( +# self.xmin +# + jnp.log1p( +# q_samples * (jnp.exp(self.alpha * (self.xmax - self.xmin)) - 1.0) +# ) +# / self.alpha +# ) +# return self.add_name(samples[None]) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# variable = x[self.naming[0]] +# log_in_range = jnp.where( +# (variable >= self.xmax) | (variable <= self.xmin), +# jnp.zeros_like(variable) - jnp.inf, +# jnp.zeros_like(variable), +# ) +# log_p = self.alpha * variable + jnp.log(self.normalization) +# return log_p + log_in_range + + +# @jaxtyped(typechecker=typechecker) +# class Normal(Prior): +# mean: Float = 0.0 +# std: Float = 1.0 + +# def __repr__(self): +# return f"Normal(mean={self.mean}, std={self.std})" + +# def __init__( +# self, +# mean: Float, +# std: Float, +# naming: list[str], +# transforms: dict[str, tuple[str, Callable]] = {}, +# **kwargs, +# ): +# super().__init__(naming, transforms) +# assert self.n_dim == 1, "Normal needs to be 1D distributions" +# self.mean = mean +# self.std = std + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# """ +# Sample from a normal distribution. + +# Parameters +# ---------- +# rng_key : PRNGKeyArray +# A random key to use for sampling. +# n_samples : int +# The number of samples to draw. + +# Returns +# ------- +# samples : dict +# Samples from the distribution. The keys are the names of the parameters. + +# """ +# samples = jax.random.normal(rng_key, (n_samples,)) +# samples = self.mean + samples * self.std +# return self.add_name(samples[None]) + +# def log_prob(self, x: dict[str, Array]) -> Float: +# variable = x[self.naming[0]] +# output = ( +# -0.5 * jnp.log(2 * jnp.pi) +# - jnp.log(self.std) +# - 0.5 * ((variable - self.mean) / self.std) ** 2 +# ) +# return output diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 55a5d0ce..9e06d6ab 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -1,12 +1,11 @@ from abc import ABC, abstractmethod -from dataclasses import field -from typing import Callable, Union +from typing import Callable import jax import jax.numpy as jnp -from beartype import beartype as typechecker from chex import assert_rank -from jaxtyping import Array, Float, jaxtyped +from jaxtyping import Float, Array + class Transform(ABC): """ @@ -16,7 +15,8 @@ class Transform(ABC): """ name_mapping: tuple[list[str], list[str]] - transform_func: Callable[[dict[str, Float]], dict[str, Float]] + transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " n_dim"]] + def __init__( self, name_mapping: tuple[list[str], list[str]], @@ -45,7 +45,7 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: The log Jacobian determinant. """ raise NotImplementedError - + @abstractmethod def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ @@ -92,7 +92,8 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: output_params = self.transform_func(input_params) x[self.name_mapping[1][0]] = output_params return x - + + class Scale(UnivariateTransform): scale: Float @@ -105,6 +106,7 @@ def __init__( self.scale = scale self.transform_func = lambda x: x * self.scale + class Offset(UnivariateTransform): offset: Float @@ -117,6 +119,7 @@ def __init__( self.offset = offset self.transform_func = lambda x: x + self.offset + class Logit(UnivariateTransform): """ Logit transform following @@ -135,10 +138,11 @@ def __init__( super().__init__(name_mapping) self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) + class ArcSine(UnivariateTransform): """ ArcSine transformation - + Parameters ---------- name_mapping : tuple[list[str], list[str]] From 194c565722beb10fd4a5c41934434583e7de9908 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 12:52:28 -0400 Subject: [PATCH 058/248] Revert "Update transforms.py" This reverts commit b9a725506012f80db3d297fe66f8e73fc632d37e. --- src/jimgw/transforms.py | 55 ----------------------------------------- 1 file changed, 55 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 2feac5a2..d8ee9536 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -175,58 +175,3 @@ def __init__( ): super().__init__(name_mapping) self.transform_func = lambda x: jnp.arccos(x) - - -class PowerLawTransform(UnivariateTransform): - """ - PowerLaw transformation - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - - """ - - xmin: Float - xmax: Float - alpha: Float - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - xmin: Float, - xmax: Float, - alpha: Float, - ): - super().__init__(name_mapping) - self.xmin = xmin - self.xmax = xmax - self.alpha = alpha - self.transform_func = lambda x: (self.xmin ** (1.0 + self.alpha)+ x* (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)))** (1.0 / (1.0 + self.alpha)), - - - -class ParetoTransform(UnivariateTransform): - """ - Pareto transformation: Power law when alpha = -1 - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - xmin: Float, - xmax: Float, - ): - super().__init__(name_mapping) - self.xmin = xmin - self.xmax = xmax - self.transform_func = lambda x: self.xmin * jnp.exp( - x * jnp.log(self.xmax / self.xmin) - ) From 5807f3c110564e5215344a5e00755027b8535b59 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 12:52:35 -0400 Subject: [PATCH 059/248] Revert "Update prior.py to include powerLaw" This reverts commit d761f6bfb83da27d83813f4cb1fce44a2882ebac. --- src/jimgw/prior.py | 191 ++++++++++++++++++--------------------------- 1 file changed, 78 insertions(+), 113 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 637bbc2b..de378973 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -10,7 +10,7 @@ from jimgw.single_event.detector import GroundBased2G, detector_preset from jimgw.single_event.utils import zenith_azimuth_to_ra_dec -from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine, PowerLawTransform, ParetoTransform +from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine class Prior(Distribution): @@ -311,41 +311,6 @@ def __init__(self, parameter_names: list[str], **kwargs): ] ) -@jaxtyped(typechecker=typechecker) -class PowerLaw(SequentialTransform): - xmin: float - xmax: float - alpha: float - - def __repr__(self): - return f"PowerLaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.parameter_names})" - - def __init__( - self, - xmin: float, - xmax: float, - alpha: float, - parameter_names: list[str], - ): - self.parameter_names = parameter_names - assert self.n_dim == 1, "Power law needs to be 1D distributions" - self.xmax = xmax - self.xmin = xmin - self.alpha = alpha - if self.alpha == -1.0: - transform = ParetoTransform((self.parameter_names, self.parameter_names), xmin, xmax) - else: - transform = PowerLawTransform( - (self.parameter_names, self.parameter_names), xmin, xmax, alpha - ) - super().__init__( - LogisticDistribution(self.parameter_names), - [ - Logit((self.parameter_names, self.parameter_names)), - transform, - ], - ) - # ====================== Things below may need rework ====================== @@ -539,83 +504,83 @@ def log_prob(self, x: dict[str, Float]) -> Float: return output + jnp.log(jnp.sin(zenith)) -# @jaxtyped(typechecker=typechecker) -# class PowerLaw(Prior): -# """ -# A prior following the power-law with alpha in the range [xmin, xmax). -# p(x) ~ x^{\alpha} -# """ - -# xmin: float = 0.0 -# xmax: float = 1.0 -# alpha: float = 0.0 -# normalization: float = 1.0 - -# def __repr__(self): -# return f"Powerlaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" - -# def __init__( -# self, -# xmin: float, -# xmax: float, -# alpha: Union[Int, float], -# naming: list[str], -# transforms: dict[str, tuple[str, Callable]] = {}, -# **kwargs, -# ): -# super().__init__(naming, transforms) -# if alpha < 0.0: -# assert xmin > 0.0, "With negative alpha, xmin must > 0" -# assert self.n_dim == 1, "Powerlaw needs to be 1D distributions" -# self.xmax = xmax -# self.xmin = xmin -# self.alpha = alpha -# if alpha == -1: -# self.normalization = float(1.0 / jnp.log(self.xmax / self.xmin)) -# else: -# self.normalization = (1 + self.alpha) / ( -# self.xmax ** (1 + self.alpha) - self.xmin ** (1 + self.alpha) -# ) - -# def sample( -# self, rng_key: PRNGKeyArray, n_samples: int -# ) -> dict[str, Float[Array, " n_samples"]]: -# """ -# Sample from a power-law distribution. - -# Parameters -# ---------- -# rng_key : PRNGKeyArray -# A random key to use for sampling. -# n_samples : int -# The number of samples to draw. - -# Returns -# ------- -# samples : dict -# Samples from the distribution. The keys are the names of the parameters. - -# """ -# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) -# if self.alpha == -1: -# samples = self.xmin * jnp.exp(q_samples * jnp.log(self.xmax / self.xmin)) -# else: -# samples = ( -# self.xmin ** (1.0 + self.alpha) -# + q_samples -# * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) -# ) ** (1.0 / (1.0 + self.alpha)) -# return self.add_name(samples[None]) - -# def log_prob(self, x: dict[str, Float]) -> Float: -# variable = x[self.naming[0]] -# log_in_range = jnp.where( -# (variable >= self.xmax) | (variable <= self.xmin), -# jnp.zeros_like(variable) - jnp.inf, -# jnp.zeros_like(variable), -# ) -# log_p = self.alpha * jnp.log(variable) + jnp.log(self.normalization) -# return log_p + log_in_range +@jaxtyped(typechecker=typechecker) +class PowerLaw(Prior): + """ + A prior following the power-law with alpha in the range [xmin, xmax). + p(x) ~ x^{\alpha} + """ + + xmin: float = 0.0 + xmax: float = 1.0 + alpha: float = 0.0 + normalization: float = 1.0 + + def __repr__(self): + return f"Powerlaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" + + def __init__( + self, + xmin: float, + xmax: float, + alpha: Union[Int, float], + naming: list[str], + transforms: dict[str, tuple[str, Callable]] = {}, + **kwargs, + ): + super().__init__(naming, transforms) + if alpha < 0.0: + assert xmin > 0.0, "With negative alpha, xmin must > 0" + assert self.n_dim == 1, "Powerlaw needs to be 1D distributions" + self.xmax = xmax + self.xmin = xmin + self.alpha = alpha + if alpha == -1: + self.normalization = float(1.0 / jnp.log(self.xmax / self.xmin)) + else: + self.normalization = (1 + self.alpha) / ( + self.xmax ** (1 + self.alpha) - self.xmin ** (1 + self.alpha) + ) + + def sample( + self, rng_key: PRNGKeyArray, n_samples: int + ) -> dict[str, Float[Array, " n_samples"]]: + """ + Sample from a power-law distribution. + + Parameters + ---------- + rng_key : PRNGKeyArray + A random key to use for sampling. + n_samples : int + The number of samples to draw. + + Returns + ------- + samples : dict + Samples from the distribution. The keys are the names of the parameters. + + """ + q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) + if self.alpha == -1: + samples = self.xmin * jnp.exp(q_samples * jnp.log(self.xmax / self.xmin)) + else: + samples = ( + self.xmin ** (1.0 + self.alpha) + + q_samples + * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) ** (1.0 / (1.0 + self.alpha)) + return self.add_name(samples[None]) + + def log_prob(self, x: dict[str, Float]) -> Float: + variable = x[self.naming[0]] + log_in_range = jnp.where( + (variable >= self.xmax) | (variable <= self.xmin), + jnp.zeros_like(variable) - jnp.inf, + jnp.zeros_like(variable), + ) + log_p = self.alpha * jnp.log(variable) + jnp.log(self.normalization) + return log_p + log_in_range @jaxtyped(typechecker=typechecker) From 8256d0ee94c4d787d0257280d01461ce5127a8cd Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 13:00:09 -0400 Subject: [PATCH 060/248] Comment out old prior --- src/jimgw/prior.py | 692 ++++++++++++++++++++++----------------------- 1 file changed, 346 insertions(+), 346 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index dc983a50..499bd1c2 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -313,349 +313,349 @@ def __init__(self, parameter_names: list[str], **kwargs): # ====================== Things below may need rework ====================== -@jaxtyped(typechecker=typechecker) -class AlignedSpin(Prior): - """ - Prior distribution for the aligned (z) component of the spin. - - This assume the prior distribution on the spin magnitude to be uniform in [0, amax] - with its orientation uniform on a sphere - - p(chi) = -log(|chi| / amax) / 2 / amax - - This is useful when comparing results between an aligned-spin run and - a precessing spin run. - - See (A7) of https://arxiv.org/abs/1805.10457. - """ - - amax: Float = 0.99 - chi_axis: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) - cdf_vals: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) - - def __repr__(self): - return f"Alignedspin(amax={self.amax}, naming={self.naming})" - - def __init__( - self, - amax: Float, - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - super().__init__(naming, transforms) - assert self.n_dim == 1, "Alignedspin needs to be 1D distributions" - self.amax = amax - - # build the interpolation table for the ppf of the one-sided distribution - chi_axis = jnp.linspace(1e-31, self.amax, num=1000) - cdf_vals = -chi_axis * (jnp.log(chi_axis / self.amax) - 1.0) / self.amax - self.chi_axis = chi_axis - self.cdf_vals = cdf_vals - - @property - def xmin(self): - return -self.amax - - @property - def xmax(self): - return self.amax - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from the Alignedspin distribution. - - for chi > 0; - p(chi) = -log(chi / amax) / amax # halved normalization constant - cdf(chi) = -chi * (log(chi / amax) - 1) / amax - - Since there is a pole at chi=0, we will sample with the following steps - 1. Map the samples with quantile > 0.5 to positive chi and negative otherwise - 2a. For negative chi, map the quantile back to [0, 1] via q -> 2(0.5 - q) - 2b. For positive chi, map the quantile back to [0, 1] via q -> 2(q - 0.5) - 3. Map the quantile to chi via the ppf by checking against the table - built during the initialization - 4. add back the sign - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : dict - Samples from the distribution. The keys are the names of the parameters. - - """ - q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) - # 1. calculate the sign of chi from the q_samples - sign_samples = jnp.where( - q_samples >= 0.5, - jnp.zeros_like(q_samples) + 1.0, - jnp.zeros_like(q_samples) - 1.0, - ) - # 2. remap q_samples - q_samples = jnp.where( - q_samples >= 0.5, - 2 * (q_samples - 0.5), - 2 * (0.5 - q_samples), - ) - # 3. map the quantile to chi via interpolation - samples = jnp.interp( - q_samples, - self.cdf_vals, - self.chi_axis, - ) - # 4. add back the sign - samples *= sign_samples - - return self.add_name(samples[None]) - - def log_prob(self, x: dict[str, Float]) -> Float: - variable = x[self.naming[0]] - log_p = jnp.where( - (variable >= self.amax) | (variable <= -self.amax), - jnp.zeros_like(variable) - jnp.inf, - jnp.log(-jnp.log(jnp.absolute(variable) / self.amax) / 2.0 / self.amax), - ) - return log_p - - -@jaxtyped(typechecker=typechecker) -class EarthFrame(Prior): - """ - Prior distribution for sky location in Earth frame. - """ - - ifos: list = field(default_factory=list) - gmst: float = 0.0 - delta_x: Float[Array, " 3"] = field(default_factory=lambda: jnp.zeros(3)) - - def __repr__(self): - return f"EarthFrame(naming={self.naming})" - - def __init__(self, gps: Float, ifos: list, **kwargs): - self.naming = ["zenith", "azimuth"] - if len(ifos) < 2: - return ValueError( - "At least two detectors are needed to define the Earth frame" - ) - elif isinstance(ifos[0], str): - self.ifos = [detector_preset[ifos[0]], detector_preset[ifos[1]]] - elif isinstance(ifos[0], GroundBased2G): - self.ifos = ifos[:1] - else: - return ValueError( - "ifos should be a list of detector names or GroundBased2G objects" - ) - self.gmst = float( - Time(gps, format="gps").sidereal_time("apparent", "greenwich").rad - ) - self.delta_x = self.ifos[1].vertex - self.ifos[0].vertex - - self.transforms = { - "azimuth": ( - "ra", - lambda params: zenith_azimuth_to_ra_dec( - params["zenith"], - params["azimuth"], - gmst=self.gmst, - delta_x=self.delta_x, - )[0], - ), - "zenith": ( - "dec", - lambda params: zenith_azimuth_to_ra_dec( - params["zenith"], - params["azimuth"], - gmst=self.gmst, - delta_x=self.delta_x, - )[1], - ), - } - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - rng_keys = jax.random.split(rng_key, 2) - zenith = jnp.arccos( - jax.random.uniform(rng_keys[0], (n_samples,), minval=-1.0, maxval=1.0) - ) - azimuth = jax.random.uniform( - rng_keys[1], (n_samples,), minval=0, maxval=2 * jnp.pi - ) - return self.add_name(jnp.stack([zenith, azimuth], axis=1).T) - - def log_prob(self, x: dict[str, Float]) -> Float: - zenith = x["zenith"] - azimuth = x["azimuth"] - output = jnp.where( - (zenith > jnp.pi) | (zenith < 0) | (azimuth > 2 * jnp.pi) | (azimuth < 0), - jnp.zeros_like(0) - jnp.inf, - jnp.zeros_like(0), - ) - return output + jnp.log(jnp.sin(zenith)) - - -@jaxtyped(typechecker=typechecker) -class PowerLaw(Prior): - """ - A prior following the power-law with alpha in the range [xmin, xmax). - p(x) ~ x^{\alpha} - """ - - xmin: float = 0.0 - xmax: float = 1.0 - alpha: float = 0.0 - normalization: float = 1.0 - - def __repr__(self): - return f"Powerlaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" - - def __init__( - self, - xmin: float, - xmax: float, - alpha: Union[Int, float], - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - super().__init__(naming, transforms) - if alpha < 0.0: - assert xmin > 0.0, "With negative alpha, xmin must > 0" - assert self.n_dim == 1, "Powerlaw needs to be 1D distributions" - self.xmax = xmax - self.xmin = xmin - self.alpha = alpha - if alpha == -1: - self.normalization = float(1.0 / jnp.log(self.xmax / self.xmin)) - else: - self.normalization = (1 + self.alpha) / ( - self.xmax ** (1 + self.alpha) - self.xmin ** (1 + self.alpha) - ) - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from a power-law distribution. - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : dict - Samples from the distribution. The keys are the names of the parameters. - - """ - q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) - if self.alpha == -1: - samples = self.xmin * jnp.exp(q_samples * jnp.log(self.xmax / self.xmin)) - else: - samples = ( - self.xmin ** (1.0 + self.alpha) - + q_samples - * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - ) ** (1.0 / (1.0 + self.alpha)) - return self.add_name(samples[None]) - - def log_prob(self, x: dict[str, Float]) -> Float: - variable = x[self.naming[0]] - log_in_range = jnp.where( - (variable >= self.xmax) | (variable <= self.xmin), - jnp.zeros_like(variable) - jnp.inf, - jnp.zeros_like(variable), - ) - log_p = self.alpha * jnp.log(variable) + jnp.log(self.normalization) - return log_p + log_in_range - - -@jaxtyped(typechecker=typechecker) -class Exponential(Prior): - """ - A prior following the power-law with alpha in the range [xmin, xmax). - p(x) ~ exp(\alpha x) - """ - - xmin: float = 0.0 - xmax: float = jnp.inf - alpha: float = -1.0 - normalization: float = 1.0 - - def __repr__(self): - return f"Exponential(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" - - def __init__( - self, - xmin: Float, - xmax: Float, - alpha: Union[Int, Float], - naming: list[str], - transforms: dict[str, tuple[str, Callable]] = {}, - **kwargs, - ): - super().__init__(naming, transforms) - if alpha < 0.0: - assert xmin != -jnp.inf, "With negative alpha, xmin must finite" - if alpha > 0.0: - assert xmax != jnp.inf, "With positive alpha, xmax must finite" - assert not jnp.isclose(alpha, 0.0), "alpha=zero is given, use Uniform instead" - assert self.n_dim == 1, "Exponential needs to be 1D distributions" - - self.xmax = xmax - self.xmin = xmin - self.alpha = alpha - - self.normalization = self.alpha / ( - jnp.exp(self.alpha * self.xmax) - jnp.exp(self.alpha * self.xmin) - ) - - def sample( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - """ - Sample from a exponential distribution. - - Parameters - ---------- - rng_key : PRNGKeyArray - A random key to use for sampling. - n_samples : int - The number of samples to draw. - - Returns - ------- - samples : dict - Samples from the distribution. The keys are the names of the parameters. - - """ - q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) - samples = ( - self.xmin - + jnp.log1p( - q_samples * (jnp.exp(self.alpha * (self.xmax - self.xmin)) - 1.0) - ) - / self.alpha - ) - return self.add_name(samples[None]) - - def log_prob(self, x: dict[str, Float]) -> Float: - variable = x[self.naming[0]] - log_in_range = jnp.where( - (variable >= self.xmax) | (variable <= self.xmin), - jnp.zeros_like(variable) - jnp.inf, - jnp.zeros_like(variable), - ) - log_p = self.alpha * variable + jnp.log(self.normalization) - return log_p + log_in_range +# @jaxtyped(typechecker=typechecker) +# class AlignedSpin(Prior): +# """ +# Prior distribution for the aligned (z) component of the spin. + +# This assume the prior distribution on the spin magnitude to be uniform in [0, amax] +# with its orientation uniform on a sphere + +# p(chi) = -log(|chi| / amax) / 2 / amax + +# This is useful when comparing results between an aligned-spin run and +# a precessing spin run. + +# See (A7) of https://arxiv.org/abs/1805.10457. +# """ + +# amax: Float = 0.99 +# chi_axis: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) +# cdf_vals: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) + +# def __repr__(self): +# return f"Alignedspin(amax={self.amax}, naming={self.naming})" + +# def __init__( +# self, +# amax: Float, +# naming: list[str], +# transforms: dict[str, tuple[str, Callable]] = {}, +# **kwargs, +# ): +# super().__init__(naming, transforms) +# assert self.n_dim == 1, "Alignedspin needs to be 1D distributions" +# self.amax = amax + +# # build the interpolation table for the ppf of the one-sided distribution +# chi_axis = jnp.linspace(1e-31, self.amax, num=1000) +# cdf_vals = -chi_axis * (jnp.log(chi_axis / self.amax) - 1.0) / self.amax +# self.chi_axis = chi_axis +# self.cdf_vals = cdf_vals + +# @property +# def xmin(self): +# return -self.amax + +# @property +# def xmax(self): +# return self.amax + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# """ +# Sample from the Alignedspin distribution. + +# for chi > 0; +# p(chi) = -log(chi / amax) / amax # halved normalization constant +# cdf(chi) = -chi * (log(chi / amax) - 1) / amax + +# Since there is a pole at chi=0, we will sample with the following steps +# 1. Map the samples with quantile > 0.5 to positive chi and negative otherwise +# 2a. For negative chi, map the quantile back to [0, 1] via q -> 2(0.5 - q) +# 2b. For positive chi, map the quantile back to [0, 1] via q -> 2(q - 0.5) +# 3. Map the quantile to chi via the ppf by checking against the table +# built during the initialization +# 4. add back the sign + +# Parameters +# ---------- +# rng_key : PRNGKeyArray +# A random key to use for sampling. +# n_samples : int +# The number of samples to draw. + +# Returns +# ------- +# samples : dict +# Samples from the distribution. The keys are the names of the parameters. + +# """ +# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) +# # 1. calculate the sign of chi from the q_samples +# sign_samples = jnp.where( +# q_samples >= 0.5, +# jnp.zeros_like(q_samples) + 1.0, +# jnp.zeros_like(q_samples) - 1.0, +# ) +# # 2. remap q_samples +# q_samples = jnp.where( +# q_samples >= 0.5, +# 2 * (q_samples - 0.5), +# 2 * (0.5 - q_samples), +# ) +# # 3. map the quantile to chi via interpolation +# samples = jnp.interp( +# q_samples, +# self.cdf_vals, +# self.chi_axis, +# ) +# # 4. add back the sign +# samples *= sign_samples + +# return self.add_name(samples[None]) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# variable = x[self.naming[0]] +# log_p = jnp.where( +# (variable >= self.amax) | (variable <= -self.amax), +# jnp.zeros_like(variable) - jnp.inf, +# jnp.log(-jnp.log(jnp.absolute(variable) / self.amax) / 2.0 / self.amax), +# ) +# return log_p + + +# @jaxtyped(typechecker=typechecker) +# class EarthFrame(Prior): +# """ +# Prior distribution for sky location in Earth frame. +# """ + +# ifos: list = field(default_factory=list) +# gmst: float = 0.0 +# delta_x: Float[Array, " 3"] = field(default_factory=lambda: jnp.zeros(3)) + +# def __repr__(self): +# return f"EarthFrame(naming={self.naming})" + +# def __init__(self, gps: Float, ifos: list, **kwargs): +# self.naming = ["zenith", "azimuth"] +# if len(ifos) < 2: +# return ValueError( +# "At least two detectors are needed to define the Earth frame" +# ) +# elif isinstance(ifos[0], str): +# self.ifos = [detector_preset[ifos[0]], detector_preset[ifos[1]]] +# elif isinstance(ifos[0], GroundBased2G): +# self.ifos = ifos[:1] +# else: +# return ValueError( +# "ifos should be a list of detector names or GroundBased2G objects" +# ) +# self.gmst = float( +# Time(gps, format="gps").sidereal_time("apparent", "greenwich").rad +# ) +# self.delta_x = self.ifos[1].vertex - self.ifos[0].vertex + +# self.transforms = { +# "azimuth": ( +# "ra", +# lambda params: zenith_azimuth_to_ra_dec( +# params["zenith"], +# params["azimuth"], +# gmst=self.gmst, +# delta_x=self.delta_x, +# )[0], +# ), +# "zenith": ( +# "dec", +# lambda params: zenith_azimuth_to_ra_dec( +# params["zenith"], +# params["azimuth"], +# gmst=self.gmst, +# delta_x=self.delta_x, +# )[1], +# ), +# } + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# rng_keys = jax.random.split(rng_key, 2) +# zenith = jnp.arccos( +# jax.random.uniform(rng_keys[0], (n_samples,), minval=-1.0, maxval=1.0) +# ) +# azimuth = jax.random.uniform( +# rng_keys[1], (n_samples,), minval=0, maxval=2 * jnp.pi +# ) +# return self.add_name(jnp.stack([zenith, azimuth], axis=1).T) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# zenith = x["zenith"] +# azimuth = x["azimuth"] +# output = jnp.where( +# (zenith > jnp.pi) | (zenith < 0) | (azimuth > 2 * jnp.pi) | (azimuth < 0), +# jnp.zeros_like(0) - jnp.inf, +# jnp.zeros_like(0), +# ) +# return output + jnp.log(jnp.sin(zenith)) + + +# @jaxtyped(typechecker=typechecker) +# class PowerLaw(Prior): +# """ +# A prior following the power-law with alpha in the range [xmin, xmax). +# p(x) ~ x^{\alpha} +# """ + +# xmin: float = 0.0 +# xmax: float = 1.0 +# alpha: float = 0.0 +# normalization: float = 1.0 + +# def __repr__(self): +# return f"Powerlaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" + +# def __init__( +# self, +# xmin: float, +# xmax: float, +# alpha: Union[Int, float], +# naming: list[str], +# transforms: dict[str, tuple[str, Callable]] = {}, +# **kwargs, +# ): +# super().__init__(naming, transforms) +# if alpha < 0.0: +# assert xmin > 0.0, "With negative alpha, xmin must > 0" +# assert self.n_dim == 1, "Powerlaw needs to be 1D distributions" +# self.xmax = xmax +# self.xmin = xmin +# self.alpha = alpha +# if alpha == -1: +# self.normalization = float(1.0 / jnp.log(self.xmax / self.xmin)) +# else: +# self.normalization = (1 + self.alpha) / ( +# self.xmax ** (1 + self.alpha) - self.xmin ** (1 + self.alpha) +# ) + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# """ +# Sample from a power-law distribution. + +# Parameters +# ---------- +# rng_key : PRNGKeyArray +# A random key to use for sampling. +# n_samples : int +# The number of samples to draw. + +# Returns +# ------- +# samples : dict +# Samples from the distribution. The keys are the names of the parameters. + +# """ +# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) +# if self.alpha == -1: +# samples = self.xmin * jnp.exp(q_samples * jnp.log(self.xmax / self.xmin)) +# else: +# samples = ( +# self.xmin ** (1.0 + self.alpha) +# + q_samples +# * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) +# ) ** (1.0 / (1.0 + self.alpha)) +# return self.add_name(samples[None]) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# variable = x[self.naming[0]] +# log_in_range = jnp.where( +# (variable >= self.xmax) | (variable <= self.xmin), +# jnp.zeros_like(variable) - jnp.inf, +# jnp.zeros_like(variable), +# ) +# log_p = self.alpha * jnp.log(variable) + jnp.log(self.normalization) +# return log_p + log_in_range + + +# @jaxtyped(typechecker=typechecker) +# class Exponential(Prior): +# """ +# A prior following the power-law with alpha in the range [xmin, xmax). +# p(x) ~ exp(\alpha x) +# """ + +# xmin: float = 0.0 +# xmax: float = jnp.inf +# alpha: float = -1.0 +# normalization: float = 1.0 + +# def __repr__(self): +# return f"Exponential(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" + +# def __init__( +# self, +# xmin: Float, +# xmax: Float, +# alpha: Union[Int, Float], +# naming: list[str], +# transforms: dict[str, tuple[str, Callable]] = {}, +# **kwargs, +# ): +# super().__init__(naming, transforms) +# if alpha < 0.0: +# assert xmin != -jnp.inf, "With negative alpha, xmin must finite" +# if alpha > 0.0: +# assert xmax != jnp.inf, "With positive alpha, xmax must finite" +# assert not jnp.isclose(alpha, 0.0), "alpha=zero is given, use Uniform instead" +# assert self.n_dim == 1, "Exponential needs to be 1D distributions" + +# self.xmax = xmax +# self.xmin = xmin +# self.alpha = alpha + +# self.normalization = self.alpha / ( +# jnp.exp(self.alpha * self.xmax) - jnp.exp(self.alpha * self.xmin) +# ) + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# """ +# Sample from a exponential distribution. + +# Parameters +# ---------- +# rng_key : PRNGKeyArray +# A random key to use for sampling. +# n_samples : int +# The number of samples to draw. + +# Returns +# ------- +# samples : dict +# Samples from the distribution. The keys are the names of the parameters. + +# """ +# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) +# samples = ( +# self.xmin +# + jnp.log1p( +# q_samples * (jnp.exp(self.alpha * (self.xmax - self.xmin)) - 1.0) +# ) +# / self.alpha +# ) +# return self.add_name(samples[None]) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# variable = x[self.naming[0]] +# log_in_range = jnp.where( +# (variable >= self.xmax) | (variable <= self.xmin), +# jnp.zeros_like(variable) - jnp.inf, +# jnp.zeros_like(variable), +# ) +# log_p = self.alpha * variable + jnp.log(self.normalization) +# return log_p + log_in_range From b42b0f4622b2a457ae759eabbbbc6c6641af8da4 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 13:01:07 -0400 Subject: [PATCH 061/248] Reformat --- src/jimgw/prior.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 499bd1c2..5e48cb8a 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -6,8 +6,6 @@ from flowMC.nfmodel.base import Distribution from jaxtyping import Array, Float, PRNGKeyArray, jaxtyped -from jimgw.single_event.detector import GroundBased2G, detector_preset -from jimgw.single_event.utils import zenith_azimuth_to_ra_dec from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine From a9629b25e984c104375a2710fd2c8e496faaa5b8 Mon Sep 17 00:00:00 2001 From: kazewong Date: Fri, 26 Jul 2024 13:09:46 -0400 Subject: [PATCH 062/248] Update Prior naming --- src/jimgw/prior.py | 34 +++++++++++++++++----------------- test/test_prior.py | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 5e48cb8a..0f8b70b1 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -132,7 +132,7 @@ def log_prob(self, x: dict[str, Float]) -> Float: return -0.5 * variable**2 - 0.5 * jnp.log(2 * jnp.pi) -class SequentialTransform(Prior): +class SequentialTransformPrior(Prior): """ Transform a prior distribution by applying a sequence of transforms. """ @@ -182,7 +182,7 @@ def transform(self, x: dict[str, Float]) -> dict[str, Float]: return x -class Combine(Prior): +class CombinePrior(Prior): """ A prior class constructed by joinning multiple priors together to form a multivariate prior. This assumes the priors composing the Combine class are independent. @@ -220,12 +220,12 @@ def log_prob(self, x: dict[str, Float]) -> Float: @jaxtyped(typechecker=typechecker) -class Uniform(SequentialTransform): +class UniformPrior(SequentialTransformPrior): xmin: float xmax: float def __repr__(self): - return f"Uniform(xmin={self.xmin}, xmax={self.xmax}, parameter_names={self.parameter_names})" + return f"UniformPrior(xmin={self.xmin}, xmax={self.xmax}, parameter_names={self.parameter_names})" def __init__( self, @@ -234,7 +234,7 @@ def __init__( parameter_names: list[str], ): self.parameter_names = parameter_names - assert self.n_dim == 1, "Uniform needs to be 1D distributions" + assert self.n_dim == 1, "UniformPrior needs to be 1D distributions" self.xmax = xmax self.xmin = xmin super().__init__( @@ -248,43 +248,43 @@ def __init__( @jaxtyped(typechecker=typechecker) -class Sine(SequentialTransform): +class SinePrior(SequentialTransformPrior): """ A prior distribution where the pdf is proportional to sin(x) in the range [0, pi]. """ def __repr__(self): - return f"Sine(parameter_names={self.parameter_names})" + return f"SinePrior(parameter_names={self.parameter_names})" def __init__(self, parameter_names: list[str]): self.parameter_names = parameter_names - assert self.n_dim == 1, "Sine needs to be 1D distributions" + assert self.n_dim == 1, "SinePrior needs to be 1D distributions" super().__init__( - Uniform(-1.0, 1.0, f"cos_{self.parameter_names}"), + UniformPrior(-1.0, 1.0, f"cos_{self.parameter_names}"), [ArcCosine(([f"cos_{self.parameter_names}"], [self.parameter_names]))], ) @jaxtyped(typechecker=typechecker) -class Cosine(SequentialTransform): +class CosinePrior(SequentialTransformPrior): """ A prior distribution where the pdf is proportional to cos(x) in the range [-pi/2, pi/2]. """ def __repr__(self): - return f"Cosine(parameter_names={self.parameter_names})" + return f"CosinePrior(parameter_names={self.parameter_names})" def __init__(self, parameter_names: list[str]): self.parameter_names = parameter_names - assert self.n_dim == 1, "Cosine needs to be 1D distributions" + assert self.n_dim == 1, "CosinePrior needs to be 1D distributions" super().__init__( - Uniform(-1.0, 1.0, f"sin_{self.parameter_names}"), + UniformPrior(-1.0, 1.0, f"sin_{self.parameter_names}"), [ArcSine(([f"sin_{self.parameter_names}"], [self.parameter_names]))], ) @jaxtyped(typechecker=typechecker) -class UniformSphere(Combine): +class UniformSpherePrior(CombinePrior): def __repr__(self): return f"UniformSphere(parameter_names={self.parameter_names})" @@ -301,9 +301,9 @@ def __init__(self, parameter_names: list[str], **kwargs): ] super().__init__( [ - Uniform(0.0, 1.0, [self.parameter_names[0]]), - Sine([self.parameter_names[1]]), - Uniform(0.0, 2 * jnp.pi, [self.parameter_names[2]]), + UniformPrior(0.0, 1.0, [self.parameter_names[0]]), + SinePrior([self.parameter_names[1]]), + UniformPrior(0.0, 2 * jnp.pi, [self.parameter_names[2]]), ] ) diff --git a/test/test_prior.py b/test/test_prior.py index b6eb7c87..98803075 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -6,7 +6,7 @@ def test_logistic(self): p = Logit() def test_uniform(self): - p = Uniform(0.0, 10.0, ['x']) + p = UniformPrior(0.0, 10.0, ['x']) samples = p._dist.base_prior.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.allclose(log_prob, -jnp.log(10.0)) From fbba5af599f87fae064e12d6d593398d48b3f8ce Mon Sep 17 00:00:00 2001 From: kazewong Date: Fri, 26 Jul 2024 13:12:13 -0400 Subject: [PATCH 063/248] Standize Transform naming --- src/jimgw/prior.py | 29 +++++++++++++++++++++++------ src/jimgw/transforms.py | 10 +++++----- test/test_prior.py | 11 ++++++----- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 0f8b70b1..3d26eb04 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -6,7 +6,14 @@ from flowMC.nfmodel.base import Distribution from jaxtyping import Array, Float, PRNGKeyArray, jaxtyped -from jimgw.transforms import Transform, Logit, Scale, Offset, ArcSine, ArcCosine +from jimgw.transforms import ( + Transform, + LogitTransform, + ScaleTransform, + OffsetTransform, + ArcSineTransform, + ArcCosineTransform, +) class Prior(Distribution): @@ -240,9 +247,11 @@ def __init__( super().__init__( LogisticDistribution(self.parameter_names), [ - Logit((self.parameter_names, self.parameter_names)), - Scale((self.parameter_names, self.parameter_names), xmax - xmin), - Offset((self.parameter_names, self.parameter_names), xmin), + LogitTransform((self.parameter_names, self.parameter_names)), + ScaleTransform( + (self.parameter_names, self.parameter_names), xmax - xmin + ), + OffsetTransform((self.parameter_names, self.parameter_names), xmin), ], ) @@ -261,7 +270,11 @@ def __init__(self, parameter_names: list[str]): assert self.n_dim == 1, "SinePrior needs to be 1D distributions" super().__init__( UniformPrior(-1.0, 1.0, f"cos_{self.parameter_names}"), - [ArcCosine(([f"cos_{self.parameter_names}"], [self.parameter_names]))], + [ + ArcCosineTransform( + ([f"cos_{self.parameter_names}"], [self.parameter_names]) + ) + ], ) @@ -279,7 +292,11 @@ def __init__(self, parameter_names: list[str]): assert self.n_dim == 1, "CosinePrior needs to be 1D distributions" super().__init__( UniformPrior(-1.0, 1.0, f"sin_{self.parameter_names}"), - [ArcSine(([f"sin_{self.parameter_names}"], [self.parameter_names]))], + [ + ArcSineTransform( + ([f"sin_{self.parameter_names}"], [self.parameter_names]) + ) + ], ) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index d8ee9536..5022fc88 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -94,7 +94,7 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: return x -class Scale(UnivariateTransform): +class ScaleTransform(UnivariateTransform): scale: Float def __init__( @@ -107,7 +107,7 @@ def __init__( self.transform_func = lambda x: x * self.scale -class Offset(UnivariateTransform): +class OffsetTransform(UnivariateTransform): offset: Float def __init__( @@ -120,7 +120,7 @@ def __init__( self.transform_func = lambda x: x + self.offset -class Logit(UnivariateTransform): +class LogitTransform(UnivariateTransform): """ Logit transform following @@ -139,7 +139,7 @@ def __init__( self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) -class ArcSine(UnivariateTransform): +class ArcSineTransform(UnivariateTransform): """ ArcSine transformation @@ -158,7 +158,7 @@ def __init__( self.transform_func = lambda x: jnp.arcsin(x) -class ArcCosine(UnivariateTransform): +class ArcCosineTransform(UnivariateTransform): """ ArcCosine transformation diff --git a/test/test_prior.py b/test/test_prior.py index 98803075..6431d7d0 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -3,20 +3,21 @@ class TestUnivariatePrior: def test_logistic(self): - p = Logit() + p = LogitTransform() def test_uniform(self): - p = UniformPrior(0.0, 10.0, ['x']) + p = UniformPrior(0.0, 10.0, ["x"]) samples = p._dist.base_prior.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.allclose(log_prob, -jnp.log(10.0)) + class TestPriorOperations: def test_combine(self): raise NotImplementedError - + def test_sequence(self): raise NotImplementedError - + def test_factor(self): - raise NotImplementedError \ No newline at end of file + raise NotImplementedError From dd27330d115e4b8abac2e34b9f31f7a24926c347 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:47:03 -0400 Subject: [PATCH 064/248] Updated Test files --- test/test_prior.py | 2 +- test/test_transform.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_prior.py b/test/test_prior.py index 53b065da..8b137891 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -1 +1 @@ -from jimgw.prior import Composite, Unconstrained_Uniform, Uniform + diff --git a/test/test_transform.py b/test/test_transform.py index 08cd6ff2..5d9fe464 100644 --- a/test/test_transform.py +++ b/test/test_transform.py @@ -1,8 +1,8 @@ import numpy as np import jax.numpy as jnp -class VerifyTransform: - def verify_sky_location_transform(self): +class TestTransform: + def test_sky_location_transform(self): from bilby.gw.utils import zenith_azimuth_to_ra_dec as bilby_earth_to_sky from bilby.gw.detector.networks import InterferometerList @@ -21,7 +21,7 @@ def verify_sky_location_transform(self): jimgw_sky_location = np.array(jimgw_earth_to_sky(zenith, azimuth, Time(geocent_time, format="gps").sidereal_time("apparent", "greenwich").rad, detector_preset[ifos[0]].vertex - detector_preset[ifos[1]].vertex)) assert np.allclose(bilby_sky_location, jimgw_sky_location, atol=1e-4) - def verify_spin_transform(self): + def test_spin_transform(self): from bilby.gw.conversion import bilby_to_lalsimulation_spins as bilby_spin_transform from bilby.gw.conversion import symmetric_mass_ratio_to_mass_ratio, chirp_mass_and_mass_ratio_to_component_masses @@ -45,4 +45,4 @@ def verify_spin_transform(self): MsunInkg = 1.9884e30 bilby_spin = jnp.array(bilby_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, m1*MsunInkg, m2*MsunInkg, fRef, phiRef)) jimgw_spin = jnp.array(jimgw_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, M_c, eta, fRef, phiRef)) - assert np.allclose(bilby_spin, jimgw_spin) + assert np.allclose(bilby_spin, jimgw_spin, atol=1e-4) From 4a5d932aea617a36d2e7e26a7bf61ff0007953d6 Mon Sep 17 00:00:00 2001 From: kazewong Date: Fri, 26 Jul 2024 15:13:55 -0400 Subject: [PATCH 065/248] Fixing naming problem and add base_distribution tracer --- src/jimgw/prior.py | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 3d26eb04..dc6a492b 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -27,6 +27,7 @@ class Prior(Distribution): """ parameter_names: list[str] + composite: bool = False @property def n_dim(self): @@ -61,7 +62,6 @@ def sample( def log_prob(self, x: dict[str, Array]) -> Float: raise NotImplementedError - @jaxtyped(typechecker=typechecker) class LogisticDistribution(Prior): @@ -70,6 +70,7 @@ def __repr__(self): def __init__(self, parameter_names: list[str], **kwargs): super().__init__(parameter_names) + self.composite = False assert self.n_dim == 1, "LogisticDistribution needs to be 1D distributions" def sample( @@ -108,6 +109,7 @@ def __repr__(self): def __init__(self, parameter_names: list[str], **kwargs): super().__init__(parameter_names) + self.composite = False assert ( self.n_dim == 1 ), "StandardNormalDistribution needs to be 1D distributions" @@ -161,11 +163,12 @@ def __init__( self.parameter_names = base_prior.parameter_names for transform in transforms: self.parameter_names = transform.propagate_name(self.parameter_names) + self.composite = True def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: - output = self.sample_base(rng_key, n_samples) + output = self.base_prior.sample(rng_key, n_samples) return jax.vmap(self.transform)(output) def log_prob(self, x: dict[str, Float]) -> Float: @@ -178,11 +181,6 @@ def log_prob(self, x: dict[str, Float]) -> Float: output -= log_jacobian return output - def sample_base( - self, rng_key: PRNGKeyArray, n_samples: int - ) -> dict[str, Float[Array, " n_samples"]]: - return self.base_prior.sample(rng_key, n_samples) - def transform(self, x: dict[str, Float]) -> dict[str, Float]: for transform in self.transforms: x = transform.forward(x) @@ -195,10 +193,12 @@ class CombinePrior(Prior): This assumes the priors composing the Combine class are independent. """ - priors: list[Prior] = field(default_factory=list) + base_prior: list[Prior] = field(default_factory=list) def __repr__(self): - return f"Combine(priors={self.priors}, parameter_names={self.parameter_names})" + return ( + f"Combine(priors={self.base_prior}, parameter_names={self.parameter_names})" + ) def __init__( self, @@ -207,21 +207,22 @@ def __init__( parameter_names = [] for prior in priors: parameter_names += prior.parameter_names - self.priors = priors + self.base_prior = priors self.parameter_names = parameter_names + self.composite = True def sample( self, rng_key: PRNGKeyArray, n_samples: int ) -> dict[str, Float[Array, " n_samples"]]: output = {} - for prior in self.priors: + for prior in self.base_prior: rng_key, subkey = jax.random.split(rng_key) output.update(prior.sample(subkey, n_samples)) return output def log_prob(self, x: dict[str, Float]) -> Float: output = 0.0 - for prior in self.priors: + for prior in self.base_prior: output += prior.log_prob(x) return output @@ -269,10 +270,10 @@ def __init__(self, parameter_names: list[str]): self.parameter_names = parameter_names assert self.n_dim == 1, "SinePrior needs to be 1D distributions" super().__init__( - UniformPrior(-1.0, 1.0, f"cos_{self.parameter_names}"), + UniformPrior(-1.0, 1.0, [f"cos_{self.parameter_names[0]}"]), [ ArcCosineTransform( - ([f"cos_{self.parameter_names}"], [self.parameter_names]) + ([f"cos_{self.parameter_names[0]}"], [self.parameter_names[0]]) ) ], ) @@ -291,10 +292,10 @@ def __init__(self, parameter_names: list[str]): self.parameter_names = parameter_names assert self.n_dim == 1, "CosinePrior needs to be 1D distributions" super().__init__( - UniformPrior(-1.0, 1.0, f"sin_{self.parameter_names}"), + UniformPrior(-1.0, 1.0, f"sin_{self.parameter_names[0]}"), [ ArcSineTransform( - ([f"sin_{self.parameter_names}"], [self.parameter_names]) + ([f"sin_{self.parameter_names[0]}"], [self.parameter_names[0]]) ) ], ) @@ -324,6 +325,17 @@ def __init__(self, parameter_names: list[str], **kwargs): ] ) +def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: + if prior.composite: + if isinstance(prior.base_prior, list): + for subprior in prior.base_prior: + output = trace_prior_parent(subprior, output) + elif isinstance(prior.base_prior, Prior): + output = trace_prior_parent(prior.base_prior, output) + else: + output.append(prior) + + return output # ====================== Things below may need rework ====================== From 27dc87090ff3ed29cb52792f957d52de3595a6c8 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 26 Jul 2024 15:37:19 -0400 Subject: [PATCH 066/248] Updated powerlaw --- src/jimgw/prior.py | 43 +++++++++++++++++++++++++++++++ src/jimgw/transforms.py | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index dc6a492b..16c28b8e 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -13,6 +13,8 @@ OffsetTransform, ArcSineTransform, ArcCosineTransform, + PowerLawTransform, + ParetoTransform, ) @@ -62,6 +64,7 @@ def sample( def log_prob(self, x: dict[str, Array]) -> Float: raise NotImplementedError + @jaxtyped(typechecker=typechecker) class LogisticDistribution(Prior): @@ -325,6 +328,45 @@ def __init__(self, parameter_names: list[str], **kwargs): ] ) + +@jaxtyped(typechecker=typechecker) +class PowerLawPrior(SequentialTransformPrior): + xmin: float + xmax: float + alpha: float + + def __repr__(self): + return f"PowerLaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.parameter_names})" + + def __init__( + self, + xmin: float, + xmax: float, + alpha: float, + parameter_names: list[str], + ): + self.parameter_names = parameter_names + assert self.n_dim == 1, "Power law needs to be 1D distributions" + self.xmax = xmax + self.xmin = xmin + self.alpha = alpha + if self.alpha == -1.0: + transform = ParetoTransform( + (self.parameter_names, self.parameter_names), xmin, xmax + ) + else: + transform = PowerLawTransform( + (self.parameter_names, self.parameter_names), xmin, xmax, alpha + ) + super().__init__( + LogisticDistribution(self.parameter_names), + [ + Logit((self.parameter_names, self.parameter_names)), + transform, + ], + ) + + def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: if prior.composite: if isinstance(prior.base_prior, list): @@ -337,6 +379,7 @@ def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: return output + # ====================== Things below may need rework ====================== diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 5022fc88..527ea7b8 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -175,3 +175,60 @@ def __init__( ): super().__init__(name_mapping) self.transform_func = lambda x: jnp.arccos(x) + + +class PowerLawTransform(UnivariateTransform): + """ + PowerLaw transformation + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + """ + + xmin: Float + xmax: Float + alpha: Float + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + xmin: Float, + xmax: Float, + alpha: Float, + ): + super().__init__(name_mapping) + self.xmin = xmin + self.xmax = xmax + self.alpha = alpha + self.transform_func = ( + lambda x: ( + self.xmin ** (1.0 + self.alpha) + + x + * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) + ** (1.0 / (1.0 + self.alpha)), + ) + + +class ParetoTransform(UnivariateTransform): + """ + Pareto transformation: Power law when alpha = -1 + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + xmin: Float, + xmax: Float, + ): + super().__init__(name_mapping) + self.xmin = xmin + self.xmax = xmax + self.transform_func = lambda x: self.xmin * jnp.exp( + x * jnp.log(self.xmax / self.xmin) + ) From f7b883d6d0bff8f5be1eaddfbd042e601b1b620a Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 26 Jul 2024 15:38:28 -0400 Subject: [PATCH 067/248] Updated powerlaw --- src/jimgw/prior.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 16c28b8e..9f52fdbb 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -361,7 +361,7 @@ def __init__( super().__init__( LogisticDistribution(self.parameter_names), [ - Logit((self.parameter_names, self.parameter_names)), + LogitTransform((self.parameter_names, self.parameter_names)), transform, ], ) From 1665b1cfbc0400647f1ccabb2ed79479c4f32c43 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:26:20 -0400 Subject: [PATCH 068/248] Updated powerlaw --- src/jimgw/test.py | 70 +++++++++++++++++++++++++++++++++++++++++ src/jimgw/transforms.py | 12 +++---- 2 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 src/jimgw/test.py diff --git a/src/jimgw/test.py b/src/jimgw/test.py new file mode 100644 index 00000000..c114b190 --- /dev/null +++ b/src/jimgw/test.py @@ -0,0 +1,70 @@ +import jax +import jax.numpy as jnp +from jimgw.prior import PowerLawPrior + +alpha = -1.0 +xmin = 1.0 +xmax = 10.0 + + +q_samples = jax.random.uniform(jax.random.PRNGKey(42), (100,), minval=0.0, maxval=1.0) +if alpha == -1: + samples = xmin * jnp.exp(q_samples * jnp.log(xmax / xmin)) +else: + samples = ( + xmin ** (1.0 + alpha) + + q_samples * (xmax ** (1.0 + alpha) - xmin ** (1.0 + alpha)) + ) ** (1.0 / (1.0 + alpha)) +samples[None] + +PowerLawPrior(xmin, xmax, alpha, ["x"]).sample(jax.random.PRNGKey(42), 100) + + +############## +if alpha == -1: + normalization = float(1.0 / jnp.log(xmax / xmin)) +else: + normalization = (1 + alpha) / (xmax ** (1 + alpha) - xmin ** (1 + alpha)) +variable = 1.5 +log_in_range = jnp.where( + (variable >= xmax) | (variable <= xmin), + jnp.zeros_like(variable) - jnp.inf, + jnp.zeros_like(variable), +) +log_p = alpha * jnp.log(variable) + jnp.log(normalization) +log_p + log_in_range + +PowerLawPrior(xmin, xmax, alpha, ["x"]).log_prob({"x": variable}) + + +# transform_func = lambda x: ( +# xmin ** (1.0 + alpha) + x * (xmax ** (1.0 + alpha) - xmin ** (1.0 + alpha)) +# ) ** (1.0 / (1.0 + alpha)) +# input_params = variable +# assert_rank(input_params, 0) +# output_params = transform_func(input_params) +# jacobian = jax.jacfwd(transform_func)(input_params) +# x[variable] = output_params +# return x, jnp.log(jacobian) + + +import numpy as np + +alpha = -2.0 +xmin = 1.0 +xmax = 20.0 +p = PowerLawPrior(xmin, xmax, alpha, ["x"]) +grid = np.linspace(xmin, xmax, 20) +transform = [] +log_prob = [] +for y in grid: + transform.append(p.transform({"x": y})["x"].item()) + log_prob.append(np.exp(p.log_prob({"x": y}).item())) +import matplotlib.pyplot as plt + +plt.plot(grid, transform) +plt.savefig("transform.png") +plt.close() +plt.plot(transform, log_prob) +plt.savefig("log_prob.png") +plt.close() diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 527ea7b8..da84a059 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -201,14 +201,10 @@ def __init__( self.xmin = xmin self.xmax = xmax self.alpha = alpha - self.transform_func = ( - lambda x: ( - self.xmin ** (1.0 + self.alpha) - + x - * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - ) - ** (1.0 / (1.0 + self.alpha)), - ) + self.transform_func = lambda x: ( + self.xmin ** (1.0 + self.alpha) + + x * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) ** (1.0 / (1.0 + self.alpha)) class ParetoTransform(UnivariateTransform): From f51847d614d14de03569317e28cc738a9d89d915 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:49:15 -0400 Subject: [PATCH 069/248] Updated powerlaw --- src/jimgw/test.py | 70 ----------------------------------------------- 1 file changed, 70 deletions(-) delete mode 100644 src/jimgw/test.py diff --git a/src/jimgw/test.py b/src/jimgw/test.py deleted file mode 100644 index c114b190..00000000 --- a/src/jimgw/test.py +++ /dev/null @@ -1,70 +0,0 @@ -import jax -import jax.numpy as jnp -from jimgw.prior import PowerLawPrior - -alpha = -1.0 -xmin = 1.0 -xmax = 10.0 - - -q_samples = jax.random.uniform(jax.random.PRNGKey(42), (100,), minval=0.0, maxval=1.0) -if alpha == -1: - samples = xmin * jnp.exp(q_samples * jnp.log(xmax / xmin)) -else: - samples = ( - xmin ** (1.0 + alpha) - + q_samples * (xmax ** (1.0 + alpha) - xmin ** (1.0 + alpha)) - ) ** (1.0 / (1.0 + alpha)) -samples[None] - -PowerLawPrior(xmin, xmax, alpha, ["x"]).sample(jax.random.PRNGKey(42), 100) - - -############## -if alpha == -1: - normalization = float(1.0 / jnp.log(xmax / xmin)) -else: - normalization = (1 + alpha) / (xmax ** (1 + alpha) - xmin ** (1 + alpha)) -variable = 1.5 -log_in_range = jnp.where( - (variable >= xmax) | (variable <= xmin), - jnp.zeros_like(variable) - jnp.inf, - jnp.zeros_like(variable), -) -log_p = alpha * jnp.log(variable) + jnp.log(normalization) -log_p + log_in_range - -PowerLawPrior(xmin, xmax, alpha, ["x"]).log_prob({"x": variable}) - - -# transform_func = lambda x: ( -# xmin ** (1.0 + alpha) + x * (xmax ** (1.0 + alpha) - xmin ** (1.0 + alpha)) -# ) ** (1.0 / (1.0 + alpha)) -# input_params = variable -# assert_rank(input_params, 0) -# output_params = transform_func(input_params) -# jacobian = jax.jacfwd(transform_func)(input_params) -# x[variable] = output_params -# return x, jnp.log(jacobian) - - -import numpy as np - -alpha = -2.0 -xmin = 1.0 -xmax = 20.0 -p = PowerLawPrior(xmin, xmax, alpha, ["x"]) -grid = np.linspace(xmin, xmax, 20) -transform = [] -log_prob = [] -for y in grid: - transform.append(p.transform({"x": y})["x"].item()) - log_prob.append(np.exp(p.log_prob({"x": y}).item())) -import matplotlib.pyplot as plt - -plt.plot(grid, transform) -plt.savefig("transform.png") -plt.close() -plt.plot(transform, log_prob) -plt.savefig("log_prob.png") -plt.close() From f7876e6403a2ce90db95bcbd11a3daa335730b0f Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:09:42 -0400 Subject: [PATCH 070/248] Updated prior.py to include UniformComponenChirpMassPrior --- src/jimgw/prior.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 9f52fdbb..c182ee16 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -367,6 +367,22 @@ def __init__( ) +@jaxtyped(typechecker=typechecker) +class UniformComponenChirpMassPrior(PowerLawPrior): + """ + A prior in the range [xmin, xmax) for chirp mass which assumes the + component mass to be uniform. + + p(\cal M) ~ \cal M + """ + + def __repr__(self): + return f"UniformComponentChirpMass(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" + + def __init__(self, xmin: float, xmax: float, parameter_names: list[str]): + super().__init__(xmin, xmax, 1.0, parameter_names) + + def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: if prior.composite: if isinstance(prior.base_prior, list): From 0b2c7cf296b152753b0f757dec7333f97dd668c9 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 21:16:29 -0400 Subject: [PATCH 071/248] Fix priors --- src/jimgw/prior.py | 26 ++++++++++++-------------- src/jimgw/transforms.py | 18 ------------------ 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index dc6a492b..b09e8cc7 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -12,7 +12,6 @@ ScaleTransform, OffsetTransform, ArcSineTransform, - ArcCosineTransform, ) @@ -30,7 +29,7 @@ class Prior(Distribution): composite: bool = False @property - def n_dim(self): + def n_dim(self) -> int: return len(self.parameter_names) def __init__(self, parameter_names: list[str]): @@ -270,12 +269,13 @@ def __init__(self, parameter_names: list[str]): self.parameter_names = parameter_names assert self.n_dim == 1, "SinePrior needs to be 1D distributions" super().__init__( - UniformPrior(-1.0, 1.0, [f"cos_{self.parameter_names[0]}"]), + CosinePrior([f"{self.parameter_names[0]}"]), [ - ArcCosineTransform( - ([f"cos_{self.parameter_names[0]}"], [self.parameter_names[0]]) + OffsetTransform( + (([f"{self.parameter_names[0]}"], [f"{self.parameter_names[0]}"])), + jnp.pi / 2, ) - ], + ] ) @@ -292,7 +292,7 @@ def __init__(self, parameter_names: list[str]): self.parameter_names = parameter_names assert self.n_dim == 1, "CosinePrior needs to be 1D distributions" super().__init__( - UniformPrior(-1.0, 1.0, f"sin_{self.parameter_names[0]}"), + UniformPrior(-1.0, 1.0, [f"sin_{self.parameter_names[0]}"]), [ ArcSineTransform( ([f"sin_{self.parameter_names[0]}"], [self.parameter_names[0]]) @@ -308,14 +308,12 @@ def __repr__(self): return f"UniformSphere(parameter_names={self.parameter_names})" def __init__(self, parameter_names: list[str], **kwargs): - assert ( - len(parameter_names) == 1 - ), "UniformSphere only takes the name of the vector" - parameter_names = parameter_names[0] + self.parameter_names = parameter_names + assert self.n_dim == 1, "UniformSpherePrior only takes the name of the vector" self.parameter_names = [ - f"{parameter_names}_mag", - f"{parameter_names}_theta", - f"{parameter_names}_phi", + f"{self.parameter_names[0]}_mag", + f"{self.parameter_names[0]}_theta", + f"{self.parameter_names[0]}_phi", ] super().__init__( [ diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 5022fc88..3ea71e9c 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -157,21 +157,3 @@ def __init__( super().__init__(name_mapping) self.transform_func = lambda x: jnp.arcsin(x) - -class ArcCosineTransform(UnivariateTransform): - """ - ArcCosine transformation - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): - super().__init__(name_mapping) - self.transform_func = lambda x: jnp.arccos(x) From d917f96da3b764980885fc811b7120b4a9183342 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 26 Jul 2024 21:17:57 -0400 Subject: [PATCH 072/248] Add prior tests --- test/test_prior.py | 69 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/test/test_prior.py b/test/test_prior.py index 6431d7d0..48902b3b 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -1,23 +1,66 @@ from jimgw.prior import * +import scipy.stats as stats class TestUnivariatePrior: def test_logistic(self): - p = LogitTransform() - - def test_uniform(self): - p = UniformPrior(0.0, 10.0, ["x"]) - samples = p._dist.base_prior.sample(jax.random.PRNGKey(0), 10000) + p = LogisticDistribution(["x"]) + # Check that the log_prob is finite + samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) - assert jnp.allclose(log_prob, -jnp.log(10.0)) + assert jnp.all(jnp.isfinite(log_prob)) + # Cross-check log_prob with scipy.stats.logistic + x = jnp.linspace(-10.0, 10.0, 1000) + assert jnp.allclose(jax.vmap(p.log_prob)(p.add_name(x[None])), stats.logistic.logpdf(x)) + def test_standard_normal(self): + p = StandardNormalDistribution(["x"]) + # Check that the log_prob is finite + samples = p.sample(jax.random.PRNGKey(0), 10000) + log_prob = jax.vmap(p.log_prob)(samples) + assert jnp.all(jnp.isfinite(log_prob)) + # Cross-check log_prob with scipy.stats.norm + x = jnp.linspace(-10.0, 10.0, 1000) + assert jnp.allclose(jax.vmap(p.log_prob)(p.add_name(x[None])), stats.norm.logpdf(x)) -class TestPriorOperations: - def test_combine(self): - raise NotImplementedError + def test_uniform(self): + xmin, xmax = -10.0, 10.0 + p = UniformPrior(xmin, xmax, ["x"]) + # Check that the log_prob is correct in the support + samples = trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000) + log_prob = jax.vmap(p.log_prob)(samples) + assert jnp.allclose(log_prob, jnp.log(1.0 / (xmax - xmin))) - def test_sequence(self): - raise NotImplementedError + def test_sine(self): + p = SinePrior(["x"]) + # Check that the log_prob is finite + samples = trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000) + log_prob = jax.vmap(p.log_prob)(samples) + assert jnp.all(jnp.isfinite(log_prob)) + # Check that the log_prob is correct in the support + x = trace_prior_parent(p, [])[0].add_name(jnp.linspace(-10.0, 10.0, 1000)[None]) + y = jax.vmap(p.base_prior.base_prior.transform)(x) + y = jax.vmap(p.base_prior.transform)(y) + y = jax.vmap(p.transform)(y) + assert jnp.allclose(jax.vmap(p.log_prob)(x), jnp.log(jnp.sin(y['x'])/2.0)) + + def test_cosine(self): + p = CosinePrior(["x"]) + # Check that the log_prob is finite + samples = trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000) + log_prob = jax.vmap(p.log_prob)(samples) + assert jnp.all(jnp.isfinite(log_prob)) + # Check that the log_prob is correct in the support + x = trace_prior_parent(p, [])[0].add_name(jnp.linspace(-10.0, 10.0, 1000)[None]) + y = jax.vmap(p.base_prior.transform)(x) + y = jax.vmap(p.transform)(y) + assert jnp.allclose(jax.vmap(p.log_prob)(x), jnp.log(jnp.cos(y['x'])/2.0)) - def test_factor(self): - raise NotImplementedError + def test_uniform_sphere(self): + p = UniformSpherePrior(["x"]) + # Check that the log_prob is finite + samples = {} + for i in range(3): + samples.update(trace_prior_parent(p)[i].sample(jax.random.PRNGKey(0), 10000)) + log_prob = jax.vmap(p.log_prob)(samples) + assert jnp.all(jnp.isfinite(log_prob)) From 142fe5446db1044a3c6385060a963afbfa65cef3 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 12:19:25 -0400 Subject: [PATCH 073/248] Set constraint on powerlaw prior input --- src/jimgw/prior.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index c1baca3c..b349d074 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -348,6 +348,8 @@ def __init__( self.xmax = xmax self.xmin = xmin self.alpha = alpha + assert self.xmin < self.xmax, "xmin must be less than xmax" + assert self.xmin > 0.0, "x must be positive" if self.alpha == -1.0: transform = ParetoTransform( (self.parameter_names, self.parameter_names), xmin, xmax From fa6a6e25aa472e0d19233aca66c8daca9e60214e Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 12:20:23 -0400 Subject: [PATCH 074/248] Added test_power_law --- test/test_prior.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/test_prior.py b/test/test_prior.py index 48902b3b..0f9e4566 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -64,3 +64,33 @@ def test_uniform_sphere(self): samples.update(trace_prior_parent(p)[i].sample(jax.random.PRNGKey(0), 10000)) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.all(jnp.isfinite(log_prob)) + + def test_power_law(self): + from bilby.core.prior.analytical import PowerLaw + def func(alpha): + xmin = 1.0 + xmax = 20.0 + p = PowerLawPrior(xmin, xmax, alpha, ["x"]) + # Check that all the samples are finite + powerlaw_samples = p.sample(jax.random.PRNGKey(0), 10000) + assert jnp.all(jnp.isfinite(powerlaw_samples['x'])) + + # Check that all the log_probs are finite + samples = trace_prior_parent(p)[0].sample(jax.random.PRNGKey(0), 10000)['x'] + base_log_p = jax.vmap(p.log_prob, [0])({'x':samples}) + assert jnp.all(jnp.isfinite(log_p)) + + # Check that the log_prob is correct in the support + samples = jnp.linspace(xmin, xmax, 1000) + transformed_samples = jax.vmap(p.transform)({'x': samples})['x'] + assert jnp.allclose(jax.vmap(p.log_prob)({'x':samples}), PowerLaw(alpha, xmin, xmax).ln_prob(transformed_samples)) + + # Test Pareto Transform + func(-1.0) + # Test other values of alpha + positive_alpha = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] + for alpha_val in positive_alpha: + func(alpha_val) + negative_alpha = [-0.5, -1.5, -2.0, -2.5, -3.0, -3.5, -4.0, -4.5, -5.0] + for alpha_val in negative_alpha: + func(alpha_val) From a8c06737056cf32ef7347f38af3ceceeabc3818d Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 12:22:25 -0400 Subject: [PATCH 075/248] Updated ParetoTransform to avoid divide by zero --- src/jimgw/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 4c1380cf..48e29a06 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -207,5 +207,5 @@ def __init__( self.xmin = xmin self.xmax = xmax self.transform_func = lambda x: self.xmin * jnp.exp( - x * jnp.log(self.xmax / self.xmin) + x * jnp.log(self.xmax) - x * jnp.log(self.xmin) ) From 6ce5b369ee8cb7eead770412341dda61141b2c3b Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 12:29:19 -0400 Subject: [PATCH 076/248] Revert update on ParetoTransform --- src/jimgw/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 48e29a06..4c1380cf 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -207,5 +207,5 @@ def __init__( self.xmin = xmin self.xmax = xmax self.transform_func = lambda x: self.xmin * jnp.exp( - x * jnp.log(self.xmax) - x * jnp.log(self.xmin) + x * jnp.log(self.xmax / self.xmin) ) From e9511b9aa64e5dd7851760b49f9dbf0bdfe87061 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 12:29:49 -0400 Subject: [PATCH 077/248] Updated test_prior.py --- test/test_prior.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_prior.py b/test/test_prior.py index 0f9e4566..83392751 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -78,7 +78,7 @@ def func(alpha): # Check that all the log_probs are finite samples = trace_prior_parent(p)[0].sample(jax.random.PRNGKey(0), 10000)['x'] base_log_p = jax.vmap(p.log_prob, [0])({'x':samples}) - assert jnp.all(jnp.isfinite(log_p)) + assert jnp.all(jnp.isfinite(base_log_p)) # Check that the log_prob is correct in the support samples = jnp.linspace(xmin, xmax, 1000) From 54082eaa7707c115e90ee842f5d791a12aa80bfc Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:00:06 -0400 Subject: [PATCH 078/248] Updated test_prior.py --- test/test_prior.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/test_prior.py b/test/test_prior.py index 83392751..cee29934 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -68,8 +68,8 @@ def test_uniform_sphere(self): def test_power_law(self): from bilby.core.prior.analytical import PowerLaw def func(alpha): - xmin = 1.0 - xmax = 20.0 + xmin = 0.05 + xmax = 10.0 p = PowerLawPrior(xmin, xmax, alpha, ["x"]) # Check that all the samples are finite powerlaw_samples = p.sample(jax.random.PRNGKey(0), 10000) @@ -81,10 +81,15 @@ def func(alpha): assert jnp.all(jnp.isfinite(base_log_p)) # Check that the log_prob is correct in the support - samples = jnp.linspace(xmin, xmax, 1000) + samples = jnp.linspace(-10.0, 10.0, 1000) transformed_samples = jax.vmap(p.transform)({'x': samples})['x'] - assert jnp.allclose(jax.vmap(p.log_prob)({'x':samples}), PowerLaw(alpha, xmin, xmax).ln_prob(transformed_samples)) - + # cut off the samples that are outside the support + samples = samples[transformed_samples >= xmin] + transformed_samples = transformed_samples[transformed_samples >= xmin] + samples = samples[transformed_samples <= xmax] + transformed_samples = transformed_samples[transformed_samples <= xmax] + assert jnp.allclose(jax.vmap(p.log_prob)({'x':samples}), PowerLaw(alpha, xmin, xmax).ln_prob(transformed_samples), atol=1e-4) + # Test Pareto Transform func(-1.0) # Test other values of alpha @@ -93,4 +98,4 @@ def func(alpha): func(alpha_val) negative_alpha = [-0.5, -1.5, -2.0, -2.5, -3.0, -3.5, -4.0, -4.5, -5.0] for alpha_val in negative_alpha: - func(alpha_val) + func(alpha_val) \ No newline at end of file From 9ad64a1ca0bf0a2c0ea945b5cae3f02c38c07199 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:05:02 -0400 Subject: [PATCH 079/248] Updated test_prior.py --- test/test_prior.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/test_prior.py b/test/test_prior.py index cee29934..c0106fa9 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -5,6 +5,9 @@ class TestUnivariatePrior: def test_logistic(self): p = LogisticDistribution(["x"]) + # Check that all the samples are finite + samples = p.sample(jax.random.PRNGKey(0), 10000) + assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is finite samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) @@ -15,6 +18,9 @@ def test_logistic(self): def test_standard_normal(self): p = StandardNormalDistribution(["x"]) + # Check that all the samples are finite + samples = p.sample(jax.random.PRNGKey(0), 10000) + assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is finite samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) @@ -26,6 +32,9 @@ def test_standard_normal(self): def test_uniform(self): xmin, xmax = -10.0, 10.0 p = UniformPrior(xmin, xmax, ["x"]) + # Check that all the samples are finite + samples = p.sample(jax.random.PRNGKey(0), 10000) + assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is correct in the support samples = trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) @@ -33,6 +42,9 @@ def test_uniform(self): def test_sine(self): p = SinePrior(["x"]) + # Check that all the samples are finite + samples = p.sample(jax.random.PRNGKey(0), 10000) + assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is finite samples = trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) @@ -46,6 +58,9 @@ def test_sine(self): def test_cosine(self): p = CosinePrior(["x"]) + # Check that all the samples are finite + samples = p.sample(jax.random.PRNGKey(0), 10000) + assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is finite samples = trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) @@ -58,6 +73,10 @@ def test_cosine(self): def test_uniform_sphere(self): p = UniformSpherePrior(["x"]) + # Check that all the samples are finite + samples = p.sample(jax.random.PRNGKey(0), 10000) + assert jnp.all(jnp.isfinite(samples['x'])) + # Check that the log_prob is finite samples = {} for i in range(3): From ce437c417d5d9279f0fed75dce44c2f179edca62 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:12:32 -0400 Subject: [PATCH 080/248] Updated test_prior.py --- test/test_prior.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_prior.py b/test/test_prior.py index c0106fa9..7cd4d8f8 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -75,8 +75,9 @@ def test_uniform_sphere(self): p = UniformSpherePrior(["x"]) # Check that all the samples are finite samples = p.sample(jax.random.PRNGKey(0), 10000) - assert jnp.all(jnp.isfinite(samples['x'])) - + assert jnp.all(jnp.isfinite(samples['x_mag'])) + assert jnp.all(jnp.isfinite(samples['x_theta'])) + assert jnp.all(jnp.isfinite(samples['x_phi'])) # Check that the log_prob is finite samples = {} for i in range(3): From 18d5184837c2adc9e2db7df0c9d5b071ac406cf6 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:17:26 -0400 Subject: [PATCH 081/248] Updated test_prior.py --- test/test_prior.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_prior.py b/test/test_prior.py index 7cd4d8f8..cdfdbae0 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -96,7 +96,7 @@ def func(alpha): assert jnp.all(jnp.isfinite(powerlaw_samples['x'])) # Check that all the log_probs are finite - samples = trace_prior_parent(p)[0].sample(jax.random.PRNGKey(0), 10000)['x'] + samples = (trace_prior_parent(p)[0].sample(jax.random.PRNGKey(0), 10000))['x'] base_log_p = jax.vmap(p.log_prob, [0])({'x':samples}) assert jnp.all(jnp.isfinite(base_log_p)) From 7c05d14df30b4ba8894967da0f1334ba95419ce2 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:27:02 -0400 Subject: [PATCH 082/248] Updated test_prior.py --- test/test_prior.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_prior.py b/test/test_prior.py index cdfdbae0..11206877 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -81,7 +81,7 @@ def test_uniform_sphere(self): # Check that the log_prob is finite samples = {} for i in range(3): - samples.update(trace_prior_parent(p)[i].sample(jax.random.PRNGKey(0), 10000)) + samples.update(trace_prior_parent(p, [])[i].sample(jax.random.PRNGKey(0), 10000)) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.all(jnp.isfinite(log_prob)) @@ -96,7 +96,7 @@ def func(alpha): assert jnp.all(jnp.isfinite(powerlaw_samples['x'])) # Check that all the log_probs are finite - samples = (trace_prior_parent(p)[0].sample(jax.random.PRNGKey(0), 10000))['x'] + samples = (trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000))['x'] base_log_p = jax.vmap(p.log_prob, [0])({'x':samples}) assert jnp.all(jnp.isfinite(base_log_p)) From f647bf0155a2cb8b6a4cfe460cd71c348e344e31 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Mon, 29 Jul 2024 13:31:38 -0400 Subject: [PATCH 083/248] Remove unnecessary test --- test/test_prior.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/test_prior.py b/test/test_prior.py index 11206877..d9dbe27a 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -5,9 +5,6 @@ class TestUnivariatePrior: def test_logistic(self): p = LogisticDistribution(["x"]) - # Check that all the samples are finite - samples = p.sample(jax.random.PRNGKey(0), 10000) - assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is finite samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) @@ -18,9 +15,6 @@ def test_logistic(self): def test_standard_normal(self): p = StandardNormalDistribution(["x"]) - # Check that all the samples are finite - samples = p.sample(jax.random.PRNGKey(0), 10000) - assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is finite samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) @@ -118,4 +112,4 @@ def func(alpha): func(alpha_val) negative_alpha = [-0.5, -1.5, -2.0, -2.5, -3.0, -3.5, -4.0, -4.5, -5.0] for alpha_val in negative_alpha: - func(alpha_val) \ No newline at end of file + func(alpha_val) From 6171ce5d7d00935eedb1bebb0067768b1364ca57 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Mon, 29 Jul 2024 13:32:24 -0400 Subject: [PATCH 084/248] Reformat --- src/jimgw/prior.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index b349d074..2d22c368 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -278,7 +278,7 @@ def __init__(self, parameter_names: list[str]): (([f"{self.parameter_names[0]}"], [f"{self.parameter_names[0]}"])), jnp.pi / 2, ) - ] + ], ) From ab7447be5d5ddae1f11768248ef825975a673bda Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Mon, 29 Jul 2024 15:15:43 -0400 Subject: [PATCH 085/248] Change naming --- src/jimgw/prior.py | 142 +++++++++++++++------------------------------ 1 file changed, 46 insertions(+), 96 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 2d22c368..3697df2f 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -250,11 +250,25 @@ def __init__( super().__init__( LogisticDistribution(self.parameter_names), [ - LogitTransform((self.parameter_names, self.parameter_names)), + LogitTransform( + ( + [f"{self.parameter_names[0]}_base"], + [f"({self.parameter_names[0]}-({xmin}))/{(xmax-xmin)}"], + ) + ), ScaleTransform( - (self.parameter_names, self.parameter_names), xmax - xmin + ( + ( + [f"({self.parameter_names[0]}-({xmin}))/{(xmax-xmin)}"], + [f"{self.parameter_names[0]}-({xmin})"], + ), + xmax - xmin, + ), + ), + OffsetTransform( + ([f"{self.parameter_names[0]}-({xmin})"], self.parameter_names), + xmin, ), - OffsetTransform((self.parameter_names, self.parameter_names), xmin), ], ) @@ -275,7 +289,12 @@ def __init__(self, parameter_names: list[str]): CosinePrior([f"{self.parameter_names[0]}"]), [ OffsetTransform( - (([f"{self.parameter_names[0]}"], [f"{self.parameter_names[0]}"])), + ( + ( + [f"{self.parameter_names[0]}-pi/2"], + [f"{self.parameter_names[0]}"], + ) + ), jnp.pi / 2, ) ], @@ -295,10 +314,10 @@ def __init__(self, parameter_names: list[str]): self.parameter_names = parameter_names assert self.n_dim == 1, "CosinePrior needs to be 1D distributions" super().__init__( - UniformPrior(-1.0, 1.0, [f"sin_{self.parameter_names[0]}"]), + UniformPrior(-1.0, 1.0, [f"sin({self.parameter_names[0]})"]), [ ArcSineTransform( - ([f"sin_{self.parameter_names[0]}"], [self.parameter_names[0]]) + ([f"sin({self.parameter_names[0]})"], [self.parameter_names[0]]) ) ], ) @@ -308,7 +327,7 @@ def __init__(self, parameter_names: list[str]): class UniformSpherePrior(CombinePrior): def __repr__(self): - return f"UniformSphere(parameter_names={self.parameter_names})" + return f"UniformSpherePrior(parameter_names={self.parameter_names})" def __init__(self, parameter_names: list[str], **kwargs): self.parameter_names = parameter_names @@ -334,7 +353,7 @@ class PowerLawPrior(SequentialTransformPrior): alpha: float def __repr__(self): - return f"PowerLaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.parameter_names})" + return f"PowerLawPrior(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.parameter_names})" def __init__( self, @@ -352,35 +371,45 @@ def __init__( assert self.xmin > 0.0, "x must be positive" if self.alpha == -1.0: transform = ParetoTransform( - (self.parameter_names, self.parameter_names), xmin, xmax + ([f"{self.parameter_names[0]}_before_transform"], self.parameter_names), + xmin, + xmax, ) else: transform = PowerLawTransform( - (self.parameter_names, self.parameter_names), xmin, xmax, alpha + ([f"{self.parameter_names[0]}_before_transform"], self.parameter_names), + xmin, + xmax, + alpha, ) super().__init__( LogisticDistribution(self.parameter_names), [ - LogitTransform((self.parameter_names, self.parameter_names)), + LogitTransform( + ( + [f"{self.parameter_names[0]}_base"], + [f"{self.parameter_names[0]}_before_transform"], + ) + ), transform, ], ) @jaxtyped(typechecker=typechecker) -class UniformComponenChirpMassPrior(PowerLawPrior): +class UniformInComponentsChirpMassPrior(PowerLawPrior): """ A prior in the range [xmin, xmax) for chirp mass which assumes the - component mass to be uniform. + component masses to be uniformly distributed. - p(\cal M) ~ \cal M + p(M_c) ~ M_c """ def __repr__(self): - return f"UniformComponentChirpMass(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" + return f"UniformInComponentsChirpMassPrior(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" - def __init__(self, xmin: float, xmax: float, parameter_names: list[str]): - super().__init__(xmin, xmax, 1.0, parameter_names) + def __init__(self, xmin: float, xmax: float): + super().__init__(xmin, xmax, 1.0, ["M_c"]) def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: @@ -588,85 +617,6 @@ def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: # return output + jnp.log(jnp.sin(zenith)) -# @jaxtyped(typechecker=typechecker) -# class PowerLaw(Prior): -# """ -# A prior following the power-law with alpha in the range [xmin, xmax). -# p(x) ~ x^{\alpha} -# """ - -# xmin: float = 0.0 -# xmax: float = 1.0 -# alpha: float = 0.0 -# normalization: float = 1.0 - -# def __repr__(self): -# return f"Powerlaw(xmin={self.xmin}, xmax={self.xmax}, alpha={self.alpha}, naming={self.naming})" - -# def __init__( -# self, -# xmin: float, -# xmax: float, -# alpha: Union[Int, float], -# naming: list[str], -# transforms: dict[str, tuple[str, Callable]] = {}, -# **kwargs, -# ): -# super().__init__(naming, transforms) -# if alpha < 0.0: -# assert xmin > 0.0, "With negative alpha, xmin must > 0" -# assert self.n_dim == 1, "Powerlaw needs to be 1D distributions" -# self.xmax = xmax -# self.xmin = xmin -# self.alpha = alpha -# if alpha == -1: -# self.normalization = float(1.0 / jnp.log(self.xmax / self.xmin)) -# else: -# self.normalization = (1 + self.alpha) / ( -# self.xmax ** (1 + self.alpha) - self.xmin ** (1 + self.alpha) -# ) - -# def sample( -# self, rng_key: PRNGKeyArray, n_samples: int -# ) -> dict[str, Float[Array, " n_samples"]]: -# """ -# Sample from a power-law distribution. - -# Parameters -# ---------- -# rng_key : PRNGKeyArray -# A random key to use for sampling. -# n_samples : int -# The number of samples to draw. - -# Returns -# ------- -# samples : dict -# Samples from the distribution. The keys are the names of the parameters. - -# """ -# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) -# if self.alpha == -1: -# samples = self.xmin * jnp.exp(q_samples * jnp.log(self.xmax / self.xmin)) -# else: -# samples = ( -# self.xmin ** (1.0 + self.alpha) -# + q_samples -# * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) -# ) ** (1.0 / (1.0 + self.alpha)) -# return self.add_name(samples[None]) - -# def log_prob(self, x: dict[str, Float]) -> Float: -# variable = x[self.naming[0]] -# log_in_range = jnp.where( -# (variable >= self.xmax) | (variable <= self.xmin), -# jnp.zeros_like(variable) - jnp.inf, -# jnp.zeros_like(variable), -# ) -# log_p = self.alpha * jnp.log(variable) + jnp.log(self.normalization) -# return log_p + log_in_range - - # @jaxtyped(typechecker=typechecker) # class Exponential(Prior): # """ From 1b670566f24efa86d2f824e21c4d4079533c856a Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Mon, 29 Jul 2024 16:25:02 -0400 Subject: [PATCH 086/248] Removed bilby --- test/test_prior.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/test_prior.py b/test/test_prior.py index d9dbe27a..22ae827e 100644 --- a/test/test_prior.py +++ b/test/test_prior.py @@ -80,7 +80,13 @@ def test_uniform_sphere(self): assert jnp.all(jnp.isfinite(log_prob)) def test_power_law(self): - from bilby.core.prior.analytical import PowerLaw + def powerlaw_log_pdf(x, alpha, xmin, xmax): + if alpha == -1.0: + normalization = 1./(jnp.log(xmax) - jnp.log(xmin)) + else: + normalization = (1.0 + alpha) / (xmax**(1.0 + alpha) - xmin**(1.0 + alpha)) + return jnp.log(normalization) + alpha * jnp.log(x) + def func(alpha): xmin = 0.05 xmax = 10.0 @@ -102,11 +108,13 @@ def func(alpha): transformed_samples = transformed_samples[transformed_samples >= xmin] samples = samples[transformed_samples <= xmax] transformed_samples = transformed_samples[transformed_samples <= xmax] - assert jnp.allclose(jax.vmap(p.log_prob)({'x':samples}), PowerLaw(alpha, xmin, xmax).ln_prob(transformed_samples), atol=1e-4) + # log pdf of powerlaw + assert jnp.allclose(jax.vmap(p.log_prob)({'x':samples}), powerlaw_log_pdf(transformed_samples, alpha, xmin, xmax), atol=1e-4) # Test Pareto Transform func(-1.0) # Test other values of alpha + print("Testing PowerLawPrior") positive_alpha = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] for alpha_val in positive_alpha: func(alpha_val) From 9d59c4ddd75cd2583795c5403aeae1895833305d Mon Sep 17 00:00:00 2001 From: kazewong Date: Mon, 29 Jul 2024 17:00:48 -0400 Subject: [PATCH 087/248] Move unit test to unit directory And create integration directory --- docs/tutorials/naming_system.md | 0 test/integration/test_GW150914.py | 0 test/{ => unit}/test_detector.py | 0 test/{ => unit}/test_prior.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/tutorials/naming_system.md create mode 100644 test/integration/test_GW150914.py rename test/{ => unit}/test_detector.py (100%) rename test/{ => unit}/test_prior.py (100%) diff --git a/docs/tutorials/naming_system.md b/docs/tutorials/naming_system.md new file mode 100644 index 00000000..e69de29b diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test_detector.py b/test/unit/test_detector.py similarity index 100% rename from test/test_detector.py rename to test/unit/test_detector.py diff --git a/test/test_prior.py b/test/unit/test_prior.py similarity index 100% rename from test/test_prior.py rename to test/unit/test_prior.py From b3110dfd33d6df85cd69e1fa4b2face55979076c Mon Sep 17 00:00:00 2001 From: kazewong Date: Mon, 29 Jul 2024 17:27:17 -0400 Subject: [PATCH 088/248] Update priors naming issue add test_GW150914.py --- .github/workflows/python-package.yml | 6 +- src/jimgw/prior.py | 12 ++- test/integration/test_GW150914.py | 116 +++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 12 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 4806efed..1b45e23c 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -3,11 +3,7 @@ name: Python package -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] +on: push, pull_request jobs: build: diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 3697df2f..f224b119 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -248,7 +248,7 @@ def __init__( self.xmax = xmax self.xmin = xmin super().__init__( - LogisticDistribution(self.parameter_names), + LogisticDistribution([f"{self.parameter_names[0]}_base"]), [ LogitTransform( ( @@ -258,12 +258,10 @@ def __init__( ), ScaleTransform( ( - ( - [f"({self.parameter_names[0]}-({xmin}))/{(xmax-xmin)}"], - [f"{self.parameter_names[0]}-({xmin})"], - ), - xmax - xmin, + [f"({self.parameter_names[0]}-({xmin}))/{(xmax-xmin)}"], + [f"{self.parameter_names[0]}-({xmin})"], ), + xmax - xmin, ), OffsetTransform( ([f"{self.parameter_names[0]}-({xmin})"], self.parameter_names), @@ -383,7 +381,7 @@ def __init__( alpha, ) super().__init__( - LogisticDistribution(self.parameter_names), + LogisticDistribution([f"{self.parameter_names[0]}_base"]), [ LogitTransform( ( diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index e69de29b..816647fa 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -0,0 +1,116 @@ +import time + +import jax +import jax.numpy as jnp + +from jimgw.jim import Jim +from jimgw.prior import CombinePrior, UniformPrior +from jimgw.single_event.detector import H1, L1 +from jimgw.single_event.likelihood import TransientLikelihoodFD +from jimgw.single_event.waveform import RippleIMRPhenomD +from flowMC.strategy.optimization import optimization_Adam + +jax.config.update("jax_enable_x64", True) + +########################################### +########## First we grab data ############# +########################################### + +total_time_start = time.time() + +# first, fetch a 4s segment centered on GW150914 +gps = 1126259462.4 +duration = 4 +post_trigger_duration = 2 +start_pad = duration - post_trigger_duration +end_pad = post_trigger_duration +fmin = 20.0 +fmax = 1024.0 + +ifos = ["H1", "L1"] + +H1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) +L1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) + +Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) +q_prior = UniformPrior( + 0.125, + 1.0, + parameter_names=["q"], +) +s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) +s2z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) +dL_prior = UniformPrior(0.0, 2000.0, parameter_names=["d_L"]) +t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) +phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) +cos_iota_prior = UniformPrior( + -1.0, + 1.0, + parameter_names=["cos_iota"], +) +psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) +ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) +sin_dec_prior = UniformPrior( + -1.0, + 1.0, + parameter_names=["sin_dec"], +) + +prior = CombinePrior( + [ + Mc_prior, + q_prior, + s1z_prior, + s2z_prior, + dL_prior, + t_c_prior, + phase_c_prior, + cos_iota_prior, + psi_prior, + ra_prior, + sin_dec_prior, + ] +) +likelihood = TransientLikelihoodFD( + [H1, L1], + waveform=RippleIMRPhenomD(), + trigger_time=gps, + duration=4, + post_trigger_duration=2, +) + + +mass_matrix = jnp.eye(11) +mass_matrix = mass_matrix.at[1, 1].set(1e-3) +mass_matrix = mass_matrix.at[5, 5].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 3e-3} + +Adam_optimizer = optimization_Adam(n_steps=5, learning_rate=0.01, noise_level=1) + +n_epochs = 2 +n_loop_training = 1 +learning_rate = 1e-4 + + +jim = Jim( + likelihood, + prior, + n_loop_training=n_loop_training, + n_loop_production=1, + n_local_steps=5, + n_global_steps=5, + n_chains=4, + n_epochs=n_epochs, + learning_rate=learning_rate, + n_max_examples=30, + n_flow_samples=100, + momentum=0.9, + batch_size=100, + use_global=True, + train_thinning=1, + output_thinning=1, + local_sampler_arg=local_sampler_arg, + strategies=[Adam_optimizer, "default"], +) + +jim.sample(jax.random.PRNGKey(42)) From 024c5c3fa6bb9dd69ea5f2dd573046a6d849baac Mon Sep 17 00:00:00 2001 From: kazewong Date: Mon, 29 Jul 2024 17:53:00 -0400 Subject: [PATCH 089/248] base parameter names seem working --- src/jimgw/jim.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 08b82b05..1a694172 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -7,7 +7,7 @@ from jaxtyping import Array, Float, PRNGKeyArray from jimgw.base import LikelihoodBase -from jimgw.prior import Prior +from jimgw.prior import Prior, trace_prior_parent class Jim(object): @@ -26,11 +26,18 @@ def __init__( self, likelihood: LikelihoodBase, prior: Prior, - parameter_names: list[str], + parameter_names: list[str] | None = None, **kwargs, ): self.likelihood = likelihood self.prior = prior + if parameter_names is None: + print("No parameter names provided. Will try to trace the prior.") + parents = [] + trace_prior_parent(prior, parents) + parameter_names = [] + for parent in parents: + parameter_names.extend(parent.parameter_names) self.parameter_names = parameter_names seed = kwargs.get("seed", 0) From ac43d54ffb97089513b71e269fcf8bde954d7839 Mon Sep 17 00:00:00 2001 From: kazewong Date: Mon, 29 Jul 2024 17:55:44 -0400 Subject: [PATCH 090/248] fix cosine naming error --- src/jimgw/prior.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index f224b119..6a8f10ca 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -284,7 +284,7 @@ def __init__(self, parameter_names: list[str]): self.parameter_names = parameter_names assert self.n_dim == 1, "SinePrior needs to be 1D distributions" super().__init__( - CosinePrior([f"{self.parameter_names[0]}"]), + CosinePrior([f"{self.parameter_names[0]}-pi/2"]), [ OffsetTransform( ( From 151c37df64d147bf5b7e5ddfffa9dd29f0e6d221 Mon Sep 17 00:00:00 2001 From: kazewong Date: Mon, 29 Jul 2024 17:59:20 -0400 Subject: [PATCH 091/248] prior test now all pass --- .github/workflows/python-package.yml | 2 +- test/unit/test_prior.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1b45e23c..d70c2ae1 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -3,7 +3,7 @@ name: Python package -on: push, pull_request +on: [push, pull_request] jobs: build: diff --git a/test/unit/test_prior.py b/test/unit/test_prior.py index 22ae827e..20d71de4 100644 --- a/test/unit/test_prior.py +++ b/test/unit/test_prior.py @@ -96,20 +96,20 @@ def func(alpha): assert jnp.all(jnp.isfinite(powerlaw_samples['x'])) # Check that all the log_probs are finite - samples = (trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000))['x'] - base_log_p = jax.vmap(p.log_prob, [0])({'x':samples}) + samples = (trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000))['x_base'] + base_log_p = jax.vmap(p.log_prob, [0])({'x_base':samples}) assert jnp.all(jnp.isfinite(base_log_p)) # Check that the log_prob is correct in the support samples = jnp.linspace(-10.0, 10.0, 1000) - transformed_samples = jax.vmap(p.transform)({'x': samples})['x'] + transformed_samples = jax.vmap(p.transform)({'x_base': samples})['x'] # cut off the samples that are outside the support samples = samples[transformed_samples >= xmin] transformed_samples = transformed_samples[transformed_samples >= xmin] samples = samples[transformed_samples <= xmax] transformed_samples = transformed_samples[transformed_samples <= xmax] # log pdf of powerlaw - assert jnp.allclose(jax.vmap(p.log_prob)({'x':samples}), powerlaw_log_pdf(transformed_samples, alpha, xmin, xmax), atol=1e-4) + assert jnp.allclose(jax.vmap(p.log_prob)({'x_base':samples}), powerlaw_log_pdf(transformed_samples, alpha, xmin, xmax), atol=1e-4) # Test Pareto Transform func(-1.0) From 576ce7cd14cda25522c4dda9cfce906b2f4570f1 Mon Sep 17 00:00:00 2001 From: kazewong Date: Mon, 29 Jul 2024 18:20:29 -0400 Subject: [PATCH 092/248] Fix likelihood sampling issue due to parameter name transformation --- test/integration/test_GW150914.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index 816647fa..2650cd1c 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -36,24 +36,25 @@ q_prior = UniformPrior( 0.125, 1.0, - parameter_names=["q"], + parameter_names=["q"], # Need name transformation in likelihood to work ) s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) s2z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) +# Current likelihood sampling will fail and give nan because of large number dL_prior = UniformPrior(0.0, 2000.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) cos_iota_prior = UniformPrior( -1.0, 1.0, - parameter_names=["cos_iota"], + parameter_names=["cos_iota"], # Need name transformation in likelihood to work ) psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) sin_dec_prior = UniformPrior( -1.0, 1.0, - parameter_names=["sin_dec"], + parameter_names=["sin_dec"], # Need name transformation in likelihood to work ) prior = CombinePrior( From 7625dbe04eab2c436d2ae27c91d2ee3148c98b0d Mon Sep 17 00:00:00 2001 From: kazewong Date: Tue, 30 Jul 2024 14:51:56 -0400 Subject: [PATCH 093/248] Instead of defining univariate and multivariate, favor bijective versus nonbijective --- src/jimgw/transforms.py | 87 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 9 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 4c1380cf..902b5b30 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -10,12 +10,10 @@ class Transform(ABC): """ Base class for transform. - - The idea of transform should be used on distribtuion, + The purpose of this class is purely for keeping name """ name_mapping: tuple[list[str], list[str]] - transform_func: Callable[[dict[str, Float]], dict[str, Float]] def __init__( self, @@ -23,6 +21,17 @@ def __init__( ): self.name_mapping = name_mapping + def propagate_name(self, x: list[str]) -> list[str]: + input_set = set(x) + from_set = set(self.name_mapping[0]) + to_set = set(self.name_mapping[1]) + return list(input_set - from_set | to_set) + +class BijectiveTransform(Transform): + + transform_func: Callable[[dict[str, Float]], dict[str, Float]] + inverse_transform_func: Callable[[dict[str, Float]], dict[str, Float]] + def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: return self.transform(x) @@ -62,13 +71,64 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: The transformed dictionary. """ raise NotImplementedError + + @abstractmethod + def inverse(self, y: dict[str, Float]) -> dict[str, Float]: + """ + Inverse transform the input y to original coordinate x. - def propagate_name(self, x: list[str]) -> list[str]: - input_set = set(x) - from_set = set(self.name_mapping[0]) - to_set = set(self.name_mapping[1]) - return list(input_set - from_set | to_set) + Parameters + ---------- + y : dict[str, Float] + The transformed dictionary. + + Returns + ------- + x : dict[str, Float] + The original dictionary. + """ + raise NotImplementedError + + @abstractmethod + def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: + """ + Pull back the input y to original coordinate x and return the log Jacobian determinant. + + Parameters + ---------- + y : dict[str, Float] + The transformed dictionary. + + Returns + ------- + x : dict[str, Float] + The original dictionary. + log_det : Float + The log Jacobian determinant. + """ + raise NotImplementedError + +class NonBijectiveTransform(Transform): + + def __call__(self, x: dict[str, Float]) -> dict[str, Float]: + return self.forward(x) + + @abstractmethod + def forward(self, x: dict[str, Float]) -> dict[str, Float]: + """ + Push forward the input x to transformed coordinate y. + Parameters + ---------- + x : dict[str, Float] + The input dictionary. + + Returns + ------- + y : dict[str, Float] + The transformed dictionary. + """ + raise NotImplementedError class UnivariateTransform(Transform): @@ -94,7 +154,7 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: return x -class ScaleTransform(UnivariateTransform): +class ScaleTransform(BijectiveTransform): scale: Float def __init__( @@ -105,6 +165,15 @@ def __init__( super().__init__(name_mapping) self.scale = scale self.transform_func = lambda x: x * self.scale + self.inverse_transform_func = lambda x: x / self.scale + + def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: + input_params = x.pop(self.name_mapping[0][0]) + assert_rank(input_params, 0) + output_params = self.transform_func(input_params) + jacobian = jnp.log(jnp.abs(self.scale)) + x[self.name_mapping[1][0]] = output_params + return x, jacobian class OffsetTransform(UnivariateTransform): From 9ac722f57dbfed388bd0ab2aa2244f9463a5cc85 Mon Sep 17 00:00:00 2001 From: kazewong Date: Tue, 30 Jul 2024 16:29:54 -0400 Subject: [PATCH 094/248] Adding some transform, should be working --- src/jimgw/transforms.py | 188 ++++++++++++++++++---------------------- 1 file changed, 84 insertions(+), 104 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 902b5b30..904fe225 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -4,7 +4,7 @@ import jax import jax.numpy as jnp from chex import assert_rank -from jaxtyping import Float +from jaxtyping import Float, Array class Transform(ABC): @@ -29,13 +29,12 @@ def propagate_name(self, x: list[str]) -> list[str]: class BijectiveTransform(Transform): - transform_func: Callable[[dict[str, Float]], dict[str, Float]] - inverse_transform_func: Callable[[dict[str, Float]], dict[str, Float]] + transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " n_dim"]] + inverse_transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " n_dim"]] def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: return self.transform(x) - @abstractmethod def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ Transform the input x to transformed coordinate y and return the log Jacobian determinant. @@ -53,9 +52,12 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: log_det : Float The log Jacobian determinant. """ - raise NotImplementedError + input_params = jax.tree.map(lambda key: x.pop(key), self.name_mapping[0]) + output_params = self.transform_func(input_params) + jacobian = jnp.array(jax.jacfwd(self.transform_func)(input_params)) + jax.tree.map(lambda key, value: x.update({key: value}), self.name_mapping[1], output_params) + return x, jnp.log(jnp.linalg.det(jacobian)) - @abstractmethod def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ Push forward the input x to transformed coordinate y. @@ -70,9 +72,11 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: y : dict[str, Float] The transformed dictionary. """ - raise NotImplementedError + input_params = jax.tree.map(lambda key: x.pop(key), self.name_mapping[0]) + output_params = self.transform_func(input_params) + jax.tree.map(lambda key, value: x.update({key: value}), self.name_mapping[1], output_params) + return x - @abstractmethod def inverse(self, y: dict[str, Float]) -> dict[str, Float]: """ Inverse transform the input y to original coordinate x. @@ -87,9 +91,12 @@ def inverse(self, y: dict[str, Float]) -> dict[str, Float]: x : dict[str, Float] The original dictionary. """ - raise NotImplementedError + output_params = jax.tree.map(lambda key: y.pop(key), self.name_mapping[1]) + input_params = self.inverse_transform_func(output_params) + jacobian = jnp.array(jax.jacfwd(self.inverse_transform_func)(output_params)) + jax.tree.map(lambda key, value: y.update({key: value}), self.name_mapping[0], input_params) + return y, jnp.log(jnp.linalg.det(jacobian)) - @abstractmethod def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ Pull back the input y to original coordinate x and return the log Jacobian determinant. @@ -106,7 +113,10 @@ def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: log_det : Float The log Jacobian determinant. """ - raise NotImplementedError + output_params = jax.tree.map(lambda key: y.pop(key), self.name_mapping[1]) + input_params = self.inverse_transform_func(output_params) + jax.tree.map(lambda key, value: y.update({key: value}), self.name_mapping[0], input_params) + return y class NonBijectiveTransform(Transform): @@ -130,30 +140,6 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ raise NotImplementedError -class UnivariateTransform(Transform): - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): - super().__init__(name_mapping) - - def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: - input_params = x.pop(self.name_mapping[0][0]) - assert_rank(input_params, 0) - output_params = self.transform_func(input_params) - jacobian = jax.jacfwd(self.transform_func)(input_params) - x[self.name_mapping[1][0]] = output_params - return x, jnp.log(jacobian) - - def forward(self, x: dict[str, Float]) -> dict[str, Float]: - input_params = x.pop(self.name_mapping[0][0]) - assert_rank(input_params, 0) - output_params = self.transform_func(input_params) - x[self.name_mapping[1][0]] = output_params - return x - - class ScaleTransform(BijectiveTransform): scale: Float @@ -164,19 +150,10 @@ def __init__( ): super().__init__(name_mapping) self.scale = scale - self.transform_func = lambda x: x * self.scale - self.inverse_transform_func = lambda x: x / self.scale - - def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: - input_params = x.pop(self.name_mapping[0][0]) - assert_rank(input_params, 0) - output_params = self.transform_func(input_params) - jacobian = jnp.log(jnp.abs(self.scale)) - x[self.name_mapping[1][0]] = output_params - return x, jacobian - + self.transform_func = lambda x: [x[0] * self.scale] + self.inverse_transform_func = lambda x: [x[0] / self.scale] -class OffsetTransform(UnivariateTransform): +class OffsetTransform(BijectiveTransform): offset: Float def __init__( @@ -186,10 +163,11 @@ def __init__( ): super().__init__(name_mapping) self.offset = offset - self.transform_func = lambda x: x + self.offset + self.transform_func = lambda x: [x[0] + self.offset] + self.inverse_transform_func = lambda x: [x[0] - self.offset] -class LogitTransform(UnivariateTransform): +class LogitTransform(BijectiveTransform): """ Logit transform following @@ -205,10 +183,11 @@ def __init__( name_mapping: tuple[list[str], list[str]], ): super().__init__(name_mapping) - self.transform_func = lambda x: 1 / (1 + jnp.exp(-x)) + self.transform_func = lambda x: [1 / (1 + jnp.exp(-x[0]))] + self.inverse_transform_func = lambda x: [jnp.log(x[0] / (1 - x[0]))] -class ArcSineTransform(UnivariateTransform): +class ArcSineTransform(BijectiveTransform): """ ArcSine transformation @@ -225,56 +204,57 @@ def __init__( ): super().__init__(name_mapping) self.transform_func = lambda x: jnp.arcsin(x) - - -class PowerLawTransform(UnivariateTransform): - """ - PowerLaw transformation - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - """ - - xmin: Float - xmax: Float - alpha: Float - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - xmin: Float, - xmax: Float, - alpha: Float, - ): - super().__init__(name_mapping) - self.xmin = xmin - self.xmax = xmax - self.alpha = alpha - self.transform_func = lambda x: ( - self.xmin ** (1.0 + self.alpha) - + x * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - ) ** (1.0 / (1.0 + self.alpha)) - - -class ParetoTransform(UnivariateTransform): - """ - Pareto transformation: Power law when alpha = -1 - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - xmin: Float, - xmax: Float, - ): - super().__init__(name_mapping) - self.xmin = xmin - self.xmax = xmax - self.transform_func = lambda x: self.xmin * jnp.exp( - x * jnp.log(self.xmax / self.xmin) - ) + self.inverse_transform_func = lambda x: jnp.sin(x) + + +# class PowerLawTransform(UnivariateTransform): +# """ +# PowerLaw transformation +# Parameters +# ---------- +# name_mapping : tuple[list[str], list[str]] +# The name mapping between the input and output dictionary. +# """ + +# xmin: Float +# xmax: Float +# alpha: Float + +# def __init__( +# self, +# name_mapping: tuple[list[str], list[str]], +# xmin: Float, +# xmax: Float, +# alpha: Float, +# ): +# super().__init__(name_mapping) +# self.xmin = xmin +# self.xmax = xmax +# self.alpha = alpha +# self.transform_func = lambda x: ( +# self.xmin ** (1.0 + self.alpha) +# + x * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) +# ) ** (1.0 / (1.0 + self.alpha)) + + +# class ParetoTransform(UnivariateTransform): +# """ +# Pareto transformation: Power law when alpha = -1 +# Parameters +# ---------- +# name_mapping : tuple[list[str], list[str]] +# The name mapping between the input and output dictionary. +# """ + +# def __init__( +# self, +# name_mapping: tuple[list[str], list[str]], +# xmin: Float, +# xmax: Float, +# ): +# super().__init__(name_mapping) +# self.xmin = xmin +# self.xmax = xmax +# self.transform_func = lambda x: self.xmin * jnp.exp( +# x * jnp.log(self.xmax / self.xmin) +# ) From 935b31484e06ddbfa3b985e22a8303c0dc50b7a3 Mon Sep 17 00:00:00 2001 From: kazewong Date: Tue, 30 Jul 2024 16:47:20 -0400 Subject: [PATCH 095/248] Refactor into NtoN and NtoM transform --- src/jimgw/prior.py | 5 ++-- src/jimgw/transforms.py | 52 ++++++++++++++++++++++++++++++----------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 6a8f10ca..22985a8a 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -8,6 +8,7 @@ from jimgw.transforms import ( Transform, + NtoNTransform, LogitTransform, ScaleTransform, OffsetTransform, @@ -149,7 +150,7 @@ class SequentialTransformPrior(Prior): """ base_prior: Prior - transforms: list[Transform] + transforms: list[NtoNTransform] def __repr__(self): return f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" @@ -157,7 +158,7 @@ def __repr__(self): def __init__( self, base_prior: Prior, - transforms: list[Transform], + transforms: list[NtoNTransform], ): self.base_prior = base_prior diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 904fe225..4e62316e 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -27,13 +27,10 @@ def propagate_name(self, x: list[str]) -> list[str]: to_set = set(self.name_mapping[1]) return list(input_set - from_set | to_set) -class BijectiveTransform(Transform): - transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " n_dim"]] - inverse_transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " n_dim"]] +class NtoNTransform(Transform): - def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: - return self.transform(x) + transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " n_dim"]] def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ @@ -55,7 +52,11 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: input_params = jax.tree.map(lambda key: x.pop(key), self.name_mapping[0]) output_params = self.transform_func(input_params) jacobian = jnp.array(jax.jacfwd(self.transform_func)(input_params)) - jax.tree.map(lambda key, value: x.update({key: value}), self.name_mapping[1], output_params) + jax.tree.map( + lambda key, value: x.update({key: value}), + self.name_mapping[1], + output_params, + ) return x, jnp.log(jnp.linalg.det(jacobian)) def forward(self, x: dict[str, Float]) -> dict[str, Float]: @@ -74,9 +75,21 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ input_params = jax.tree.map(lambda key: x.pop(key), self.name_mapping[0]) output_params = self.transform_func(input_params) - jax.tree.map(lambda key, value: x.update({key: value}), self.name_mapping[1], output_params) + jax.tree.map( + lambda key, value: x.update({key: value}), + self.name_mapping[1], + output_params, + ) return x - + + +class BijectiveTransform(NtoNTransform): + + inverse_transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " n_dim"]] + + def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: + return self.transform(x) + def inverse(self, y: dict[str, Float]) -> dict[str, Float]: """ Inverse transform the input y to original coordinate x. @@ -94,7 +107,11 @@ def inverse(self, y: dict[str, Float]) -> dict[str, Float]: output_params = jax.tree.map(lambda key: y.pop(key), self.name_mapping[1]) input_params = self.inverse_transform_func(output_params) jacobian = jnp.array(jax.jacfwd(self.inverse_transform_func)(output_params)) - jax.tree.map(lambda key, value: y.update({key: value}), self.name_mapping[0], input_params) + jax.tree.map( + lambda key, value: y.update({key: value}), + self.name_mapping[0], + input_params, + ) return y, jnp.log(jnp.linalg.det(jacobian)) def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: @@ -115,14 +132,21 @@ def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ output_params = jax.tree.map(lambda key: y.pop(key), self.name_mapping[1]) input_params = self.inverse_transform_func(output_params) - jax.tree.map(lambda key, value: y.update({key: value}), self.name_mapping[0], input_params) + jax.tree.map( + lambda key, value: y.update({key: value}), + self.name_mapping[0], + input_params, + ) return y -class NonBijectiveTransform(Transform): - + +class NtoMTransform(Transform): + + transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " m_dim"]] + def __call__(self, x: dict[str, Float]) -> dict[str, Float]: return self.forward(x) - + @abstractmethod def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ @@ -140,6 +164,7 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ raise NotImplementedError + class ScaleTransform(BijectiveTransform): scale: Float @@ -153,6 +178,7 @@ def __init__( self.transform_func = lambda x: [x[0] * self.scale] self.inverse_transform_func = lambda x: [x[0] / self.scale] + class OffsetTransform(BijectiveTransform): offset: Float From 685670a3c97b3b0300ce6a7ef6351bd56375c3e1 Mon Sep 17 00:00:00 2001 From: kazewong Date: Tue, 30 Jul 2024 17:06:02 -0400 Subject: [PATCH 096/248] Fix bugs in ArcSine --- src/jimgw/prior.py | 4 ++-- src/jimgw/transforms.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 22985a8a..f291b23a 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -13,8 +13,8 @@ ScaleTransform, OffsetTransform, ArcSineTransform, - PowerLawTransform, - ParetoTransform, + # PowerLawTransform, + # ParetoTransform, ) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 4e62316e..ca759a07 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -229,8 +229,8 @@ def __init__( name_mapping: tuple[list[str], list[str]], ): super().__init__(name_mapping) - self.transform_func = lambda x: jnp.arcsin(x) - self.inverse_transform_func = lambda x: jnp.sin(x) + self.transform_func = lambda x: [jnp.arcsin(x[0])] + self.inverse_transform_func = lambda x: [jnp.sin(x[0])] # class PowerLawTransform(UnivariateTransform): From 39126f5cf51be98d7ac5d53ed3ba3ed4e37c5259 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Tue, 30 Jul 2024 17:41:48 -0400 Subject: [PATCH 097/248] Add inverse mass transform --- src/jimgw/single_event/utils.py | 54 +++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 3f0decce..870c70ae 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -61,7 +61,7 @@ def m1m2_to_Mq(m1: Float, m2: Float): return M_tot, q -def Mq_to_m1m2(trans_M_tot: Float, trans_q: Float): +def M_q_to_m1_m2(trans_M_tot: Float, trans_q: Float): """ Transforming the Total mass M and mass ratio q to the primary mass m1 and secondary mass m2. @@ -87,7 +87,7 @@ def Mq_to_m1m2(trans_M_tot: Float, trans_q: Float): return m1, m2 -def Mc_q_to_m1m2(Mc: Float, q: Float) -> tuple[Float, Float]: +def Mc_q_to_m1_m2(Mc: Float, q: Float) -> tuple[Float, Float]: """ Transforming the chirp mass Mc and mass ratio q to the primary mass m1 and secondary mass m2. @@ -113,6 +113,56 @@ def Mc_q_to_m1m2(Mc: Float, q: Float) -> tuple[Float, Float]: return m1, m2 +def m1_m2_to_Mc_q(m1: Float, m2: Float) -> tuple[Float, Float]: + """ + Transforming the primary mass m1 and secondary mass m2 to the chirp mass Mc + and mass ratio q. + + Parameters + ---------- + m1 : Float + Primary mass. + m2 : Float + Secondary mass. + + Returns + ------- + Mc : Float + Chirp mass. + q : Float + Mass ratio. + """ + M_tot = m1 + m2 + eta = m1 * m2 / M_tot ** 2 + Mc = M_tot * eta ** (3.0 / 5) + q = m2 / m1 + return Mc, q + + +def m1_m2_to_M_eta(m1: Float, m2: Float) -> tuple[Float, Float]: + """ + Transforming the primary mass m1 and secondary mass m2 to the total mass M + and symmetric mass ratio eta. + + Parameters + ---------- + m1 : Float + Primary mass. + m2 : Float + Secondary mass. + + Returns + ------- + M : Float + Total mass. + eta : Float + Symmetric mass ratio. + """ + M = m1 + m2 + eta = m1 * m2 / M ** 2 + return M, eta + + def ra_dec_to_theta_phi(ra: Float, dec: Float, gmst: Float) -> tuple[Float, Float]: """ Transforming the right ascension ra and declination dec to the polar angle From 46bd0442c6552a8967a0f9be388fa778a385bd4c Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Tue, 30 Jul 2024 23:27:24 -0400 Subject: [PATCH 098/248] Update sequential prior class and test_GW15014.py. --- src/jimgw/prior.py | 31 +++++++++++++++++-------------- test/integration/test_GW150914.py | 26 +++++++++----------------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index f291b23a..e23d0dfa 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -7,8 +7,7 @@ from jaxtyping import Array, Float, PRNGKeyArray, jaxtyped from jimgw.transforms import ( - Transform, - NtoNTransform, + BijectiveTransform, LogitTransform, ScaleTransform, OffsetTransform, @@ -61,7 +60,7 @@ def sample( ) -> dict[str, Float[Array, " n_samples"]]: raise NotImplementedError - def log_prob(self, x: dict[str, Array]) -> Float: + def log_prob(self, z: dict[str, Array]) -> Float: raise NotImplementedError @@ -99,7 +98,7 @@ def sample( samples = jnp.log(samples / (1 - samples)) return self.add_name(samples[None]) - def log_prob(self, x: dict[str, Float]) -> Float: + def log_prob(self, z: dict[str, Float]) -> Float: variable = x[self.parameter_names[0]] return -variable - 2 * jnp.log(1 + jnp.exp(-variable)) @@ -139,7 +138,7 @@ def sample( samples = jax.random.normal(rng_key, (n_samples,)) return self.add_name(samples[None]) - def log_prob(self, x: dict[str, Float]) -> Float: + def log_prob(self, z: dict[str, Float]) -> Float: variable = x[self.parameter_names[0]] return -0.5 * variable**2 - 0.5 * jnp.log(2 * jnp.pi) @@ -147,10 +146,12 @@ def log_prob(self, x: dict[str, Float]) -> Float: class SequentialTransformPrior(Prior): """ Transform a prior distribution by applying a sequence of transforms. + The space before the transform is named as x, + and the space after the transform is named as z """ base_prior: Prior - transforms: list[NtoNTransform] + transforms: list[BijectiveTransform] def __repr__(self): return f"Sequential(priors={self.base_prior}, parameter_names={self.parameter_names})" @@ -158,7 +159,7 @@ def __repr__(self): def __init__( self, base_prior: Prior, - transforms: list[NtoNTransform], + transforms: list[BijectiveTransform], ): self.base_prior = base_prior @@ -174,14 +175,16 @@ def sample( output = self.base_prior.sample(rng_key, n_samples) return jax.vmap(self.transform)(output) - def log_prob(self, x: dict[str, Float]) -> Float: + def log_prob(self, z: dict[str, Float]) -> Float: """ - log_prob has to be evaluated in the space of the base_prior. + Evaluating the probability of the transformed variable z. + This is what flowMC should sample from """ - output = self.base_prior.log_prob(x) - for transform in self.transforms: - x, log_jacobian = transform.transform(x) + output = 0 + for transform in reversed(self.transforms): + z, log_jacobian = transform.inverse(z) output -= log_jacobian + output += self.base_prior.log_prob(z) return output def transform(self, x: dict[str, Float]) -> dict[str, Float]: @@ -223,10 +226,10 @@ def sample( output.update(prior.sample(subkey, n_samples)) return output - def log_prob(self, x: dict[str, Float]) -> Float: + def log_prob(self, z: dict[str, Float]) -> Float: output = 0.0 for prior in self.base_prior: - output += prior.log_prob(x) + output += prior.log_prob(z) return output diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index 2650cd1c..deb3fb98 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -4,7 +4,7 @@ import jax.numpy as jnp from jimgw.jim import Jim -from jimgw.prior import CombinePrior, UniformPrior +from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD @@ -33,10 +33,10 @@ L1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) -q_prior = UniformPrior( +eta_prior = UniformPrior( 0.125, - 1.0, - parameter_names=["q"], # Need name transformation in likelihood to work + 0.25, + parameter_names=["eta"], # Need name transformation in likelihood to work ) s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) s2z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) @@ -44,32 +44,24 @@ dL_prior = UniformPrior(0.0, 2000.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) -cos_iota_prior = UniformPrior( - -1.0, - 1.0, - parameter_names=["cos_iota"], # Need name transformation in likelihood to work -) +iota_prior = CosinePrior(parameter_names=["iota"]) psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) -sin_dec_prior = UniformPrior( - -1.0, - 1.0, - parameter_names=["sin_dec"], # Need name transformation in likelihood to work -) +dec_prior = SinePrior(parameter_names=["dec"]) prior = CombinePrior( [ Mc_prior, - q_prior, + eta_prior, s1z_prior, s2z_prior, dL_prior, t_c_prior, phase_c_prior, - cos_iota_prior, + iota_prior, psi_prior, ra_prior, - sin_dec_prior, + dec_prior, ] ) likelihood = TransientLikelihoodFD( From 8e1441b389560bcb07f6a137705aba6db384d074 Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 31 Jul 2024 08:52:16 -0400 Subject: [PATCH 099/248] correct sign errors and minior bugs --- src/jimgw/prior.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index e23d0dfa..13ab622f 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -99,7 +99,7 @@ def sample( return self.add_name(samples[None]) def log_prob(self, z: dict[str, Float]) -> Float: - variable = x[self.parameter_names[0]] + variable = z[self.parameter_names[0]] return -variable - 2 * jnp.log(1 + jnp.exp(-variable)) @@ -139,7 +139,7 @@ def sample( return self.add_name(samples[None]) def log_prob(self, z: dict[str, Float]) -> Float: - variable = x[self.parameter_names[0]] + variable = z[self.parameter_names[0]] return -0.5 * variable**2 - 0.5 * jnp.log(2 * jnp.pi) @@ -183,7 +183,7 @@ def log_prob(self, z: dict[str, Float]) -> Float: output = 0 for transform in reversed(self.transforms): z, log_jacobian = transform.inverse(z) - output -= log_jacobian + output += log_jacobian output += self.base_prior.log_prob(z) return output From dfdfffad0bc6847ad1519e7884426292b691ba80 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 31 Jul 2024 09:43:00 -0400 Subject: [PATCH 100/248] Add mass transform --- src/jimgw/single_event/utils.py | 58 ++++++++++++++++----------------- src/jimgw/transforms.py | 44 ++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 870c70ae..4ee5c25e 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -87,14 +87,14 @@ def M_q_to_m1_m2(trans_M_tot: Float, trans_q: Float): return m1, m2 -def Mc_q_to_m1_m2(Mc: Float, q: Float) -> tuple[Float, Float]: +def Mc_q_to_m1_m2(M_c: Float, q: Float) -> tuple[Float, Float]: """ - Transforming the chirp mass Mc and mass ratio q to the primary mass m1 and + Transforming the chirp mass M_c and mass ratio q to the primary mass m1 and secondary mass m2. Parameters ---------- - Mc : Float + M_c : Float Chirp mass. q : Float Mass ratio. @@ -107,36 +107,36 @@ def Mc_q_to_m1_m2(Mc: Float, q: Float) -> tuple[Float, Float]: Secondary mass. """ eta = q / (1 + q) ** 2 - M_tot = Mc / eta ** (3.0 / 5) + M_tot = M_c / eta ** (3.0 / 5) m1 = M_tot / (1 + q) m2 = m1 * q return m1, m2 -def m1_m2_to_Mc_q(m1: Float, m2: Float) -> tuple[Float, Float]: - """ - Transforming the primary mass m1 and secondary mass m2 to the chirp mass Mc - and mass ratio q. - - Parameters - ---------- - m1 : Float - Primary mass. - m2 : Float - Secondary mass. - - Returns - ------- - Mc : Float - Chirp mass. - q : Float - Mass ratio. - """ - M_tot = m1 + m2 - eta = m1 * m2 / M_tot ** 2 - Mc = M_tot * eta ** (3.0 / 5) - q = m2 / m1 - return Mc, q +def m1_m2_to_M_c_q(m1: Float, m2: Float) -> tuple[Float, Float]: + """ + Transforming the primary mass m1 and secondary mass m2 to the chirp mass M_c + and mass ratio q. + + Parameters + ---------- + m1 : Float + Primary mass. + m2 : Float + Secondary mass. + + Returns + ------- + M_c : Float + Chirp mass. + q : Float + Mass ratio. + """ + M_tot = m1 + m2 + eta = m1 * m2 / M_tot**2 + M_c = M_tot * eta ** (3.0 / 5) + q = m2 / m1 + return M_c, q def m1_m2_to_M_eta(m1: Float, m2: Float) -> tuple[Float, Float]: @@ -159,7 +159,7 @@ def m1_m2_to_M_eta(m1: Float, m2: Float) -> tuple[Float, Float]: Symmetric mass ratio. """ M = m1 + m2 - eta = m1 * m2 / M ** 2 + eta = m1 * m2 / M**2 return M, eta diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index ca759a07..e377afd2 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -3,9 +3,10 @@ import jax import jax.numpy as jnp -from chex import assert_rank from jaxtyping import Float, Array +from jimgw.single_event.utils import m1_m2_to_Mc_q, Mc_q_to_m1_m2 + class Transform(ABC): """ @@ -233,6 +234,47 @@ def __init__( self.inverse_transform_func = lambda x: [jnp.sin(x[0])] +class ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): + """ + Transform component masses to chirp mass and mass ratio. + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + assert name_mapping == (["m_1", "m_2"], ["M_c", "q"]) + super().__init__(name_mapping) + self.transform_func = lambda x: m1_m2_to_Mc_q(x[0], x[1]) + self.inverse_transform_func = lambda x: Mc_q_to_m1_m2(x[0], x[1]) + + +def inverse(transform: BijectiveTransform) -> BijectiveTransform: + """ + Inverse the transform. + + Parameters + ---------- + transform : BijectiveTransform + The transform to be inverted. + + Returns + ------- + BijectiveTransform + The inverted transform. + """ + return BijectiveTransform( + name_mapping=transform.name_mapping, + transform_func=transform.inverse_transform_func, + inverse_transform_func=transform.transform_func, + ) + + # class PowerLawTransform(UnivariateTransform): # """ # PowerLaw transformation From dd03bf64f8522ff0f39800ea644c900360e90115 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:50:39 -0400 Subject: [PATCH 101/248] Fixed powerLaw --- src/jimgw/prior.py | 4 +- src/jimgw/transforms.py | 107 +++++++++++++++++++++------------------- 2 files changed, 58 insertions(+), 53 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 13ab622f..aff1ebf5 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -12,8 +12,8 @@ ScaleTransform, OffsetTransform, ArcSineTransform, - # PowerLawTransform, - # ParetoTransform, + PowerLawTransform, + ParetoTransform, ) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index ca759a07..08787071 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -233,54 +233,59 @@ def __init__( self.inverse_transform_func = lambda x: [jnp.sin(x[0])] -# class PowerLawTransform(UnivariateTransform): -# """ -# PowerLaw transformation -# Parameters -# ---------- -# name_mapping : tuple[list[str], list[str]] -# The name mapping between the input and output dictionary. -# """ - -# xmin: Float -# xmax: Float -# alpha: Float - -# def __init__( -# self, -# name_mapping: tuple[list[str], list[str]], -# xmin: Float, -# xmax: Float, -# alpha: Float, -# ): -# super().__init__(name_mapping) -# self.xmin = xmin -# self.xmax = xmax -# self.alpha = alpha -# self.transform_func = lambda x: ( -# self.xmin ** (1.0 + self.alpha) -# + x * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) -# ) ** (1.0 / (1.0 + self.alpha)) - - -# class ParetoTransform(UnivariateTransform): -# """ -# Pareto transformation: Power law when alpha = -1 -# Parameters -# ---------- -# name_mapping : tuple[list[str], list[str]] -# The name mapping between the input and output dictionary. -# """ - -# def __init__( -# self, -# name_mapping: tuple[list[str], list[str]], -# xmin: Float, -# xmax: Float, -# ): -# super().__init__(name_mapping) -# self.xmin = xmin -# self.xmax = xmax -# self.transform_func = lambda x: self.xmin * jnp.exp( -# x * jnp.log(self.xmax / self.xmin) -# ) +class PowerLawTransform(UnivariateTransform): + """ + PowerLaw transformation + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + """ + + xmin: Float + xmax: Float + alpha: Float + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + xmin: Float, + xmax: Float, + alpha: Float, + ): + super().__init__(name_mapping) + self.xmin = xmin + self.xmax = xmax + self.alpha = alpha + self.transform_func = lambda x: ( + self.xmin ** (1.0 + self.alpha) + + x * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) ** (1.0 / (1.0 + self.alpha)) + self.inverse_transform_func = lambda x: ( + (x ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + / (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) + + +class ParetoTransform(BijectiveTransform): + """ + Pareto transformation: Power law when alpha = -1 + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + xmin: Float, + xmax: Float, + ): + super().__init__(name_mapping) + self.xmin = xmin + self.xmax = xmax + self.transform_func = lambda x: self.xmin * jnp.exp( + x * jnp.log(self.xmax / self.xmin) + ) + self.inverse_transform_func = lambda x: (jnp.log(x / self.xmin) / jnp.log(self.xmax / self.xmin)) From 735415e8f1895d0aa3bcefd2a3f94f1c951f207e Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:57:46 -0400 Subject: [PATCH 102/248] Fixed powerLaw --- src/jimgw/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 08787071..7a016704 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -233,7 +233,7 @@ def __init__( self.inverse_transform_func = lambda x: [jnp.sin(x[0])] -class PowerLawTransform(UnivariateTransform): +class PowerLawTransform(BijectiveTransform): """ PowerLaw transformation Parameters From cec7447cbbb482a026612ce2bc77316395374247 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:12:03 -0400 Subject: [PATCH 103/248] Fixed powerLaw --- src/jimgw/transforms.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 7a016704..e1aa50ba 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -259,10 +259,10 @@ def __init__( self.alpha = alpha self.transform_func = lambda x: ( self.xmin ** (1.0 + self.alpha) - + x * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + + x[0] * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) ) ** (1.0 / (1.0 + self.alpha)) self.inverse_transform_func = lambda x: ( - (x ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + (x[0] ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) / (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) ) @@ -286,6 +286,6 @@ def __init__( self.xmin = xmin self.xmax = xmax self.transform_func = lambda x: self.xmin * jnp.exp( - x * jnp.log(self.xmax / self.xmin) + x[0] * jnp.log(self.xmax / self.xmin) ) - self.inverse_transform_func = lambda x: (jnp.log(x / self.xmin) / jnp.log(self.xmax / self.xmin)) + self.inverse_transform_func = lambda x: (jnp.log(x[0] / self.xmin) / jnp.log(self.xmax / self.xmin)) From 74e553d82510a43552d13438aa227edd9dba6bb4 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:30:28 -0400 Subject: [PATCH 104/248] Fixed powerLaw --- src/jimgw/transforms.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index e1aa50ba..3071f964 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -257,14 +257,14 @@ def __init__( self.xmin = xmin self.xmax = xmax self.alpha = alpha - self.transform_func = lambda x: ( + self.transform_func = lambda x: [( self.xmin ** (1.0 + self.alpha) + x[0] * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - ) ** (1.0 / (1.0 + self.alpha)) - self.inverse_transform_func = lambda x: ( + ) ** (1.0 / (1.0 + self.alpha))] + self.inverse_transform_func = lambda x: [( (x[0] ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) / (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - ) + )] class ParetoTransform(BijectiveTransform): @@ -285,7 +285,7 @@ def __init__( super().__init__(name_mapping) self.xmin = xmin self.xmax = xmax - self.transform_func = lambda x: self.xmin * jnp.exp( + self.transform_func = lambda x: [self.xmin * jnp.exp( x[0] * jnp.log(self.xmax / self.xmin) - ) - self.inverse_transform_func = lambda x: (jnp.log(x[0] / self.xmin) / jnp.log(self.xmax / self.xmin)) + )] + self.inverse_transform_func = lambda x: [(jnp.log(x[0] / self.xmin) / jnp.log(self.xmax / self.xmin))] From 9d87e58fa19fc4169ab8f4184e3c1738e817aa6f Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 31 Jul 2024 11:08:53 -0400 Subject: [PATCH 105/248] Add simplex transform --- src/jimgw/single_event/utils.py | 4 ++-- src/jimgw/transforms.py | 31 ++++++++++++++++--------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 4ee5c25e..3d07bb57 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -37,7 +37,7 @@ def inner_product( return 4.0 * jnp.real(trapezoid(integrand, dx=df)) -def m1m2_to_Mq(m1: Float, m2: Float): +def m1_m2_to_M_q(m1: Float, m2: Float): """ Transforming the primary mass m1 and secondary mass m2 to the Total mass M and mass ratio q. @@ -113,7 +113,7 @@ def Mc_q_to_m1_m2(M_c: Float, q: Float) -> tuple[Float, Float]: return m1, m2 -def m1_m2_to_M_c_q(m1: Float, m2: Float) -> tuple[Float, Float]: +def m1_m2_to_Mc_q(m1: Float, m2: Float) -> tuple[Float, Float]: """ Transforming the primary mass m1 and secondary mass m2 to the chirp mass M_c and mass ratio q. diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index e377afd2..be181fcd 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -196,7 +196,7 @@ def __init__( class LogitTransform(BijectiveTransform): """ - Logit transform following + Logit transformation Parameters ---------- @@ -254,25 +254,26 @@ def __init__( self.inverse_transform_func = lambda x: Mc_q_to_m1_m2(x[0], x[1]) -def inverse(transform: BijectiveTransform) -> BijectiveTransform: +class RectangleToTriangleTransform(BijectiveTransform): """ - Inverse the transform. + Transform a rectangle grid with bounds [0, 1] x [0, 1] to a triangle grid with vertices (0, 0), (1, 0), (0, 1), while preserving the area density. Parameters ---------- - transform : BijectiveTransform - The transform to be inverted. - - Returns - ------- - BijectiveTransform - The inverted transform. + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. """ - return BijectiveTransform( - name_mapping=transform.name_mapping, - transform_func=transform.inverse_transform_func, - inverse_transform_func=transform.transform_func, - ) + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + self.transform_func = lambda x: [ + 1 - jnp.sqrt(1 - x[0]), + x[1] * jnp.sqrt(1 - x[0]), + ] + self.inverse_transform_func = lambda x: [1 - (1 - x[0]) ** 2, x[1] / (1 - x[0])] # class PowerLawTransform(UnivariateTransform): From 52fa0eb28308aae155c566c677317b94645a83ea Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 31 Jul 2024 11:10:23 -0400 Subject: [PATCH 106/248] Change transform to take dictionary as input for transform_func --- src/jimgw/transforms.py | 119 +++++++++++++++++++++++++++------------- 1 file changed, 80 insertions(+), 39 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index ca759a07..13d66173 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -30,7 +30,7 @@ def propagate_name(self, x: list[str]) -> list[str]: class NtoNTransform(Transform): - transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " n_dim"]] + transform_func: Callable[[dict[str, Float]], dict[str, Float]] def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ @@ -49,15 +49,22 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: log_det : Float The log Jacobian determinant. """ - input_params = jax.tree.map(lambda key: x.pop(key), self.name_mapping[0]) - output_params = self.transform_func(input_params) - jacobian = jnp.array(jax.jacfwd(self.transform_func)(input_params)) + x_copy = x.copy() + output_params = self.transform_func(x_copy) + jacobian = jax.jacfwd(self.transform_func)(x_copy) + jacobian = jnp.array(jax.tree.leaves(jacobian)) + jacobian = jnp.log( + jnp.linalg.det(jacobian.reshape(int(jnp.sqrt(jacobian.size)), -1)) + ) jax.tree.map( - lambda key, value: x.update({key: value}), - self.name_mapping[1], - output_params, + lambda key: x_copy.pop(key), + self.name_mapping[0], ) - return x, jnp.log(jnp.linalg.det(jacobian)) + jax.tree.map( + lambda key: x_copy.update({key: output_params[key]}), + list(output_params.keys()), + ) + return x_copy, jacobian def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ @@ -73,19 +80,22 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: y : dict[str, Float] The transformed dictionary. """ - input_params = jax.tree.map(lambda key: x.pop(key), self.name_mapping[0]) - output_params = self.transform_func(input_params) + x_copy = x.copy() + output_params = self.transform_func(x_copy) jax.tree.map( - lambda key, value: x.update({key: value}), - self.name_mapping[1], - output_params, + lambda key: x_copy.pop(key), + self.name_mapping[0], + ) + jax.tree.map( + lambda key: x_copy.update({key: output_params[key]}), + list(output_params.keys()), ) - return x + return x_copy class BijectiveTransform(NtoNTransform): - inverse_transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " n_dim"]] + inverse_transform_func: Callable[[dict[str, Float]], dict[str, Float]] def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: return self.transform(x) @@ -104,15 +114,22 @@ def inverse(self, y: dict[str, Float]) -> dict[str, Float]: x : dict[str, Float] The original dictionary. """ - output_params = jax.tree.map(lambda key: y.pop(key), self.name_mapping[1]) - input_params = self.inverse_transform_func(output_params) - jacobian = jnp.array(jax.jacfwd(self.inverse_transform_func)(output_params)) + y_copy = y.copy() + output_params = self.inverse_transform_func(y_copy) + jacobian = jax.jacfwd(self.inverse_transform_func)(y_copy) + jacobian = jnp.array(jax.tree.leaves(jacobian)) + jacobian = jnp.log( + jnp.linalg.det(jacobian.reshape(int(jnp.sqrt(jacobian.size)), -1)) + ) jax.tree.map( - lambda key, value: y.update({key: value}), - self.name_mapping[0], - input_params, + lambda key: y_copy.pop(key), + self.name_mapping[1], + ) + jax.tree.map( + lambda key: y_copy.update({key: output_params[key]}), + list(output_params.keys()), ) - return y, jnp.log(jnp.linalg.det(jacobian)) + return y_copy, jacobian def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ @@ -130,14 +147,17 @@ def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: log_det : Float The log Jacobian determinant. """ - output_params = jax.tree.map(lambda key: y.pop(key), self.name_mapping[1]) - input_params = self.inverse_transform_func(output_params) + y_copy = y.copy() + output_params = self.inverse_transform_func(y_copy) jax.tree.map( - lambda key, value: y.update({key: value}), - self.name_mapping[0], - input_params, + lambda key: y_copy.pop(key), + self.name_mapping[1], ) - return y + jax.tree.map( + lambda key: y_copy.update({key: output_params[key]}), + list(output_params.keys()), + ) + return y_copy class NtoMTransform(Transform): @@ -175,8 +195,14 @@ def __init__( ): super().__init__(name_mapping) self.scale = scale - self.transform_func = lambda x: [x[0] * self.scale] - self.inverse_transform_func = lambda x: [x[0] / self.scale] + self.transform_func = lambda x: { + name_mapping[1][i]: x[name_mapping[0][i]] * self.scale + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: x[name_mapping[1][i]] / self.scale + for i in range(len(name_mapping[1])) + } class OffsetTransform(BijectiveTransform): @@ -189,9 +215,14 @@ def __init__( ): super().__init__(name_mapping) self.offset = offset - self.transform_func = lambda x: [x[0] + self.offset] - self.inverse_transform_func = lambda x: [x[0] - self.offset] - + self.transform_func = lambda x: { + name_mapping[1][i]: x[name_mapping[0][i]] + self.offset + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: x[name_mapping[1][i]] - self.offset + for i in range(len(name_mapping[1])) + } class LogitTransform(BijectiveTransform): """ @@ -209,9 +240,14 @@ def __init__( name_mapping: tuple[list[str], list[str]], ): super().__init__(name_mapping) - self.transform_func = lambda x: [1 / (1 + jnp.exp(-x[0]))] - self.inverse_transform_func = lambda x: [jnp.log(x[0] / (1 - x[0]))] - + self.transform_func = lambda x: { + name_mapping[1][i]: jnp.log(x[name_mapping[0][i]] / (1 - x[name_mapping[0][i]])) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: 1 / (1 + jnp.exp(-x[name_mapping[1][i]])) + for i in range(len(name_mapping[1])) + } class ArcSineTransform(BijectiveTransform): """ @@ -229,9 +265,14 @@ def __init__( name_mapping: tuple[list[str], list[str]], ): super().__init__(name_mapping) - self.transform_func = lambda x: [jnp.arcsin(x[0])] - self.inverse_transform_func = lambda x: [jnp.sin(x[0])] - + self.transform_func = lambda x: { + name_mapping[1][i]: jnp.arcsin(x[name_mapping[0][i]]) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: jnp.sin(x[name_mapping[1][i]]) + for i in range(len(name_mapping[1])) + } # class PowerLawTransform(UnivariateTransform): # """ From 2aef04bc78147221b6d1abd65316447f49a00e50 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Wed, 31 Jul 2024 11:11:23 -0400 Subject: [PATCH 107/248] Updated test_prior.py --- test/unit/test_prior.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/test/unit/test_prior.py b/test/unit/test_prior.py index 20d71de4..1cbed508 100644 --- a/test/unit/test_prior.py +++ b/test/unit/test_prior.py @@ -96,20 +96,16 @@ def func(alpha): assert jnp.all(jnp.isfinite(powerlaw_samples['x'])) # Check that all the log_probs are finite - samples = (trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000))['x_base'] - base_log_p = jax.vmap(p.log_prob, [0])({'x_base':samples}) - assert jnp.all(jnp.isfinite(base_log_p)) + samples = p.sample(jax.random.PRNGKey(0), 10000) + log_p = jax.vmap(p.log_prob, [0])(samples) + assert jnp.all(jnp.isfinite(log_p)) # Check that the log_prob is correct in the support - samples = jnp.linspace(-10.0, 10.0, 1000) - transformed_samples = jax.vmap(p.transform)({'x_base': samples})['x'] - # cut off the samples that are outside the support - samples = samples[transformed_samples >= xmin] - transformed_samples = transformed_samples[transformed_samples >= xmin] - samples = samples[transformed_samples <= xmax] - transformed_samples = transformed_samples[transformed_samples <= xmax] + samples = p.sample(jax.random.PRNGKey(0), 10000) + log_prob = jax.vmap(p.log_prob)(samples) + standard_log_prob = powerlaw_log_pdf(samples['x'], alpha, xmin, xmax) # log pdf of powerlaw - assert jnp.allclose(jax.vmap(p.log_prob)({'x_base':samples}), powerlaw_log_pdf(transformed_samples, alpha, xmin, xmax), atol=1e-4) + assert jnp.allclose(log_prob, standard_log_prob, atol=1e-4) # Test Pareto Transform func(-1.0) From 1e389bb1e19332576ece2d2911144a84e9dc16af Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 31 Jul 2024 11:46:05 -0400 Subject: [PATCH 108/248] Add UniformComponentMassPrior --- src/jimgw/prior.py | 51 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 13ab622f..7880648d 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -12,8 +12,7 @@ ScaleTransform, OffsetTransform, ArcSineTransform, - # PowerLawTransform, - # ParetoTransform, + RectangleToTriangleTransform, ) @@ -399,7 +398,53 @@ def __init__( @jaxtyped(typechecker=typechecker) -class UniformInComponentsChirpMassPrior(PowerLawPrior): +class UniformComponentMassPrior(SequentialTransformPrior): + """ + A prior in the range [xmin, xmax) for component masses which assumes the + component masses to be uniformly distributed. + """ + + xmin: float + xmax: float + + def __repr__(self): + return f"UniformComponentMassPrior(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" + + def __init__(self, xmin: float, xmax: float, parameter_names: list[str]): + self.parameter_names = parameter_names + assert self.n_dim == 2, "UniformComponentMassPrior needs to be 2D distributions" + self.xmax = xmax + self.xmin = xmin + super().__init__( + CombinePrior( + [ + UniformPrior(xmin, xmax, ["x_1"]), + UniformPrior(xmin, xmax, ["x_2"]), + ] + ), + [ + ScaleTransform( + ( + ["x_1", "x_2"], + [f"x_1/({xmax-xmin})", f"x_2/({xmax-xmin})"], + ), + 1 / (xmax - xmin), + ), + RectangleToTriangleTransform( + ( + [ + f"{self.parameter_names[0]}/({xmax-xmin})", + f"{self.parameter_names[1]}/({xmax-xmin})", + ], + [self.parameter_names[0], self.parameter_names[1]], + ) + ), + ], + ) + + +@jaxtyped(typechecker=typechecker) +class UniformComponentChirpMassPrior(PowerLawPrior): """ A prior in the range [xmin, xmax) for chirp mass which assumes the component masses to be uniformly distributed. From 0252392aacf4fa53e18a7d5169e57dac6bcd15bb Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 31 Jul 2024 12:26:25 -0400 Subject: [PATCH 109/248] prior system should work with dictionary function now --- src/jimgw/jim.py | 10 +++------- test/integration/test_GW150914.py | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 1a694172..81d56836 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -32,12 +32,8 @@ def __init__( self.likelihood = likelihood self.prior = prior if parameter_names is None: - print("No parameter names provided. Will try to trace the prior.") - parents = [] - trace_prior_parent(prior, parents) - parameter_names = [] - for parent in parents: - parameter_names.extend(parent.parameter_names) + print("No parameter names provided. Using prior names.") + parameter_names = prior.parameter_names self.parameter_names = parameter_names seed = kwargs.get("seed", 0) @@ -79,7 +75,7 @@ def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: def posterior(self, params: Float[Array, " n_dim"], data: dict): named_params = self.add_name(params) - prior = self.prior.log_prob(named_params) + prior = self.prior.log_prob(named_params) return self.likelihood.evaluate(named_params, data) + prior def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index deb3fb98..4028164f 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -106,4 +106,4 @@ strategies=[Adam_optimizer, "default"], ) -jim.sample(jax.random.PRNGKey(42)) +# jim.sample(jax.random.PRNGKey(42)) From cb75bb9878036038e0dc8733bf51bddaa5a57f8a Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 31 Jul 2024 12:31:03 -0400 Subject: [PATCH 110/248] update transform --- src/jimgw/transforms.py | 22 ++++++++++++++-------- test/integration/test_GW150914.py | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 13d66173..bfabc2ad 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -32,6 +32,10 @@ class NtoNTransform(Transform): transform_func: Callable[[dict[str, Float]], dict[str, Float]] + @property + def n_dim(self) -> int: + return len(self.name_mapping[0]) + def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ Transform the input x to transformed coordinate y and return the log Jacobian determinant. @@ -50,11 +54,12 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: The log Jacobian determinant. """ x_copy = x.copy() - output_params = self.transform_func(x_copy) - jacobian = jax.jacfwd(self.transform_func)(x_copy) + transform_params = dict((key, x_copy[key]) for key in self.name_mapping[0]) + output_params = self.transform_func(transform_params) + jacobian = jax.jacfwd(self.transform_func)(transform_params) jacobian = jnp.array(jax.tree.leaves(jacobian)) jacobian = jnp.log( - jnp.linalg.det(jacobian.reshape(int(jnp.sqrt(jacobian.size)), -1)) + jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim)) ) jax.tree.map( lambda key: x_copy.pop(key), @@ -115,11 +120,12 @@ def inverse(self, y: dict[str, Float]) -> dict[str, Float]: The original dictionary. """ y_copy = y.copy() - output_params = self.inverse_transform_func(y_copy) - jacobian = jax.jacfwd(self.inverse_transform_func)(y_copy) + transform_params = dict((key, y_copy[key]) for key in self.name_mapping[1]) + output_params = self.inverse_transform_func(transform_params) + jacobian = jax.jacfwd(self.inverse_transform_func)(transform_params) jacobian = jnp.array(jax.tree.leaves(jacobian)) jacobian = jnp.log( - jnp.linalg.det(jacobian.reshape(int(jnp.sqrt(jacobian.size)), -1)) + jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim)) ) jax.tree.map( lambda key: y_copy.pop(key), @@ -241,11 +247,11 @@ def __init__( ): super().__init__(name_mapping) self.transform_func = lambda x: { - name_mapping[1][i]: jnp.log(x[name_mapping[0][i]] / (1 - x[name_mapping[0][i]])) + name_mapping[1][i]: 1 / (1 + jnp.exp(-x[name_mapping[0][i]])) for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { - name_mapping[0][i]: 1 / (1 + jnp.exp(-x[name_mapping[1][i]])) + name_mapping[0][i]: jnp.log(x[name_mapping[1][i]] / (1 - x[name_mapping[1][i]])) for i in range(len(name_mapping[1])) } diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index 4028164f..deb3fb98 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -106,4 +106,4 @@ strategies=[Adam_optimizer, "default"], ) -# jim.sample(jax.random.PRNGKey(42)) +jim.sample(jax.random.PRNGKey(42)) From 561e628785e290ed4d07f49a1b3dd7010140457c Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 31 Jul 2024 13:16:52 -0400 Subject: [PATCH 111/248] Move subclassing structure --- src/jimgw/jim.py | 29 ++++++++++---- src/jimgw/transforms.py | 86 +++++++++++++++-------------------------- 2 files changed, 54 insertions(+), 61 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 81d56836..08878293 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -8,6 +8,7 @@ from jimgw.base import LikelihoodBase from jimgw.prior import Prior, trace_prior_parent +from jimgw.transforms import BijectiveTransform, NtoMTransform class Jim(object): @@ -19,22 +20,30 @@ class Jim(object): prior: Prior # Name of parameters to sample from - parameter_names: list[str] + sample_transforms: list[BijectiveTransform] + likelihood_transforms: list[NtoMTransform] sampler: Sampler def __init__( self, likelihood: LikelihoodBase, prior: Prior, - parameter_names: list[str] | None = None, + sample_transforms: list[BijectiveTransform] = [], + likelihood_transforms: list[NtoMTransform] = [], **kwargs, ): self.likelihood = likelihood self.prior = prior - if parameter_names is None: - print("No parameter names provided. Using prior names.") - parameter_names = prior.parameter_names - self.parameter_names = parameter_names + + self.sample_transforms = sample_transforms + self.likelihood_transforms = likelihood_transforms + + if len(sample_transforms) == 0: + print("No sample transforms provided. Using prior parameters as sampling parameters") + + if len(likelihood_transforms) == 0: + print("No likelihood transforms provided. Using prior parameters as likelihood parameters") + seed = kwargs.get("seed", 0) @@ -75,7 +84,13 @@ def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: def posterior(self, params: Float[Array, " n_dim"], data: dict): named_params = self.add_name(params) - prior = self.prior.log_prob(named_params) + transform_jacobian = 0.0 + for transform in self.sample_transforms: + named_params, jacobian = transform.inverse(named_params) + transform_jacobian += jacobian + prior = self.prior.log_prob(named_params) + transform_jacobian + for transform in self.likelihood_transforms: + named_params = transform.forward(named_params) return self.likelihood.evaluate(named_params, data) + prior def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index bfabc2ad..fec18a79 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -28,18 +28,13 @@ def propagate_name(self, x: list[str]) -> list[str]: return list(input_set - from_set | to_set) -class NtoNTransform(Transform): +class NtoMTransform(Transform): transform_func: Callable[[dict[str, Float]], dict[str, Float]] - @property - def n_dim(self) -> int: - return len(self.name_mapping[0]) - - def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: + def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ - Transform the input x to transformed coordinate y and return the log Jacobian determinant. - This only works if the transform is a N -> N transform. + Push forward the input x to transformed coordinate y. Parameters ---------- @@ -50,17 +45,9 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: ------- y : dict[str, Float] The transformed dictionary. - log_det : Float - The log Jacobian determinant. """ x_copy = x.copy() - transform_params = dict((key, x_copy[key]) for key in self.name_mapping[0]) - output_params = self.transform_func(transform_params) - jacobian = jax.jacfwd(self.transform_func)(transform_params) - jacobian = jnp.array(jax.tree.leaves(jacobian)) - jacobian = jnp.log( - jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim)) - ) + output_params = self.transform_func(x_copy) jax.tree.map( lambda key: x_copy.pop(key), self.name_mapping[0], @@ -69,11 +56,21 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: lambda key: x_copy.update({key: output_params[key]}), list(output_params.keys()), ) - return x_copy, jacobian + return x_copy - def forward(self, x: dict[str, Float]) -> dict[str, Float]: + +class NtoNTransform(NtoMTransform): + + transform_func: Callable[[dict[str, Float]], dict[str, Float]] + + @property + def n_dim(self) -> int: + return len(self.name_mapping[0]) + + def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ - Push forward the input x to transformed coordinate y. + Transform the input x to transformed coordinate y and return the log Jacobian determinant. + This only works if the transform is a N -> N transform. Parameters ---------- @@ -84,9 +81,15 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: ------- y : dict[str, Float] The transformed dictionary. + log_det : Float + The log Jacobian determinant. """ x_copy = x.copy() - output_params = self.transform_func(x_copy) + transform_params = dict((key, x_copy[key]) for key in self.name_mapping[0]) + output_params = self.transform_func(transform_params) + jacobian = jax.jacfwd(self.transform_func)(transform_params) + jacobian = jnp.array(jax.tree.leaves(jacobian)) + jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) jax.tree.map( lambda key: x_copy.pop(key), self.name_mapping[0], @@ -95,16 +98,13 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: lambda key: x_copy.update({key: output_params[key]}), list(output_params.keys()), ) - return x_copy + return x_copy, jacobian class BijectiveTransform(NtoNTransform): inverse_transform_func: Callable[[dict[str, Float]], dict[str, Float]] - def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: - return self.transform(x) - def inverse(self, y: dict[str, Float]) -> dict[str, Float]: """ Inverse transform the input y to original coordinate x. @@ -124,9 +124,7 @@ def inverse(self, y: dict[str, Float]) -> dict[str, Float]: output_params = self.inverse_transform_func(transform_params) jacobian = jax.jacfwd(self.inverse_transform_func)(transform_params) jacobian = jnp.array(jax.tree.leaves(jacobian)) - jacobian = jnp.log( - jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim)) - ) + jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) jax.tree.map( lambda key: y_copy.pop(key), self.name_mapping[1], @@ -166,31 +164,6 @@ def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: return y_copy -class NtoMTransform(Transform): - - transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " m_dim"]] - - def __call__(self, x: dict[str, Float]) -> dict[str, Float]: - return self.forward(x) - - @abstractmethod - def forward(self, x: dict[str, Float]) -> dict[str, Float]: - """ - Push forward the input x to transformed coordinate y. - - Parameters - ---------- - x : dict[str, Float] - The input dictionary. - - Returns - ------- - y : dict[str, Float] - The transformed dictionary. - """ - raise NotImplementedError - - class ScaleTransform(BijectiveTransform): scale: Float @@ -230,6 +203,7 @@ def __init__( for i in range(len(name_mapping[1])) } + class LogitTransform(BijectiveTransform): """ Logit transform following @@ -251,10 +225,13 @@ def __init__( for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { - name_mapping[0][i]: jnp.log(x[name_mapping[1][i]] / (1 - x[name_mapping[1][i]])) + name_mapping[0][i]: jnp.log( + x[name_mapping[1][i]] / (1 - x[name_mapping[1][i]]) + ) for i in range(len(name_mapping[1])) } + class ArcSineTransform(BijectiveTransform): """ ArcSine transformation @@ -280,6 +257,7 @@ def __init__( for i in range(len(name_mapping[1])) } + # class PowerLawTransform(UnivariateTransform): # """ # PowerLaw transformation From 1aaf5ea91a6774ecc89314af3b953ac34c75539f Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 31 Jul 2024 13:59:44 -0400 Subject: [PATCH 112/248] Remove transformation --- src/jimgw/jim.py | 4 ++-- src/jimgw/transforms.py | 37 ++++++++----------------------------- 2 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 81d56836..03bcbc39 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -7,7 +7,7 @@ from jaxtyping import Array, Float, PRNGKeyArray from jimgw.base import LikelihoodBase -from jimgw.prior import Prior, trace_prior_parent +from jimgw.prior import Prior class Jim(object): @@ -75,7 +75,7 @@ def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: def posterior(self, params: Float[Array, " n_dim"], data: dict): named_params = self.add_name(params) - prior = self.prior.log_prob(named_params) + prior = self.prior.log_prob(named_params) return self.likelihood.evaluate(named_params, data) + prior def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 4a84d82f..7d1b5f3a 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -59,9 +59,7 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: output_params = self.transform_func(transform_params) jacobian = jax.jacfwd(self.transform_func)(transform_params) jacobian = jnp.array(jax.tree.leaves(jacobian)) - jacobian = jnp.log( - jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim)) - ) + jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) jax.tree.map( lambda key: x_copy.pop(key), self.name_mapping[0], @@ -125,9 +123,7 @@ def inverse(self, y: dict[str, Float]) -> dict[str, Float]: output_params = self.inverse_transform_func(transform_params) jacobian = jax.jacfwd(self.inverse_transform_func)(transform_params) jacobian = jnp.array(jax.tree.leaves(jacobian)) - jacobian = jnp.log( - jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim)) - ) + jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) jax.tree.map( lambda key: y_copy.pop(key), self.name_mapping[1], @@ -231,6 +227,7 @@ def __init__( for i in range(len(name_mapping[1])) } + class LogitTransform(BijectiveTransform): """ Logit transformation @@ -252,10 +249,13 @@ def __init__( for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { - name_mapping[0][i]: jnp.log(x[name_mapping[1][i]] / (1 - x[name_mapping[1][i]])) + name_mapping[0][i]: jnp.log( + x[name_mapping[1][i]] / (1 - x[name_mapping[1][i]]) + ) for i in range(len(name_mapping[1])) } + class ArcSineTransform(BijectiveTransform): """ ArcSine transformation @@ -281,6 +281,7 @@ def __init__( for i in range(len(name_mapping[1])) } + class ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): """ Transform component masses to chirp mass and mass ratio. @@ -301,28 +302,6 @@ def __init__( self.inverse_transform_func = lambda x: Mc_q_to_m1_m2(x[0], x[1]) -class RectangleToTriangleTransform(BijectiveTransform): - """ - Transform a rectangle grid with bounds [0, 1] x [0, 1] to a triangle grid with vertices (0, 0), (1, 0), (0, 1), while preserving the area density. - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): - super().__init__(name_mapping) - self.transform_func = lambda x: [ - 1 - jnp.sqrt(1 - x[0]), - x[1] * jnp.sqrt(1 - x[0]), - ] - self.inverse_transform_func = lambda x: [1 - (1 - x[0]) ** 2, x[1] / (1 - x[0])] - - # class PowerLawTransform(UnivariateTransform): # """ # PowerLaw transformation From 5bfdda13161e954861774881701f49855ff7a267 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 31 Jul 2024 14:01:02 -0400 Subject: [PATCH 113/248] Remove prior --- src/jimgw/prior.py | 47 ---------------------------------------------- 1 file changed, 47 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 7880648d..031a4133 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -12,7 +12,6 @@ ScaleTransform, OffsetTransform, ArcSineTransform, - RectangleToTriangleTransform, ) @@ -397,52 +396,6 @@ def __init__( ) -@jaxtyped(typechecker=typechecker) -class UniformComponentMassPrior(SequentialTransformPrior): - """ - A prior in the range [xmin, xmax) for component masses which assumes the - component masses to be uniformly distributed. - """ - - xmin: float - xmax: float - - def __repr__(self): - return f"UniformComponentMassPrior(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" - - def __init__(self, xmin: float, xmax: float, parameter_names: list[str]): - self.parameter_names = parameter_names - assert self.n_dim == 2, "UniformComponentMassPrior needs to be 2D distributions" - self.xmax = xmax - self.xmin = xmin - super().__init__( - CombinePrior( - [ - UniformPrior(xmin, xmax, ["x_1"]), - UniformPrior(xmin, xmax, ["x_2"]), - ] - ), - [ - ScaleTransform( - ( - ["x_1", "x_2"], - [f"x_1/({xmax-xmin})", f"x_2/({xmax-xmin})"], - ), - 1 / (xmax - xmin), - ), - RectangleToTriangleTransform( - ( - [ - f"{self.parameter_names[0]}/({xmax-xmin})", - f"{self.parameter_names[1]}/({xmax-xmin})", - ], - [self.parameter_names[0], self.parameter_names[1]], - ) - ), - ], - ) - - @jaxtyped(typechecker=typechecker) class UniformComponentChirpMassPrior(PowerLawPrior): """ From 77a6ad1785bb46a5d5f9163d9741e79d83ebacb9 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 31 Jul 2024 14:03:04 -0400 Subject: [PATCH 114/248] Merge branch '98-moving-naming-tracking-into-jim-class-from-prior-class' into transform --- src/jimgw/jim.py | 32 ++++++++++--- src/jimgw/transforms.py | 101 +++++++++++----------------------------- 2 files changed, 53 insertions(+), 80 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 03bcbc39..3186c2c4 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -8,6 +8,7 @@ from jimgw.base import LikelihoodBase from jimgw.prior import Prior +from jimgw.transforms import BijectiveTransform, NtoMTransform class Jim(object): @@ -19,22 +20,33 @@ class Jim(object): prior: Prior # Name of parameters to sample from - parameter_names: list[str] + sample_transforms: list[BijectiveTransform] + likelihood_transforms: list[NtoMTransform] sampler: Sampler def __init__( self, likelihood: LikelihoodBase, prior: Prior, - parameter_names: list[str] | None = None, + sample_transforms: list[BijectiveTransform] = [], + likelihood_transforms: list[NtoMTransform] = [], **kwargs, ): self.likelihood = likelihood self.prior = prior - if parameter_names is None: - print("No parameter names provided. Using prior names.") - parameter_names = prior.parameter_names - self.parameter_names = parameter_names + + self.sample_transforms = sample_transforms + self.likelihood_transforms = likelihood_transforms + + if len(sample_transforms) == 0: + print( + "No sample transforms provided. Using prior parameters as sampling parameters" + ) + + if len(likelihood_transforms) == 0: + print( + "No likelihood transforms provided. Using prior parameters as likelihood parameters" + ) seed = kwargs.get("seed", 0) @@ -75,7 +87,13 @@ def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: def posterior(self, params: Float[Array, " n_dim"], data: dict): named_params = self.add_name(params) - prior = self.prior.log_prob(named_params) + transform_jacobian = 0.0 + for transform in self.sample_transforms: + named_params, jacobian = transform.inverse(named_params) + transform_jacobian += jacobian + prior = self.prior.log_prob(named_params) + transform_jacobian + for transform in self.likelihood_transforms: + named_params = transform.forward(named_params) return self.likelihood.evaluate(named_params, data) + prior def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 7d1b5f3a..8de02f40 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -1,11 +1,9 @@ -from abc import ABC, abstractmethod +from abc import ABC from typing import Callable import jax import jax.numpy as jnp -from jaxtyping import Float, Array - -from jimgw.single_event.utils import m1_m2_to_Mc_q, Mc_q_to_m1_m2 +from jaxtyping import Float class Transform(ABC): @@ -29,18 +27,13 @@ def propagate_name(self, x: list[str]) -> list[str]: return list(input_set - from_set | to_set) -class NtoNTransform(Transform): +class NtoMTransform(Transform): transform_func: Callable[[dict[str, Float]], dict[str, Float]] - @property - def n_dim(self) -> int: - return len(self.name_mapping[0]) - - def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: + def forward(self, x: dict[str, Float]) -> dict[str, Float]: """ - Transform the input x to transformed coordinate y and return the log Jacobian determinant. - This only works if the transform is a N -> N transform. + Push forward the input x to transformed coordinate y. Parameters ---------- @@ -51,15 +44,9 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: ------- y : dict[str, Float] The transformed dictionary. - log_det : Float - The log Jacobian determinant. """ x_copy = x.copy() - transform_params = dict((key, x_copy[key]) for key in self.name_mapping[0]) - output_params = self.transform_func(transform_params) - jacobian = jax.jacfwd(self.transform_func)(transform_params) - jacobian = jnp.array(jax.tree.leaves(jacobian)) - jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) + output_params = self.transform_func(x_copy) jax.tree.map( lambda key: x_copy.pop(key), self.name_mapping[0], @@ -68,11 +55,21 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: lambda key: x_copy.update({key: output_params[key]}), list(output_params.keys()), ) - return x_copy, jacobian + return x_copy - def forward(self, x: dict[str, Float]) -> dict[str, Float]: + +class NtoNTransform(NtoMTransform): + + transform_func: Callable[[dict[str, Float]], dict[str, Float]] + + @property + def n_dim(self) -> int: + return len(self.name_mapping[0]) + + def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ - Push forward the input x to transformed coordinate y. + Transform the input x to transformed coordinate y and return the log Jacobian determinant. + This only works if the transform is a N -> N transform. Parameters ---------- @@ -83,9 +80,15 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: ------- y : dict[str, Float] The transformed dictionary. + log_det : Float + The log Jacobian determinant. """ x_copy = x.copy() - output_params = self.transform_func(x_copy) + transform_params = dict((key, x_copy[key]) for key in self.name_mapping[0]) + output_params = self.transform_func(transform_params) + jacobian = jax.jacfwd(self.transform_func)(transform_params) + jacobian = jnp.array(jax.tree.leaves(jacobian)) + jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) jax.tree.map( lambda key: x_copy.pop(key), self.name_mapping[0], @@ -94,16 +97,13 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: lambda key: x_copy.update({key: output_params[key]}), list(output_params.keys()), ) - return x_copy + return x_copy, jacobian class BijectiveTransform(NtoNTransform): inverse_transform_func: Callable[[dict[str, Float]], dict[str, Float]] - def __call__(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: - return self.transform(x) - def inverse(self, y: dict[str, Float]) -> dict[str, Float]: """ Inverse transform the input y to original coordinate x. @@ -163,31 +163,6 @@ def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: return y_copy -class NtoMTransform(Transform): - - transform_func: Callable[[Float[Array, " n_dim"]], Float[Array, " m_dim"]] - - def __call__(self, x: dict[str, Float]) -> dict[str, Float]: - return self.forward(x) - - @abstractmethod - def forward(self, x: dict[str, Float]) -> dict[str, Float]: - """ - Push forward the input x to transformed coordinate y. - - Parameters - ---------- - x : dict[str, Float] - The input dictionary. - - Returns - ------- - y : dict[str, Float] - The transformed dictionary. - """ - raise NotImplementedError - - class ScaleTransform(BijectiveTransform): scale: Float @@ -230,7 +205,7 @@ def __init__( class LogitTransform(BijectiveTransform): """ - Logit transformation + Logit transform following Parameters ---------- @@ -282,26 +257,6 @@ def __init__( } -class ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): - """ - Transform component masses to chirp mass and mass ratio. - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): - assert name_mapping == (["m_1", "m_2"], ["M_c", "q"]) - super().__init__(name_mapping) - self.transform_func = lambda x: m1_m2_to_Mc_q(x[0], x[1]) - self.inverse_transform_func = lambda x: Mc_q_to_m1_m2(x[0], x[1]) - - # class PowerLawTransform(UnivariateTransform): # """ # PowerLaw transformation From 2401df8a8acf989305c158051b887cc5620b3218 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Wed, 31 Jul 2024 16:02:55 -0400 Subject: [PATCH 115/248] Add mass transform --- src/jimgw/single_event/utils.py | 40 +++++++++++++++++++ src/jimgw/transforms.py | 70 +++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 3d07bb57..aaf4b947 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -163,6 +163,46 @@ def m1_m2_to_M_eta(m1: Float, m2: Float) -> tuple[Float, Float]: return M, eta +def Mc_q_to_eta(M_c: Float, q: Float) -> Float: + """ + Transforming the chirp mass M_c and mass ratio q to the symmetric mass ratio eta. + + Parameters + ---------- + M_c : Float + Chirp mass. + q : Float + Mass ratio. + + Returns + ------- + eta : Float + Symmetric mass ratio. + """ + eta = q / (1 + q) ** 2 + return eta + + +def eta_to_q(eta: Float) -> Float: + """ + Transforming the symmetric mass ratio eta to the mass ratio q. + + Copied and modified from bilby/gw/conversion.py + + Parameters + ---------- + eta : Float + Symmetric mass ratio. + + Returns + ------- + q : Float + Mass ratio. + """ + temp = 1 / eta / 2 - 1 + return temp - (temp**2 - 1) ** 0.5 + + def ra_dec_to_theta_phi(ra: Float, dec: Float, gmst: Float) -> tuple[Float, Float]: """ Transforming the right ascension ra and declination dec to the polar angle diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 8de02f40..2e80e3c5 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -5,6 +5,8 @@ import jax.numpy as jnp from jaxtyping import Float +from jimgw.single_event.utils import Mc_q_to_m1_m2, m1_m2_to_Mc_q, Mc_q_to_eta, eta_to_q + class Transform(ABC): """ @@ -257,6 +259,74 @@ def __init__( } +class ChirpMassMassRatioToComponentMassesTransform(BijectiveTransform): + """ + Transform chirp mass and mass ratio to component masses + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + + def named_transform(x): + Mc = x[name_mapping[0][0]] + q = x[name_mapping[0][1]] + m1, m2 = Mc_q_to_m1_m2(Mc, q) + return {name_mapping[1][0]: m1, name_mapping[1][1]: m2} + + self.transform_func = named_transform + + def named_inverse_transform(x): + m1 = x[name_mapping[1][0]] + m2 = x[name_mapping[1][1]] + Mc, q = m1_m2_to_Mc_q(m1, m2) + return {name_mapping[0][0]: Mc, name_mapping[0][1]: q} + + self.inverse_transform_func = named_inverse_transform + + +class ChirpMassMassRatioToChirpMassSymmetricMassRatioTransform(BijectiveTransform): + """ + Transform chirp mass and mass ratio to chirp mass and symmetric mass ratio + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + + def named_transform(x): + Mc = x[name_mapping[0][0]] + q = x[name_mapping[0][1]] + eta = Mc_q_to_eta(Mc, q) + return {name_mapping[1][0]: Mc, name_mapping[1][1]: eta} + + self.transform_func = named_transform + + def named_inverse_transform(x): + Mc = x[name_mapping[1][0]] + eta = x[name_mapping[1][1]] + q = eta_to_q(Mc, eta) + return {name_mapping[0][0]: Mc, name_mapping[0][1]: q} + + self.inverse_transform_func = named_inverse_transform + + # class PowerLawTransform(UnivariateTransform): # """ # PowerLaw transformation From ff35a82f0c4db96f64507ef5bcf4b06a58738000 Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 31 Jul 2024 16:24:48 -0400 Subject: [PATCH 116/248] Add Bound transforming transform --- src/jimgw/transforms.py | 112 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index fec18a79..57e787a5 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -4,7 +4,8 @@ import jax import jax.numpy as jnp from chex import assert_rank -from jaxtyping import Float, Array +from beartype import beartype as typechecker +from jaxtyping import Float, Array, jaxtyped class Transform(ABC): @@ -258,6 +259,115 @@ def __init__( } +@jaxtyped(typechecker=typechecker) +class BoundToBound(BijectiveTransform): + + """ + Bound to bound transformation + """ + + original_lower_bound: Float[Array, " n_dim"] + original_upper_bound: Float[Array, " n_dim"] + target_lower_bound: Float[Array, " n_dim"] + target_upper_bound: Float[Array, " n_dim"] + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + original_lower_bound: Float[Array, " n_dim"], + original_upper_bound: Float[Array, " n_dim"], + target_lower_bound: Float[Array, " n_dim"], + target_upper_bound: Float[Array, " n_dim"], + ): + super().__init__(name_mapping) + self.original_lower_bound = original_lower_bound + self.original_upper_bound = original_upper_bound + self.target_lower_bound = target_lower_bound + self.target_upper_bound = target_upper_bound + + self.transform_func = lambda x: { + name_mapping[1][i]: (x[name_mapping[0][i]] - self.original_lower_bound[i]) + * (self.target_upper_bound[i] - self.target_lower_bound[i]) + / (self.original_upper_bound[i] - self.original_lower_bound[i]) + + self.target_lower_bound[i] + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: (x[name_mapping[1][i]] - self.target_lower_bound[i]) + * (self.original_upper_bound[i] - self.original_lower_bound[i]) + / (self.target_upper_bound[i] - self.target_lower_bound[i]) + + self.original_lower_bound[i] + for i in range(len(name_mapping[1])) + } + +class BoundToUnbound(BijectiveTransform): + """ + Bound to unbound transformation + """ + + original_lower_bound: Float + original_upper_bound: Float + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + original_lower_bound: Float, + original_upper_bound: Float, + ): + + def logit(x): + return jnp.log(x / (1 - x)) + + super().__init__(name_mapping) + self.original_lower_bound = original_lower_bound + self.original_upper_bound = original_upper_bound + + self.transform_func = lambda x: { + name_mapping[1][i]: logit( + (x[name_mapping[0][i]] - self.original_lower_bound) + / (self.original_upper_bound - self.original_lower_bound) + ) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: ( + self.original_upper_bound - self.original_lower_bound + ) + / ( + 1 + + jnp.exp(-x[name_mapping[1][i]]) + ) + + self.original_lower_bound[i] + for i in range(len(name_mapping[1])) + } + +class SingleSidedUnboundTransform(BijectiveTransform): + """ + Unbound upper limit transformation + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + self.transform_func = lambda x: { + name_mapping[1][i]: jnp.exp(x[name_mapping[0][i]]) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: jnp.log(x[name_mapping[1][i]]) + for i in range(len(name_mapping[1])) + } + + + # class PowerLawTransform(UnivariateTransform): # """ # PowerLaw transformation From 47af9cfb2f70ac18e3547cd2f1be537f2e83aa57 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Wed, 31 Jul 2024 23:08:44 -0400 Subject: [PATCH 117/248] no nans now, but the code can be consolidate --- src/jimgw/jim.py | 13 +++++++++++-- src/jimgw/transforms.py | 5 +++-- test/integration/test_GW150914.py | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 08878293..39867e9f 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -22,6 +22,7 @@ class Jim(object): # Name of parameters to sample from sample_transforms: list[BijectiveTransform] likelihood_transforms: list[NtoMTransform] + parameter_names: list[str] sampler: Sampler def __init__( @@ -37,9 +38,14 @@ def __init__( self.sample_transforms = sample_transforms self.likelihood_transforms = likelihood_transforms + self.parameter_names = prior.parameter_names if len(sample_transforms) == 0: print("No sample transforms provided. Using prior parameters as sampling parameters") + else: + print("Using sample transforms") + for transform in sample_transforms: + self.parameter_names = transform.propagate_name(self.parameter_names) if len(likelihood_transforms) == 0: print("No likelihood transforms provided. Using prior parameters as likelihood parameters") @@ -91,12 +97,15 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): prior = self.prior.log_prob(named_params) + transform_jacobian for transform in self.likelihood_transforms: named_params = transform.forward(named_params) - return self.likelihood.evaluate(named_params, data) + prior + named_params = jax.tree.map(lambda x:x[0], named_params) # This [0] should be consolidate + return self.likelihood.evaluate(named_params, data) + prior[0] # This prior [0] should be consolidate def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): if initial_guess.size == 0: initial_guess_named = self.prior.sample(key, self.Sampler.n_chains) - initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T + for transform in self.sample_transforms: + initial_guess_named = jax.vmap(transform.forward)(initial_guess_named) + initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T[0] # This [0] should be consolidate self.Sampler.sample(initial_guess, None) # type: ignore def maximize_likelihood( diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 57e787a5..b832ccdc 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -300,6 +300,7 @@ def __init__( for i in range(len(name_mapping[1])) } +@jaxtyped(typechecker=typechecker) class BoundToUnbound(BijectiveTransform): """ Bound to unbound transformation @@ -319,8 +320,8 @@ def logit(x): return jnp.log(x / (1 - x)) super().__init__(name_mapping) - self.original_lower_bound = original_lower_bound - self.original_upper_bound = original_upper_bound + self.original_lower_bound = jnp.atleast_1d(original_lower_bound) + self.original_upper_bound = jnp.atleast_1d(original_upper_bound) self.transform_func = lambda x: { name_mapping[1][i]: logit( diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index deb3fb98..d82e8d7a 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -8,6 +8,7 @@ from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD +from jimgw.transforms import BoundToUnbound from flowMC.strategy.optimization import optimization_Adam jax.config.update("jax_enable_x64", True) @@ -64,6 +65,21 @@ dec_prior, ] ) + +sample_transforms = [ + BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), + BoundToUnbound(name_mapping = [["eta"], ["eta_unbounded"]], original_lower_bound=0.125, original_upper_bound=0.25), + BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["s2_z"], ["s2_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), + BoundToUnbound(name_mapping = [["t_c"], ["t_c_unbounded"]] , original_lower_bound=-0.05, original_upper_bound=0.05), + BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=-jnp.pi/2, original_upper_bound=jnp.pi/2), + BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["ra"], ["ra_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=0.0, original_upper_bound=jnp.pi) +] + likelihood = TransientLikelihoodFD( [H1, L1], waveform=RippleIMRPhenomD(), @@ -88,6 +104,7 @@ jim = Jim( likelihood, prior, + sample_transforms=sample_transforms, n_loop_training=n_loop_training, n_loop_production=1, n_local_steps=5, From b5e06a6972264597a03eb7f1afd2ab42d1e1eabd Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 10:19:40 -0400 Subject: [PATCH 118/248] Solve conflict --- src/jimgw/transforms.py | 107 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 2e80e3c5..b405f066 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -3,7 +3,8 @@ import jax import jax.numpy as jnp -from jaxtyping import Float +from beartype import beartype as typechecker +from jaxtyping import Float, Array, jaxtyped from jimgw.single_event.utils import Mc_q_to_m1_m2, m1_m2_to_Mc_q, Mc_q_to_eta, eta_to_q @@ -259,6 +260,110 @@ def __init__( } +@jaxtyped(typechecker=typechecker) +class BoundToBound(BijectiveTransform): + """ + Bound to bound transformation + """ + + original_lower_bound: Float[Array, " n_dim"] + original_upper_bound: Float[Array, " n_dim"] + target_lower_bound: Float[Array, " n_dim"] + target_upper_bound: Float[Array, " n_dim"] + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + original_lower_bound: Float[Array, " n_dim"], + original_upper_bound: Float[Array, " n_dim"], + target_lower_bound: Float[Array, " n_dim"], + target_upper_bound: Float[Array, " n_dim"], + ): + super().__init__(name_mapping) + self.original_lower_bound = original_lower_bound + self.original_upper_bound = original_upper_bound + self.target_lower_bound = target_lower_bound + self.target_upper_bound = target_upper_bound + + self.transform_func = lambda x: { + name_mapping[1][i]: (x[name_mapping[0][i]] - self.original_lower_bound[i]) + * (self.target_upper_bound[i] - self.target_lower_bound[i]) + / (self.original_upper_bound[i] - self.original_lower_bound[i]) + + self.target_lower_bound[i] + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: (x[name_mapping[1][i]] - self.target_lower_bound[i]) + * (self.original_upper_bound[i] - self.original_lower_bound[i]) + / (self.target_upper_bound[i] - self.target_lower_bound[i]) + + self.original_lower_bound[i] + for i in range(len(name_mapping[1])) + } + + +class BoundToUnbound(BijectiveTransform): + """ + Bound to unbound transformation + """ + + original_lower_bound: Float + original_upper_bound: Float + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + original_lower_bound: Float, + original_upper_bound: Float, + ): + + def logit(x): + return jnp.log(x / (1 - x)) + + super().__init__(name_mapping) + self.original_lower_bound = original_lower_bound + self.original_upper_bound = original_upper_bound + + self.transform_func = lambda x: { + name_mapping[1][i]: logit( + (x[name_mapping[0][i]] - self.original_lower_bound) + / (self.original_upper_bound - self.original_lower_bound) + ) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: (self.original_upper_bound - self.original_lower_bound) + / (1 + jnp.exp(-x[name_mapping[1][i]])) + + self.original_lower_bound[i] + for i in range(len(name_mapping[1])) + } + + +class SingleSidedUnboundTransform(BijectiveTransform): + """ + Unbound upper limit transformation + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + self.transform_func = lambda x: { + name_mapping[1][i]: jnp.exp(x[name_mapping[0][i]]) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: jnp.log(x[name_mapping[1][i]]) + for i in range(len(name_mapping[1])) + } + + class ChirpMassMassRatioToComponentMassesTransform(BijectiveTransform): """ Transform chirp mass and mass ratio to component masses From 255fad34c3a05c1031dd3cda58e67e1e4f45dac5 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:29:56 -0400 Subject: [PATCH 119/248] Fixed powerLaw --- src/jimgw/jim.py | 11 +++++--- src/jimgw/transforms.py | 57 ++++++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 08878293..3186c2c4 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -7,7 +7,7 @@ from jaxtyping import Array, Float, PRNGKeyArray from jimgw.base import LikelihoodBase -from jimgw.prior import Prior, trace_prior_parent +from jimgw.prior import Prior from jimgw.transforms import BijectiveTransform, NtoMTransform @@ -39,11 +39,14 @@ def __init__( self.likelihood_transforms = likelihood_transforms if len(sample_transforms) == 0: - print("No sample transforms provided. Using prior parameters as sampling parameters") + print( + "No sample transforms provided. Using prior parameters as sampling parameters" + ) if len(likelihood_transforms) == 0: - print("No likelihood transforms provided. Using prior parameters as likelihood parameters") - + print( + "No likelihood transforms provided. Using prior parameters as likelihood parameters" + ) seed = kwargs.get("seed", 0) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 88af8cb4..fa2ead90 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -1,9 +1,8 @@ -from abc import ABC, abstractmethod +from abc import ABC from typing import Callable import jax import jax.numpy as jnp -from chex import assert_rank from beartype import beartype as typechecker from jaxtyping import Float, Array, jaxtyped @@ -261,7 +260,6 @@ def __init__( @jaxtyped(typechecker=typechecker) class BoundToBound(BijectiveTransform): - """ Bound to bound transformation """ @@ -300,6 +298,7 @@ def __init__( for i in range(len(name_mapping[1])) } + class BoundToUnbound(BijectiveTransform): """ Bound to unbound transformation @@ -314,7 +313,7 @@ def __init__( original_lower_bound: Float, original_upper_bound: Float, ): - + def logit(x): return jnp.log(x / (1 - x)) @@ -330,17 +329,13 @@ def logit(x): for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { - name_mapping[0][i]: ( - self.original_upper_bound - self.original_lower_bound - ) - / ( - 1 - + jnp.exp(-x[name_mapping[1][i]]) - ) + name_mapping[0][i]: (self.original_upper_bound - self.original_lower_bound) + / (1 + jnp.exp(-x[name_mapping[1][i]])) + self.original_lower_bound[i] for i in range(len(name_mapping[1])) } + class SingleSidedUnboundTransform(BijectiveTransform): """ Unbound upper limit transformation @@ -367,7 +362,6 @@ def __init__( } - class PowerLawTransform(BijectiveTransform): """ PowerLaw transformation @@ -392,14 +386,22 @@ def __init__( self.xmin = xmin self.xmax = xmax self.alpha = alpha - self.transform_func = lambda x: [( - self.xmin ** (1.0 + self.alpha) - + x[0] * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - ) ** (1.0 / (1.0 + self.alpha))] - self.inverse_transform_func = lambda x: [( - (x[0] ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - / (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - )] + self.transform_func = lambda x: { + name_mapping[1][i]: ( + self.xmin ** (1.0 + self.alpha) + + x[0] + * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) + ** (1.0 / (1.0 + self.alpha)) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: ( + (x[0] ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + / (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) + for i in range(len(name_mapping[1])) + } class ParetoTransform(BijectiveTransform): @@ -420,7 +422,14 @@ def __init__( super().__init__(name_mapping) self.xmin = xmin self.xmax = xmax - self.transform_func = lambda x: [self.xmin * jnp.exp( - x[0] * jnp.log(self.xmax / self.xmin) - )] - self.inverse_transform_func = lambda x: [(jnp.log(x[0] / self.xmin) / jnp.log(self.xmax / self.xmin))] + self.transform_func = lambda x: { + name_mapping[1][i]: self.xmin + * jnp.exp(x[0] * jnp.log(self.xmax / self.xmin)) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: ( + jnp.log(x[0] / self.xmin) / jnp.log(self.xmax / self.xmin) + ) + for i in range(len(name_mapping[1])) + } From fcdc120014c2bc07b736a3b48b10dfcad33fb498 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:36:31 -0400 Subject: [PATCH 120/248] Fixed powerLaw --- src/jimgw/transforms.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index fa2ead90..9c7035d4 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -389,7 +389,7 @@ def __init__( self.transform_func = lambda x: { name_mapping[1][i]: ( self.xmin ** (1.0 + self.alpha) - + x[0] + + x[name_mapping[0][i]] * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) ) ** (1.0 / (1.0 + self.alpha)) @@ -397,7 +397,7 @@ def __init__( } self.inverse_transform_func = lambda x: { name_mapping[0][i]: ( - (x[0] ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + (x[name_mapping[1][i]] ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) / (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) ) for i in range(len(name_mapping[1])) @@ -424,12 +424,12 @@ def __init__( self.xmax = xmax self.transform_func = lambda x: { name_mapping[1][i]: self.xmin - * jnp.exp(x[0] * jnp.log(self.xmax / self.xmin)) + * jnp.exp(x[name_mapping[0][i]] * jnp.log(self.xmax / self.xmin)) for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { name_mapping[0][i]: ( - jnp.log(x[0] / self.xmin) / jnp.log(self.xmax / self.xmin) + jnp.log(x[name_mapping[1][i]] / self.xmin) / jnp.log(self.xmax / self.xmin) ) for i in range(len(name_mapping[1])) } From 06eb3ad5b430b168e0647cc8744267bf09575632 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:39:09 -0400 Subject: [PATCH 121/248] Reformatted --- src/jimgw/transforms.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 9c7035d4..cfd03b3f 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -397,7 +397,10 @@ def __init__( } self.inverse_transform_func = lambda x: { name_mapping[0][i]: ( - (x[name_mapping[1][i]] ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ( + x[name_mapping[1][i]] ** (1.0 + self.alpha) + - self.xmin ** (1.0 + self.alpha) + ) / (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) ) for i in range(len(name_mapping[1])) @@ -429,7 +432,8 @@ def __init__( } self.inverse_transform_func = lambda x: { name_mapping[0][i]: ( - jnp.log(x[name_mapping[1][i]] / self.xmin) / jnp.log(self.xmax / self.xmin) + jnp.log(x[name_mapping[1][i]] / self.xmin) + / jnp.log(self.xmax / self.xmin) ) for i in range(len(name_mapping[1])) } From 8e5b326c295f94f2390ab40427dd63455f2839b0 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 11:06:30 -0400 Subject: [PATCH 122/248] Add sky position transform --- src/jimgw/single_event/utils.py | 143 +++++++++++++++++++++++++------- src/jimgw/transforms.py | 47 ++++++++++- 2 files changed, 155 insertions(+), 35 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index aaf4b947..2373c5be 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -203,32 +203,6 @@ def eta_to_q(eta: Float) -> Float: return temp - (temp**2 - 1) ** 0.5 -def ra_dec_to_theta_phi(ra: Float, dec: Float, gmst: Float) -> tuple[Float, Float]: - """ - Transforming the right ascension ra and declination dec to the polar angle - theta and azimuthal angle phi. - - Parameters - ---------- - ra : Float - Right ascension. - dec : Float - Declination. - gmst : Float - Greenwich mean sidereal time. - - Returns - ------- - theta : Float - Polar angle. - phi : Float - Azimuthal angle. - """ - phi = ra - gmst - theta = jnp.pi / 2 - dec - return theta, phi - - def euler_rotation(delta_x: Float[Array, " 3"]): """ Calculate the rotation matrix mapping the vector (0, 0, 1) to delta_x @@ -239,11 +213,10 @@ def euler_rotation(delta_x: Float[Array, " 3"]): Copied and modified from bilby-cython/geometry.pyx """ - norm = jnp.power( - delta_x[0] * delta_x[0] + delta_x[1] * delta_x[1] + delta_x[2] * delta_x[2], 0.5 - ) + norm = jnp.linalg.vector_norm(delta_x) + cos_beta = delta_x[2] / norm - sin_beta = jnp.power(1 - cos_beta**2, 0.5) + sin_beta = jnp.sqrt(1 - cos_beta**2) alpha = jnp.atan2(-delta_x[1] * cos_beta, delta_x[0]) gamma = jnp.atan2(delta_x[1], delta_x[0]) @@ -318,7 +291,7 @@ def zenith_azimuth_to_theta_phi( + rotation[0][2] * cos_zenith, ) + 2 * jnp.pi, - (2 * jnp.pi), + 2 * jnp.pi, ) return theta, phi @@ -345,6 +318,7 @@ def theta_phi_to_ra_dec(theta: Float, phi: Float, gmst: Float) -> tuple[Float, F """ ra = phi + gmst dec = jnp.pi / 2 - theta + ra = ra % (2 * jnp.pi) return ra, dec @@ -376,10 +350,115 @@ def zenith_azimuth_to_ra_dec( """ theta, phi = zenith_azimuth_to_theta_phi(zenith, azimuth, delta_x) ra, dec = theta_phi_to_ra_dec(theta, phi, gmst) - ra = ra % (2 * jnp.pi) return ra, dec +def ra_dec_to_theta_phi(ra: Float, dec: Float, gmst: Float) -> tuple[Float, Float]: + """ + Transforming the right ascension ra and declination dec to the polar angle + theta and azimuthal angle phi. + + Parameters + ---------- + ra : Float + Right ascension. + dec : Float + Declination. + gmst : Float + Greenwich mean sidereal time. + + Returns + ------- + theta : Float + Polar angle. + phi : Float + Azimuthal angle. + """ + phi = ra - gmst + theta = jnp.pi / 2 - dec + phi = (phi + 2 * jnp.pi) % (2 * jnp.pi) + return theta, phi + + +def theta_phi_to_zenith_azimuth( + theta: Float, phi: Float, delta_x: Float[Array, " 3"] +) -> tuple[Float, Float]: + """ + Transforming the polar angle and azimuthal angle to the zenith angle and azimuthal angle. + + Parameters + ---------- + theta : Float + Polar angle. + phi : Float + Azimuthal angle. + delta_x : Float + The vector pointing from the first detector to the second detector. + + Returns + ------- + zenith : Float + Zenith angle. + azimuth : Float + Azimuthal angle. + """ + sin_theta = jnp.sin(theta) + cos_theta = jnp.cos(theta) + sin_phi = jnp.sin(phi) + cos_phi = jnp.cos(phi) + + rotation = euler_rotation(delta_x) + rotation = jnp.linalg.inv(rotation) + + zenith = jnp.acos( + rotation[2][0] * sin_theta * cos_phi + + rotation[2][1] * sin_theta * sin_phi + + rotation[2][2] * cos_theta + ) + azimuth = jnp.fmod( + jnp.atan2( + rotation[1][0] * sin_theta * cos_phi + + rotation[1][1] * sin_theta * sin_phi + + rotation[1][2] * cos_theta, + rotation[0][0] * sin_theta * cos_phi + + rotation[0][1] * sin_theta * sin_phi + + rotation[0][2] * cos_theta, + ) + + 2 * jnp.pi, + 2 * jnp.pi, + ) + return zenith, azimuth + + +def ra_dec_to_zenith_azimuth( + ra: Float, dec: Float, gmst: Float, delta_x: Float[Array, " 3"] +) -> tuple[Float, Float]: + """ + Transforming the right ascension and declination to the zenith angle and azimuthal angle. + + Parameters + ---------- + ra : Float + Right ascension. + dec : Float + Declination. + gmst : Float + Greenwich mean sidereal time. + delta_x : Float + The vector pointing from the first detector to the second detector. + + Returns + ------- + zenith : Float + Zenith angle. + azimuth : Float + Azimuthal angle. + """ + theta, phi = ra_dec_to_theta_phi(ra, dec, gmst) + zenith, azimuth = theta_phi_to_zenith_azimuth(theta, phi, delta_x) + return zenith, azimuth + + def log_i0(x: Float[Array, " n"]) -> Float[Array, " n"]: """ A numerically stable method to evaluate log of diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 5d0d1643..d644c8e1 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -6,7 +6,14 @@ from beartype import beartype as typechecker from jaxtyping import Float, Array, jaxtyped -from jimgw.single_event.utils import Mc_q_to_m1_m2, m1_m2_to_Mc_q, Mc_q_to_eta, eta_to_q +from jimgw.single_event.utils import ( + Mc_q_to_m1_m2, + m1_m2_to_Mc_q, + Mc_q_to_eta, + eta_to_q, + ra_dec_to_zenith_azimuth, + zenith_azimuth_to_ra_dec, +) class Transform(ABC): @@ -300,7 +307,7 @@ def __init__( for i in range(len(name_mapping[1])) } - + class BoundToUnbound(BijectiveTransform): """ Bound to unbound transformation @@ -315,7 +322,7 @@ def __init__( original_lower_bound: Float, original_upper_bound: Float, ): - + def logit(x): return jnp.log(x / (1 - x)) @@ -432,6 +439,40 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform +class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): + """ + Transform sky frame to detector frame sky position + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + + def named_transform(x): + ra = x[name_mapping[0][0]] + dec = x[name_mapping[0][1]] + zenith, azimuth = ra_dec_to_zenith_azimuth(ra, dec) + return {name_mapping[1][0]: zenith, name_mapping[1][1]: azimuth} + + self.transform_func = named_transform + + def named_inverse_transform(x): + zenith = x[name_mapping[1][0]] + azimuth = x[name_mapping[1][1]] + ra, dec = zenith_azimuth_to_ra_dec(zenith, azimuth) + return {name_mapping[0][0]: ra, name_mapping[0][1]: dec} + + self.inverse_transform_func = named_inverse_transform + + # class PowerLawTransform(UnivariateTransform): # """ # PowerLaw transformation From 10d51b2b05723b4e0eb3e9b0e3bf2342b841b535 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 11:21:06 -0400 Subject: [PATCH 123/248] Modify sky position transform --- src/jimgw/single_event/utils.py | 64 ++++----------------------------- src/jimgw/transforms.py | 19 ++++++++-- 2 files changed, 23 insertions(+), 60 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 2373c5be..705857fe 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -245,8 +245,8 @@ def euler_rotation(delta_x: Float[Array, " 3"]): return rotation -def zenith_azimuth_to_theta_phi( - zenith: Float, azimuth: Float, delta_x: Float[Array, " 3"] +def angle_rotation( + zenith: Float, azimuth: Float, rotation: Float[Array, " 3 3"] ) -> tuple[Float, Float]: """ Transforming the azimuthal angle and zenith angle in Earth frame to the polar angle and azimuthal angle in sky frame. @@ -274,8 +274,6 @@ def zenith_azimuth_to_theta_phi( sin_zenith = jnp.sin(zenith) cos_zenith = jnp.cos(zenith) - rotation = euler_rotation(delta_x) - theta = jnp.acos( rotation[2][0] * sin_zenith * cos_azimuth + rotation[2][1] * sin_zenith * sin_azimuth @@ -323,7 +321,7 @@ def theta_phi_to_ra_dec(theta: Float, phi: Float, gmst: Float) -> tuple[Float, F def zenith_azimuth_to_ra_dec( - zenith: Float, azimuth: Float, gmst: Float, delta_x: Float[Array, " 3"] + zenith: Float, azimuth: Float, gmst: Float, rotation: Float[Array, " 3 3"] ) -> tuple[Float, Float]: """ Transforming the azimuthal angle and zenith angle in Earth frame to right ascension and declination. @@ -348,7 +346,7 @@ def zenith_azimuth_to_ra_dec( dec : Float Declination. """ - theta, phi = zenith_azimuth_to_theta_phi(zenith, azimuth, delta_x) + theta, phi = angle_rotation(zenith, azimuth, rotation) ra, dec = theta_phi_to_ra_dec(theta, phi, gmst) return ra, dec @@ -380,58 +378,8 @@ def ra_dec_to_theta_phi(ra: Float, dec: Float, gmst: Float) -> tuple[Float, Floa return theta, phi -def theta_phi_to_zenith_azimuth( - theta: Float, phi: Float, delta_x: Float[Array, " 3"] -) -> tuple[Float, Float]: - """ - Transforming the polar angle and azimuthal angle to the zenith angle and azimuthal angle. - - Parameters - ---------- - theta : Float - Polar angle. - phi : Float - Azimuthal angle. - delta_x : Float - The vector pointing from the first detector to the second detector. - - Returns - ------- - zenith : Float - Zenith angle. - azimuth : Float - Azimuthal angle. - """ - sin_theta = jnp.sin(theta) - cos_theta = jnp.cos(theta) - sin_phi = jnp.sin(phi) - cos_phi = jnp.cos(phi) - - rotation = euler_rotation(delta_x) - rotation = jnp.linalg.inv(rotation) - - zenith = jnp.acos( - rotation[2][0] * sin_theta * cos_phi - + rotation[2][1] * sin_theta * sin_phi - + rotation[2][2] * cos_theta - ) - azimuth = jnp.fmod( - jnp.atan2( - rotation[1][0] * sin_theta * cos_phi - + rotation[1][1] * sin_theta * sin_phi - + rotation[1][2] * cos_theta, - rotation[0][0] * sin_theta * cos_phi - + rotation[0][1] * sin_theta * sin_phi - + rotation[0][2] * cos_theta, - ) - + 2 * jnp.pi, - 2 * jnp.pi, - ) - return zenith, azimuth - - def ra_dec_to_zenith_azimuth( - ra: Float, dec: Float, gmst: Float, delta_x: Float[Array, " 3"] + ra: Float, dec: Float, gmst: Float, rotation: Float[Array, " 3 3"] ) -> tuple[Float, Float]: """ Transforming the right ascension and declination to the zenith angle and azimuthal angle. @@ -455,7 +403,7 @@ def ra_dec_to_zenith_azimuth( Azimuthal angle. """ theta, phi = ra_dec_to_theta_phi(ra, dec, gmst) - zenith, azimuth = theta_phi_to_zenith_azimuth(theta, phi, delta_x) + zenith, azimuth = angle_rotation(theta, phi, rotation) return zenith, azimuth diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index d644c8e1..5a889569 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -13,6 +13,7 @@ eta_to_q, ra_dec_to_zenith_azimuth, zenith_azimuth_to_ra_dec, + euler_rotation, ) @@ -450,16 +451,28 @@ class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): """ + gmst: Float + rotation: Float[Array, " 3 3"] + rotation_inv: Float[Array, " 3 3"] + def __init__( self, name_mapping: tuple[list[str], list[str]], + gmst: Float, + delta_x: Float, ): super().__init__(name_mapping) + self.gmst = gmst + self.rotation = euler_rotation(delta_x) + self.rotation_inv = jnp.linalg.inv(self.rotation) + def named_transform(x): ra = x[name_mapping[0][0]] dec = x[name_mapping[0][1]] - zenith, azimuth = ra_dec_to_zenith_azimuth(ra, dec) + zenith, azimuth = ra_dec_to_zenith_azimuth( + ra, dec, self.gmst, self.rotation + ) return {name_mapping[1][0]: zenith, name_mapping[1][1]: azimuth} self.transform_func = named_transform @@ -467,7 +480,9 @@ def named_transform(x): def named_inverse_transform(x): zenith = x[name_mapping[1][0]] azimuth = x[name_mapping[1][1]] - ra, dec = zenith_azimuth_to_ra_dec(zenith, azimuth) + ra, dec = zenith_azimuth_to_ra_dec( + zenith, azimuth, self.gmst, self.rotation_inv + ) return {name_mapping[0][0]: ra, name_mapping[0][1]: dec} self.inverse_transform_func = named_inverse_transform From 8368d00bd9a5d3a41e670036bdcfcbeec07dfef3 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 11:55:30 -0400 Subject: [PATCH 124/248] Change util func name --- src/jimgw/single_event/utils.py | 2 +- src/jimgw/transforms.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 705857fe..8721c176 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -163,7 +163,7 @@ def m1_m2_to_M_eta(m1: Float, m2: Float) -> tuple[Float, Float]: return M, eta -def Mc_q_to_eta(M_c: Float, q: Float) -> Float: +def q_to_eta(q: Float) -> Float: """ Transforming the chirp mass M_c and mass ratio q to the symmetric mass ratio eta. diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 5a889569..165432eb 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -9,7 +9,7 @@ from jimgw.single_event.utils import ( Mc_q_to_m1_m2, m1_m2_to_Mc_q, - Mc_q_to_eta, + q_to_eta, eta_to_q, ra_dec_to_zenith_azimuth, zenith_azimuth_to_ra_dec, @@ -426,7 +426,7 @@ def __init__( def named_transform(x): Mc = x[name_mapping[0][0]] q = x[name_mapping[0][1]] - eta = Mc_q_to_eta(Mc, q) + eta = q_to_eta(q) return {name_mapping[1][0]: Mc, name_mapping[1][1]: eta} self.transform_func = named_transform From 1cb0d11c2f58c8413cc34f823ca7943c0f820f59 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 11:59:38 -0400 Subject: [PATCH 125/248] Revert "Merge branch '98-moving-naming-tracking-into-jim-class-from-prior-class' into transform" This reverts commit b4f60527bf7ec4e002698e32458b7da659a88da5, reversing changes made to 8368d00bd9a5d3a41e670036bdcfcbeec07dfef3. --- src/jimgw/jim.py | 24 ++--- src/jimgw/transforms.py | 147 +++++++++++++++++++++++++++--- test/integration/test_GW150914.py | 17 ---- 3 files changed, 142 insertions(+), 46 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 39867e9f..3186c2c4 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -7,7 +7,7 @@ from jaxtyping import Array, Float, PRNGKeyArray from jimgw.base import LikelihoodBase -from jimgw.prior import Prior, trace_prior_parent +from jimgw.prior import Prior from jimgw.transforms import BijectiveTransform, NtoMTransform @@ -22,7 +22,6 @@ class Jim(object): # Name of parameters to sample from sample_transforms: list[BijectiveTransform] likelihood_transforms: list[NtoMTransform] - parameter_names: list[str] sampler: Sampler def __init__( @@ -38,18 +37,16 @@ def __init__( self.sample_transforms = sample_transforms self.likelihood_transforms = likelihood_transforms - self.parameter_names = prior.parameter_names if len(sample_transforms) == 0: - print("No sample transforms provided. Using prior parameters as sampling parameters") - else: - print("Using sample transforms") - for transform in sample_transforms: - self.parameter_names = transform.propagate_name(self.parameter_names) + print( + "No sample transforms provided. Using prior parameters as sampling parameters" + ) if len(likelihood_transforms) == 0: - print("No likelihood transforms provided. Using prior parameters as likelihood parameters") - + print( + "No likelihood transforms provided. Using prior parameters as likelihood parameters" + ) seed = kwargs.get("seed", 0) @@ -97,15 +94,12 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): prior = self.prior.log_prob(named_params) + transform_jacobian for transform in self.likelihood_transforms: named_params = transform.forward(named_params) - named_params = jax.tree.map(lambda x:x[0], named_params) # This [0] should be consolidate - return self.likelihood.evaluate(named_params, data) + prior[0] # This prior [0] should be consolidate + return self.likelihood.evaluate(named_params, data) + prior def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): if initial_guess.size == 0: initial_guess_named = self.prior.sample(key, self.Sampler.n_chains) - for transform in self.sample_transforms: - initial_guess_named = jax.vmap(transform.forward)(initial_guess_named) - initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T[0] # This [0] should be consolidate + initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T self.Sampler.sample(initial_guess, None) # type: ignore def maximize_likelihood( diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index b832ccdc..165432eb 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -1,12 +1,21 @@ -from abc import ABC, abstractmethod +from abc import ABC from typing import Callable import jax import jax.numpy as jnp -from chex import assert_rank from beartype import beartype as typechecker from jaxtyping import Float, Array, jaxtyped +from jimgw.single_event.utils import ( + Mc_q_to_m1_m2, + m1_m2_to_Mc_q, + q_to_eta, + eta_to_q, + ra_dec_to_zenith_azimuth, + zenith_azimuth_to_ra_dec, + euler_rotation, +) + class Transform(ABC): """ @@ -261,7 +270,6 @@ def __init__( @jaxtyped(typechecker=typechecker) class BoundToBound(BijectiveTransform): - """ Bound to bound transformation """ @@ -300,7 +308,7 @@ def __init__( for i in range(len(name_mapping[1])) } -@jaxtyped(typechecker=typechecker) + class BoundToUnbound(BijectiveTransform): """ Bound to unbound transformation @@ -315,13 +323,13 @@ def __init__( original_lower_bound: Float, original_upper_bound: Float, ): - + def logit(x): return jnp.log(x / (1 - x)) super().__init__(name_mapping) - self.original_lower_bound = jnp.atleast_1d(original_lower_bound) - self.original_upper_bound = jnp.atleast_1d(original_upper_bound) + self.original_lower_bound = original_lower_bound + self.original_upper_bound = original_upper_bound self.transform_func = lambda x: { name_mapping[1][i]: logit( @@ -331,17 +339,13 @@ def logit(x): for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { - name_mapping[0][i]: ( - self.original_upper_bound - self.original_lower_bound - ) - / ( - 1 - + jnp.exp(-x[name_mapping[1][i]]) - ) + name_mapping[0][i]: (self.original_upper_bound - self.original_lower_bound) + / (1 + jnp.exp(-x[name_mapping[1][i]])) + self.original_lower_bound[i] for i in range(len(name_mapping[1])) } + class SingleSidedUnboundTransform(BijectiveTransform): """ Unbound upper limit transformation @@ -368,6 +372,121 @@ def __init__( } +class ChirpMassMassRatioToComponentMassesTransform(BijectiveTransform): + """ + Transform chirp mass and mass ratio to component masses + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + + def named_transform(x): + Mc = x[name_mapping[0][0]] + q = x[name_mapping[0][1]] + m1, m2 = Mc_q_to_m1_m2(Mc, q) + return {name_mapping[1][0]: m1, name_mapping[1][1]: m2} + + self.transform_func = named_transform + + def named_inverse_transform(x): + m1 = x[name_mapping[1][0]] + m2 = x[name_mapping[1][1]] + Mc, q = m1_m2_to_Mc_q(m1, m2) + return {name_mapping[0][0]: Mc, name_mapping[0][1]: q} + + self.inverse_transform_func = named_inverse_transform + + +class ChirpMassMassRatioToChirpMassSymmetricMassRatioTransform(BijectiveTransform): + """ + Transform chirp mass and mass ratio to chirp mass and symmetric mass ratio + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + + def named_transform(x): + Mc = x[name_mapping[0][0]] + q = x[name_mapping[0][1]] + eta = q_to_eta(q) + return {name_mapping[1][0]: Mc, name_mapping[1][1]: eta} + + self.transform_func = named_transform + + def named_inverse_transform(x): + Mc = x[name_mapping[1][0]] + eta = x[name_mapping[1][1]] + q = eta_to_q(Mc, eta) + return {name_mapping[0][0]: Mc, name_mapping[0][1]: q} + + self.inverse_transform_func = named_inverse_transform + + +class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): + """ + Transform sky frame to detector frame sky position + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + gmst: Float + rotation: Float[Array, " 3 3"] + rotation_inv: Float[Array, " 3 3"] + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + gmst: Float, + delta_x: Float, + ): + super().__init__(name_mapping) + + self.gmst = gmst + self.rotation = euler_rotation(delta_x) + self.rotation_inv = jnp.linalg.inv(self.rotation) + + def named_transform(x): + ra = x[name_mapping[0][0]] + dec = x[name_mapping[0][1]] + zenith, azimuth = ra_dec_to_zenith_azimuth( + ra, dec, self.gmst, self.rotation + ) + return {name_mapping[1][0]: zenith, name_mapping[1][1]: azimuth} + + self.transform_func = named_transform + + def named_inverse_transform(x): + zenith = x[name_mapping[1][0]] + azimuth = x[name_mapping[1][1]] + ra, dec = zenith_azimuth_to_ra_dec( + zenith, azimuth, self.gmst, self.rotation_inv + ) + return {name_mapping[0][0]: ra, name_mapping[0][1]: dec} + + self.inverse_transform_func = named_inverse_transform + # class PowerLawTransform(UnivariateTransform): # """ diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index d82e8d7a..deb3fb98 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -8,7 +8,6 @@ from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD -from jimgw.transforms import BoundToUnbound from flowMC.strategy.optimization import optimization_Adam jax.config.update("jax_enable_x64", True) @@ -65,21 +64,6 @@ dec_prior, ] ) - -sample_transforms = [ - BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), - BoundToUnbound(name_mapping = [["eta"], ["eta_unbounded"]], original_lower_bound=0.125, original_upper_bound=0.25), - BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = [["s2_z"], ["s2_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), - BoundToUnbound(name_mapping = [["t_c"], ["t_c_unbounded"]] , original_lower_bound=-0.05, original_upper_bound=0.05), - BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=-jnp.pi/2, original_upper_bound=jnp.pi/2), - BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["ra"], ["ra_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=0.0, original_upper_bound=jnp.pi) -] - likelihood = TransientLikelihoodFD( [H1, L1], waveform=RippleIMRPhenomD(), @@ -104,7 +88,6 @@ jim = Jim( likelihood, prior, - sample_transforms=sample_transforms, n_loop_training=n_loop_training, n_loop_production=1, n_local_steps=5, From 78e93c502ed24b77f74f2cbb8b662ca54f3c4d56 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 12:03:00 -0400 Subject: [PATCH 126/248] Merge --- src/jimgw/jim.py | 19 +++++++++++++++++-- src/jimgw/transforms.py | 5 +++-- test/integration/test_GW150914.py | 17 +++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 3186c2c4..2a9cb952 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -22,6 +22,7 @@ class Jim(object): # Name of parameters to sample from sample_transforms: list[BijectiveTransform] likelihood_transforms: list[NtoMTransform] + parameter_names: list[str] sampler: Sampler def __init__( @@ -37,11 +38,16 @@ def __init__( self.sample_transforms = sample_transforms self.likelihood_transforms = likelihood_transforms + self.parameter_names = prior.parameter_names if len(sample_transforms) == 0: print( "No sample transforms provided. Using prior parameters as sampling parameters" ) + else: + print("Using sample transforms") + for transform in sample_transforms: + self.parameter_names = transform.propagate_name(self.parameter_names) if len(likelihood_transforms) == 0: print( @@ -94,12 +100,21 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): prior = self.prior.log_prob(named_params) + transform_jacobian for transform in self.likelihood_transforms: named_params = transform.forward(named_params) - return self.likelihood.evaluate(named_params, data) + prior + named_params = jax.tree.map( + lambda x: x[0], named_params + ) # This [0] should be consolidate + return ( + self.likelihood.evaluate(named_params, data) + prior[0] + ) # This prior [0] should be consolidate def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): if initial_guess.size == 0: initial_guess_named = self.prior.sample(key, self.Sampler.n_chains) - initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T + for transform in self.sample_transforms: + initial_guess_named = jax.vmap(transform.forward)(initial_guess_named) + initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T[ + 0 + ] # This [0] should be consolidate self.Sampler.sample(initial_guess, None) # type: ignore def maximize_likelihood( diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 165432eb..10c42c33 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -309,6 +309,7 @@ def __init__( } +@jaxtyped(typechecker=typechecker) class BoundToUnbound(BijectiveTransform): """ Bound to unbound transformation @@ -328,8 +329,8 @@ def logit(x): return jnp.log(x / (1 - x)) super().__init__(name_mapping) - self.original_lower_bound = original_lower_bound - self.original_upper_bound = original_upper_bound + self.original_lower_bound = jnp.atleast_1d(original_lower_bound) + self.original_upper_bound = jnp.atleast_1d(original_upper_bound) self.transform_func = lambda x: { name_mapping[1][i]: logit( diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index deb3fb98..d82e8d7a 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -8,6 +8,7 @@ from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD +from jimgw.transforms import BoundToUnbound from flowMC.strategy.optimization import optimization_Adam jax.config.update("jax_enable_x64", True) @@ -64,6 +65,21 @@ dec_prior, ] ) + +sample_transforms = [ + BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), + BoundToUnbound(name_mapping = [["eta"], ["eta_unbounded"]], original_lower_bound=0.125, original_upper_bound=0.25), + BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["s2_z"], ["s2_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), + BoundToUnbound(name_mapping = [["t_c"], ["t_c_unbounded"]] , original_lower_bound=-0.05, original_upper_bound=0.05), + BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=-jnp.pi/2, original_upper_bound=jnp.pi/2), + BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["ra"], ["ra_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=0.0, original_upper_bound=jnp.pi) +] + likelihood = TransientLikelihoodFD( [H1, L1], waveform=RippleIMRPhenomD(), @@ -88,6 +104,7 @@ jim = Jim( likelihood, prior, + sample_transforms=sample_transforms, n_loop_training=n_loop_training, n_loop_production=1, n_local_steps=5, From 080bd8bb36b97f4a87ec2cbd7d84900e42d9b9e8 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 12:51:35 -0400 Subject: [PATCH 127/248] Modify integration test --- src/jimgw/transforms.py | 21 ++++----------------- test/integration/test_GW150914.py | 21 +++++++++++++-------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 10c42c33..3fc3cd1a 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -407,9 +407,9 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform -class ChirpMassMassRatioToChirpMassSymmetricMassRatioTransform(BijectiveTransform): +class MassRatioToSymmetricMassRatioTransform(BijectiveTransform): """ - Transform chirp mass and mass ratio to chirp mass and symmetric mass ratio + Transform mass ratio to symmetric mass ratio Parameters ---------- @@ -424,21 +424,8 @@ def __init__( ): super().__init__(name_mapping) - def named_transform(x): - Mc = x[name_mapping[0][0]] - q = x[name_mapping[0][1]] - eta = q_to_eta(q) - return {name_mapping[1][0]: Mc, name_mapping[1][1]: eta} - - self.transform_func = named_transform - - def named_inverse_transform(x): - Mc = x[name_mapping[1][0]] - eta = x[name_mapping[1][1]] - q = eta_to_q(Mc, eta) - return {name_mapping[0][0]: Mc, name_mapping[0][1]: q} - - self.inverse_transform_func = named_inverse_transform + self.transform_func = lambda x: {name_mapping[1][0]: q_to_eta(x[name_mapping[0][0]])} + self.inverse_transform_func = lambda x: {name_mapping[0][0]: eta_to_q(x[name_mapping[1][0]])} class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index d82e8d7a..4cee35e9 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -8,7 +8,7 @@ from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD -from jimgw.transforms import BoundToUnbound +from jimgw.transforms import BoundToUnbound, MassRatioToSymmetricMassRatioTransform from flowMC.strategy.optimization import optimization_Adam jax.config.update("jax_enable_x64", True) @@ -34,10 +34,10 @@ L1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) -eta_prior = UniformPrior( +q_prior = UniformPrior( 0.125, - 0.25, - parameter_names=["eta"], # Need name transformation in likelihood to work + 1, + parameter_names=["q"], ) s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) s2z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) @@ -45,15 +45,15 @@ dL_prior = UniformPrior(0.0, 2000.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) -iota_prior = CosinePrior(parameter_names=["iota"]) +iota_prior = SinePrior(parameter_names=["iota"]) psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) -dec_prior = SinePrior(parameter_names=["dec"]) +dec_prior = CosinePrior(parameter_names=["dec"]) prior = CombinePrior( [ Mc_prior, - eta_prior, + q_prior, s1z_prior, s2z_prior, dL_prior, @@ -68,7 +68,7 @@ sample_transforms = [ BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), - BoundToUnbound(name_mapping = [["eta"], ["eta_unbounded"]], original_lower_bound=0.125, original_upper_bound=0.25), + BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=0.125, original_upper_bound=1), BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), BoundToUnbound(name_mapping = [["s2_z"], ["s2_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), @@ -80,6 +80,10 @@ BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=0.0, original_upper_bound=jnp.pi) ] +likelihood_transforms = [ + MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), +] + likelihood = TransientLikelihoodFD( [H1, L1], waveform=RippleIMRPhenomD(), @@ -105,6 +109,7 @@ likelihood, prior, sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, n_loop_training=n_loop_training, n_loop_production=1, n_local_steps=5, From 4058d327418b3dbd9b742cb7af212a76eac8162f Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 12:56:23 -0400 Subject: [PATCH 128/248] Reformat --- src/jimgw/transforms.py | 8 ++++++-- test/integration/test_GW150914.py | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 3fc3cd1a..0326b28c 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -424,8 +424,12 @@ def __init__( ): super().__init__(name_mapping) - self.transform_func = lambda x: {name_mapping[1][0]: q_to_eta(x[name_mapping[0][0]])} - self.inverse_transform_func = lambda x: {name_mapping[0][0]: eta_to_q(x[name_mapping[1][0]])} + self.transform_func = lambda x: { + name_mapping[1][0]: q_to_eta(x[name_mapping[0][0]]) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][0]: eta_to_q(x[name_mapping[1][0]]) + } class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index 4cee35e9..753eb6b1 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -36,7 +36,7 @@ Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) q_prior = UniformPrior( 0.125, - 1, + 1., parameter_names=["q"], ) s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) @@ -68,7 +68,7 @@ sample_transforms = [ BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), - BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=0.125, original_upper_bound=1), + BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=0.125, original_upper_bound=1.), BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), BoundToUnbound(name_mapping = [["s2_z"], ["s2_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), From cdf771d1f3225045607601353a3f9e7ef3cfac54 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 13:03:35 -0400 Subject: [PATCH 129/248] Add typecheck --- src/jimgw/transforms.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 0326b28c..1bb17c18 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -174,6 +174,7 @@ def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: return y_copy +@jaxtyped(typechecker=typechecker) class ScaleTransform(BijectiveTransform): scale: Float @@ -194,6 +195,7 @@ def __init__( } +@jaxtyped(typechecker=typechecker) class OffsetTransform(BijectiveTransform): offset: Float @@ -214,6 +216,7 @@ def __init__( } +@jaxtyped(typechecker=typechecker) class LogitTransform(BijectiveTransform): """ Logit transform following @@ -242,6 +245,7 @@ def __init__( } +@jaxtyped(typechecker=typechecker) class ArcSineTransform(BijectiveTransform): """ ArcSine transformation @@ -347,6 +351,7 @@ def logit(x): } +@jaxtyped(typechecker=typechecker) class SingleSidedUnboundTransform(BijectiveTransform): """ Unbound upper limit transformation @@ -373,6 +378,7 @@ def __init__( } +@jaxtyped(typechecker=typechecker) class ChirpMassMassRatioToComponentMassesTransform(BijectiveTransform): """ Transform chirp mass and mass ratio to component masses @@ -407,6 +413,7 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform +@jaxtyped(typechecker=typechecker) class MassRatioToSymmetricMassRatioTransform(BijectiveTransform): """ Transform mass ratio to symmetric mass ratio @@ -432,6 +439,7 @@ def __init__( } +@jaxtyped(typechecker=typechecker) class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): """ Transform sky frame to detector frame sky position From 02c5650a064fa749c2a63a9e2c885c230ca05523 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 13:21:35 -0400 Subject: [PATCH 130/248] minor typo --- src/jimgw/jim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 2a9cb952..19f74606 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -70,7 +70,7 @@ def __init__( self.prior.n_dim, num_layers, hidden_size, num_bins, subkey ) - self.Sampler = Sampler( + self.sampler = Sampler( self.prior.n_dim, rng_key, None, # type: ignore From ce7ac34d2e8094ab1cfaa31fafa657c1887fcf45 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 13:38:40 -0400 Subject: [PATCH 131/248] Rename sampler --- src/jimgw/jim.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 19f74606..f21aff42 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -109,13 +109,13 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): if initial_guess.size == 0: - initial_guess_named = self.prior.sample(key, self.Sampler.n_chains) + initial_guess_named = self.prior.sample(key, self.sampler.n_chains) for transform in self.sample_transforms: initial_guess_named = jax.vmap(transform.forward)(initial_guess_named) initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T[ 0 ] # This [0] should be consolidate - self.Sampler.sample(initial_guess, None) # type: ignore + self.sampler.sample(initial_guess, None) # type: ignore def maximize_likelihood( self, @@ -148,8 +148,8 @@ def print_summary(self, transform: bool = True): """ - train_summary = self.Sampler.get_sampler_state(training=True) - production_summary = self.Sampler.get_sampler_state(training=False) + train_summary = self.sampler.get_sampler_state(training=True) + production_summary = self.sampler.get_sampler_state(training=False) training_chain = train_summary["chains"].reshape(-1, self.prior.n_dim).T training_chain = self.prior.add_name(training_chain) @@ -215,9 +215,9 @@ def get_samples(self, training: bool = False) -> dict: """ if training: - chains = self.Sampler.get_sampler_state(training=True)["chains"] + chains = self.sampler.get_sampler_state(training=True)["chains"] else: - chains = self.Sampler.get_sampler_state(training=False)["chains"] + chains = self.sampler.get_sampler_state(training=False)["chains"] chains = self.prior.transform(self.prior.add_name(chains.transpose(2, 0, 1))) return chains From 7d44aa4ba9763a31a8310b40b3eac29d47e47893 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 13:40:53 -0400 Subject: [PATCH 132/248] Fix test --- test/integration/test_GW150914.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index 753eb6b1..6193032a 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -74,10 +74,10 @@ BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), BoundToUnbound(name_mapping = [["t_c"], ["t_c_unbounded"]] , original_lower_bound=-0.05, original_upper_bound=0.05), BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=-jnp.pi/2, original_upper_bound=jnp.pi/2), + BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=0., original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["ra"], ["ra_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=0.0, original_upper_bound=jnp.pi) + BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=-jnp.pi / 2, original_upper_bound=jnp.pi / 2) ] likelihood_transforms = [ From 45be9ae70c570b681831879467feb6f969d7062f Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:31:56 -0400 Subject: [PATCH 133/248] Revert "Fixed PowerLaw prior" --- src/jimgw/jim.py | 7 +- src/jimgw/prior.py | 4 +- src/jimgw/transforms.py | 142 ++++++++++++++++++---------------------- test/unit/test_prior.py | 18 +++-- 4 files changed, 78 insertions(+), 93 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index ce097d44..39867e9f 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -7,7 +7,7 @@ from jaxtyping import Array, Float, PRNGKeyArray from jimgw.base import LikelihoodBase -from jimgw.prior import Prior +from jimgw.prior import Prior, trace_prior_parent from jimgw.transforms import BijectiveTransform, NtoMTransform @@ -48,9 +48,8 @@ def __init__( self.parameter_names = transform.propagate_name(self.parameter_names) if len(likelihood_transforms) == 0: - print( - "No likelihood transforms provided. Using prior parameters as likelihood parameters" - ) + print("No likelihood transforms provided. Using prior parameters as likelihood parameters") + seed = kwargs.get("seed", 0) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index aff1ebf5..13ab622f 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -12,8 +12,8 @@ ScaleTransform, OffsetTransform, ArcSineTransform, - PowerLawTransform, - ParetoTransform, + # PowerLawTransform, + # ParetoTransform, ) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 75d0420a..b832ccdc 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -1,8 +1,9 @@ -from abc import ABC +from abc import ABC, abstractmethod from typing import Callable import jax import jax.numpy as jnp +from chex import assert_rank from beartype import beartype as typechecker from jaxtyping import Float, Array, jaxtyped @@ -260,6 +261,7 @@ def __init__( @jaxtyped(typechecker=typechecker) class BoundToBound(BijectiveTransform): + """ Bound to bound transformation """ @@ -298,7 +300,6 @@ def __init__( for i in range(len(name_mapping[1])) } - @jaxtyped(typechecker=typechecker) class BoundToUnbound(BijectiveTransform): """ @@ -314,7 +315,7 @@ def __init__( original_lower_bound: Float, original_upper_bound: Float, ): - + def logit(x): return jnp.log(x / (1 - x)) @@ -330,13 +331,17 @@ def logit(x): for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { - name_mapping[0][i]: (self.original_upper_bound - self.original_lower_bound) - / (1 + jnp.exp(-x[name_mapping[1][i]])) + name_mapping[0][i]: ( + self.original_upper_bound - self.original_lower_bound + ) + / ( + 1 + + jnp.exp(-x[name_mapping[1][i]]) + ) + self.original_lower_bound[i] for i in range(len(name_mapping[1])) } - class SingleSidedUnboundTransform(BijectiveTransform): """ Unbound upper limit transformation @@ -363,78 +368,55 @@ def __init__( } -class PowerLawTransform(BijectiveTransform): - """ - PowerLaw transformation - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - """ - - xmin: Float - xmax: Float - alpha: Float - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - xmin: Float, - xmax: Float, - alpha: Float, - ): - super().__init__(name_mapping) - self.xmin = xmin - self.xmax = xmax - self.alpha = alpha - self.transform_func = lambda x: { - name_mapping[1][i]: ( - self.xmin ** (1.0 + self.alpha) - + x[name_mapping[0][i]] - * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - ) - ** (1.0 / (1.0 + self.alpha)) - for i in range(len(name_mapping[0])) - } - self.inverse_transform_func = lambda x: { - name_mapping[0][i]: ( - ( - x[name_mapping[1][i]] ** (1.0 + self.alpha) - - self.xmin ** (1.0 + self.alpha) - ) - / (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) - ) - for i in range(len(name_mapping[1])) - } - - -class ParetoTransform(BijectiveTransform): - """ - Pareto transformation: Power law when alpha = -1 - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - xmin: Float, - xmax: Float, - ): - super().__init__(name_mapping) - self.xmin = xmin - self.xmax = xmax - self.transform_func = lambda x: { - name_mapping[1][i]: self.xmin - * jnp.exp(x[name_mapping[0][i]] * jnp.log(self.xmax / self.xmin)) - for i in range(len(name_mapping[0])) - } - self.inverse_transform_func = lambda x: { - name_mapping[0][i]: ( - jnp.log(x[name_mapping[1][i]] / self.xmin) - / jnp.log(self.xmax / self.xmin) - ) - for i in range(len(name_mapping[1])) - } +# class PowerLawTransform(UnivariateTransform): +# """ +# PowerLaw transformation +# Parameters +# ---------- +# name_mapping : tuple[list[str], list[str]] +# The name mapping between the input and output dictionary. +# """ + +# xmin: Float +# xmax: Float +# alpha: Float + +# def __init__( +# self, +# name_mapping: tuple[list[str], list[str]], +# xmin: Float, +# xmax: Float, +# alpha: Float, +# ): +# super().__init__(name_mapping) +# self.xmin = xmin +# self.xmax = xmax +# self.alpha = alpha +# self.transform_func = lambda x: ( +# self.xmin ** (1.0 + self.alpha) +# + x * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) +# ) ** (1.0 / (1.0 + self.alpha)) + + +# class ParetoTransform(UnivariateTransform): +# """ +# Pareto transformation: Power law when alpha = -1 +# Parameters +# ---------- +# name_mapping : tuple[list[str], list[str]] +# The name mapping between the input and output dictionary. +# """ + +# def __init__( +# self, +# name_mapping: tuple[list[str], list[str]], +# xmin: Float, +# xmax: Float, +# ): +# super().__init__(name_mapping) +# self.xmin = xmin +# self.xmax = xmax +# self.transform_func = lambda x: self.xmin * jnp.exp( +# x * jnp.log(self.xmax / self.xmin) +# ) diff --git a/test/unit/test_prior.py b/test/unit/test_prior.py index 1cbed508..20d71de4 100644 --- a/test/unit/test_prior.py +++ b/test/unit/test_prior.py @@ -96,16 +96,20 @@ def func(alpha): assert jnp.all(jnp.isfinite(powerlaw_samples['x'])) # Check that all the log_probs are finite - samples = p.sample(jax.random.PRNGKey(0), 10000) - log_p = jax.vmap(p.log_prob, [0])(samples) - assert jnp.all(jnp.isfinite(log_p)) + samples = (trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000))['x_base'] + base_log_p = jax.vmap(p.log_prob, [0])({'x_base':samples}) + assert jnp.all(jnp.isfinite(base_log_p)) # Check that the log_prob is correct in the support - samples = p.sample(jax.random.PRNGKey(0), 10000) - log_prob = jax.vmap(p.log_prob)(samples) - standard_log_prob = powerlaw_log_pdf(samples['x'], alpha, xmin, xmax) + samples = jnp.linspace(-10.0, 10.0, 1000) + transformed_samples = jax.vmap(p.transform)({'x_base': samples})['x'] + # cut off the samples that are outside the support + samples = samples[transformed_samples >= xmin] + transformed_samples = transformed_samples[transformed_samples >= xmin] + samples = samples[transformed_samples <= xmax] + transformed_samples = transformed_samples[transformed_samples <= xmax] # log pdf of powerlaw - assert jnp.allclose(log_prob, standard_log_prob, atol=1e-4) + assert jnp.allclose(jax.vmap(p.log_prob)({'x_base':samples}), powerlaw_log_pdf(transformed_samples, alpha, xmin, xmax), atol=1e-4) # Test Pareto Transform func(-1.0) From e9288c8116b2209cdc1de477bac5fc0742cc9482 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 14:43:14 -0400 Subject: [PATCH 134/248] Fix BoundToUnbound transform --- src/jimgw/jim.py | 11 +++-------- src/jimgw/transforms.py | 6 +++--- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index f21aff42..4ccf0451 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -100,21 +100,16 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): prior = self.prior.log_prob(named_params) + transform_jacobian for transform in self.likelihood_transforms: named_params = transform.forward(named_params) - named_params = jax.tree.map( - lambda x: x[0], named_params - ) # This [0] should be consolidate return ( - self.likelihood.evaluate(named_params, data) + prior[0] - ) # This prior [0] should be consolidate + self.likelihood.evaluate(named_params, data) + prior + ) def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): if initial_guess.size == 0: initial_guess_named = self.prior.sample(key, self.sampler.n_chains) for transform in self.sample_transforms: initial_guess_named = jax.vmap(transform.forward)(initial_guess_named) - initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T[ - 0 - ] # This [0] should be consolidate + initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T self.sampler.sample(initial_guess, None) # type: ignore def maximize_likelihood( diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 1bb17c18..c6c4332f 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -338,13 +338,13 @@ def logit(x): self.transform_func = lambda x: { name_mapping[1][i]: logit( - (x[name_mapping[0][i]] - self.original_lower_bound) - / (self.original_upper_bound - self.original_lower_bound) + (x[name_mapping[0][i]] - self.original_lower_bound[i]) + / (self.original_upper_bound[i] - self.original_lower_bound[i]) ) for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { - name_mapping[0][i]: (self.original_upper_bound - self.original_lower_bound) + name_mapping[0][i]: (self.original_upper_bound[i] - self.original_lower_bound[i]) / (1 + jnp.exp(-x[name_mapping[1][i]])) + self.original_lower_bound[i] for i in range(len(name_mapping[1])) From d5a34f25acaac917df94e8e90ad85574116f4d50 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 1 Aug 2024 15:03:55 -0400 Subject: [PATCH 135/248] Updated prior.py and transforms.py --- src/jimgw/prior.py | 4 +- src/jimgw/transforms.py | 125 ++++++++++++++++++++++++---------------- 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 13ab622f..aff1ebf5 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -12,8 +12,8 @@ ScaleTransform, OffsetTransform, ArcSineTransform, - # PowerLawTransform, - # ParetoTransform, + PowerLawTransform, + ParetoTransform, ) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index b832ccdc..5a154f96 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -368,55 +368,78 @@ def __init__( } +class PowerLawTransform(BijectiveTransform): + """ + PowerLaw transformation + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + """ + + xmin: Float + xmax: Float + alpha: Float + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + xmin: Float, + xmax: Float, + alpha: Float, + ): + super().__init__(name_mapping) + self.xmin = xmin + self.xmax = xmax + self.alpha = alpha + self.transform_func = lambda x: { + name_mapping[1][i]: ( + self.xmin ** (1.0 + self.alpha) + + x[name_mapping[0][i]] + * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) + ** (1.0 / (1.0 + self.alpha)) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: ( + ( + x[name_mapping[1][i]] ** (1.0 + self.alpha) + - self.xmin ** (1.0 + self.alpha) + ) + / (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) + for i in range(len(name_mapping[1])) + } -# class PowerLawTransform(UnivariateTransform): -# """ -# PowerLaw transformation -# Parameters -# ---------- -# name_mapping : tuple[list[str], list[str]] -# The name mapping between the input and output dictionary. -# """ - -# xmin: Float -# xmax: Float -# alpha: Float - -# def __init__( -# self, -# name_mapping: tuple[list[str], list[str]], -# xmin: Float, -# xmax: Float, -# alpha: Float, -# ): -# super().__init__(name_mapping) -# self.xmin = xmin -# self.xmax = xmax -# self.alpha = alpha -# self.transform_func = lambda x: ( -# self.xmin ** (1.0 + self.alpha) -# + x * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) -# ) ** (1.0 / (1.0 + self.alpha)) - - -# class ParetoTransform(UnivariateTransform): -# """ -# Pareto transformation: Power law when alpha = -1 -# Parameters -# ---------- -# name_mapping : tuple[list[str], list[str]] -# The name mapping between the input and output dictionary. -# """ - -# def __init__( -# self, -# name_mapping: tuple[list[str], list[str]], -# xmin: Float, -# xmax: Float, -# ): -# super().__init__(name_mapping) -# self.xmin = xmin -# self.xmax = xmax -# self.transform_func = lambda x: self.xmin * jnp.exp( -# x * jnp.log(self.xmax / self.xmin) -# ) + +class ParetoTransform(BijectiveTransform): + """ + Pareto transformation: Power law when alpha = -1 + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + xmin: Float, + xmax: Float, + ): + super().__init__(name_mapping) + self.xmin = xmin + self.xmax = xmax + self.transform_func = lambda x: { + name_mapping[1][i]: self.xmin + * jnp.exp(x[name_mapping[0][i]] * jnp.log(self.xmax / self.xmin)) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: ( + jnp.log(x[name_mapping[1][i]] / self.xmin) + / jnp.log(self.xmax / self.xmin) + ) + for i in range(len(name_mapping[1])) + } From 34f3d260ac937fd4bd763b16f084f7fc99437405 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 1 Aug 2024 15:07:42 -0400 Subject: [PATCH 136/248] Updated test_prior.py --- test/unit/test_prior.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/unit/test_prior.py b/test/unit/test_prior.py index 20d71de4..26f89179 100644 --- a/test/unit/test_prior.py +++ b/test/unit/test_prior.py @@ -79,6 +79,7 @@ def test_uniform_sphere(self): log_prob = jax.vmap(p.log_prob)(samples) assert jnp.all(jnp.isfinite(log_prob)) + def test_power_law(self): def powerlaw_log_pdf(x, alpha, xmin, xmax): if alpha == -1.0: @@ -96,20 +97,16 @@ def func(alpha): assert jnp.all(jnp.isfinite(powerlaw_samples['x'])) # Check that all the log_probs are finite - samples = (trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000))['x_base'] - base_log_p = jax.vmap(p.log_prob, [0])({'x_base':samples}) - assert jnp.all(jnp.isfinite(base_log_p)) + samples = p.sample(jax.random.PRNGKey(0), 10000) + log_p = jax.vmap(p.log_prob, [0])(samples) + assert jnp.all(jnp.isfinite(log_p)) # Check that the log_prob is correct in the support - samples = jnp.linspace(-10.0, 10.0, 1000) - transformed_samples = jax.vmap(p.transform)({'x_base': samples})['x'] - # cut off the samples that are outside the support - samples = samples[transformed_samples >= xmin] - transformed_samples = transformed_samples[transformed_samples >= xmin] - samples = samples[transformed_samples <= xmax] - transformed_samples = transformed_samples[transformed_samples <= xmax] + samples = p.sample(jax.random.PRNGKey(0), 10000) + log_prob = jax.vmap(p.log_prob)(samples) + standard_log_prob = powerlaw_log_pdf(samples['x'], alpha, xmin, xmax) # log pdf of powerlaw - assert jnp.allclose(jax.vmap(p.log_prob)({'x_base':samples}), powerlaw_log_pdf(transformed_samples, alpha, xmin, xmax), atol=1e-4) + assert jnp.allclose(log_prob, standard_log_prob, atol=1e-4) # Test Pareto Transform func(-1.0) @@ -120,4 +117,4 @@ def func(alpha): func(alpha_val) negative_alpha = [-0.5, -1.5, -2.0, -2.5, -3.0, -3.5, -4.0, -4.5, -5.0] for alpha_val in negative_alpha: - func(alpha_val) + func(alpha_val) \ No newline at end of file From fe500a792a04a4409874b0564392643c6678033b Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 15:19:06 -0400 Subject: [PATCH 137/248] Use ifos list --- test/integration/test_GW150914.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index 6193032a..9adda574 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -28,10 +28,10 @@ fmin = 20.0 fmax = 1024.0 -ifos = ["H1", "L1"] +ifos = [H1, L1] -H1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) -L1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) +for ifo in ifos: + ifo.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) q_prior = UniformPrior( @@ -85,7 +85,7 @@ ] likelihood = TransientLikelihoodFD( - [H1, L1], + ifos, waveform=RippleIMRPhenomD(), trigger_time=gps, duration=4, From 0d28520440f95697a36fa166841e83cd3e4b18aa Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 17:13:01 -0400 Subject: [PATCH 138/248] Fix jim summary and get_samples --- src/jimgw/jim.py | 64 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 4ccf0451..91a358a6 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -137,7 +137,7 @@ def negative_posterior(x: Float[Array, " n_dim"]): best_fit = optimizer.get_result()[0] return best_fit - def print_summary(self, transform: bool = True): + def print_summary(self): """ Generate summary of the run @@ -146,19 +146,45 @@ def print_summary(self, transform: bool = True): train_summary = self.sampler.get_sampler_state(training=True) production_summary = self.sampler.get_sampler_state(training=False) - training_chain = train_summary["chains"].reshape(-1, self.prior.n_dim).T - training_chain = self.prior.add_name(training_chain) - if transform: - training_chain = self.prior.transform(training_chain) + training_chain = train_summary["chains"].reshape(-1, len(self.parameter_names)).T + if self.sample_transforms: + transformed_chain = {} + named_sample = self.add_name(training_chain[0]) + for transform in self.sample_transforms: + named_sample = transform.inverse(named_sample) + for key, value in named_sample.items(): + transformed_chain[key] = [value] + for sample in training_chain[1:]: + named_sample = self.add_name(sample) + for transform in self.sample_transforms: + named_sample = transform.inverse(named_sample) + for key, value in named_sample.items(): + transformed_chain[key].append(value) + training_chain = transformed_chain + else: + training_chain = self.add_name(training_chain) training_log_prob = train_summary["log_prob"] training_local_acceptance = train_summary["local_accs"] training_global_acceptance = train_summary["global_accs"] training_loss = train_summary["loss_vals"] - production_chain = production_summary["chains"].reshape(-1, self.prior.n_dim).T - production_chain = self.prior.add_name(production_chain) - if transform: - production_chain = self.prior.transform(production_chain) + production_chain = production_summary["chains"].reshape(-1, len(self.parameter_names)).T + if self.sample_transforms: + transformed_chain = {} + named_sample = self.add_name(production_chain[0]) + for transform in self.sample_transforms: + named_sample = transform.inverse(named_sample) + for key, value in named_sample.items(): + transformed_chain[key] = [value] + for sample in production_chain[1:]: + named_sample = self.add_name(sample) + for transform in self.sample_transforms: + named_sample = transform.inverse(named_sample) + for key, value in named_sample.items(): + transformed_chain[key].append(value) + production_chain = transformed_chain + else: + production_chain = self.add_name(production_chain) production_log_prob = production_summary["log_prob"] production_local_acceptance = production_summary["local_accs"] production_global_acceptance = production_summary["global_accs"] @@ -214,8 +240,24 @@ def get_samples(self, training: bool = False) -> dict: else: chains = self.sampler.get_sampler_state(training=False)["chains"] - chains = self.prior.transform(self.prior.add_name(chains.transpose(2, 0, 1))) - return chains + # Need rewrite to output chains instead of flattened samples + chains = chains.reshape(-1, len(self.parameter_names)).T + if self.sample_transforms: + transformed_chain = {} + named_sample = self.add_name(chains[0]) + for transform in self.sample_transforms: + named_sample = transform.inverse(named_sample) + for key, value in named_sample.items(): + transformed_chain[key] = [value] + for sample in chains[1:]: + named_sample = self.add_name(sample) + for transform in self.sample_transforms: + named_sample = transform.inverse(named_sample) + for key, value in named_sample.items(): + transformed_chain[key].append(value) + return transformed_chain + else: + return self.add_name(chains) def plot(self): pass From f7e3fe882c44914eda36b12fe6a633d153dd4c0a Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 17:30:15 -0400 Subject: [PATCH 139/248] Fix jim output functions --- src/jimgw/jim.py | 24 ++++++++++++++---------- src/jimgw/transforms.py | 8 ++++---- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 91a358a6..74f65efc 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -151,13 +151,13 @@ def print_summary(self): transformed_chain = {} named_sample = self.add_name(training_chain[0]) for transform in self.sample_transforms: - named_sample = transform.inverse(named_sample) + named_sample = transform.backward(named_sample) for key, value in named_sample.items(): transformed_chain[key] = [value] for sample in training_chain[1:]: named_sample = self.add_name(sample) for transform in self.sample_transforms: - named_sample = transform.inverse(named_sample) + named_sample = transform.backward(named_sample) for key, value in named_sample.items(): transformed_chain[key].append(value) training_chain = transformed_chain @@ -173,13 +173,13 @@ def print_summary(self): transformed_chain = {} named_sample = self.add_name(production_chain[0]) for transform in self.sample_transforms: - named_sample = transform.inverse(named_sample) + named_sample = transform.backward(named_sample) for key, value in named_sample.items(): transformed_chain[key] = [value] for sample in production_chain[1:]: named_sample = self.add_name(sample) for transform in self.sample_transforms: - named_sample = transform.inverse(named_sample) + named_sample = transform.backward(named_sample) for key, value in named_sample.items(): transformed_chain[key].append(value) production_chain = transformed_chain @@ -192,7 +192,7 @@ def print_summary(self): print("Training summary") print("=" * 10) for key, value in training_chain.items(): - print(f"{key}: {value.mean():.3f} +/- {value.std():.3f}") + print(f"{key}: {jnp.array(value).mean():.3f} +/- {jnp.array(value).std():.3f}") print( f"Log probability: {training_log_prob.mean():.3f} +/- {training_log_prob.std():.3f}" ) @@ -209,7 +209,7 @@ def print_summary(self): print("Production summary") print("=" * 10) for key, value in production_chain.items(): - print(f"{key}: {value.mean():.3f} +/- {value.std():.3f}") + print(f"{key}: {jnp.array(value).mean():.3f} +/- {jnp.array(value).std():.3f}") print( f"Log probability: {production_log_prob.mean():.3f} +/- {production_log_prob.std():.3f}" ) @@ -246,18 +246,22 @@ def get_samples(self, training: bool = False) -> dict: transformed_chain = {} named_sample = self.add_name(chains[0]) for transform in self.sample_transforms: - named_sample = transform.inverse(named_sample) + named_sample = transform.backward(named_sample) for key, value in named_sample.items(): transformed_chain[key] = [value] for sample in chains[1:]: named_sample = self.add_name(sample) for transform in self.sample_transforms: - named_sample = transform.inverse(named_sample) + named_sample = transform.backward(named_sample) for key, value in named_sample.items(): transformed_chain[key].append(value) - return transformed_chain + output = transformed_chain else: - return self.add_name(chains) + output = self.add_name(chains) + + for key in output.keys(): + output[key] = jnp.array(output[key]) + return output def plot(self): pass diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index c6c4332f..9aea6503 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -115,7 +115,7 @@ class BijectiveTransform(NtoNTransform): inverse_transform_func: Callable[[dict[str, Float]], dict[str, Float]] - def inverse(self, y: dict[str, Float]) -> dict[str, Float]: + def inverse(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: """ Inverse transform the input y to original coordinate x. @@ -128,6 +128,8 @@ def inverse(self, y: dict[str, Float]) -> dict[str, Float]: ------- x : dict[str, Float] The original dictionary. + log_det : Float + The log Jacobian determinant. """ y_copy = y.copy() transform_params = dict((key, y_copy[key]) for key in self.name_mapping[1]) @@ -145,7 +147,7 @@ def inverse(self, y: dict[str, Float]) -> dict[str, Float]: ) return y_copy, jacobian - def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: + def backward(self, y: dict[str, Float]) -> dict[str, Float]: """ Pull back the input y to original coordinate x and return the log Jacobian determinant. @@ -158,8 +160,6 @@ def backward(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: ------- x : dict[str, Float] The original dictionary. - log_det : Float - The log Jacobian determinant. """ y_copy = y.copy() output_params = self.inverse_transform_func(y_copy) From 68bef542d34706429ebc1e3a6fd894a9c61e6c88 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 18:14:23 -0400 Subject: [PATCH 140/248] Modify Transform --- src/jimgw/transforms.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 9aea6503..a963b3a7 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -5,7 +5,9 @@ import jax.numpy as jnp from beartype import beartype as typechecker from jaxtyping import Float, Array, jaxtyped +from astropy.time import Time +from jimgw.single_event.detector import GroundBased2G from jimgw.single_event.utils import ( Mc_q_to_m1_m2, m1_m2_to_Mc_q, @@ -395,20 +397,17 @@ def __init__( name_mapping: tuple[list[str], list[str]], ): super().__init__(name_mapping) + assert "M_c" in name_mapping[0] and "q" in name_mapping[0] and "m_1" in name_mapping[1] and "m_2" in name_mapping[1] def named_transform(x): - Mc = x[name_mapping[0][0]] - q = x[name_mapping[0][1]] - m1, m2 = Mc_q_to_m1_m2(Mc, q) - return {name_mapping[1][0]: m1, name_mapping[1][1]: m2} + m1, m2 = Mc_q_to_m1_m2(x["M_c"], x["q"]) + return {"m_1": m1, "m_2": m2} self.transform_func = named_transform def named_inverse_transform(x): - m1 = x[name_mapping[1][0]] - m2 = x[name_mapping[1][1]] - Mc, q = m1_m2_to_Mc_q(m1, m2) - return {name_mapping[0][0]: Mc, name_mapping[0][1]: q} + Mc, q = m1_m2_to_Mc_q(x["m_1"], x["m_2"]) + return {"M_c": Mc, "q": q} self.inverse_transform_func = named_inverse_transform @@ -458,32 +457,31 @@ class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): def __init__( self, name_mapping: tuple[list[str], list[str]], - gmst: Float, - delta_x: Float, + gps_time: Float, + ifos: GroundBased2G, ): super().__init__(name_mapping) - self.gmst = gmst + self.gmst = Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad + delta_x = ifos[0].vertex - ifos[1].vertex self.rotation = euler_rotation(delta_x) self.rotation_inv = jnp.linalg.inv(self.rotation) + + assert "ra" in name_mapping[0] and "dec" in name_mapping[0] and "zenith" in name_mapping[1] and "azimuth" in name_mapping[1] def named_transform(x): - ra = x[name_mapping[0][0]] - dec = x[name_mapping[0][1]] zenith, azimuth = ra_dec_to_zenith_azimuth( - ra, dec, self.gmst, self.rotation + x["ra"], x["dec"], self.gmst, self.rotation ) - return {name_mapping[1][0]: zenith, name_mapping[1][1]: azimuth} + return {"zenith": zenith, "azimuth": azimuth} self.transform_func = named_transform def named_inverse_transform(x): - zenith = x[name_mapping[1][0]] - azimuth = x[name_mapping[1][1]] ra, dec = zenith_azimuth_to_ra_dec( - zenith, azimuth, self.gmst, self.rotation_inv + x["zenith"], x["azimuth"], self.gmst, self.rotation_inv ) - return {name_mapping[0][0]: ra, name_mapping[0][1]: dec} + return {"ra": ra, "dec": dec} self.inverse_transform_func = named_inverse_transform From 87593e1fe370fb752293ec3e3e34117c12f2ccbb Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 18:59:11 -0400 Subject: [PATCH 141/248] Fix jim output --- src/jimgw/jim.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 74f65efc..9b16c424 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -146,7 +146,7 @@ def print_summary(self): train_summary = self.sampler.get_sampler_state(training=True) production_summary = self.sampler.get_sampler_state(training=False) - training_chain = train_summary["chains"].reshape(-1, len(self.parameter_names)).T + training_chain = train_summary["chains"].reshape(-1, len(self.parameter_names)) if self.sample_transforms: transformed_chain = {} named_sample = self.add_name(training_chain[0]) @@ -168,7 +168,7 @@ def print_summary(self): training_global_acceptance = train_summary["global_accs"] training_loss = train_summary["loss_vals"] - production_chain = production_summary["chains"].reshape(-1, len(self.parameter_names)).T + production_chain = production_summary["chains"].reshape(-1, len(self.parameter_names)) if self.sample_transforms: transformed_chain = {} named_sample = self.add_name(production_chain[0]) @@ -241,7 +241,7 @@ def get_samples(self, training: bool = False) -> dict: chains = self.sampler.get_sampler_state(training=False)["chains"] # Need rewrite to output chains instead of flattened samples - chains = chains.reshape(-1, len(self.parameter_names)).T + chains = chains.reshape(-1, len(self.parameter_names)) if self.sample_transforms: transformed_chain = {} named_sample = self.add_name(chains[0]) From 730fe31e718a4c498d93dee90c72c27c455c9a6a Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Thu, 1 Aug 2024 19:25:51 -0400 Subject: [PATCH 142/248] Add comment --- src/jimgw/jim.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 9b16c424..274d5597 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -148,6 +148,7 @@ def print_summary(self): training_chain = train_summary["chains"].reshape(-1, len(self.parameter_names)) if self.sample_transforms: + # Need rewrite to vectorize transformed_chain = {} named_sample = self.add_name(training_chain[0]) for transform in self.sample_transforms: @@ -170,6 +171,7 @@ def print_summary(self): production_chain = production_summary["chains"].reshape(-1, len(self.parameter_names)) if self.sample_transforms: + # Need rewrite to vectorize transformed_chain = {} named_sample = self.add_name(production_chain[0]) for transform in self.sample_transforms: @@ -240,7 +242,7 @@ def get_samples(self, training: bool = False) -> dict: else: chains = self.sampler.get_sampler_state(training=False)["chains"] - # Need rewrite to output chains instead of flattened samples + # Need rewrite to output chains instead of flattened samples and vectorize chains = chains.reshape(-1, len(self.parameter_names)) if self.sample_transforms: transformed_chain = {} From ed727afeef73eb1c51b3400c30a2f63eec95464a Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:16:12 -0400 Subject: [PATCH 143/248] Updated test_prior.py --- test/unit/test_prior.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/test_prior.py b/test/unit/test_prior.py index 26f89179..c68b4dc4 100644 --- a/test/unit/test_prior.py +++ b/test/unit/test_prior.py @@ -30,7 +30,7 @@ def test_uniform(self): samples = p.sample(jax.random.PRNGKey(0), 10000) assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is correct in the support - samples = trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000) + samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.allclose(log_prob, jnp.log(1.0 / (xmax - xmin))) @@ -40,7 +40,7 @@ def test_sine(self): samples = p.sample(jax.random.PRNGKey(0), 10000) assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is finite - samples = trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000) + samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.all(jnp.isfinite(log_prob)) # Check that the log_prob is correct in the support @@ -48,7 +48,7 @@ def test_sine(self): y = jax.vmap(p.base_prior.base_prior.transform)(x) y = jax.vmap(p.base_prior.transform)(y) y = jax.vmap(p.transform)(y) - assert jnp.allclose(jax.vmap(p.log_prob)(x), jnp.log(jnp.sin(y['x'])/2.0)) + assert jnp.allclose(jax.vmap(p.log_prob)(y), jnp.log(jnp.sin(y['x'])/2.0)) def test_cosine(self): p = CosinePrior(["x"]) @@ -56,14 +56,14 @@ def test_cosine(self): samples = p.sample(jax.random.PRNGKey(0), 10000) assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is finite - samples = trace_prior_parent(p, [])[0].sample(jax.random.PRNGKey(0), 10000) + samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.all(jnp.isfinite(log_prob)) # Check that the log_prob is correct in the support x = trace_prior_parent(p, [])[0].add_name(jnp.linspace(-10.0, 10.0, 1000)[None]) y = jax.vmap(p.base_prior.transform)(x) y = jax.vmap(p.transform)(y) - assert jnp.allclose(jax.vmap(p.log_prob)(x), jnp.log(jnp.cos(y['x'])/2.0)) + assert jnp.allclose(jax.vmap(p.log_prob)(y), jnp.log(jnp.cos(y['x'])/2.0)) def test_uniform_sphere(self): p = UniformSpherePrior(["x"]) From 9d191dacd1ccc3088a8b936006cf38280a832569 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:22:41 -0400 Subject: [PATCH 144/248] Updated test_prior.py --- test/unit/test_prior.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/test/unit/test_prior.py b/test/unit/test_prior.py index c68b4dc4..c42d76be 100644 --- a/test/unit/test_prior.py +++ b/test/unit/test_prior.py @@ -30,7 +30,6 @@ def test_uniform(self): samples = p.sample(jax.random.PRNGKey(0), 10000) assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is correct in the support - samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.allclose(log_prob, jnp.log(1.0 / (xmax - xmin))) @@ -40,7 +39,6 @@ def test_sine(self): samples = p.sample(jax.random.PRNGKey(0), 10000) assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is finite - samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.all(jnp.isfinite(log_prob)) # Check that the log_prob is correct in the support @@ -56,7 +54,6 @@ def test_cosine(self): samples = p.sample(jax.random.PRNGKey(0), 10000) assert jnp.all(jnp.isfinite(samples['x'])) # Check that the log_prob is finite - samples = p.sample(jax.random.PRNGKey(0), 10000) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.all(jnp.isfinite(log_prob)) # Check that the log_prob is correct in the support @@ -73,9 +70,6 @@ def test_uniform_sphere(self): assert jnp.all(jnp.isfinite(samples['x_theta'])) assert jnp.all(jnp.isfinite(samples['x_phi'])) # Check that the log_prob is finite - samples = {} - for i in range(3): - samples.update(trace_prior_parent(p, [])[i].sample(jax.random.PRNGKey(0), 10000)) log_prob = jax.vmap(p.log_prob)(samples) assert jnp.all(jnp.isfinite(log_prob)) @@ -97,14 +91,12 @@ def func(alpha): assert jnp.all(jnp.isfinite(powerlaw_samples['x'])) # Check that all the log_probs are finite - samples = p.sample(jax.random.PRNGKey(0), 10000) - log_p = jax.vmap(p.log_prob, [0])(samples) + log_p = jax.vmap(p.log_prob, [0])(powerlaw_samples) assert jnp.all(jnp.isfinite(log_p)) # Check that the log_prob is correct in the support - samples = p.sample(jax.random.PRNGKey(0), 10000) - log_prob = jax.vmap(p.log_prob)(samples) - standard_log_prob = powerlaw_log_pdf(samples['x'], alpha, xmin, xmax) + log_prob = jax.vmap(p.log_prob)(powerlaw_samples) + standard_log_prob = powerlaw_log_pdf(powerlaw_samples['x'], alpha, xmin, xmax) # log pdf of powerlaw assert jnp.allclose(log_prob, standard_log_prob, atol=1e-4) From 5a5ff2f345ea147f9995b8f5986cc1c6b03996ea Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 10:53:50 -0400 Subject: [PATCH 145/248] Add sky position transform --- src/jimgw/jim.py | 2 +- test/integration/test_GW150914.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 274d5597..2cc463b2 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -94,7 +94,7 @@ def add_name(self, x: Float[Array, " n_dim"]) -> dict[str, Float]: def posterior(self, params: Float[Array, " n_dim"], data: dict): named_params = self.add_name(params) transform_jacobian = 0.0 - for transform in self.sample_transforms: + for transform in reversed(self.sample_transforms): named_params, jacobian = transform.inverse(named_params) transform_jacobian += jacobian prior = self.prior.log_prob(named_params) + transform_jacobian diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index 9adda574..f29b8ad6 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -8,7 +8,7 @@ from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD -from jimgw.transforms import BoundToUnbound, MassRatioToSymmetricMassRatioTransform +from jimgw.transforms import BoundToUnbound, MassRatioToSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform from flowMC.strategy.optimization import optimization_Adam jax.config.update("jax_enable_x64", True) @@ -76,8 +76,9 @@ BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=0., original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["ra"], ["ra_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=-jnp.pi / 2, original_upper_bound=jnp.pi / 2) + SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), + BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]],original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), ] likelihood_transforms = [ From bd915efaaaef6e6e7e10a4af39908f47d1d6c50b Mon Sep 17 00:00:00 2001 From: kazewong Date: Fri, 2 Aug 2024 11:03:57 -0400 Subject: [PATCH 146/248] Add powerlaw transform back --- src/jimgw/transforms.py | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 1f9e1185..033e4882 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -485,6 +485,51 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform +class PowerLawTransform(BijectiveTransform): + """ + PowerLaw transformation + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + """ + + xmin: Float + xmax: Float + alpha: Float + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + xmin: Float, + xmax: Float, + alpha: Float, + ): + super().__init__(name_mapping) + self.xmin = xmin + self.xmax = xmax + self.alpha = alpha + self.transform_func = lambda x: { + name_mapping[1][i]: ( + self.xmin ** (1.0 + self.alpha) + + x[name_mapping[0][i]] + * (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) + ** (1.0 / (1.0 + self.alpha)) + for i in range(len(name_mapping[0])) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][i]: ( + ( + x[name_mapping[1][i]] ** (1.0 + self.alpha) + - self.xmin ** (1.0 + self.alpha) + ) + / (self.xmax ** (1.0 + self.alpha) - self.xmin ** (1.0 + self.alpha)) + ) + for i in range(len(name_mapping[1])) + } + + class ParetoTransform(BijectiveTransform): """ Pareto transformation: Power law when alpha = -1 From ba86a65701994a2cbbc773888e41b8bf305c9768 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 11:47:12 -0400 Subject: [PATCH 147/248] Move single_event prior and transform --- src/jimgw/jim.py | 16 +- src/jimgw/prior.py | 219 --------------------------- src/jimgw/single_event/prior.py | 198 ++++++++++++++++++++++++ src/jimgw/single_event/transforms.py | 124 +++++++++++++++ src/jimgw/single_event/utils.py | 12 +- src/jimgw/transforms.py | 120 +-------------- 6 files changed, 341 insertions(+), 348 deletions(-) create mode 100644 src/jimgw/single_event/prior.py create mode 100644 src/jimgw/single_event/transforms.py diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 2cc463b2..7d86fecf 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -100,9 +100,7 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): prior = self.prior.log_prob(named_params) + transform_jacobian for transform in self.likelihood_transforms: named_params = transform.forward(named_params) - return ( - self.likelihood.evaluate(named_params, data) + prior - ) + return self.likelihood.evaluate(named_params, data) + prior def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): if initial_guess.size == 0: @@ -169,7 +167,9 @@ def print_summary(self): training_global_acceptance = train_summary["global_accs"] training_loss = train_summary["loss_vals"] - production_chain = production_summary["chains"].reshape(-1, len(self.parameter_names)) + production_chain = production_summary["chains"].reshape( + -1, len(self.parameter_names) + ) if self.sample_transforms: # Need rewrite to vectorize transformed_chain = {} @@ -194,7 +194,9 @@ def print_summary(self): print("Training summary") print("=" * 10) for key, value in training_chain.items(): - print(f"{key}: {jnp.array(value).mean():.3f} +/- {jnp.array(value).std():.3f}") + print( + f"{key}: {jnp.array(value).mean():.3f} +/- {jnp.array(value).std():.3f}" + ) print( f"Log probability: {training_log_prob.mean():.3f} +/- {training_log_prob.std():.3f}" ) @@ -211,7 +213,9 @@ def print_summary(self): print("Production summary") print("=" * 10) for key, value in production_chain.items(): - print(f"{key}: {jnp.array(value).mean():.3f} +/- {jnp.array(value).std():.3f}") + print( + f"{key}: {jnp.array(value).mean():.3f} +/- {jnp.array(value).std():.3f}" + ) print( f"Log probability: {production_log_prob.mean():.3f} +/- {production_log_prob.std():.3f}" ) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index e4415618..5227ffa5 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -398,227 +398,8 @@ def __init__( ) -@jaxtyped(typechecker=typechecker) -class UniformComponentChirpMassPrior(PowerLawPrior): - """ - A prior in the range [xmin, xmax) for chirp mass which assumes the - component masses to be uniformly distributed. - - p(M_c) ~ M_c - """ - - def __repr__(self): - return f"UniformInComponentsChirpMassPrior(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" - - def __init__(self, xmin: float, xmax: float): - super().__init__(xmin, xmax, 1.0, ["M_c"]) - - -def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: - if prior.composite: - if isinstance(prior.base_prior, list): - for subprior in prior.base_prior: - output = trace_prior_parent(subprior, output) - elif isinstance(prior.base_prior, Prior): - output = trace_prior_parent(prior.base_prior, output) - else: - output.append(prior) - - return output - - # ====================== Things below may need rework ====================== - -# @jaxtyped(typechecker=typechecker) -# class AlignedSpin(Prior): -# """ -# Prior distribution for the aligned (z) component of the spin. - -# This assume the prior distribution on the spin magnitude to be uniform in [0, amax] -# with its orientation uniform on a sphere - -# p(chi) = -log(|chi| / amax) / 2 / amax - -# This is useful when comparing results between an aligned-spin run and -# a precessing spin run. - -# See (A7) of https://arxiv.org/abs/1805.10457. -# """ - -# amax: Float = 0.99 -# chi_axis: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) -# cdf_vals: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) - -# def __repr__(self): -# return f"Alignedspin(amax={self.amax}, naming={self.naming})" - -# def __init__( -# self, -# amax: Float, -# naming: list[str], -# transforms: dict[str, tuple[str, Callable]] = {}, -# **kwargs, -# ): -# super().__init__(naming, transforms) -# assert self.n_dim == 1, "Alignedspin needs to be 1D distributions" -# self.amax = amax - -# # build the interpolation table for the ppf of the one-sided distribution -# chi_axis = jnp.linspace(1e-31, self.amax, num=1000) -# cdf_vals = -chi_axis * (jnp.log(chi_axis / self.amax) - 1.0) / self.amax -# self.chi_axis = chi_axis -# self.cdf_vals = cdf_vals - -# @property -# def xmin(self): -# return -self.amax - -# @property -# def xmax(self): -# return self.amax - -# def sample( -# self, rng_key: PRNGKeyArray, n_samples: int -# ) -> dict[str, Float[Array, " n_samples"]]: -# """ -# Sample from the Alignedspin distribution. - -# for chi > 0; -# p(chi) = -log(chi / amax) / amax # halved normalization constant -# cdf(chi) = -chi * (log(chi / amax) - 1) / amax - -# Since there is a pole at chi=0, we will sample with the following steps -# 1. Map the samples with quantile > 0.5 to positive chi and negative otherwise -# 2a. For negative chi, map the quantile back to [0, 1] via q -> 2(0.5 - q) -# 2b. For positive chi, map the quantile back to [0, 1] via q -> 2(q - 0.5) -# 3. Map the quantile to chi via the ppf by checking against the table -# built during the initialization -# 4. add back the sign - -# Parameters -# ---------- -# rng_key : PRNGKeyArray -# A random key to use for sampling. -# n_samples : int -# The number of samples to draw. - -# Returns -# ------- -# samples : dict -# Samples from the distribution. The keys are the names of the parameters. - -# """ -# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) -# # 1. calculate the sign of chi from the q_samples -# sign_samples = jnp.where( -# q_samples >= 0.5, -# jnp.zeros_like(q_samples) + 1.0, -# jnp.zeros_like(q_samples) - 1.0, -# ) -# # 2. remap q_samples -# q_samples = jnp.where( -# q_samples >= 0.5, -# 2 * (q_samples - 0.5), -# 2 * (0.5 - q_samples), -# ) -# # 3. map the quantile to chi via interpolation -# samples = jnp.interp( -# q_samples, -# self.cdf_vals, -# self.chi_axis, -# ) -# # 4. add back the sign -# samples *= sign_samples - -# return self.add_name(samples[None]) - -# def log_prob(self, x: dict[str, Float]) -> Float: -# variable = x[self.naming[0]] -# log_p = jnp.where( -# (variable >= self.amax) | (variable <= -self.amax), -# jnp.zeros_like(variable) - jnp.inf, -# jnp.log(-jnp.log(jnp.absolute(variable) / self.amax) / 2.0 / self.amax), -# ) -# return log_p - - -# @jaxtyped(typechecker=typechecker) -# class EarthFrame(Prior): -# """ -# Prior distribution for sky location in Earth frame. -# """ - -# ifos: list = field(default_factory=list) -# gmst: float = 0.0 -# delta_x: Float[Array, " 3"] = field(default_factory=lambda: jnp.zeros(3)) - -# def __repr__(self): -# return f"EarthFrame(naming={self.naming})" - -# def __init__(self, gps: Float, ifos: list, **kwargs): -# self.naming = ["zenith", "azimuth"] -# if len(ifos) < 2: -# return ValueError( -# "At least two detectors are needed to define the Earth frame" -# ) -# elif isinstance(ifos[0], str): -# self.ifos = [detector_preset[ifos[0]], detector_preset[ifos[1]]] -# elif isinstance(ifos[0], GroundBased2G): -# self.ifos = ifos[:1] -# else: -# return ValueError( -# "ifos should be a list of detector names or GroundBased2G objects" -# ) -# self.gmst = float( -# Time(gps, format="gps").sidereal_time("apparent", "greenwich").rad -# ) -# self.delta_x = self.ifos[1].vertex - self.ifos[0].vertex - -# self.transforms = { -# "azimuth": ( -# "ra", -# lambda params: zenith_azimuth_to_ra_dec( -# params["zenith"], -# params["azimuth"], -# gmst=self.gmst, -# delta_x=self.delta_x, -# )[0], -# ), -# "zenith": ( -# "dec", -# lambda params: zenith_azimuth_to_ra_dec( -# params["zenith"], -# params["azimuth"], -# gmst=self.gmst, -# delta_x=self.delta_x, -# )[1], -# ), -# } - -# def sample( -# self, rng_key: PRNGKeyArray, n_samples: int -# ) -> dict[str, Float[Array, " n_samples"]]: -# rng_keys = jax.random.split(rng_key, 2) -# zenith = jnp.arccos( -# jax.random.uniform(rng_keys[0], (n_samples,), minval=-1.0, maxval=1.0) -# ) -# azimuth = jax.random.uniform( -# rng_keys[1], (n_samples,), minval=0, maxval=2 * jnp.pi -# ) -# return self.add_name(jnp.stack([zenith, azimuth], axis=1).T) - -# def log_prob(self, x: dict[str, Float]) -> Float: -# zenith = x["zenith"] -# azimuth = x["azimuth"] -# output = jnp.where( -# (zenith > jnp.pi) | (zenith < 0) | (azimuth > 2 * jnp.pi) | (azimuth < 0), -# jnp.zeros_like(0) - jnp.inf, -# jnp.zeros_like(0), -# ) -# return output + jnp.log(jnp.sin(zenith)) - - # @jaxtyped(typechecker=typechecker) # class Exponential(Prior): # """ diff --git a/src/jimgw/single_event/prior.py b/src/jimgw/single_event/prior.py new file mode 100644 index 00000000..e0173482 --- /dev/null +++ b/src/jimgw/single_event/prior.py @@ -0,0 +1,198 @@ +from dataclasses import field + +import jax +import jax.numpy as jnp +from beartype import beartype as typechecker +from flowMC.nfmodel.base import Distribution +from jaxtyping import Array, Float, PRNGKeyArray, jaxtyped + +from jimgw.prior import Prior, CombinePrior, UniformPrior, PowerLawPrior, SinePrior, CosinePrior + +@jaxtyped(typechecker=typechecker) +class UniformSpherePrior(CombinePrior): + + def __repr__(self): + return f"UniformSpherePrior(parameter_names={self.parameter_names})" + + def __init__(self, parameter_names: list[str], **kwargs): + self.parameter_names = parameter_names + assert self.n_dim == 1, "UniformSpherePrior only takes the name of the vector" + self.parameter_names = [ + f"{self.parameter_names[0]}_mag", + f"{self.parameter_names[0]}_theta", + f"{self.parameter_names[0]}_phi", + ] + super().__init__( + [ + UniformPrior(0.0, 1.0, [self.parameter_names[0]]), + SinePrior([self.parameter_names[1]]), + UniformPrior(0.0, 2 * jnp.pi, [self.parameter_names[2]]), + ] + ) + +@jaxtyped(typechecker=typechecker) +class UniformComponentMassPrior(CombinePrior): + """ + A prior in the range [xmin, xmax) for component masses which assumes the + component masses to be uniformly distributed. + """ + + def __repr__(self): + return f"UniformComponentMassPrior(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" + + def __init__(self, xmin: float, xmax: float): + self.parameter_names = ["m_1", "m_2"] + super().__init__( + [ + UniformPrior(xmin, xmax, ["m_1"]), + UniformPrior(xmin, xmax, ["m_2"]), + ] + ) + + def log_prob(self, z: dict[str, Float]) -> Float: + output = super().log_prob(z) + output += jnp.log(2.) + +@jaxtyped(typechecker=typechecker) +class UniformComponentChirpMassPrior(PowerLawPrior): + """ + A prior in the range [xmin, xmax) for chirp mass which assumes the + component masses to be uniformly distributed. + + p(M_c) ~ M_c + """ + + def __repr__(self): + return f"UniformInComponentsChirpMassPrior(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" + + def __init__(self, xmin: float, xmax: float): + super().__init__(xmin, xmax, 1.0, ["M_c"]) + + +def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: + if prior.composite: + if isinstance(prior.base_prior, list): + for subprior in prior.base_prior: + output = trace_prior_parent(subprior, output) + elif isinstance(prior.base_prior, Prior): + output = trace_prior_parent(prior.base_prior, output) + else: + output.append(prior) + + return output + + +# ====================== Things below may need rework ====================== + + +# @jaxtyped(typechecker=typechecker) +# class AlignedSpin(Prior): +# """ +# Prior distribution for the aligned (z) component of the spin. + +# This assume the prior distribution on the spin magnitude to be uniform in [0, amax] +# with its orientation uniform on a sphere + +# p(chi) = -log(|chi| / amax) / 2 / amax + +# This is useful when comparing results between an aligned-spin run and +# a precessing spin run. + +# See (A7) of https://arxiv.org/abs/1805.10457. +# """ + +# amax: Float = 0.99 +# chi_axis: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) +# cdf_vals: Array = field(default_factory=lambda: jnp.linspace(0, 1, num=1000)) + +# def __repr__(self): +# return f"Alignedspin(amax={self.amax}, naming={self.naming})" + +# def __init__( +# self, +# amax: Float, +# naming: list[str], +# transforms: dict[str, tuple[str, Callable]] = {}, +# **kwargs, +# ): +# super().__init__(naming, transforms) +# assert self.n_dim == 1, "Alignedspin needs to be 1D distributions" +# self.amax = amax + +# # build the interpolation table for the ppf of the one-sided distribution +# chi_axis = jnp.linspace(1e-31, self.amax, num=1000) +# cdf_vals = -chi_axis * (jnp.log(chi_axis / self.amax) - 1.0) / self.amax +# self.chi_axis = chi_axis +# self.cdf_vals = cdf_vals + +# @property +# def xmin(self): +# return -self.amax + +# @property +# def xmax(self): +# return self.amax + +# def sample( +# self, rng_key: PRNGKeyArray, n_samples: int +# ) -> dict[str, Float[Array, " n_samples"]]: +# """ +# Sample from the Alignedspin distribution. + +# for chi > 0; +# p(chi) = -log(chi / amax) / amax # halved normalization constant +# cdf(chi) = -chi * (log(chi / amax) - 1) / amax + +# Since there is a pole at chi=0, we will sample with the following steps +# 1. Map the samples with quantile > 0.5 to positive chi and negative otherwise +# 2a. For negative chi, map the quantile back to [0, 1] via q -> 2(0.5 - q) +# 2b. For positive chi, map the quantile back to [0, 1] via q -> 2(q - 0.5) +# 3. Map the quantile to chi via the ppf by checking against the table +# built during the initialization +# 4. add back the sign + +# Parameters +# ---------- +# rng_key : PRNGKeyArray +# A random key to use for sampling. +# n_samples : int +# The number of samples to draw. + +# Returns +# ------- +# samples : dict +# Samples from the distribution. The keys are the names of the parameters. + +# """ +# q_samples = jax.random.uniform(rng_key, (n_samples,), minval=0.0, maxval=1.0) +# # 1. calculate the sign of chi from the q_samples +# sign_samples = jnp.where( +# q_samples >= 0.5, +# jnp.zeros_like(q_samples) + 1.0, +# jnp.zeros_like(q_samples) - 1.0, +# ) +# # 2. remap q_samples +# q_samples = jnp.where( +# q_samples >= 0.5, +# 2 * (q_samples - 0.5), +# 2 * (0.5 - q_samples), +# ) +# # 3. map the quantile to chi via interpolation +# samples = jnp.interp( +# q_samples, +# self.cdf_vals, +# self.chi_axis, +# ) +# # 4. add back the sign +# samples *= sign_samples + +# return self.add_name(samples[None]) + +# def log_prob(self, x: dict[str, Float]) -> Float: +# variable = x[self.naming[0]] +# log_p = jnp.where( +# (variable >= self.amax) | (variable <= -self.amax), +# jnp.zeros_like(variable) - jnp.inf, +# jnp.log(-jnp.log(jnp.absolute(variable) / self.amax) / 2.0 / self.amax), +# ) +# return log_p diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py new file mode 100644 index 00000000..b6070510 --- /dev/null +++ b/src/jimgw/single_event/transforms.py @@ -0,0 +1,124 @@ +from abc import ABC +from typing import Callable + +import jax +import jax.numpy as jnp +from beartype import beartype as typechecker +from jaxtyping import Float, Array, jaxtyped +from astropy.time import Time + +from jimgw.single_event.detector import GroundBased2G +from jimgw.transforms import BijectiveTransform +from jimgw.single_event.utils import ( + Mc_q_to_m1_m2, + m1_m2_to_Mc_q, + q_to_eta, + eta_to_q, + ra_dec_to_zenith_azimuth, + zenith_azimuth_to_ra_dec, + euler_rotation, +) + +@jaxtyped(typechecker=typechecker) +class ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): + """ + Transform chirp mass and mass ratio to component masses + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + assert "m_1" in name_mapping[0] and "m_2" in name_mapping[0] and "M_c" in name_mapping[1] and "q" in name_mapping[1] + + def named_transform(x): + Mc, q = m1_m2_to_Mc_q(x["m_1"], x["m_2"]) + return {"M_c": Mc, "q": q} + + self.transform_func = named_transform + + def named_inverse_transform(x): + m1, m2 = Mc_q_to_m1_m2(x["M_c"], x["q"]) + return {"m_1": m1, "m_2": m2} + + self.inverse_transform_func = named_inverse_transform + + +@jaxtyped(typechecker=typechecker) +class MassRatioToSymmetricMassRatioTransform(BijectiveTransform): + """ + Transform mass ratio to symmetric mass ratio + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + + self.transform_func = lambda x: { + name_mapping[1][0]: q_to_eta(x[name_mapping[0][0]]) + } + self.inverse_transform_func = lambda x: { + name_mapping[0][0]: eta_to_q(x[name_mapping[1][0]]) + } + + +@jaxtyped(typechecker=typechecker) +class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): + """ + Transform sky frame to detector frame sky position + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + gmst: Float + rotation: Float[Array, " 3 3"] + rotation_inv: Float[Array, " 3 3"] + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + gps_time: Float, + ifos: GroundBased2G, + ): + super().__init__(name_mapping) + + self.gmst = Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad + delta_x = ifos[0].vertex - ifos[1].vertex + self.rotation = euler_rotation(delta_x) + self.rotation_inv = jnp.linalg.inv(self.rotation) + + assert "ra" in name_mapping[0] and "dec" in name_mapping[0] and "zenith" in name_mapping[1] and "azimuth" in name_mapping[1] + + def named_transform(x): + zenith, azimuth = ra_dec_to_zenith_azimuth( + x["ra"], x["dec"], self.gmst, self.rotation + ) + return {"zenith": zenith, "azimuth": azimuth} + + self.transform_func = named_transform + + def named_inverse_transform(x): + ra, dec = zenith_azimuth_to_ra_dec( + x["zenith"], x["azimuth"], self.gmst, self.rotation_inv + ) + return {"ra": ra, "dec": dec} + + self.inverse_transform_func = named_inverse_transform diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 8721c176..0d1313ca 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -259,8 +259,8 @@ def angle_rotation( Zenith angle. azimuth : Float Azimuthal angle. - delta_x : Float - The vector pointing from the first detector to the second detector. + rotation : Float[Array, " 3 3"] + The rotation matrix. Returns ------- @@ -334,8 +334,8 @@ def zenith_azimuth_to_ra_dec( Azimuthal angle. gmst : Float Greenwich mean sidereal time. - delta_x : Float - The vector pointing from the first detector to the second detector. + rotation : Float[Array, " 3 3"] + The rotation matrix. Copied and modified from bilby/gw/utils.py @@ -392,8 +392,8 @@ def ra_dec_to_zenith_azimuth( Declination. gmst : Float Greenwich mean sidereal time. - delta_x : Float - The vector pointing from the first detector to the second detector. + rotation : Float[Array, " 3 3"] + The rotation matrix. Returns ------- diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 033e4882..4c67a076 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -5,18 +5,7 @@ import jax.numpy as jnp from beartype import beartype as typechecker from jaxtyping import Float, Array, jaxtyped -from astropy.time import Time -from jimgw.single_event.detector import GroundBased2G -from jimgw.single_event.utils import ( - Mc_q_to_m1_m2, - m1_m2_to_Mc_q, - q_to_eta, - eta_to_q, - ra_dec_to_zenith_azimuth, - zenith_azimuth_to_ra_dec, - euler_rotation, -) class Transform(ABC): @@ -346,7 +335,9 @@ def logit(x): for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { - name_mapping[0][i]: (self.original_upper_bound[i] - self.original_lower_bound[i]) + name_mapping[0][i]: ( + self.original_upper_bound[i] - self.original_lower_bound[i] + ) / (1 + jnp.exp(-x[name_mapping[1][i]])) + self.original_lower_bound[i] for i in range(len(name_mapping[1])) @@ -380,111 +371,6 @@ def __init__( } -@jaxtyped(typechecker=typechecker) -class ChirpMassMassRatioToComponentMassesTransform(BijectiveTransform): - """ - Transform chirp mass and mass ratio to component masses - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): - super().__init__(name_mapping) - assert "M_c" in name_mapping[0] and "q" in name_mapping[0] and "m_1" in name_mapping[1] and "m_2" in name_mapping[1] - - def named_transform(x): - m1, m2 = Mc_q_to_m1_m2(x["M_c"], x["q"]) - return {"m_1": m1, "m_2": m2} - - self.transform_func = named_transform - - def named_inverse_transform(x): - Mc, q = m1_m2_to_Mc_q(x["m_1"], x["m_2"]) - return {"M_c": Mc, "q": q} - - self.inverse_transform_func = named_inverse_transform - - -@jaxtyped(typechecker=typechecker) -class MassRatioToSymmetricMassRatioTransform(BijectiveTransform): - """ - Transform mass ratio to symmetric mass ratio - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): - super().__init__(name_mapping) - - self.transform_func = lambda x: { - name_mapping[1][0]: q_to_eta(x[name_mapping[0][0]]) - } - self.inverse_transform_func = lambda x: { - name_mapping[0][0]: eta_to_q(x[name_mapping[1][0]]) - } - - -@jaxtyped(typechecker=typechecker) -class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): - """ - Transform sky frame to detector frame sky position - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - - """ - - gmst: Float - rotation: Float[Array, " 3 3"] - rotation_inv: Float[Array, " 3 3"] - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - gps_time: Float, - ifos: GroundBased2G, - ): - super().__init__(name_mapping) - - self.gmst = Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad - delta_x = ifos[0].vertex - ifos[1].vertex - self.rotation = euler_rotation(delta_x) - self.rotation_inv = jnp.linalg.inv(self.rotation) - - assert "ra" in name_mapping[0] and "dec" in name_mapping[0] and "zenith" in name_mapping[1] and "azimuth" in name_mapping[1] - - def named_transform(x): - zenith, azimuth = ra_dec_to_zenith_azimuth( - x["ra"], x["dec"], self.gmst, self.rotation - ) - return {"zenith": zenith, "azimuth": azimuth} - - self.transform_func = named_transform - - def named_inverse_transform(x): - ra, dec = zenith_azimuth_to_ra_dec( - x["zenith"], x["azimuth"], self.gmst, self.rotation_inv - ) - return {"ra": ra, "dec": dec} - - self.inverse_transform_func = named_inverse_transform - - class PowerLawTransform(BijectiveTransform): """ PowerLaw transformation From 3e1ea7172d6d9487298c134bd5f567607656c7b6 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 12:27:42 -0400 Subject: [PATCH 148/248] Tidy up test --- src/jimgw/single_event/prior.py | 37 +++------ src/jimgw/single_event/transforms.py | 75 ++++++++++++++---- src/jimgw/single_event/utils.py | 112 ++++++++++++++++++++++----- src/jimgw/transforms.py | 1 - test/integration/test_GW150914.py | 13 ++-- 5 files changed, 166 insertions(+), 72 deletions(-) diff --git a/src/jimgw/single_event/prior.py b/src/jimgw/single_event/prior.py index e0173482..51a754eb 100644 --- a/src/jimgw/single_event/prior.py +++ b/src/jimgw/single_event/prior.py @@ -1,12 +1,15 @@ -from dataclasses import field - -import jax import jax.numpy as jnp from beartype import beartype as typechecker -from flowMC.nfmodel.base import Distribution -from jaxtyping import Array, Float, PRNGKeyArray, jaxtyped +from jaxtyping import jaxtyped + +from jimgw.prior import ( + Prior, + CombinePrior, + UniformPrior, + PowerLawPrior, + SinePrior, +) -from jimgw.prior import Prior, CombinePrior, UniformPrior, PowerLawPrior, SinePrior, CosinePrior @jaxtyped(typechecker=typechecker) class UniformSpherePrior(CombinePrior): @@ -30,28 +33,6 @@ def __init__(self, parameter_names: list[str], **kwargs): ] ) -@jaxtyped(typechecker=typechecker) -class UniformComponentMassPrior(CombinePrior): - """ - A prior in the range [xmin, xmax) for component masses which assumes the - component masses to be uniformly distributed. - """ - - def __repr__(self): - return f"UniformComponentMassPrior(xmin={self.xmin}, xmax={self.xmax}, naming={self.parameter_names})" - - def __init__(self, xmin: float, xmax: float): - self.parameter_names = ["m_1", "m_2"] - super().__init__( - [ - UniformPrior(xmin, xmax, ["m_1"]), - UniformPrior(xmin, xmax, ["m_2"]), - ] - ) - - def log_prob(self, z: dict[str, Float]) -> Float: - output = super().log_prob(z) - output += jnp.log(2.) @jaxtyped(typechecker=typechecker) class UniformComponentChirpMassPrior(PowerLawPrior): diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index b6070510..fe12c52a 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -1,7 +1,3 @@ -from abc import ABC -from typing import Callable - -import jax import jax.numpy as jnp from beartype import beartype as typechecker from jaxtyping import Float, Array, jaxtyped @@ -10,8 +6,10 @@ from jimgw.single_event.detector import GroundBased2G from jimgw.transforms import BijectiveTransform from jimgw.single_event.utils import ( - Mc_q_to_m1_m2, m1_m2_to_Mc_q, + Mc_q_to_m1_m2, + m1_m2_to_Mc_eta, + Mc_eta_to_m1_m2, q_to_eta, eta_to_q, ra_dec_to_zenith_azimuth, @@ -19,6 +17,7 @@ euler_rotation, ) + @jaxtyped(typechecker=typechecker) class ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): """ @@ -35,7 +34,12 @@ def __init__( name_mapping: tuple[list[str], list[str]], ): super().__init__(name_mapping) - assert "m_1" in name_mapping[0] and "m_2" in name_mapping[0] and "M_c" in name_mapping[1] and "q" in name_mapping[1] + assert ( + "m_1" in name_mapping[0] + and "m_2" in name_mapping[0] + and "M_c" in name_mapping[1] + and "q" in name_mapping[1] + ) def named_transform(x): Mc, q = m1_m2_to_Mc_q(x["m_1"], x["m_2"]) @@ -50,6 +54,43 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform +@jaxtyped(typechecker=typechecker) +class ComponentMassesToChirpMassSymmetricMassRatioTransform(BijectiveTransform): + """ + Transform mass ratio to symmetric mass ratio + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + assert ( + "m_1" in name_mapping[0] + and "m_2" in name_mapping[0] + and "M_c" in name_mapping[1] + and "eta" in name_mapping[1] + ) + + def named_transform(x): + Mc, eta = m1_m2_to_Mc_eta(x["m_1"], x["m_2"]) + return {"M_c": Mc, "eta": eta} + + self.transform_func = named_transform + + def named_inverse_transform(x): + m1, m2 = Mc_eta_to_m1_m2(x["M_c"], x["q"]) + return {"m_1": m1, "m_2": m2} + + self.inverse_transform_func = named_inverse_transform + + @jaxtyped(typechecker=typechecker) class MassRatioToSymmetricMassRatioTransform(BijectiveTransform): """ @@ -67,13 +108,10 @@ def __init__( name_mapping: tuple[list[str], list[str]], ): super().__init__(name_mapping) + assert "q" == name_mapping[0][0] and "eta" == name_mapping[1][0] - self.transform_func = lambda x: { - name_mapping[1][0]: q_to_eta(x[name_mapping[0][0]]) - } - self.inverse_transform_func = lambda x: { - name_mapping[0][0]: eta_to_q(x[name_mapping[1][0]]) - } + self.transform_func = lambda x: {"eta": q_to_eta(x["q"])} + self.inverse_transform_func = lambda x: {"q": eta_to_q(x["eta"])} @jaxtyped(typechecker=typechecker) @@ -100,12 +138,19 @@ def __init__( ): super().__init__(name_mapping) - self.gmst = Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad + self.gmst = ( + Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad + ) delta_x = ifos[0].vertex - ifos[1].vertex self.rotation = euler_rotation(delta_x) self.rotation_inv = jnp.linalg.inv(self.rotation) - - assert "ra" in name_mapping[0] and "dec" in name_mapping[0] and "zenith" in name_mapping[1] and "azimuth" in name_mapping[1] + + assert ( + "ra" in name_mapping[0] + and "dec" in name_mapping[0] + and "zenith" in name_mapping[1] + and "azimuth" in name_mapping[1] + ) def named_transform(x): zenith, azimuth = ra_dec_to_zenith_azimuth( diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 0d1313ca..16d6cc7c 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -56,12 +56,12 @@ def m1_m2_to_M_q(m1: Float, m2: Float): q : Float Mass ratio. """ - M_tot = jnp.log(m1 + m2) - q = jnp.log(m2 / m1) - jnp.log(1 - m2 / m1) + M_tot = m1 + m2 + q = m2 / m1 return M_tot, q -def M_q_to_m1_m2(trans_M_tot: Float, trans_q: Float): +def M_q_to_m1_m2(M_tot: Float, q: Float): """ Transforming the Total mass M and mass ratio q to the primary mass m1 and secondary mass m2. @@ -80,13 +80,37 @@ def M_q_to_m1_m2(trans_M_tot: Float, trans_q: Float): m2 : Float Secondary mass. """ - M_tot = jnp.exp(trans_M_tot) - q = 1.0 / (1 + jnp.exp(-trans_q)) m1 = M_tot / (1 + q) m2 = m1 * q return m1, m2 +def m1_m2_to_Mc_q(m1: Float, m2: Float) -> tuple[Float, Float]: + """ + Transforming the primary mass m1 and secondary mass m2 to the chirp mass M_c + and mass ratio q. + + Parameters + ---------- + m1 : Float + Primary mass. + m2 : Float + Secondary mass. + + Returns + ------- + M_c : Float + Chirp mass. + q : Float + Mass ratio. + """ + M_tot = m1 + m2 + eta = m1 * m2 / M_tot**2 + M_c = M_tot * eta ** (3.0 / 5) + q = m2 / m1 + return M_c, q + + def Mc_q_to_m1_m2(M_c: Float, q: Float) -> tuple[Float, Float]: """ Transforming the chirp mass M_c and mass ratio q to the primary mass m1 and @@ -113,10 +137,10 @@ def Mc_q_to_m1_m2(M_c: Float, q: Float) -> tuple[Float, Float]: return m1, m2 -def m1_m2_to_Mc_q(m1: Float, m2: Float) -> tuple[Float, Float]: +def m1_m2_to_M_eta(m1: Float, m2: Float) -> tuple[Float, Float]: """ - Transforming the primary mass m1 and secondary mass m2 to the chirp mass M_c - and mass ratio q. + Transforming the primary mass m1 and secondary mass m2 to the total mass M + and symmetric mass ratio eta. Parameters ---------- @@ -127,21 +151,43 @@ def m1_m2_to_Mc_q(m1: Float, m2: Float) -> tuple[Float, Float]: Returns ------- - M_c : Float - Chirp mass. - q : Float - Mass ratio. + M : Float + Total mass. + eta : Float + Symmetric mass ratio. """ M_tot = m1 + m2 eta = m1 * m2 / M_tot**2 - M_c = M_tot * eta ** (3.0 / 5) - q = m2 / m1 - return M_c, q + return M_tot, eta -def m1_m2_to_M_eta(m1: Float, m2: Float) -> tuple[Float, Float]: +def M_eta_to_m1_m2(M_tot: Float, eta: Float) -> tuple[Float, Float]: """ - Transforming the primary mass m1 and secondary mass m2 to the total mass M + Transforming the total mass M and symmetric mass ratio eta to the primary mass m1 + and secondary mass m2. + + Parameters + ---------- + M : Float + Total mass. + eta : Float + Symmetric mass ratio. + + Returns + ------- + m1 : Float + Primary mass. + m2 : Float + Secondary mass. + """ + m1 = M_tot * (1 + jnp.sqrt(1 - 4 * eta)) / 2 + m2 = M_tot * (1 - jnp.sqrt(1 - 4 * eta)) / 2 + return m1, m2 + + +def m1_m2_to_Mc_eta(m1: Float, m2: Float) -> tuple[Float, Float]: + """ + Transforming the primary mass m1 and secondary mass m2 to the chirp mass M_c and symmetric mass ratio eta. Parameters @@ -153,14 +199,40 @@ def m1_m2_to_M_eta(m1: Float, m2: Float) -> tuple[Float, Float]: Returns ------- - M : Float - Total mass. + M_c : Float + Chirp mass. eta : Float Symmetric mass ratio. """ M = m1 + m2 eta = m1 * m2 / M**2 - return M, eta + M_c = M * eta ** (3.0 / 5) + return M_c, eta + + +def Mc_eta_to_m1_m2(M_c: Float, eta: Float) -> tuple[Float, Float]: + """ + Transforming the chirp mass M_c and symmetric mass ratio eta to the primary mass m1 + and secondary mass m2. + + Parameters + ---------- + M_c : Float + Chirp mass. + eta : Float + Symmetric mass ratio. + + Returns + ------- + m1 : Float + Primary mass. + m2 : Float + Secondary mass. + """ + M = M_c / eta ** (3.0 / 5) + m1 = M * (1 + jnp.sqrt(1 - 4 * eta)) / 2 + m2 = M * (1 - jnp.sqrt(1 - 4 * eta)) / 2 + return m1, m2 def q_to_eta(q: Float) -> Float: diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 4c67a076..715d49de 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -7,7 +7,6 @@ from jaxtyping import Float, Array, jaxtyped - class Transform(ABC): """ Base class for transform. diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index f29b8ad6..6fddf9ea 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -8,7 +8,9 @@ from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD -from jimgw.transforms import BoundToUnbound, MassRatioToSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform +from jimgw.transforms import BoundToUnbound +from jimgw.single_event.transforms import ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, ComponentMassesToChirpMassMassRatioTransform, MassRatioToSymmetricMassRatioTransform +from jimgw.single_event.utils import Mc_q_to_m1_m2 from flowMC.strategy.optimization import optimization_Adam jax.config.update("jax_enable_x64", True) @@ -34,14 +36,9 @@ ifo.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) -q_prior = UniformPrior( - 0.125, - 1., - parameter_names=["q"], -) +q_prior = UniformPrior(0.125, 1.0, parameter_names=["q"]) s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) s2z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) -# Current likelihood sampling will fail and give nan because of large number dL_prior = UniformPrior(0.0, 2000.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) @@ -77,7 +74,7 @@ BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=0., original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), - BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]],original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), ] From 6ad882a40df8358a00b63ff794467fca46a25396 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 12:38:27 -0400 Subject: [PATCH 149/248] Add utils.py --- src/jimgw/utils.py | 13 +++++++++++++ test/unit/test_prior.py | 1 + 2 files changed, 14 insertions(+) create mode 100644 src/jimgw/utils.py diff --git a/src/jimgw/utils.py b/src/jimgw/utils.py new file mode 100644 index 00000000..e629903a --- /dev/null +++ b/src/jimgw/utils.py @@ -0,0 +1,13 @@ +from jimgw.prior import Prior + +def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: + if prior.composite: + if isinstance(prior.base_prior, list): + for subprior in prior.base_prior: + output = trace_prior_parent(subprior, output) + elif isinstance(prior.base_prior, Prior): + output = trace_prior_parent(prior.base_prior, output) + else: + output.append(prior) + + return output diff --git a/test/unit/test_prior.py b/test/unit/test_prior.py index c42d76be..852ded16 100644 --- a/test/unit/test_prior.py +++ b/test/unit/test_prior.py @@ -1,4 +1,5 @@ from jimgw.prior import * +from jimgw.utils import trace_prior_parent import scipy.stats as stats From ede2b99faa70219201b17147477041b2fe9bb71f Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 12:39:40 -0400 Subject: [PATCH 150/248] Move log_i0 --- src/jimgw/single_event/utils.py | 20 -------------------- src/jimgw/utils.py | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 16d6cc7c..b2fb8c25 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -1,6 +1,5 @@ import jax.numpy as jnp from jax.scipy.integrate import trapezoid -from jax.scipy.special import i0e from jaxtyping import Array, Float @@ -477,22 +476,3 @@ def ra_dec_to_zenith_azimuth( theta, phi = ra_dec_to_theta_phi(ra, dec, gmst) zenith, azimuth = angle_rotation(theta, phi, rotation) return zenith, azimuth - - -def log_i0(x: Float[Array, " n"]) -> Float[Array, " n"]: - """ - A numerically stable method to evaluate log of - a modified Bessel function of order 0. - It is used in the phase-marginalized likelihood. - - Parameters - ========== - x: array-like - Value(s) at which to evaluate the function - - Returns - ======= - array-like: - The natural logarithm of the bessel function - """ - return jnp.log(i0e(x)) + x diff --git a/src/jimgw/utils.py b/src/jimgw/utils.py index e629903a..70c6e166 100644 --- a/src/jimgw/utils.py +++ b/src/jimgw/utils.py @@ -1,5 +1,10 @@ +import jax.numpy as jnp +from jax.scipy.special import i0e +from jaxtyping import Array, Float + from jimgw.prior import Prior + def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: if prior.composite: if isinstance(prior.base_prior, list): @@ -11,3 +16,22 @@ def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: output.append(prior) return output + + +def log_i0(x: Float[Array, " n"]) -> Float[Array, " n"]: + """ + A numerically stable method to evaluate log of + a modified Bessel function of order 0. + It is used in the phase-marginalized likelihood. + + Parameters + ========== + x: array-like + Value(s) at which to evaluate the function + + Returns + ======= + array-like: + The natural logarithm of the bessel function + """ + return jnp.log(i0e(x)) + x From e76469611477fd80b3e9f16d19aba21f25132bc7 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 12:43:12 -0400 Subject: [PATCH 151/248] Fixing check --- src/jimgw/single_event/likelihood.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/single_event/likelihood.py b/src/jimgw/single_event/likelihood.py index f10aeed1..ce2e8f0e 100644 --- a/src/jimgw/single_event/likelihood.py +++ b/src/jimgw/single_event/likelihood.py @@ -12,7 +12,7 @@ from jimgw.base import LikelihoodBase from jimgw.prior import Prior from jimgw.single_event.detector import Detector -from jimgw.single_event.utils import log_i0 +from jimgw.utils import log_i0 from jimgw.single_event.waveform import Waveform From 093dd090944062e64db88eef788d0a7a4cf006ba Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:51:20 -0400 Subject: [PATCH 152/248] Updated jim.py --- src/jimgw/jim.py | 87 +++++++++++------------------------------------- 1 file changed, 19 insertions(+), 68 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 7d86fecf..a31e614b 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -135,7 +135,7 @@ def negative_posterior(x: Float[Array, " n_dim"]): best_fit = optimizer.get_result()[0] return best_fit - def print_summary(self): + def print_summary(self, transform: bool = True): """ Generate summary of the run @@ -144,49 +144,21 @@ def print_summary(self): train_summary = self.sampler.get_sampler_state(training=True) production_summary = self.sampler.get_sampler_state(training=False) - training_chain = train_summary["chains"].reshape(-1, len(self.parameter_names)) - if self.sample_transforms: - # Need rewrite to vectorize - transformed_chain = {} - named_sample = self.add_name(training_chain[0]) - for transform in self.sample_transforms: - named_sample = transform.backward(named_sample) - for key, value in named_sample.items(): - transformed_chain[key] = [value] - for sample in training_chain[1:]: - named_sample = self.add_name(sample) - for transform in self.sample_transforms: - named_sample = transform.backward(named_sample) - for key, value in named_sample.items(): - transformed_chain[key].append(value) - training_chain = transformed_chain - else: - training_chain = self.add_name(training_chain) + training_chain = train_summary["chains"].reshape(-1, self.prior.n_dim).T + training_chain = self.add_name(training_chain) + if transform: + for sample_transform in self.sample_transforms: + training_chain = sample_transform.backward(training_chain) training_log_prob = train_summary["log_prob"] training_local_acceptance = train_summary["local_accs"] training_global_acceptance = train_summary["global_accs"] training_loss = train_summary["loss_vals"] - production_chain = production_summary["chains"].reshape( - -1, len(self.parameter_names) - ) - if self.sample_transforms: - # Need rewrite to vectorize - transformed_chain = {} - named_sample = self.add_name(production_chain[0]) - for transform in self.sample_transforms: - named_sample = transform.backward(named_sample) - for key, value in named_sample.items(): - transformed_chain[key] = [value] - for sample in production_chain[1:]: - named_sample = self.add_name(sample) - for transform in self.sample_transforms: - named_sample = transform.backward(named_sample) - for key, value in named_sample.items(): - transformed_chain[key].append(value) - production_chain = transformed_chain - else: - production_chain = self.add_name(production_chain) + production_chain = production_summary["chains"].reshape(-1, self.prior.n_dim).T + production_chain = self.add_name(production_chain) + if transform: + for sample_transform in self.sample_transforms: + production_chain = sample_transform.backward(production_chain) production_log_prob = production_summary["log_prob"] production_local_acceptance = production_summary["local_accs"] production_global_acceptance = production_summary["global_accs"] @@ -194,9 +166,7 @@ def print_summary(self): print("Training summary") print("=" * 10) for key, value in training_chain.items(): - print( - f"{key}: {jnp.array(value).mean():.3f} +/- {jnp.array(value).std():.3f}" - ) + print(f"{key}: {value.mean():.3f} +/- {value.std():.3f}") print( f"Log probability: {training_log_prob.mean():.3f} +/- {training_log_prob.std():.3f}" ) @@ -213,9 +183,7 @@ def print_summary(self): print("Production summary") print("=" * 10) for key, value in production_chain.items(): - print( - f"{key}: {jnp.array(value).mean():.3f} +/- {jnp.array(value).std():.3f}" - ) + print(f"{key}: {value.mean():.3f} +/- {value.std():.3f}") print( f"Log probability: {production_log_prob.mean():.3f} +/- {production_log_prob.std():.3f}" ) @@ -242,32 +210,15 @@ def get_samples(self, training: bool = False) -> dict: """ if training: - chains = self.sampler.get_sampler_state(training=True)["chains"] + chains = self.sampler.get_sampler_state(training=True)["chains"] # (500, 10100, 15) else: chains = self.sampler.get_sampler_state(training=False)["chains"] - # Need rewrite to output chains instead of flattened samples and vectorize - chains = chains.reshape(-1, len(self.parameter_names)) - if self.sample_transforms: - transformed_chain = {} - named_sample = self.add_name(chains[0]) - for transform in self.sample_transforms: - named_sample = transform.backward(named_sample) - for key, value in named_sample.items(): - transformed_chain[key] = [value] - for sample in chains[1:]: - named_sample = self.add_name(sample) - for transform in self.sample_transforms: - named_sample = transform.backward(named_sample) - for key, value in named_sample.items(): - transformed_chain[key].append(value) - output = transformed_chain - else: - output = self.add_name(chains) - - for key in output.keys(): - output[key] = jnp.array(output[key]) - return output + chains = chains.transpose(2, 0, 1) + chains = self.add_name(chains) + for sample_transform in self.sample_transforms: + chains = sample_transform.backward(chains) + return chains def plot(self): pass From ceb0b7f743c93a2bf011ccbb2b385a48d2b20d15 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:12:57 -0400 Subject: [PATCH 153/248] Added spin transform --- src/jimgw/single_event/transforms.py | 54 ++++++++- src/jimgw/single_event/utils.py | 158 ++++++++++++++++++++++++++ test/integration/test_GW150914_pv2.py | 141 +++++++++++++++++++++++ 3 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 test/integration/test_GW150914_pv2.py diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index fe12c52a..2f7805e4 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -4,7 +4,7 @@ from astropy.time import Time from jimgw.single_event.detector import GroundBased2G -from jimgw.transforms import BijectiveTransform +from jimgw.transforms import BijectiveTransform, NtoNTransform from jimgw.single_event.utils import ( m1_m2_to_Mc_q, Mc_q_to_m1_m2, @@ -15,6 +15,7 @@ ra_dec_to_zenith_azimuth, zenith_azimuth_to_ra_dec, euler_rotation, + spin_to_cartesian_spin, ) @@ -167,3 +168,54 @@ def named_inverse_transform(x): return {"ra": ra, "dec": dec} self.inverse_transform_func = named_inverse_transform + + +@jaxtyped(typechecker=typechecker) +class SpinToCartesianSpinTransform(NtoNTransform): + """ + Spin to Cartesian spin transformation + """ + + freq_ref: Float + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + freq_ref: Float, + ): + super().__init__(name_mapping) + + self.freq_ref = freq_ref + + assert ( + "theta_jn" in name_mapping[0] + and "phi_jl" in name_mapping[0] + and "theta1" in name_mapping[0] + and "theta2" in name_mapping[0] + and "phi12" in name_mapping[0] + and "a1" in name_mapping[0] + and "a2" in name_mapping[0] + and "iota" in name_mapping[1] + and "s1_x" in name_mapping[1] + and "s1_y" in name_mapping[1] + and "s1_z" in name_mapping[1] + and "s2_x" in name_mapping[1] + and "s2_y" in name_mapping[1] + and "s2_z" in name_mapping[1] + ) + + def named_transform(x): + iota, s1x, s1y, s1z, s2x, s2y, s2z = spin_to_cartesian_spin( + x["theta_jn"], x["phi_jl"], x["theta1"], x["theta2"], x["phi12"], x["a1"], x["a2"], x['M_c'], x['q'], self.freq_ref, x['phase_c'] + ) + return { + "iota": iota, + "s1_x": s1x, + "s1_y": s1y, + "s1_z": s1z, + "s2_x": s2x, + "s2_y": s2y, + "s2_z": s2z, + } + + self.transform_func = named_transform diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index b2fb8c25..396e5474 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -391,6 +391,164 @@ def theta_phi_to_ra_dec(theta: Float, phi: Float, gmst: Float) -> tuple[Float, F return ra, dec +def spin_to_cartesian_spin( + thetaJN: Float, + phiJL: Float, + theta1: Float, + theta2: Float, + phi12: Float, + chi1: Float, + chi2: Float, + M_c: Float, + q: Float, + fRef: Float, + phiRef: Float, +) -> tuple[Float, Float, Float, Float, Float, Float, Float]: + """ + Transforming the spin parameters + + The code is based on the approach used in LALsimulation: + https://lscsoft.docs.ligo.org/lalsuite/lalsimulation/group__lalsimulation__inference.html + + Parameters: + ------- + thetaJN: Float + Zenith angle between the total angular momentum and the line of sight + phiJL: Float + Difference between total and orbital angular momentum azimuthal angles + theta1: Float + Zenith angle between the spin and orbital angular momenta for the primary object + theta2: Float + Zenith angle between the spin and orbital angular momenta for the secondary object + phi12: Float + Difference between the azimuthal angles of the individual spin vector projections + onto the orbital plane + chi1: Float + Primary object aligned spin: + chi2: Float + Secondary object aligned spin: + M_c: Float + The chirp mass + eta: Float + The symmetric mass ratio + fRef: Float + The reference frequency + phiRef: Float + Binary phase at a reference frequency + + Returns: + ------- + iota: Float + Zenith angle between the orbital angular momentum and the line of sight + S1x: Float + The x-component of the primary spin + S1y: Float + The y-component of the primary spin + S1z: Float + The z-component of the primary spin + S2x: Float + The x-component of the secondary spin + S2y: Float + The y-component of the secondary spin + S2z: Float + The z-component of the secondary spin + """ + + def rotate_y(angle, vec): + """ + Rotate the vector (x, y, z) about y-axis + """ + cos_angle = jnp.cos(angle) + sin_angle = jnp.sin(angle) + rotation_matrix = jnp.array( + [[cos_angle, 0, sin_angle], [0, 1, 0], [-sin_angle, 0, cos_angle]] + ) + rotated_vec = jnp.dot(rotation_matrix, vec) + return rotated_vec + + def rotate_z(angle, vec): + """ + Rotate the vector (x, y, z) about z-axis + """ + cos_angle = jnp.cos(angle) + sin_angle = jnp.sin(angle) + rotation_matrix = jnp.array( + [[cos_angle, -sin_angle, 0], [sin_angle, cos_angle, 0], [0, 0, 1]] + ) + rotated_vec = jnp.dot(rotation_matrix, vec) + return rotated_vec + + LNh = jnp.array([0.0, 0.0, 1.0]) + + s1hat = jnp.array( + [ + jnp.sin(theta1) * jnp.cos(phiRef), + jnp.sin(theta1) * jnp.sin(phiRef), + jnp.cos(theta1), + ] + ) + s2hat = jnp.array( + [ + jnp.sin(theta2) * jnp.cos(phi12 + phiRef), + jnp.sin(theta2) * jnp.sin(phi12 + phiRef), + jnp.cos(theta2), + ] + ) + + m1, m2 = Mc_q_to_m1_m2(M_c, q) + eta = q / (1 + q) ** 2 + v0 = jnp.cbrt((m1 + m2) * Msun * jnp.pi * fRef) + + Lmag = ((m1 + m2) * (m1 + m2) * eta / v0) * (1.0 + v0 * v0 * (1.5 + eta / 6.0)) + s1 = m1 * m1 * chi1 * s1hat + s2 = m2 * m2 * chi2 * s2hat + J = s1 + s2 + jnp.array([0.0, 0.0, Lmag]) + + Jhat = J / jnp.linalg.norm(J) + theta0 = jnp.arccos(Jhat[2]) + phi0 = jnp.arctan2(Jhat[1], Jhat[0]) + + # Rotation 1: + s1hat = rotate_z(-phi0, s1hat) + s2hat = rotate_z(-phi0, s2hat) + + # Rotation 2: + LNh = rotate_y(-theta0, LNh) + s1hat = rotate_y(-theta0, s1hat) + s2hat = rotate_y(-theta0, s2hat) + + # Rotation 3: + LNh = rotate_z(phiJL - jnp.pi, LNh) + s1hat = rotate_z(phiJL - jnp.pi, s1hat) + s2hat = rotate_z(phiJL - jnp.pi, s2hat) + + # Compute iota + N = jnp.array([0.0, jnp.sin(thetaJN), jnp.cos(thetaJN)]) + iota = jnp.arccos(jnp.dot(N, LNh)) + + thetaLJ = jnp.arccos(LNh[2]) + phiL = jnp.arctan2(LNh[1], LNh[0]) + + # Rotation 4: + s1hat = rotate_z(-phiL, s1hat) + s2hat = rotate_z(-phiL, s2hat) + N = rotate_z(-phiL, N) + + # Rotation 5: + s1hat = rotate_y(-thetaLJ, s1hat) + s2hat = rotate_y(-thetaLJ, s2hat) + N = rotate_y(-thetaLJ, N) + + # Rotation 6: + phiN = jnp.arctan2(N[1], N[0]) + s1hat = rotate_z(jnp.pi / 2.0 - phiN - phiRef, s1hat) + s2hat = rotate_z(jnp.pi / 2.0 - phiN - phiRef, s2hat) + + S1 = s1hat * chi1 + S2 = s2hat * chi2 + return iota, S1[0], S1[1], S1[2], S2[0], S2[1], S2[2] + + def zenith_azimuth_to_ra_dec( zenith: Float, azimuth: Float, gmst: Float, rotation: Float[Array, " 3 3"] ) -> tuple[Float, Float]: diff --git a/test/integration/test_GW150914_pv2.py b/test/integration/test_GW150914_pv2.py new file mode 100644 index 00000000..da2e510d --- /dev/null +++ b/test/integration/test_GW150914_pv2.py @@ -0,0 +1,141 @@ +import time + +import jax +import jax.numpy as jnp + +from jimgw.jim import Jim +from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior +from jimgw.single_event.detector import H1, L1 +from jimgw.single_event.likelihood import TransientLikelihoodFD +from jimgw.single_event.waveform import RippleIMRPhenomD +from jimgw.transforms import BoundToUnbound +from jimgw.single_event.transforms import ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, ComponentMassesToChirpMassMassRatioTransform, MassRatioToSymmetricMassRatioTransform, SpinToCartesianSpinTransform +from jimgw.single_event.utils import Mc_q_to_m1_m2 +from flowMC.strategy.optimization import optimization_Adam + +jax.config.update("jax_enable_x64", True) + +########################################### +########## First we grab data ############# +########################################### + +total_time_start = time.time() + +# first, fetch a 4s segment centered on GW150914 +gps = 1126259462.4 +duration = 4 +post_trigger_duration = 2 +start_pad = duration - post_trigger_duration +end_pad = post_trigger_duration +fmin = 20.0 +fmax = 1024.0 + +ifos = [H1, L1] + +for ifo in ifos: + ifo.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) + +Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) +q_prior = UniformPrior(0.125, 1., parameter_names=["q"]) +theta_jn_prior = SinePrior(parameter_names=["theta_jn"]) +phi_jl_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_jl"]) +theta_1_prior = SinePrior(parameter_names=["theta_1"]) +theta_2_prior = SinePrior(parameter_names=["theta_2"]) +phi_12_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_12"]) +a1_prior = UniformPrior(0.0, 1.0, parameter_names=["a1"]) +a2_prior = UniformPrior(0.0, 1.0, parameter_names=["a2"]) +dL_prior = UniformPrior(0.0, 2000.0, parameter_names=["d_L"]) +t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) +phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) +psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) +ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) +dec_prior = CosinePrior(parameter_names=["dec"]) + +prior = CombinePrior( + [ + Mc_prior, + q_prior, + theta_jn_prior, + phi_jl_prior, + theta_1_prior, + theta_2_prior, + phi_12_prior, + a1_prior, + a2_prior, + dL_prior, + t_c_prior, + phase_c_prior, + psi_prior, + ra_prior, + dec_prior, + ] +) + +sample_transforms = [ + BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), + BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=0.125, original_upper_bound=1.), + BoundToUnbound(name_mapping = [["theta_jn"], ["theta_jn_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["phi_jl"], ["phi_jl_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["theta_1"], ["theta_1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["theta_2"], ["theta_2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["phi_12"], ["phi_12_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["a1"], ["a1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["a2"], ["a2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), + BoundToUnbound(name_mapping = [["t_c"], ["t_c_unbounded"]] , original_lower_bound=-0.05, original_upper_bound=0.05), + BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["ra"], ["ra_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=-jnp.pi / 2, original_upper_bound=jnp.pi / 2) +] + +likelihood_transforms = [ + SpinToCartesianSpinTransform(name_mapping=[["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a1", "a2"], ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"]], freq_ref=20.0), + MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), +] + +likelihood = TransientLikelihoodFD( + ifos, + waveform=RippleIMRPhenomD(), + trigger_time=gps, + duration=4, + post_trigger_duration=2, +) + + +mass_matrix = jnp.eye(15) +mass_matrix = mass_matrix.at[1, 1].set(1e-3) +mass_matrix = mass_matrix.at[9, 9].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 3e-3} + +Adam_optimizer = optimization_Adam(n_steps=5, learning_rate=0.01, noise_level=1) + +n_epochs = 2 +n_loop_training = 1 +learning_rate = 1e-4 + + +jim = Jim( + likelihood, + prior, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, + n_loop_training=n_loop_training, + n_loop_production=1, + n_local_steps=5, + n_global_steps=5, + n_chains=4, + n_epochs=n_epochs, + learning_rate=learning_rate, + n_max_examples=30, + n_flow_samples=100, + momentum=0.9, + batch_size=100, + use_global=True, + train_thinning=1, + output_thinning=1, + local_sampler_arg=local_sampler_arg, + strategies=[Adam_optimizer, "default"], +) + +jim.sample(jax.random.PRNGKey(42)) \ No newline at end of file From b972aead4452fc5b8e1d90c716cffe937003f559 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:17:05 -0400 Subject: [PATCH 154/248] Updated transform.py --- src/jimgw/single_event/transforms.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 2f7805e4..71e52259 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -190,11 +190,11 @@ def __init__( assert ( "theta_jn" in name_mapping[0] and "phi_jl" in name_mapping[0] - and "theta1" in name_mapping[0] - and "theta2" in name_mapping[0] - and "phi12" in name_mapping[0] - and "a1" in name_mapping[0] - and "a2" in name_mapping[0] + and "theta_1" in name_mapping[0] + and "theta_2" in name_mapping[0] + and "phi_12" in name_mapping[0] + and "a_1" in name_mapping[0] + and "a_2" in name_mapping[0] and "iota" in name_mapping[1] and "s1_x" in name_mapping[1] and "s1_y" in name_mapping[1] From d4a4386a0e52393e88226e41f368f594005d75df Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:17:27 -0400 Subject: [PATCH 155/248] Updated test_GW150914_pv2.py --- test/integration/test_GW150914_pv2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_GW150914_pv2.py b/test/integration/test_GW150914_pv2.py index da2e510d..8d8f7884 100644 --- a/test/integration/test_GW150914_pv2.py +++ b/test/integration/test_GW150914_pv2.py @@ -90,7 +90,7 @@ ] likelihood_transforms = [ - SpinToCartesianSpinTransform(name_mapping=[["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a1", "a2"], ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"]], freq_ref=20.0), + SpinToCartesianSpinTransform(name_mapping=[["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a_1", "a_2"], ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"]], freq_ref=20.0), MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), ] From 0d6a5e5c44a66789b7f4ba288b26e57bf396f08c Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:19:00 -0400 Subject: [PATCH 156/248] Updated transform.py --- src/jimgw/single_event/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 71e52259..aac2fddb 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -206,7 +206,7 @@ def __init__( def named_transform(x): iota, s1x, s1y, s1z, s2x, s2y, s2z = spin_to_cartesian_spin( - x["theta_jn"], x["phi_jl"], x["theta1"], x["theta2"], x["phi12"], x["a1"], x["a2"], x['M_c'], x['q'], self.freq_ref, x['phase_c'] + x["theta_jn"], x["phi_jl"], x["theta_1"], x["theta_2"], x["phi_12"], x["a_1"], x["a_2"], x['M_c'], x['q'], self.freq_ref, x['phase_c'] ) return { "iota": iota, From 91f2f8918130d6e586a3e114c9779c77288b1771 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:21:46 -0400 Subject: [PATCH 157/248] Updated test_GW150914_pv2.py --- test/integration/test_GW150914_pv2.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/integration/test_GW150914_pv2.py b/test/integration/test_GW150914_pv2.py index 8d8f7884..753ec5b3 100644 --- a/test/integration/test_GW150914_pv2.py +++ b/test/integration/test_GW150914_pv2.py @@ -42,8 +42,8 @@ theta_1_prior = SinePrior(parameter_names=["theta_1"]) theta_2_prior = SinePrior(parameter_names=["theta_2"]) phi_12_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_12"]) -a1_prior = UniformPrior(0.0, 1.0, parameter_names=["a1"]) -a2_prior = UniformPrior(0.0, 1.0, parameter_names=["a2"]) +a_1_prior = UniformPrior(0.0, 1.0, parameter_names=["a_1"]) +a_2_prior = UniformPrior(0.0, 1.0, parameter_names=["a_2"]) dL_prior = UniformPrior(0.0, 2000.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) @@ -60,8 +60,8 @@ theta_1_prior, theta_2_prior, phi_12_prior, - a1_prior, - a2_prior, + a_1_prior, + a_2_prior, dL_prior, t_c_prior, phase_c_prior, @@ -79,8 +79,8 @@ BoundToUnbound(name_mapping = [["theta_1"], ["theta_1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["theta_2"], ["theta_2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["phi_12"], ["phi_12_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["a1"], ["a1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = [["a2"], ["a2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["a_1"], ["a_1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["a_2"], ["a_2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), BoundToUnbound(name_mapping = [["t_c"], ["t_c_unbounded"]] , original_lower_bound=-0.05, original_upper_bound=0.05), BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), From 6fc8887f467027f3ce45514f35e0b61e1ff1d279 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:21:59 -0400 Subject: [PATCH 158/248] Updated utils.py --- src/jimgw/single_event/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index 396e5474..a15bd7bf 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -2,6 +2,8 @@ from jax.scipy.integrate import trapezoid from jaxtyping import Array, Float +from jimgw.constants import Msun + def inner_product( h1: Float[Array, " n_sample"], From 49d604d3db54a09376145449fc69480d3226c7f2 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 13:31:09 -0400 Subject: [PATCH 159/248] Fix mass transform test --- src/jimgw/jim.py | 16 ++++++++++++---- test/integration/test_GW150914.py | 19 +++++++++++-------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 7d86fecf..a13d0d3d 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -104,10 +104,18 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): if initial_guess.size == 0: - initial_guess_named = self.prior.sample(key, self.sampler.n_chains) - for transform in self.sample_transforms: - initial_guess_named = jax.vmap(transform.forward)(initial_guess_named) - initial_guess = jnp.stack([i for i in initial_guess_named.values()]).T + initial_guess = [] + for i in range(self.sampler.n_chains): + flag = True + while flag: + key = jax.random.split(key)[1] + guess = self.prior.sample(key, 1) + for transform in self.sample_transforms: + guess = transform.forward(guess) + guess = jnp.array([i for i in guess.values()]).T[0] + flag = not jnp.all(jnp.isfinite(guess)) + initial_guess.append(guess) + initial_guess = jnp.array(initial_guess) self.sampler.sample(initial_guess, None) # type: ignore def maximize_likelihood( diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914.py index 6fddf9ea..90cba0f1 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914.py @@ -9,7 +9,7 @@ from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD from jimgw.transforms import BoundToUnbound -from jimgw.single_event.transforms import ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, ComponentMassesToChirpMassMassRatioTransform, MassRatioToSymmetricMassRatioTransform +from jimgw.single_event.transforms import ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, ComponentMassesToChirpMassMassRatioTransform from jimgw.single_event.utils import Mc_q_to_m1_m2 from flowMC.strategy.optimization import optimization_Adam @@ -35,8 +35,10 @@ for ifo in ifos: ifo.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) -Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) -q_prior = UniformPrior(0.125, 1.0, parameter_names=["q"]) +M_c_min, M_c_max = 10.0, 80.0 +q_min, q_max = 0.125, 1.0 +m_1_prior = UniformPrior(Mc_q_to_m1_m2(M_c_min, q_max)[0], Mc_q_to_m1_m2(M_c_max, q_min)[0], parameter_names=["m_1"]) +m_2_prior = UniformPrior(Mc_q_to_m1_m2(M_c_min, q_min)[1], Mc_q_to_m1_m2(M_c_max, q_max)[1], parameter_names=["m_2"]) s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) s2z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) dL_prior = UniformPrior(0.0, 2000.0, parameter_names=["d_L"]) @@ -49,8 +51,8 @@ prior = CombinePrior( [ - Mc_prior, - q_prior, + m_1_prior, + m_2_prior, s1z_prior, s2z_prior, dL_prior, @@ -64,8 +66,9 @@ ) sample_transforms = [ - BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), - BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=0.125, original_upper_bound=1.), + ComponentMassesToChirpMassMassRatioTransform(name_mapping=[["m_1", "m_2"], ["M_c", "q"]]), + BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=M_c_min, original_upper_bound=M_c_max), + BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=q_min, original_upper_bound=q_max), BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), BoundToUnbound(name_mapping = [["s2_z"], ["s2_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), @@ -79,7 +82,7 @@ ] likelihood_transforms = [ - MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), + ComponentMassesToChirpMassSymmetricMassRatioTransform(name_mapping=[["m_1", "m_2"], ["M_c", "eta"]]), ] likelihood = TransientLikelihoodFD( From 3b7f9e882532c2056632f1f7c23487c4c72779df Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:40:40 -0400 Subject: [PATCH 160/248] Reformated --- src/jimgw/jim.py | 2 +- src/jimgw/single_event/transforms.py | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index a31e614b..b6c91582 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -210,7 +210,7 @@ def get_samples(self, training: bool = False) -> dict: """ if training: - chains = self.sampler.get_sampler_state(training=True)["chains"] # (500, 10100, 15) + chains = self.sampler.get_sampler_state(training=True)["chains"] else: chains = self.sampler.get_sampler_state(training=False)["chains"] diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index aac2fddb..c3e77846 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -168,7 +168,7 @@ def named_inverse_transform(x): return {"ra": ra, "dec": dec} self.inverse_transform_func = named_inverse_transform - + @jaxtyped(typechecker=typechecker) class SpinToCartesianSpinTransform(NtoNTransform): @@ -177,16 +177,16 @@ class SpinToCartesianSpinTransform(NtoNTransform): """ freq_ref: Float - + def __init__( self, name_mapping: tuple[list[str], list[str]], freq_ref: Float, ): super().__init__(name_mapping) - + self.freq_ref = freq_ref - + assert ( "theta_jn" in name_mapping[0] and "phi_jl" in name_mapping[0] @@ -203,10 +203,20 @@ def __init__( and "s2_y" in name_mapping[1] and "s2_z" in name_mapping[1] ) - + def named_transform(x): iota, s1x, s1y, s1z, s2x, s2y, s2z = spin_to_cartesian_spin( - x["theta_jn"], x["phi_jl"], x["theta_1"], x["theta_2"], x["phi_12"], x["a_1"], x["a_2"], x['M_c'], x['q'], self.freq_ref, x['phase_c'] + x["theta_jn"], + x["phi_jl"], + x["theta_1"], + x["theta_2"], + x["phi_12"], + x["a_1"], + x["a_2"], + x["M_c"], + x["q"], + self.freq_ref, + x["phase_c"], ) return { "iota": iota, @@ -217,5 +227,5 @@ def named_transform(x): "s2_y": s2y, "s2_z": s2z, } - + self.transform_func = named_transform From ac0b1f57ab4ebb07e7d984f79735e9a03e49be86 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 13:43:49 -0400 Subject: [PATCH 161/248] Use PowerLaw for distance --- test/integration/{test_GW150914.py => test_GW150914_D.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename test/integration/{test_GW150914.py => test_GW150914_D.py} (98%) diff --git a/test/integration/test_GW150914.py b/test/integration/test_GW150914_D.py similarity index 98% rename from test/integration/test_GW150914.py rename to test/integration/test_GW150914_D.py index 90cba0f1..9a39434e 100644 --- a/test/integration/test_GW150914.py +++ b/test/integration/test_GW150914_D.py @@ -4,7 +4,7 @@ import jax.numpy as jnp from jimgw.jim import Jim -from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior +from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD @@ -41,7 +41,7 @@ m_2_prior = UniformPrior(Mc_q_to_m1_m2(M_c_min, q_min)[1], Mc_q_to_m1_m2(M_c_max, q_max)[1], parameter_names=["m_2"]) s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) s2z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) -dL_prior = UniformPrior(0.0, 2000.0, parameter_names=["d_L"]) +dL_prior = PowerLawPrior(1.0, 2000.0, 2.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) iota_prior = SinePrior(parameter_names=["iota"]) From e0a61fa163899f8c74ad8db1d70225be527e7704 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:44:44 -0400 Subject: [PATCH 162/248] Reformated --- test/integration/test_GW150914_pv2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/test_GW150914_pv2.py b/test/integration/test_GW150914_pv2.py index 753ec5b3..5a15bd9b 100644 --- a/test/integration/test_GW150914_pv2.py +++ b/test/integration/test_GW150914_pv2.py @@ -4,7 +4,7 @@ import jax.numpy as jnp from jimgw.jim import Jim -from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior +from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD @@ -44,7 +44,7 @@ phi_12_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_12"]) a_1_prior = UniformPrior(0.0, 1.0, parameter_names=["a_1"]) a_2_prior = UniformPrior(0.0, 1.0, parameter_names=["a_2"]) -dL_prior = UniformPrior(0.0, 2000.0, parameter_names=["d_L"]) +dL_prior = PowerLawPrior(10.0, 2000.0, 2.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) From 63747724ab11c3091c986e0781592ffbcd88cb9f Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:26:40 -0400 Subject: [PATCH 163/248] Rename test_GW150914_pv2.py to test_GW150914_PV2.py --- test/integration/{test_GW150914_pv2.py => test_GW150914_PV2.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/integration/{test_GW150914_pv2.py => test_GW150914_PV2.py} (99%) diff --git a/test/integration/test_GW150914_pv2.py b/test/integration/test_GW150914_PV2.py similarity index 99% rename from test/integration/test_GW150914_pv2.py rename to test/integration/test_GW150914_PV2.py index 5a15bd9b..6be02936 100644 --- a/test/integration/test_GW150914_pv2.py +++ b/test/integration/test_GW150914_PV2.py @@ -138,4 +138,4 @@ strategies=[Adam_optimizer, "default"], ) -jim.sample(jax.random.PRNGKey(42)) \ No newline at end of file +jim.sample(jax.random.PRNGKey(42)) From fa134f9b0f575f7d5921e7083afa5b4c81b99400 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 15:04:51 -0400 Subject: [PATCH 164/248] Add heterodyne test --- src/jimgw/jim.py | 2 +- src/jimgw/single_event/likelihood.py | 66 +++++++-- test/integration/test_GW150914_D.py | 4 - .../integration/test_GW150914_D_heterodyne.py | 131 ++++++++++++++++++ 4 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 test/integration/test_GW150914_D_heterodyne.py diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index a13d0d3d..2063b0bf 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -105,7 +105,7 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): if initial_guess.size == 0: initial_guess = [] - for i in range(self.sampler.n_chains): + for _ in range(self.sampler.n_chains): flag = True while flag: key = jax.random.split(key)[1] diff --git a/src/jimgw/single_event/likelihood.py b/src/jimgw/single_event/likelihood.py index ce2e8f0e..0ccb1ce8 100644 --- a/src/jimgw/single_event/likelihood.py +++ b/src/jimgw/single_event/likelihood.py @@ -14,6 +14,7 @@ from jimgw.single_event.detector import Detector from jimgw.utils import log_i0 from jimgw.single_event.waveform import Waveform +from jimgw.transforms import BijectiveTransform, NtoMTransform class SingleEventLiklihood(LikelihoodBase): @@ -184,8 +185,6 @@ def __init__( self, detectors: list[Detector], waveform: Waveform, - prior: Prior, - bounds: Float[Array, " n_dim 2"], n_bins: int = 100, trigger_time: float = 0, duration: float = 4, @@ -194,6 +193,9 @@ def __init__( n_steps: int = 2000, ref_params: dict = {}, reference_waveform: Optional[Waveform] = None, + prior: Optional[Prior] = None, + sample_transforms: Optional[list[BijectiveTransform]] = [], + likelihood_transforms: Optional[list[NtoMTransform]] = [], **kwargs, ) -> None: super().__init__( @@ -254,17 +256,24 @@ def __init__( ) self.freq_grid_low = freq_grid[:-1] - if not ref_params: + if ref_params: + self.ref_params = ref_params + print(f"Reference parameters provided, which are {self.ref_params}") + elif prior: print("No reference parameters are provided, finding it...") ref_params = self.maximize_likelihood( - bounds=bounds, prior=prior, popsize=popsize, n_steps=n_steps + prior=prior, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, + popsize=popsize, + n_steps=n_steps, ) self.ref_params = {key: float(value) for key, value in ref_params.items()} print(f"The reference parameters are {self.ref_params}") else: - self.ref_params = ref_params - print(f"Reference parameters provided, which are {self.ref_params}") - + raise ValueError( + "Either reference parameters or parameter names must be provided" + ) # safe guard for the reference parameters # since ripple cannot handle eta=0.25 if jnp.isclose(self.ref_params["eta"], 0.25): @@ -542,25 +551,54 @@ def compute_coefficients(data, h_ref, psd, freqs, f_bins, f_bins_center): def maximize_likelihood( self, prior: Prior, - bounds: Float[Array, " n_dim 2"], + likelihood_transforms: list[BijectiveTransform], + sample_transforms: list[NtoMTransform], popsize: int = 100, n_steps: int = 2000, ): + parameter_names = prior.parameter_names + for transform in sample_transforms: + parameter_names = transform.propagate_name(parameter_names) + def y(x): - return -self.evaluate_original(prior.transform(prior.add_name(x)), {}) + named_params = dict(zip(parameter_names, x)) + for transform in reversed(sample_transforms): + named_params = transform.backward(named_params) + for transform in likelihood_transforms: + named_params = transform.forward(named_params) + return -self.evaluate_original(named_params, {}) print("Starting the optimizer") + optimizer = optimization_Adam( n_steps=n_steps, learning_rate=0.001, noise_level=1 ) - initial_position = jnp.array( - list(prior.sample(jax.random.PRNGKey(0), popsize).values()) - ).T + + key = jax.random.PRNGKey(0) + initial_position = [] + for _ in range(popsize): + flag = True + while flag: + key = jax.random.split(key)[1] + guess = prior.sample(key, 1) + for transform in sample_transforms: + guess = transform.forward(guess) + guess = jnp.array([i for i in guess.values()]).T[0] + flag = not jnp.all(jnp.isfinite(guess)) + initial_position.append(guess) + initial_position = jnp.array(initial_position) rng_key, optimized_positions, summary = optimizer.optimize( jax.random.PRNGKey(12094), y, initial_position ) - best_fit = optimized_positions[jnp.nanargmin(summary["final_log_prob"])] - return prior.transform(prior.add_name(best_fit)) + + best_fit = optimized_positions[jnp.argmin(summary["final_log_prob"])] + + named_params = dict(zip(parameter_names, best_fit)) + for transform in reversed(sample_transforms): + named_params = transform.backward(named_params) + for transform in likelihood_transforms: + named_params = transform.forward(named_params) + return named_params likelihood_presets = { diff --git a/test/integration/test_GW150914_D.py b/test/integration/test_GW150914_D.py index 9a39434e..ba3ce2d6 100644 --- a/test/integration/test_GW150914_D.py +++ b/test/integration/test_GW150914_D.py @@ -1,5 +1,3 @@ -import time - import jax import jax.numpy as jnp @@ -19,8 +17,6 @@ ########## First we grab data ############# ########################################### -total_time_start = time.time() - # first, fetch a 4s segment centered on GW150914 gps = 1126259462.4 duration = 4 diff --git a/test/integration/test_GW150914_D_heterodyne.py b/test/integration/test_GW150914_D_heterodyne.py new file mode 100644 index 00000000..66093b88 --- /dev/null +++ b/test/integration/test_GW150914_D_heterodyne.py @@ -0,0 +1,131 @@ +import jax +import jax.numpy as jnp + +from jimgw.jim import Jim +from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior +from jimgw.single_event.detector import H1, L1 +from jimgw.single_event.likelihood import HeterodynedTransientLikelihoodFD +from jimgw.single_event.waveform import RippleIMRPhenomD +from jimgw.transforms import BoundToUnbound +from jimgw.single_event.transforms import ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, ComponentMassesToChirpMassMassRatioTransform +from jimgw.single_event.utils import Mc_q_to_m1_m2 +from flowMC.strategy.optimization import optimization_Adam + +jax.config.update("jax_enable_x64", True) + +########################################### +########## First we grab data ############# +########################################### + +# first, fetch a 4s segment centered on GW150914 +gps = 1126259462.4 +duration = 4 +post_trigger_duration = 2 +start_pad = duration - post_trigger_duration +end_pad = post_trigger_duration +fmin = 20.0 +fmax = 1024.0 + +ifos = [H1, L1] + +for ifo in ifos: + ifo.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) + +M_c_min, M_c_max = 10.0, 80.0 +q_min, q_max = 0.125, 1.0 +m_1_prior = UniformPrior(Mc_q_to_m1_m2(M_c_min, q_max)[0], Mc_q_to_m1_m2(M_c_max, q_min)[0], parameter_names=["m_1"]) +m_2_prior = UniformPrior(Mc_q_to_m1_m2(M_c_min, q_min)[1], Mc_q_to_m1_m2(M_c_max, q_max)[1], parameter_names=["m_2"]) +s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) +s2z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) +dL_prior = PowerLawPrior(1.0, 2000.0, 2.0, parameter_names=["d_L"]) +t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) +phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) +iota_prior = SinePrior(parameter_names=["iota"]) +psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) +ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) +dec_prior = CosinePrior(parameter_names=["dec"]) + +prior = CombinePrior( + [ + m_1_prior, + m_2_prior, + s1z_prior, + s2z_prior, + dL_prior, + t_c_prior, + phase_c_prior, + iota_prior, + psi_prior, + ra_prior, + dec_prior, + ] +) + +sample_transforms = [ + ComponentMassesToChirpMassMassRatioTransform(name_mapping=[["m_1", "m_2"], ["M_c", "q"]]), + BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=M_c_min, original_upper_bound=M_c_max), + BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=q_min, original_upper_bound=q_max), + BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["s2_z"], ["s2_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), + BoundToUnbound(name_mapping = [["t_c"], ["t_c_unbounded"]] , original_lower_bound=-0.05, original_upper_bound=0.05), + BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=0., original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), + SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), + BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), +] + +likelihood_transforms = [ + ComponentMassesToChirpMassSymmetricMassRatioTransform(name_mapping=[["m_1", "m_2"], ["M_c", "eta"]]), +] + +likelihood = HeterodynedTransientLikelihoodFD( + ifos, + prior=prior, + waveform=RippleIMRPhenomD(), + trigger_time=gps, + duration=4, + post_trigger_duration=2, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, +) + + +mass_matrix = jnp.eye(11) +mass_matrix = mass_matrix.at[1, 1].set(1e-3) +mass_matrix = mass_matrix.at[5, 5].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 3e-3} + +Adam_optimizer = optimization_Adam(n_steps=5, learning_rate=0.01, noise_level=1) + +n_epochs = 2 +n_loop_training = 1 +learning_rate = 1e-4 + + +jim = Jim( + likelihood, + prior, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, + n_loop_training=n_loop_training, + n_loop_production=1, + n_local_steps=5, + n_global_steps=5, + n_chains=4, + n_epochs=n_epochs, + learning_rate=learning_rate, + n_max_examples=30, + n_flow_samples=100, + momentum=0.9, + batch_size=100, + use_global=True, + train_thinning=1, + output_thinning=1, + local_sampler_arg=local_sampler_arg, + strategies=[Adam_optimizer, "default"], +) + +jim.sample(jax.random.PRNGKey(42)) From 2c9c6a62fac657e9d54137ff151e9cc66e24385c Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 15:21:02 -0400 Subject: [PATCH 165/248] Fix typecheck --- src/jimgw/jim.py | 8 ++++---- src/jimgw/single_event/likelihood.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 2063b0bf..043c4672 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -102,8 +102,8 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): named_params = transform.forward(named_params) return self.likelihood.evaluate(named_params, data) + prior - def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): - if initial_guess.size == 0: + def sample(self, key: PRNGKeyArray, initial_position: Array = jnp.array([])): + if initial_position.size == 0: initial_guess = [] for _ in range(self.sampler.n_chains): flag = True @@ -115,8 +115,8 @@ def sample(self, key: PRNGKeyArray, initial_guess: Array = jnp.array([])): guess = jnp.array([i for i in guess.values()]).T[0] flag = not jnp.all(jnp.isfinite(guess)) initial_guess.append(guess) - initial_guess = jnp.array(initial_guess) - self.sampler.sample(initial_guess, None) # type: ignore + initial_position = jnp.array(initial_guess) + self.sampler.sample(initial_position, None) # type: ignore def maximize_likelihood( self, diff --git a/src/jimgw/single_event/likelihood.py b/src/jimgw/single_event/likelihood.py index 0ccb1ce8..00e6ce6b 100644 --- a/src/jimgw/single_event/likelihood.py +++ b/src/jimgw/single_event/likelihood.py @@ -194,8 +194,8 @@ def __init__( ref_params: dict = {}, reference_waveform: Optional[Waveform] = None, prior: Optional[Prior] = None, - sample_transforms: Optional[list[BijectiveTransform]] = [], - likelihood_transforms: Optional[list[NtoMTransform]] = [], + sample_transforms: list[BijectiveTransform] = [], + likelihood_transforms: list[NtoMTransform] = [], **kwargs, ) -> None: super().__init__( @@ -551,8 +551,8 @@ def compute_coefficients(data, h_ref, psd, freqs, f_bins, f_bins_center): def maximize_likelihood( self, prior: Prior, - likelihood_transforms: list[BijectiveTransform], - sample_transforms: list[NtoMTransform], + likelihood_transforms: list[NtoMTransform], + sample_transforms: list[BijectiveTransform], popsize: int = 100, n_steps: int = 2000, ): From 15e40748844a9f5ae33bf60f3aa0880ff5632dfa Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Fri, 2 Aug 2024 15:25:09 -0400 Subject: [PATCH 166/248] Shorten test runtime --- test/integration/test_GW150914_D_heterodyne.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/integration/test_GW150914_D_heterodyne.py b/test/integration/test_GW150914_D_heterodyne.py index 66093b88..b5945cee 100644 --- a/test/integration/test_GW150914_D_heterodyne.py +++ b/test/integration/test_GW150914_D_heterodyne.py @@ -90,6 +90,8 @@ post_trigger_duration=2, sample_transforms=sample_transforms, likelihood_transforms=likelihood_transforms, + n_steps=5, + popsize=10, ) From 174d43a1fdc4fecb4a8b7dd6a51aadc102aff12a Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Sat, 3 Aug 2024 07:27:18 -0400 Subject: [PATCH 167/248] Fix jim output functions --- src/jimgw/jim.py | 6 +++--- test/integration/test_GW150914_D.py | 2 ++ test/integration/test_GW150914_D_heterodyne.py | 2 ++ test/integration/test_GW150914_PV2.py | 5 +++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index cc1ff14b..fae0bc98 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -155,7 +155,7 @@ def print_summary(self, transform: bool = True): training_chain = train_summary["chains"].reshape(-1, self.prior.n_dim).T training_chain = self.add_name(training_chain) if transform: - for sample_transform in self.sample_transforms: + for sample_transform in reversed(self.sample_transforms): training_chain = sample_transform.backward(training_chain) training_log_prob = train_summary["log_prob"] training_local_acceptance = train_summary["local_accs"] @@ -165,7 +165,7 @@ def print_summary(self, transform: bool = True): production_chain = production_summary["chains"].reshape(-1, self.prior.n_dim).T production_chain = self.add_name(production_chain) if transform: - for sample_transform in self.sample_transforms: + for sample_transform in reversed(self.sample_transforms): production_chain = sample_transform.backward(production_chain) production_log_prob = production_summary["log_prob"] production_local_acceptance = production_summary["local_accs"] @@ -224,7 +224,7 @@ def get_samples(self, training: bool = False) -> dict: chains = chains.transpose(2, 0, 1) chains = self.add_name(chains) - for sample_transform in self.sample_transforms: + for sample_transform in reversed(self.sample_transforms): chains = sample_transform.backward(chains) return chains diff --git a/test/integration/test_GW150914_D.py b/test/integration/test_GW150914_D.py index ba3ce2d6..e1eee9ac 100644 --- a/test/integration/test_GW150914_D.py +++ b/test/integration/test_GW150914_D.py @@ -126,3 +126,5 @@ ) jim.sample(jax.random.PRNGKey(42)) +jim.get_samples() +jim.print_summary() diff --git a/test/integration/test_GW150914_D_heterodyne.py b/test/integration/test_GW150914_D_heterodyne.py index b5945cee..bf97efdb 100644 --- a/test/integration/test_GW150914_D_heterodyne.py +++ b/test/integration/test_GW150914_D_heterodyne.py @@ -131,3 +131,5 @@ ) jim.sample(jax.random.PRNGKey(42)) +jim.get_samples() +jim.print_summary() diff --git a/test/integration/test_GW150914_PV2.py b/test/integration/test_GW150914_PV2.py index 6be02936..c9d83a5e 100644 --- a/test/integration/test_GW150914_PV2.py +++ b/test/integration/test_GW150914_PV2.py @@ -9,8 +9,7 @@ from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD from jimgw.transforms import BoundToUnbound -from jimgw.single_event.transforms import ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, ComponentMassesToChirpMassMassRatioTransform, MassRatioToSymmetricMassRatioTransform, SpinToCartesianSpinTransform -from jimgw.single_event.utils import Mc_q_to_m1_m2 +from jimgw.single_event.transforms import MassRatioToSymmetricMassRatioTransform, SpinToCartesianSpinTransform from flowMC.strategy.optimization import optimization_Adam jax.config.update("jax_enable_x64", True) @@ -139,3 +138,5 @@ ) jim.sample(jax.random.PRNGKey(42)) +jim.get_samples() +jim.print_summary() From 14121d738d739079b395b7d18c59bc6417258a75 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Sat, 3 Aug 2024 07:29:26 -0400 Subject: [PATCH 168/248] Delete test_GW150914_PV2.py --- test/integration/test_GW150914_PV2.py | 142 -------------------------- 1 file changed, 142 deletions(-) delete mode 100644 test/integration/test_GW150914_PV2.py diff --git a/test/integration/test_GW150914_PV2.py b/test/integration/test_GW150914_PV2.py deleted file mode 100644 index c9d83a5e..00000000 --- a/test/integration/test_GW150914_PV2.py +++ /dev/null @@ -1,142 +0,0 @@ -import time - -import jax -import jax.numpy as jnp - -from jimgw.jim import Jim -from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior -from jimgw.single_event.detector import H1, L1 -from jimgw.single_event.likelihood import TransientLikelihoodFD -from jimgw.single_event.waveform import RippleIMRPhenomD -from jimgw.transforms import BoundToUnbound -from jimgw.single_event.transforms import MassRatioToSymmetricMassRatioTransform, SpinToCartesianSpinTransform -from flowMC.strategy.optimization import optimization_Adam - -jax.config.update("jax_enable_x64", True) - -########################################### -########## First we grab data ############# -########################################### - -total_time_start = time.time() - -# first, fetch a 4s segment centered on GW150914 -gps = 1126259462.4 -duration = 4 -post_trigger_duration = 2 -start_pad = duration - post_trigger_duration -end_pad = post_trigger_duration -fmin = 20.0 -fmax = 1024.0 - -ifos = [H1, L1] - -for ifo in ifos: - ifo.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) - -Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) -q_prior = UniformPrior(0.125, 1., parameter_names=["q"]) -theta_jn_prior = SinePrior(parameter_names=["theta_jn"]) -phi_jl_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_jl"]) -theta_1_prior = SinePrior(parameter_names=["theta_1"]) -theta_2_prior = SinePrior(parameter_names=["theta_2"]) -phi_12_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_12"]) -a_1_prior = UniformPrior(0.0, 1.0, parameter_names=["a_1"]) -a_2_prior = UniformPrior(0.0, 1.0, parameter_names=["a_2"]) -dL_prior = PowerLawPrior(10.0, 2000.0, 2.0, parameter_names=["d_L"]) -t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) -phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) -psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) -ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) -dec_prior = CosinePrior(parameter_names=["dec"]) - -prior = CombinePrior( - [ - Mc_prior, - q_prior, - theta_jn_prior, - phi_jl_prior, - theta_1_prior, - theta_2_prior, - phi_12_prior, - a_1_prior, - a_2_prior, - dL_prior, - t_c_prior, - phase_c_prior, - psi_prior, - ra_prior, - dec_prior, - ] -) - -sample_transforms = [ - BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), - BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=0.125, original_upper_bound=1.), - BoundToUnbound(name_mapping = [["theta_jn"], ["theta_jn_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["phi_jl"], ["phi_jl_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["theta_1"], ["theta_1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["theta_2"], ["theta_2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["phi_12"], ["phi_12_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["a_1"], ["a_1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = [["a_2"], ["a_2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), - BoundToUnbound(name_mapping = [["t_c"], ["t_c_unbounded"]] , original_lower_bound=-0.05, original_upper_bound=0.05), - BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["ra"], ["ra_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=-jnp.pi / 2, original_upper_bound=jnp.pi / 2) -] - -likelihood_transforms = [ - SpinToCartesianSpinTransform(name_mapping=[["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a_1", "a_2"], ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"]], freq_ref=20.0), - MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), -] - -likelihood = TransientLikelihoodFD( - ifos, - waveform=RippleIMRPhenomD(), - trigger_time=gps, - duration=4, - post_trigger_duration=2, -) - - -mass_matrix = jnp.eye(15) -mass_matrix = mass_matrix.at[1, 1].set(1e-3) -mass_matrix = mass_matrix.at[9, 9].set(1e-3) -local_sampler_arg = {"step_size": mass_matrix * 3e-3} - -Adam_optimizer = optimization_Adam(n_steps=5, learning_rate=0.01, noise_level=1) - -n_epochs = 2 -n_loop_training = 1 -learning_rate = 1e-4 - - -jim = Jim( - likelihood, - prior, - sample_transforms=sample_transforms, - likelihood_transforms=likelihood_transforms, - n_loop_training=n_loop_training, - n_loop_production=1, - n_local_steps=5, - n_global_steps=5, - n_chains=4, - n_epochs=n_epochs, - learning_rate=learning_rate, - n_max_examples=30, - n_flow_samples=100, - momentum=0.9, - batch_size=100, - use_global=True, - train_thinning=1, - output_thinning=1, - local_sampler_arg=local_sampler_arg, - strategies=[Adam_optimizer, "default"], -) - -jim.sample(jax.random.PRNGKey(42)) -jim.get_samples() -jim.print_summary() From 0a9f58f38a40debd06849954cebae2b70b3dca7f Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Sat, 3 Aug 2024 07:29:47 -0400 Subject: [PATCH 169/248] Create test_GW150914_Pv2.py --- test/integration/test_GW150914_Pv2.py | 142 ++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 test/integration/test_GW150914_Pv2.py diff --git a/test/integration/test_GW150914_Pv2.py b/test/integration/test_GW150914_Pv2.py new file mode 100644 index 00000000..c9d83a5e --- /dev/null +++ b/test/integration/test_GW150914_Pv2.py @@ -0,0 +1,142 @@ +import time + +import jax +import jax.numpy as jnp + +from jimgw.jim import Jim +from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior +from jimgw.single_event.detector import H1, L1 +from jimgw.single_event.likelihood import TransientLikelihoodFD +from jimgw.single_event.waveform import RippleIMRPhenomD +from jimgw.transforms import BoundToUnbound +from jimgw.single_event.transforms import MassRatioToSymmetricMassRatioTransform, SpinToCartesianSpinTransform +from flowMC.strategy.optimization import optimization_Adam + +jax.config.update("jax_enable_x64", True) + +########################################### +########## First we grab data ############# +########################################### + +total_time_start = time.time() + +# first, fetch a 4s segment centered on GW150914 +gps = 1126259462.4 +duration = 4 +post_trigger_duration = 2 +start_pad = duration - post_trigger_duration +end_pad = post_trigger_duration +fmin = 20.0 +fmax = 1024.0 + +ifos = [H1, L1] + +for ifo in ifos: + ifo.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) + +Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) +q_prior = UniformPrior(0.125, 1., parameter_names=["q"]) +theta_jn_prior = SinePrior(parameter_names=["theta_jn"]) +phi_jl_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_jl"]) +theta_1_prior = SinePrior(parameter_names=["theta_1"]) +theta_2_prior = SinePrior(parameter_names=["theta_2"]) +phi_12_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_12"]) +a_1_prior = UniformPrior(0.0, 1.0, parameter_names=["a_1"]) +a_2_prior = UniformPrior(0.0, 1.0, parameter_names=["a_2"]) +dL_prior = PowerLawPrior(10.0, 2000.0, 2.0, parameter_names=["d_L"]) +t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) +phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) +psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) +ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) +dec_prior = CosinePrior(parameter_names=["dec"]) + +prior = CombinePrior( + [ + Mc_prior, + q_prior, + theta_jn_prior, + phi_jl_prior, + theta_1_prior, + theta_2_prior, + phi_12_prior, + a_1_prior, + a_2_prior, + dL_prior, + t_c_prior, + phase_c_prior, + psi_prior, + ra_prior, + dec_prior, + ] +) + +sample_transforms = [ + BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), + BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=0.125, original_upper_bound=1.), + BoundToUnbound(name_mapping = [["theta_jn"], ["theta_jn_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["phi_jl"], ["phi_jl_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["theta_1"], ["theta_1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["theta_2"], ["theta_2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["phi_12"], ["phi_12_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["a_1"], ["a_1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["a_2"], ["a_2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), + BoundToUnbound(name_mapping = [["t_c"], ["t_c_unbounded"]] , original_lower_bound=-0.05, original_upper_bound=0.05), + BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["ra"], ["ra_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=-jnp.pi / 2, original_upper_bound=jnp.pi / 2) +] + +likelihood_transforms = [ + SpinToCartesianSpinTransform(name_mapping=[["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a_1", "a_2"], ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"]], freq_ref=20.0), + MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), +] + +likelihood = TransientLikelihoodFD( + ifos, + waveform=RippleIMRPhenomD(), + trigger_time=gps, + duration=4, + post_trigger_duration=2, +) + + +mass_matrix = jnp.eye(15) +mass_matrix = mass_matrix.at[1, 1].set(1e-3) +mass_matrix = mass_matrix.at[9, 9].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 3e-3} + +Adam_optimizer = optimization_Adam(n_steps=5, learning_rate=0.01, noise_level=1) + +n_epochs = 2 +n_loop_training = 1 +learning_rate = 1e-4 + + +jim = Jim( + likelihood, + prior, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, + n_loop_training=n_loop_training, + n_loop_production=1, + n_local_steps=5, + n_global_steps=5, + n_chains=4, + n_epochs=n_epochs, + learning_rate=learning_rate, + n_max_examples=30, + n_flow_samples=100, + momentum=0.9, + batch_size=100, + use_global=True, + train_thinning=1, + output_thinning=1, + local_sampler_arg=local_sampler_arg, + strategies=[Adam_optimizer, "default"], +) + +jim.sample(jax.random.PRNGKey(42)) +jim.get_samples() +jim.print_summary() From f33a7824f03de5d602b22cfbf2ec93b46e278316 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Mon, 12 Aug 2024 15:49:26 +0200 Subject: [PATCH 170/248] Adding transform from geocentric arrival time to detector arrival time --- src/jimgw/single_event/transforms.py | 54 ++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index c3e77846..b1735f5a 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -170,6 +170,60 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform +@jaxtyped(typechecker=typechecker) +class GeocentricArrivalTimeToDetectorArrivalTimeTransform(BijectiveTransform): + """ + Transform the geocentric arrival time to detector arrival time + + In the geocentric convention, the arrival time of the signal at the + center of Earth is gps_time + t_c + + In the detector convention, the arrival time of the signal at the + detecotr is gps_time + time_delay_from_geo_to_det + t_det + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + gmst: Float + t_c: Float + t_det: Float + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + gps_time: Float, + ifo: GroundBased2G, + ): + super().__init__(name_mapping) + + self.gmst = ( + Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad + ) + self.ifo = ifo + + assert "t_c" in name_mapping[0] and "t_det" in name_mapping[1] + + def named_transform(x): + t_det = x["t_c"] + self.ifo.delay_from_geocenter(x["ra"], x["dec"], self.gmst) + return { + "t_det": t_det, + } + + self.transform_func = named_transform + + def named_inverse_transform(x): + t_c = x["t_det"] - self.ifo.delay_from_geocenter(x["ra"], x["dec"], self.gmst) + return { + "t_c": t_c, + } + + self.inverse_transform_func = named_inverse_transform + + @jaxtyped(typechecker=typechecker) class SpinToCartesianSpinTransform(NtoNTransform): """ From 3505394c6e1f974134ecf59e326507c376c54621 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Mon, 12 Aug 2024 16:01:11 +0200 Subject: [PATCH 171/248] Adding transform from distance to SNR weighted distance --- src/jimgw/single_event/transforms.py | 88 +++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index b1735f5a..6404fb7e 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -208,7 +208,9 @@ def __init__( assert "t_c" in name_mapping[0] and "t_det" in name_mapping[1] def named_transform(x): - t_det = x["t_c"] + self.ifo.delay_from_geocenter(x["ra"], x["dec"], self.gmst) + t_det = x["t_c"] + self.ifo.delay_from_geocenter( + x["ra"], x["dec"], self.gmst + ) return { "t_det": t_det, } @@ -216,7 +218,9 @@ def named_transform(x): self.transform_func = named_transform def named_inverse_transform(x): - t_c = x["t_det"] - self.ifo.delay_from_geocenter(x["ra"], x["dec"], self.gmst) + t_c = x["t_det"] - self.ifo.delay_from_geocenter( + x["ra"], x["dec"], self.gmst + ) return { "t_c": t_c, } @@ -224,6 +228,86 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform +@jaxtyped(typechecker=typechecker) +class DistanceToSNRWeightedDistanceTransform(BijectiveTransform): + """ + Transform the luminosity distance to network SNR weighted distance + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + gmst: Float + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + gps_time: Float, + ifos: list[GroundBased2G], + ): + super().__init__(name_mapping) + + self.gmst = ( + Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad + ) + self.ifos = ifos + + assert "d_L" in name_mapping[0] and "d_hat" in name_mapping[1] + + def named_transform(x): + d_L, M_c, ra, dec, psi, iota = ( + x["d_L"], + x["M_c"], + x["ra"], + x["dec"], + x["psi"], + x["iota"], + ) + p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 + c_iota_term = jnp.cos(iota) + R_ks2 = 0.0 + for ifo in self.ifos: + antenna_pattern = ifo.antenna_pattern(ra, dec, psi, self.gmst) + p_mode_term = p_iota_term * antenna_pattern["p"] + c_mode_term = c_iota_term * antenna_pattern["c"] + R_ks2 += p_mode_term**2 + c_mode_term**2 + R_ks = jnp.sqrt(R_ks2) + d_hat = d_L / jnp.power(M_c, 5.0 / 6.0) / R_ks + return { + "d_hat": d_hat, + } + + self.transform_func = named_transform + + def named_inverse_transform(x): + d_hat, M_c, ra, dec, psi, iota = ( + x["d_hat"], + x["M_c"], + x["ra"], + x["dec"], + x["psi"], + x["iota"], + ) + p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 + c_iota_term = jnp.cos(iota) + R_ks2 = 0.0 + for ifo in self.ifos: + antenna_pattern = ifo.antenna_pattern(ra, dec, psi, self.gmst) + p_mode_term = p_iota_term * antenna_pattern["p"] + c_mode_term = c_iota_term * antenna_pattern["c"] + R_ks2 += p_mode_term**2 + c_mode_term**2 + R_ks = jnp.sqrt(R_ks2) + d_L = d_hat * jnp.power(M_c, 5.0 / 6.0) * R_ks + return { + "d_L": d_L, + } + + self.inverse_transform_func = named_inverse_transform + + @jaxtyped(typechecker=typechecker) class SpinToCartesianSpinTransform(NtoNTransform): """ From df75cebc636eec563bf361e10c476465c161fe38 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Tue, 13 Aug 2024 10:12:45 +0200 Subject: [PATCH 172/248] updating the typing for object attributes --- src/jimgw/single_event/transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 6404fb7e..e1a49e58 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -189,8 +189,7 @@ class GeocentricArrivalTimeToDetectorArrivalTimeTransform(BijectiveTransform): """ gmst: Float - t_c: Float - t_det: Float + ifo: GroundBased2G def __init__( self, @@ -241,6 +240,7 @@ class DistanceToSNRWeightedDistanceTransform(BijectiveTransform): """ gmst: Float + ifos: list[GroundBased2G] def __init__( self, From 9f2f52b323bd03923043a0be4203030229225f71 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Tue, 13 Aug 2024 11:35:34 +0200 Subject: [PATCH 173/248] Adding geocentric phase to detector phase --- src/jimgw/single_event/transforms.py | 106 +++++++++++++++++++++------ 1 file changed, 83 insertions(+), 23 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index e1a49e58..bf060b8a 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -227,6 +227,72 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform +@jaxtyped(typechecker=typechecker) +class GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(BijectiveTransform): + """ + Transform the geocentric arrival phase to detector arrival phase + + In the geocentric convention, the arrival phase of the signal at the + center of Earth is phi_c / 2 (in ripple, phi_c is the orbital phase) + + In the detector convention, the arrival phase of the signal at the + detecotr is phi_det = phi_c / 2 + arg R_det + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + gmst: Float + ifo: GroundBased2G + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + gps_time: Float, + ifo: GroundBased2G, + ): + super().__init__(name_mapping) + + self.gmst = ( + Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad + ) + self.ifo = ifo + + assert "phi_c" in name_mapping[0] and "phi_det" in name_mapping[1] + + def _calc_R_det(x): + ra, dec, psi, iota = x["ra"], x["dec"], x["psi"], x["iota"] + p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 + c_iota_term = jnp.cos(iota) + + antenna_pattern = self.ifo.antenna_pattern(ra, dec, psi, self.gmst) + p_mode_term = p_iota_term * antenna_pattern["p"] + c_mode_term = c_iota_term * antenna_pattern["c"] + + return p_mode_term - 1j * c_mode_term + + def named_transform(x): + R_det = _calc_R_det(x) + phi_det = jnp.angle(R_det) + x["phi_c"] / 2.0 + return { + "phi_det": phi_det, + } + + self.transform_func = named_transform + + def named_inverse_transform(x): + R_det = _calc_R_det(x) + phi_c = (-jnp.angle(R_det) + x["phi_det"]) * 2.0 + return { + "phi_c": phi_c, + } + + self.inverse_transform_func = named_inverse_transform + + @jaxtyped(typechecker=typechecker) class DistanceToSNRWeightedDistanceTransform(BijectiveTransform): """ @@ -257,10 +323,8 @@ def __init__( assert "d_L" in name_mapping[0] and "d_hat" in name_mapping[1] - def named_transform(x): - d_L, M_c, ra, dec, psi, iota = ( - x["d_L"], - x["M_c"], + def _calc_R_dets(x): + ra, dec, psi, iota = ( x["ra"], x["dec"], x["psi"], @@ -268,14 +332,22 @@ def named_transform(x): ) p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 c_iota_term = jnp.cos(iota) - R_ks2 = 0.0 + R_dets2 = 0.0 for ifo in self.ifos: antenna_pattern = ifo.antenna_pattern(ra, dec, psi, self.gmst) p_mode_term = p_iota_term * antenna_pattern["p"] c_mode_term = c_iota_term * antenna_pattern["c"] - R_ks2 += p_mode_term**2 + c_mode_term**2 - R_ks = jnp.sqrt(R_ks2) - d_hat = d_L / jnp.power(M_c, 5.0 / 6.0) / R_ks + R_dets2 += p_mode_term**2 + c_mode_term**2 + + return jnp.sqrt(R_dets2) + + def named_transform(x): + d_L, M_c = ( + x["d_L"], + x["M_c"], + ) + R_dets = _calc_R_dets(x) + d_hat = d_L / jnp.power(M_c, 5.0 / 6.0) / R_dets return { "d_hat": d_hat, } @@ -283,24 +355,12 @@ def named_transform(x): self.transform_func = named_transform def named_inverse_transform(x): - d_hat, M_c, ra, dec, psi, iota = ( + d_hat, M_c = ( x["d_hat"], x["M_c"], - x["ra"], - x["dec"], - x["psi"], - x["iota"], ) - p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 - c_iota_term = jnp.cos(iota) - R_ks2 = 0.0 - for ifo in self.ifos: - antenna_pattern = ifo.antenna_pattern(ra, dec, psi, self.gmst) - p_mode_term = p_iota_term * antenna_pattern["p"] - c_mode_term = c_iota_term * antenna_pattern["c"] - R_ks2 += p_mode_term**2 + c_mode_term**2 - R_ks = jnp.sqrt(R_ks2) - d_L = d_hat * jnp.power(M_c, 5.0 / 6.0) * R_ks + R_dets = _calc_R_dets(x) + d_L = d_hat * jnp.power(M_c, 5.0 / 6.0) * R_dets return { "d_L": d_L, } From b62970f2a8f05bae53408870763aa115ea957fdc Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Tue, 13 Aug 2024 11:51:22 +0200 Subject: [PATCH 174/248] Adding ZeroLikelihood for testing purpose --- src/jimgw/single_event/likelihood.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/jimgw/single_event/likelihood.py b/src/jimgw/single_event/likelihood.py index 00e6ce6b..9e775b33 100644 --- a/src/jimgw/single_event/likelihood.py +++ b/src/jimgw/single_event/likelihood.py @@ -26,6 +26,15 @@ def __init__(self, detectors: list[Detector], waveform: Waveform) -> None: self.waveform = waveform +class ZeroLikelihood(LikelihoodBase): + + def __init__(self): + pass + + def evaluate(self, params: dict[str, Float], data: dict) -> Float: + return 0.0 + + class TransientLikelihoodFD(SingleEventLiklihood): def __init__( self, From 4ea332224fa35363961220b1dffccc167599a6c3 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Tue, 13 Aug 2024 12:09:47 +0200 Subject: [PATCH 175/248] Adding the missing mode 2pi for phasing transform --- src/jimgw/single_event/transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index bf060b8a..6f4f361a 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -278,7 +278,7 @@ def named_transform(x): R_det = _calc_R_det(x) phi_det = jnp.angle(R_det) + x["phi_c"] / 2.0 return { - "phi_det": phi_det, + "phi_det": phi_det % (2. * jnp.pi), } self.transform_func = named_transform @@ -287,7 +287,7 @@ def named_inverse_transform(x): R_det = _calc_R_det(x) phi_c = (-jnp.angle(R_det) + x["phi_det"]) * 2.0 return { - "phi_c": phi_c, + "phi_c": phi_c % (2. * jnp.pi), } self.inverse_transform_func = named_inverse_transform From 7a4bae0e8d06735458b6f1e004258d1afb15aac2 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Tue, 13 Aug 2024 13:38:23 +0200 Subject: [PATCH 176/248] Test wip --- test/integration/test_extrinsic.py | 108 ++++++++++++++++++ .../integration/test_extrinsic_no_distance.py | 90 +++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 test/integration/test_extrinsic.py create mode 100644 test/integration/test_extrinsic_no_distance.py diff --git a/test/integration/test_extrinsic.py b/test/integration/test_extrinsic.py new file mode 100644 index 00000000..c4719dd8 --- /dev/null +++ b/test/integration/test_extrinsic.py @@ -0,0 +1,108 @@ +import jax +import jax.numpy as jnp + +from jimgw.jim import Jim +from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior +from jimgw.single_event.detector import H1, L1, V1 +from jimgw.single_event.likelihood import ZeroLikelihood +from jimgw.transforms import BoundToUnbound +from jimgw.single_event.transforms import MassRatioToSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, DistanceToSNRWeightedDistanceTransform, GeocentricArrivalTimeToDetectorArrivalTimeTransform, GeocentricArrivalPhaseToDetectorArrivalPhaseTransform +from flowMC.strategy.optimization import optimization_Adam + +jax.config.update("jax_enable_x64", True) + +########################################### +########## First we grab data ############# +########################################### + +# first, fetch a 4s segment centered on GW150914 +gps = 1126259462.4 + +ifos = [H1, L1, V1] + +M_c_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) +q_prior = UniformPrior(0.125, 1.0, parameter_names=["q"]) +dL_prior = PowerLawPrior(1.0, 2000.0, 2.0, parameter_names=["d_L"]) +t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) +phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) +iota_prior = SinePrior(parameter_names=["iota"]) +psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) +ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) +dec_prior = CosinePrior(parameter_names=["dec"]) + +prior = CombinePrior( + [ + M_c_prior, + q_prior, + dL_prior, + t_c_prior, + phase_c_prior, + iota_prior, + psi_prior, + ra_prior, + dec_prior, + ] +) + +sample_transforms = [ + # all the user reparametrization transform + DistanceToSNRWeightedDistanceTransform(name_mapping=[["d_L"], ["d_hat"]], gps_time=gps, ifos=ifos), + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(name_mapping = [["phase_c", "phase_det"]], gps_time=gps, ifo=ifos[0]), + GeocentricArrivalTimeToDetectorArrivalTimeTransform(name_mapping = [["t_c", "t_det"]], gps_time=gps, ifo=ifos[0]), + SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), + # all the bound to unbound transform + BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), + BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=0.125, original_upper_bound=1.0), + BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=0., original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["d_hat"], ["d_hat_unbounded"]], original_lower_bound=2.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["phase_det"], ["phase_det_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["t_det"], ["t_det_unbounded"]], original_lower_bound=-0.1, original_upper_bound=0.1), +] + +likelihood_transforms = [ + MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), +] + +likelihood = ZeroLikelihood() + +mass_matrix = jnp.eye(9) +#mass_matrix = mass_matrix.at[1, 1].set(1e-3) +#mass_matrix = mass_matrix.at[5, 5].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 3e-3} + +Adam_optimizer = optimization_Adam(n_steps=5, learning_rate=0.01, noise_level=1) + +n_epochs = 2 +n_loop_training = 1 +learning_rate = 1e-4 + + +jim = Jim( + likelihood, + prior, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, + n_loop_training=n_loop_training, + n_loop_production=1, + n_local_steps=5, + n_global_steps=5, + n_chains=4, + n_epochs=n_epochs, + learning_rate=learning_rate, + n_max_examples=30, + n_flow_samples=100, + momentum=0.9, + batch_size=100, + use_global=True, + train_thinning=1, + output_thinning=1, + local_sampler_arg=local_sampler_arg, + strategies=[Adam_optimizer, "default"], +) + +jim.sample(jax.random.PRNGKey(42)) +jim.get_samples() +jim.print_summary() diff --git a/test/integration/test_extrinsic_no_distance.py b/test/integration/test_extrinsic_no_distance.py new file mode 100644 index 00000000..d1b1e559 --- /dev/null +++ b/test/integration/test_extrinsic_no_distance.py @@ -0,0 +1,90 @@ +import jax +import jax.numpy as jnp + +from jimgw.jim import Jim +from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior +from jimgw.single_event.detector import H1, L1, V1 +from jimgw.single_event.likelihood import ZeroLikelihood +from jimgw.transforms import BoundToUnbound +from jimgw.single_event.transforms import MassRatioToSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, DistanceToSNRWeightedDistanceTransform, GeocentricArrivalTimeToDetectorArrivalTimeTransform, GeocentricArrivalPhaseToDetectorArrivalPhaseTransform +from flowMC.strategy.optimization import optimization_Adam + +jax.config.update("jax_enable_x64", True) + +########################################### +########## First we grab data ############# +########################################### + +# first, fetch a 4s segment centered on GW150914 +gps = 1126259462.4 + +ifos = [H1, L1, V1] + +t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) +phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) +ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) +dec_prior = CosinePrior(parameter_names=["dec"]) + +prior = CombinePrior( + [ + t_c_prior, + phase_c_prior, + ra_prior, + dec_prior, + ] +) + +sample_transforms = [ + # all the user reparametrization transform + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(name_mapping = [["phase_c", "phase_det"]], gps_time=gps, ifo=ifos[0]), + GeocentricArrivalTimeToDetectorArrivalTimeTransform(name_mapping = [["t_c", "t_det"]], gps_time=gps, ifo=ifos[0]), + SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), + # all the bound to unbound transform + BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["phase_det"], ["phase_det_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = [["t_det"], ["t_det_unbounded"]], original_lower_bound=-0.1, original_upper_bound=0.1), +] + +likelihood_transforms = [] + +likelihood = ZeroLikelihood() + +mass_matrix = jnp.eye(9) +#mass_matrix = mass_matrix.at[1, 1].set(1e-3) +#mass_matrix = mass_matrix.at[5, 5].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 3e-3} + +Adam_optimizer = optimization_Adam(n_steps=5, learning_rate=0.01, noise_level=1) + +n_epochs = 2 +n_loop_training = 1 +learning_rate = 1e-4 + + +jim = Jim( + likelihood, + prior, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, + n_loop_training=n_loop_training, + n_loop_production=1, + n_local_steps=5, + n_global_steps=5, + n_chains=4, + n_epochs=n_epochs, + learning_rate=learning_rate, + n_max_examples=30, + n_flow_samples=100, + momentum=0.9, + batch_size=100, + use_global=True, + train_thinning=1, + output_thinning=1, + local_sampler_arg=local_sampler_arg, + strategies=[Adam_optimizer, "default"], +) + +jim.sample(jax.random.PRNGKey(42)) +jim.get_samples() +jim.print_summary() From d5f86e52156c788a06d84c1ac5d0958ae4d0b8fb Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Tue, 13 Aug 2024 13:42:45 +0200 Subject: [PATCH 177/248] Phase renaming --- src/jimgw/single_event/transforms.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 6f4f361a..1242b40b 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -233,10 +233,10 @@ class GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(BijectiveTransform): Transform the geocentric arrival phase to detector arrival phase In the geocentric convention, the arrival phase of the signal at the - center of Earth is phi_c / 2 (in ripple, phi_c is the orbital phase) + center of Earth is phase_c / 2 (in ripple, phase_c is the orbital phase) In the detector convention, the arrival phase of the signal at the - detecotr is phi_det = phi_c / 2 + arg R_det + detecotr is phase_det = phase_c / 2 + arg R_det Parameters ---------- @@ -261,7 +261,7 @@ def __init__( ) self.ifo = ifo - assert "phi_c" in name_mapping[0] and "phi_det" in name_mapping[1] + assert "phase_c" in name_mapping[0] and "phase_det" in name_mapping[1] def _calc_R_det(x): ra, dec, psi, iota = x["ra"], x["dec"], x["psi"], x["iota"] @@ -276,18 +276,18 @@ def _calc_R_det(x): def named_transform(x): R_det = _calc_R_det(x) - phi_det = jnp.angle(R_det) + x["phi_c"] / 2.0 + phase_det = jnp.angle(R_det) + x["phase_c"] / 2.0 return { - "phi_det": phi_det % (2. * jnp.pi), + "phase_det": phase_det % (2. * jnp.pi), } self.transform_func = named_transform def named_inverse_transform(x): R_det = _calc_R_det(x) - phi_c = (-jnp.angle(R_det) + x["phi_det"]) * 2.0 + phase_c = (-jnp.angle(R_det) + x["phase_det"]) * 2.0 return { - "phi_c": phi_c % (2. * jnp.pi), + "phase_c": phase_c % (2. * jnp.pi), } self.inverse_transform_func = named_inverse_transform From 0a2e68c22611fea72a2643bbea8ab358c67c3650 Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Tue, 13 Aug 2024 06:09:13 -0700 Subject: [PATCH 178/248] wip --- src/jimgw/single_event/transforms.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 1242b40b..538d6b8e 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -208,7 +208,7 @@ def __init__( def named_transform(x): t_det = x["t_c"] + self.ifo.delay_from_geocenter( - x["ra"], x["dec"], self.gmst + x["ra"][0], x["dec"][0], self.gmst ) return { "t_det": t_det, @@ -217,8 +217,9 @@ def named_transform(x): self.transform_func = named_transform def named_inverse_transform(x): + import pdb; pdb.set_trace() t_c = x["t_det"] - self.ifo.delay_from_geocenter( - x["ra"], x["dec"], self.gmst + x["ra"][0], x["dec"][0], self.gmst ) return { "t_c": t_c, @@ -268,7 +269,7 @@ def _calc_R_det(x): p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 c_iota_term = jnp.cos(iota) - antenna_pattern = self.ifo.antenna_pattern(ra, dec, psi, self.gmst) + antenna_pattern = self.ifo.antenna_pattern(ra[0], dec[0], psi[0], self.gmst) p_mode_term = p_iota_term * antenna_pattern["p"] c_mode_term = c_iota_term * antenna_pattern["c"] @@ -278,7 +279,7 @@ def named_transform(x): R_det = _calc_R_det(x) phase_det = jnp.angle(R_det) + x["phase_c"] / 2.0 return { - "phase_det": phase_det % (2. * jnp.pi), + "phase_det": phase_det % (2.0 * jnp.pi), } self.transform_func = named_transform @@ -287,7 +288,7 @@ def named_inverse_transform(x): R_det = _calc_R_det(x) phase_c = (-jnp.angle(R_det) + x["phase_det"]) * 2.0 return { - "phase_c": phase_c % (2. * jnp.pi), + "phase_c": phase_c % (2.0 * jnp.pi), } self.inverse_transform_func = named_inverse_transform From b96512c64139661b38fd7df1f2506d480c527ebb Mon Sep 17 00:00:00 2001 From: kazewong Date: Wed, 14 Aug 2024 13:30:43 -0400 Subject: [PATCH 179/248] Push conditional bijective transform --- src/jimgw/transforms.py | 54 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 715d49de..4d2ebb45 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -61,8 +61,6 @@ def forward(self, x: dict[str, Float]) -> dict[str, Float]: class NtoNTransform(NtoMTransform): - transform_func: Callable[[dict[str, Float]], dict[str, Float]] - @property def n_dim(self) -> int: return len(self.name_mapping[0]) @@ -162,6 +160,58 @@ def backward(self, y: dict[str, Float]) -> dict[str, Float]: list(output_params.keys()), ) return y_copy + +class ConditionalBijectiveTransform(BijectiveTransform): + + conditional_names: list[str] + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + conditional_names: list[str], + ): + super().__init__(name_mapping) + self.conditional_names = conditional_names + + def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: + x_copy = x.copy() + transform_params = dict((key, x_copy[key]) for key in self.name_mapping[0]) + transform_params.update( + dict((key, x_copy[key]) for key in self.conditional_names) + ) + output_params = self.transform_func(transform_params) + jacobian = jax.jacfwd(self.transform_func)(transform_params) + jacobian = jnp.array(jax.tree.leaves(jacobian)) + jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) + jax.tree.map( + lambda key: x_copy.pop(key), + self.name_mapping[0], + ) + jax.tree.map( + lambda key: x_copy.update({key: output_params[key]}), + list(output_params.keys()), + ) + return x_copy, jacobian + + def inverse(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: + y_copy = y.copy() + transform_params = dict((key, y_copy[key]) for key in self.name_mapping[1]) + transform_params.update( + dict((key, y_copy[key]) for key in self.conditional_names) + ) + output_params = self.inverse_transform_func(transform_params) + jacobian = jax.jacfwd(self.inverse_transform_func)(transform_params) + jacobian = jnp.array(jax.tree.leaves(jacobian)) + jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) + jax.tree.map( + lambda key: y_copy.pop(key), + self.name_mapping[1], + ) + jax.tree.map( + lambda key: y_copy.update({key: output_params[key]}), + list(output_params.keys()), + ) + return y_copy, jacobian @jaxtyped(typechecker=typechecker) From 526e33cbb8eae4c32782113cd2a7495d657e5ea9 Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Wed, 14 Aug 2024 12:44:04 -0700 Subject: [PATCH 180/248] Switch to using conditional transform --- src/jimgw/single_event/transforms.py | 46 ++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 538d6b8e..2fb757c2 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -4,7 +4,7 @@ from astropy.time import Time from jimgw.single_event.detector import GroundBased2G -from jimgw.transforms import BijectiveTransform, NtoNTransform +from jimgw.transforms import ConditionalBijectiveTransform, BijectiveTransform, NtoNTransform from jimgw.single_event.utils import ( m1_m2_to_Mc_q, Mc_q_to_m1_m2, @@ -171,7 +171,7 @@ def named_inverse_transform(x): @jaxtyped(typechecker=typechecker) -class GeocentricArrivalTimeToDetectorArrivalTimeTransform(BijectiveTransform): +class GeocentricArrivalTimeToDetectorArrivalTimeTransform(ConditionalBijectiveTransform): """ Transform the geocentric arrival time to detector arrival time @@ -194,10 +194,11 @@ class GeocentricArrivalTimeToDetectorArrivalTimeTransform(BijectiveTransform): def __init__( self, name_mapping: tuple[list[str], list[str]], + conditional_names: list[str], gps_time: Float, ifo: GroundBased2G, ): - super().__init__(name_mapping) + super().__init__(name_mapping, conditional_names) self.gmst = ( Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad @@ -205,11 +206,22 @@ def __init__( self.ifo = ifo assert "t_c" in name_mapping[0] and "t_det" in name_mapping[1] + assert ( + "ra" in conditional_names + and "dec" in conditional_names + ) + + def _calc_delay(x): + ra, dec = x["ra"], x["dec"] + if hasattr(ra, "shape") and len(ra.shape) > 0: + delay = self.ifo.delay_from_geocenter(ra[0], dec[0], self.gmst) + else: + delay = self.ifo.delay_from_geocenter(ra, dec, self.gmst) + return delay def named_transform(x): - t_det = x["t_c"] + self.ifo.delay_from_geocenter( - x["ra"][0], x["dec"][0], self.gmst - ) + delay = _calc_delay(x) + t_det = x["t_c"] + delay return { "t_det": t_det, } @@ -217,10 +229,8 @@ def named_transform(x): self.transform_func = named_transform def named_inverse_transform(x): - import pdb; pdb.set_trace() - t_c = x["t_det"] - self.ifo.delay_from_geocenter( - x["ra"][0], x["dec"][0], self.gmst - ) + delay = _calc_delay(x) + t_c = x["t_det"] - delay return { "t_c": t_c, } @@ -229,7 +239,7 @@ def named_inverse_transform(x): @jaxtyped(typechecker=typechecker) -class GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(BijectiveTransform): +class GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(ConditionalBijectiveTransform): """ Transform the geocentric arrival phase to detector arrival phase @@ -252,10 +262,11 @@ class GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(BijectiveTransform): def __init__( self, name_mapping: tuple[list[str], list[str]], + conditional_names: list[str], gps_time: Float, ifo: GroundBased2G, ): - super().__init__(name_mapping) + super().__init__(name_mapping, conditional_names) self.gmst = ( Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad @@ -263,13 +274,22 @@ def __init__( self.ifo = ifo assert "phase_c" in name_mapping[0] and "phase_det" in name_mapping[1] + assert ( + "ra" in conditional_names + and "dec" in conditional_names + and "psi" in conditional_names + and "iota" in conditional_names + ) def _calc_R_det(x): ra, dec, psi, iota = x["ra"], x["dec"], x["psi"], x["iota"] p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 c_iota_term = jnp.cos(iota) - antenna_pattern = self.ifo.antenna_pattern(ra[0], dec[0], psi[0], self.gmst) + if hasattr(ra, "shape") and len(ra.shape) > 0: + antenna_pattern = self.ifo.antenna_pattern(ra[0], dec[0], psi[0], self.gmst) + else: + antenna_pattern = self.ifo.antenna_pattern(ra, dec, psi, self.gmst) p_mode_term = p_iota_term * antenna_pattern["p"] c_mode_term = c_iota_term * antenna_pattern["c"] From dbf3f3064e5135575a5c7548ee9e1c5e9157553b Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Wed, 14 Aug 2024 12:45:40 -0700 Subject: [PATCH 181/248] Switch to using conditional transform --- src/jimgw/single_event/transforms.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 2fb757c2..dfcd01b8 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -4,7 +4,11 @@ from astropy.time import Time from jimgw.single_event.detector import GroundBased2G -from jimgw.transforms import ConditionalBijectiveTransform, BijectiveTransform, NtoNTransform +from jimgw.transforms import ( + ConditionalBijectiveTransform, + BijectiveTransform, + NtoNTransform, +) from jimgw.single_event.utils import ( m1_m2_to_Mc_q, Mc_q_to_m1_m2, @@ -171,7 +175,9 @@ def named_inverse_transform(x): @jaxtyped(typechecker=typechecker) -class GeocentricArrivalTimeToDetectorArrivalTimeTransform(ConditionalBijectiveTransform): +class GeocentricArrivalTimeToDetectorArrivalTimeTransform( + ConditionalBijectiveTransform +): """ Transform the geocentric arrival time to detector arrival time @@ -206,10 +212,7 @@ def __init__( self.ifo = ifo assert "t_c" in name_mapping[0] and "t_det" in name_mapping[1] - assert ( - "ra" in conditional_names - and "dec" in conditional_names - ) + assert "ra" in conditional_names and "dec" in conditional_names def _calc_delay(x): ra, dec = x["ra"], x["dec"] @@ -239,7 +242,9 @@ def named_inverse_transform(x): @jaxtyped(typechecker=typechecker) -class GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(ConditionalBijectiveTransform): +class GeocentricArrivalPhaseToDetectorArrivalPhaseTransform( + ConditionalBijectiveTransform +): """ Transform the geocentric arrival phase to detector arrival phase @@ -287,7 +292,9 @@ def _calc_R_det(x): c_iota_term = jnp.cos(iota) if hasattr(ra, "shape") and len(ra.shape) > 0: - antenna_pattern = self.ifo.antenna_pattern(ra[0], dec[0], psi[0], self.gmst) + antenna_pattern = self.ifo.antenna_pattern( + ra[0], dec[0], psi[0], self.gmst + ) else: antenna_pattern = self.ifo.antenna_pattern(ra, dec, psi, self.gmst) p_mode_term = p_iota_term * antenna_pattern["p"] From a3753612926dd2b95b440bbc14043cdf7412df29 Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Wed, 14 Aug 2024 12:46:04 -0700 Subject: [PATCH 182/248] Fixing jacobian handling --- src/jimgw/transforms.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 4d2ebb45..a26ad9af 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -160,7 +160,8 @@ def backward(self, y: dict[str, Float]) -> dict[str, Float]: list(output_params.keys()), ) return y_copy - + + class ConditionalBijectiveTransform(BijectiveTransform): conditional_names: list[str] @@ -181,8 +182,14 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: ) output_params = self.transform_func(transform_params) jacobian = jax.jacfwd(self.transform_func)(transform_params) - jacobian = jnp.array(jax.tree.leaves(jacobian)) - jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) + jacobian_copy = { + key1: {key2: jacobian[key1][key2] for key2 in self.name_mapping[0]} + for key1 in self.name_mapping[1] + } + jacobian = jnp.array(jax.tree.leaves(jacobian_copy)) + jacobian = jnp.log( + jnp.absolute(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) + ) jax.tree.map( lambda key: x_copy.pop(key), self.name_mapping[0], @@ -192,7 +199,7 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: list(output_params.keys()), ) return x_copy, jacobian - + def inverse(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: y_copy = y.copy() transform_params = dict((key, y_copy[key]) for key in self.name_mapping[1]) @@ -201,8 +208,14 @@ def inverse(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: ) output_params = self.inverse_transform_func(transform_params) jacobian = jax.jacfwd(self.inverse_transform_func)(transform_params) - jacobian = jnp.array(jax.tree.leaves(jacobian)) - jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) + jacobian_copy = { + key1: {key2: jacobian[key1][key2] for key2 in self.name_mapping[1]} + for key1 in self.name_mapping[0] + } + jacobian = jnp.array(jax.tree.leaves(jacobian_copy)) + jacobian = jnp.log( + jnp.absolute(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) + ) jax.tree.map( lambda key: y_copy.pop(key), self.name_mapping[1], From d79af97d25e29b41a3f6690bd9df4db6ef46d54f Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Fri, 16 Aug 2024 02:56:38 -0700 Subject: [PATCH 183/248] Both arrival phase and time transform are fully vectorized --- src/jimgw/single_event/transforms.py | 43 ++++++++++++---------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index dfcd01b8..047c0607 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -214,17 +214,10 @@ def __init__( assert "t_c" in name_mapping[0] and "t_det" in name_mapping[1] assert "ra" in conditional_names and "dec" in conditional_names - def _calc_delay(x): - ra, dec = x["ra"], x["dec"] - if hasattr(ra, "shape") and len(ra.shape) > 0: - delay = self.ifo.delay_from_geocenter(ra[0], dec[0], self.gmst) - else: - delay = self.ifo.delay_from_geocenter(ra, dec, self.gmst) - return delay - def named_transform(x): - delay = _calc_delay(x) - t_det = x["t_c"] + delay + t_det = x["t_c"] + jnp.vectorize(self.ifo.delay_from_geocenter)( + x["ra"], x["dec"], self.gmst + ) return { "t_det": t_det, } @@ -232,8 +225,9 @@ def named_transform(x): self.transform_func = named_transform def named_inverse_transform(x): - delay = _calc_delay(x) - t_c = x["t_det"] - delay + t_c = x["t_det"] - jnp.vectorize(self.ifo.delay_from_geocenter)( + x["ra"], x["dec"], self.gmst + ) return { "t_c": t_c, } @@ -286,25 +280,22 @@ def __init__( and "iota" in conditional_names ) - def _calc_R_det(x): - ra, dec, psi, iota = x["ra"], x["dec"], x["psi"], x["iota"] + @jnp.vectorize + def _calc_R_det_arg(ra, dec, psi, iota, gmst): p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 c_iota_term = jnp.cos(iota) - if hasattr(ra, "shape") and len(ra.shape) > 0: - antenna_pattern = self.ifo.antenna_pattern( - ra[0], dec[0], psi[0], self.gmst - ) - else: - antenna_pattern = self.ifo.antenna_pattern(ra, dec, psi, self.gmst) + antenna_pattern = self.ifo.antenna_pattern(ra, dec, psi, gmst) p_mode_term = p_iota_term * antenna_pattern["p"] c_mode_term = c_iota_term * antenna_pattern["c"] - return p_mode_term - 1j * c_mode_term + return jnp.angle(p_mode_term - 1j * c_mode_term) def named_transform(x): - R_det = _calc_R_det(x) - phase_det = jnp.angle(R_det) + x["phase_c"] / 2.0 + R_det_arg = _calc_R_det_arg( + x["ra"], x["dec"], x["psi"], x["iota"], self.gmst + ) + phase_det = R_det_arg + x["phase_c"] / 2.0 return { "phase_det": phase_det % (2.0 * jnp.pi), } @@ -312,8 +303,10 @@ def named_transform(x): self.transform_func = named_transform def named_inverse_transform(x): - R_det = _calc_R_det(x) - phase_c = (-jnp.angle(R_det) + x["phase_det"]) * 2.0 + R_det_arg = _calc_R_det_arg( + x["ra"], x["dec"], x["psi"], x["iota"], self.gmst + ) + phase_c = (-R_det_arg + x["phase_det"]) * 2.0 return { "phase_c": phase_c % (2.0 * jnp.pi), } From bcbcbe2e8d6e1d565544cadf9b9ea8e333602cd0 Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Fri, 16 Aug 2024 03:56:26 -0700 Subject: [PATCH 184/248] Shifting distance transform to conditional --- src/jimgw/single_event/transforms.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 047c0607..4b6ada1f 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -315,7 +315,7 @@ def named_inverse_transform(x): @jaxtyped(typechecker=typechecker) -class DistanceToSNRWeightedDistanceTransform(BijectiveTransform): +class DistanceToSNRWeightedDistanceTransform(ConditionalBijectiveTransform): """ Transform the luminosity distance to network SNR weighted distance @@ -332,10 +332,11 @@ class DistanceToSNRWeightedDistanceTransform(BijectiveTransform): def __init__( self, name_mapping: tuple[list[str], list[str]], + conditional_names: list[str], gps_time: Float, ifos: list[GroundBased2G], ): - super().__init__(name_mapping) + super().__init__(name_mapping, conditional_names) self.gmst = ( Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad @@ -343,14 +344,16 @@ def __init__( self.ifos = ifos assert "d_L" in name_mapping[0] and "d_hat" in name_mapping[1] + assert ( + "ra" in conditional_names + and "dec" in conditional_names + and "psi" in conditional_names + and "iota" in conditional_names + and "M_c" in conditional_names + ) - def _calc_R_dets(x): - ra, dec, psi, iota = ( - x["ra"], - x["dec"], - x["psi"], - x["iota"], - ) + @jnp.vectorize + def _calc_R_dets(ra, dec, psi, iota): p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 c_iota_term = jnp.cos(iota) R_dets2 = 0.0 @@ -367,7 +370,7 @@ def named_transform(x): x["d_L"], x["M_c"], ) - R_dets = _calc_R_dets(x) + R_dets = _calc_R_dets(x["ra"], x["dec"], x["psi"], x["iota"]) d_hat = d_L / jnp.power(M_c, 5.0 / 6.0) / R_dets return { "d_hat": d_hat, @@ -380,7 +383,7 @@ def named_inverse_transform(x): x["d_hat"], x["M_c"], ) - R_dets = _calc_R_dets(x) + R_dets = _calc_R_dets(x["ra"], x["dec"], x["psi"], x["iota"]) d_L = d_hat * jnp.power(M_c, 5.0 / 6.0) * R_dets return { "d_L": d_L, From 8dab27b6e259063f03db8f381e8613ca7e34a9d4 Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Fri, 16 Aug 2024 03:56:48 -0700 Subject: [PATCH 185/248] update example --- test/integration/test_extrinsic.py | 62 ++++++++++--- .../integration/test_extrinsic_no_distance.py | 90 ------------------- 2 files changed, 48 insertions(+), 104 deletions(-) delete mode 100644 test/integration/test_extrinsic_no_distance.py diff --git a/test/integration/test_extrinsic.py b/test/integration/test_extrinsic.py index c4719dd8..988f6ea7 100644 --- a/test/integration/test_extrinsic.py +++ b/test/integration/test_extrinsic.py @@ -1,3 +1,12 @@ +import psutil +p = psutil.Process() +p.cpu_affinity([0]) + +import os +os.environ['CUDA_VISIBLE_DEVICES'] = '0' + +from astropy.time import Time + import jax import jax.numpy as jnp @@ -21,7 +30,6 @@ ifos = [H1, L1, V1] M_c_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) -q_prior = UniformPrior(0.125, 1.0, parameter_names=["q"]) dL_prior = PowerLawPrior(1.0, 2000.0, 2.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) @@ -33,7 +41,6 @@ prior = CombinePrior( [ M_c_prior, - q_prior, dL_prior, t_c_prior, phase_c_prior, @@ -44,31 +51,56 @@ ] ) +# calculate the d_hat range +@jnp.vectorize +def calc_R_dets(ra, dec, psi, iota): + gmst = ( + Time(gps, format="gps").sidereal_time("apparent", "greenwich").rad + ) + p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 + c_iota_term = jnp.cos(iota) + R_dets2 = 0.0 + for ifo in ifos: + antenna_pattern = ifo.antenna_pattern(ra, dec, psi, gmst) + p_mode_term = p_iota_term * antenna_pattern["p"] + c_mode_term = c_iota_term * antenna_pattern["c"] + R_dets2 += p_mode_term**2 + c_mode_term**2 + + return jnp.sqrt(R_dets2) + +key1, key2, key3, key4 = jax.random.split(jax.random.PRNGKey(1234), 4) +# generate 10000 samples for each +ra_samples = ra_prior.sample(key1, 10000)["ra"] +dec_samples = dec_prior.sample(key2, 10000)["dec"] +psi_samples = psi_prior.sample(key3, 10000)["psi"] +iota_samples = iota_prior.sample(key4, 10000)["iota"] +R_dets_samples = calc_R_dets(ra_samples, dec_samples, psi_samples, iota_samples) + +d_hat_min = dL_prior.xmin / jnp.power(M_c_prior.xmax, 5. / 6.) +d_hat_max = dL_prior.xmax / jnp.power(M_c_prior.xmin, 5. / 6.) / jnp.amin(R_dets_samples) + sample_transforms = [ # all the user reparametrization transform - DistanceToSNRWeightedDistanceTransform(name_mapping=[["d_L"], ["d_hat"]], gps_time=gps, ifos=ifos), - GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(name_mapping = [["phase_c", "phase_det"]], gps_time=gps, ifo=ifos[0]), - GeocentricArrivalTimeToDetectorArrivalTimeTransform(name_mapping = [["t_c", "t_det"]], gps_time=gps, ifo=ifos[0]), + DistanceToSNRWeightedDistanceTransform(name_mapping=[["d_L"], ["d_hat"]], conditional_names=["M_c","ra", "dec", "psi", "iota"], gps_time=gps, ifos=ifos), + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(name_mapping = [["phase_c"], ["phase_det"]], conditional_names=["ra", "dec", "psi", "iota"], gps_time=gps, ifo=ifos[0]), + GeocentricArrivalTimeToDetectorArrivalTimeTransform(name_mapping = [["t_c"], ["t_det"]], conditional_names=["ra", "dec"], gps_time=gps, ifo=ifos[0]), SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), # all the bound to unbound transform BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), - BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=0.125, original_upper_bound=1.0), BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=0., original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["d_hat"], ["d_hat_unbounded"]], original_lower_bound=2.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = [["phase_det"], ["phase_det_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = [["t_det"], ["t_det_unbounded"]], original_lower_bound=-0.1, original_upper_bound=0.1), + BoundToUnbound(name_mapping = [["d_hat"], ["d_hat_unbounded"]], original_lower_bound=d_hat_min, original_upper_bound=d_hat_max), ] -likelihood_transforms = [ - MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), -] +likelihood_transforms = [] likelihood = ZeroLikelihood() -mass_matrix = jnp.eye(9) +mass_matrix = jnp.eye(len(prior.base_prior)) #mass_matrix = mass_matrix.at[1, 1].set(1e-3) #mass_matrix = mass_matrix.at[5, 5].set(1e-3) local_sampler_arg = {"step_size": mass_matrix * 3e-3} @@ -100,9 +132,11 @@ train_thinning=1, output_thinning=1, local_sampler_arg=local_sampler_arg, - strategies=[Adam_optimizer, "default"], + strategies=["default"], ) -jim.sample(jax.random.PRNGKey(42)) -jim.get_samples() +print("Start sampling") +key = jax.random.PRNGKey(42) +jim.sample(key) jim.print_summary() +samples = jim.get_samples() diff --git a/test/integration/test_extrinsic_no_distance.py b/test/integration/test_extrinsic_no_distance.py deleted file mode 100644 index d1b1e559..00000000 --- a/test/integration/test_extrinsic_no_distance.py +++ /dev/null @@ -1,90 +0,0 @@ -import jax -import jax.numpy as jnp - -from jimgw.jim import Jim -from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior -from jimgw.single_event.detector import H1, L1, V1 -from jimgw.single_event.likelihood import ZeroLikelihood -from jimgw.transforms import BoundToUnbound -from jimgw.single_event.transforms import MassRatioToSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, DistanceToSNRWeightedDistanceTransform, GeocentricArrivalTimeToDetectorArrivalTimeTransform, GeocentricArrivalPhaseToDetectorArrivalPhaseTransform -from flowMC.strategy.optimization import optimization_Adam - -jax.config.update("jax_enable_x64", True) - -########################################### -########## First we grab data ############# -########################################### - -# first, fetch a 4s segment centered on GW150914 -gps = 1126259462.4 - -ifos = [H1, L1, V1] - -t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) -phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) -ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) -dec_prior = CosinePrior(parameter_names=["dec"]) - -prior = CombinePrior( - [ - t_c_prior, - phase_c_prior, - ra_prior, - dec_prior, - ] -) - -sample_transforms = [ - # all the user reparametrization transform - GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(name_mapping = [["phase_c", "phase_det"]], gps_time=gps, ifo=ifos[0]), - GeocentricArrivalTimeToDetectorArrivalTimeTransform(name_mapping = [["t_c", "t_det"]], gps_time=gps, ifo=ifos[0]), - SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), - # all the bound to unbound transform - BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["phase_det"], ["phase_det_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["t_det"], ["t_det_unbounded"]], original_lower_bound=-0.1, original_upper_bound=0.1), -] - -likelihood_transforms = [] - -likelihood = ZeroLikelihood() - -mass_matrix = jnp.eye(9) -#mass_matrix = mass_matrix.at[1, 1].set(1e-3) -#mass_matrix = mass_matrix.at[5, 5].set(1e-3) -local_sampler_arg = {"step_size": mass_matrix * 3e-3} - -Adam_optimizer = optimization_Adam(n_steps=5, learning_rate=0.01, noise_level=1) - -n_epochs = 2 -n_loop_training = 1 -learning_rate = 1e-4 - - -jim = Jim( - likelihood, - prior, - sample_transforms=sample_transforms, - likelihood_transforms=likelihood_transforms, - n_loop_training=n_loop_training, - n_loop_production=1, - n_local_steps=5, - n_global_steps=5, - n_chains=4, - n_epochs=n_epochs, - learning_rate=learning_rate, - n_max_examples=30, - n_flow_samples=100, - momentum=0.9, - batch_size=100, - use_global=True, - train_thinning=1, - output_thinning=1, - local_sampler_arg=local_sampler_arg, - strategies=[Adam_optimizer, "default"], -) - -jim.sample(jax.random.PRNGKey(42)) -jim.get_samples() -jim.print_summary() From fd338825bd10557c67da5b55bac9dc59a75db379 Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Fri, 16 Aug 2024 14:17:17 -0700 Subject: [PATCH 186/248] Fixing the single sided unbound transform --- src/jimgw/transforms.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index a26ad9af..8b4e2d75 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -418,17 +418,26 @@ class SingleSidedUnboundTransform(BijectiveTransform): """ + original_lower_bound: Float + def __init__( self, name_mapping: tuple[list[str], list[str]], + original_lower_bound: Float, ): super().__init__(name_mapping) + self.original_lower_bound = jnp.atleast_1d(original_lower_bound) + self.transform_func = lambda x: { - name_mapping[1][i]: jnp.exp(x[name_mapping[0][i]]) + name_mapping[1][i]: jnp.log( + x[name_mapping[0][i]] - self.original_lower_bound[i] + ) for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { - name_mapping[0][i]: jnp.log(x[name_mapping[1][i]]) + name_mapping[0][i]: jnp.exp( + x[name_mapping[1][i]] + self.original_lower_bound[i] + ) for i in range(len(name_mapping[1])) } From 450772f72f914e9c5b49c2ef9e66d6b56e0b3a62 Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Sat, 17 Aug 2024 01:36:45 -0700 Subject: [PATCH 187/248] Added code to create and reverse transforms for more flexibility --- src/jimgw/single_event/transforms.py | 202 ++++++++--------------- src/jimgw/transforms.py | 51 ++++++ test/integration/.gitignore | 2 + test/integration/test_GW150914_D.py | 37 +++-- test/integration/test_mass_transforms.py | 127 ++++++++++++++ 5 files changed, 271 insertions(+), 148 deletions(-) create mode 100644 test/integration/.gitignore create mode 100644 test/integration/test_mass_transforms.py diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index c3e77846..49f01457 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -4,7 +4,12 @@ from astropy.time import Time from jimgw.single_event.detector import GroundBased2G -from jimgw.transforms import BijectiveTransform, NtoNTransform +from jimgw.transforms import ( + BijectiveTransform, + NtoNTransform, + reverse_bijective_transform, + create_bijective_transform, +) from jimgw.single_event.utils import ( m1_m2_to_Mc_q, Mc_q_to_m1_m2, @@ -20,100 +25,65 @@ @jaxtyped(typechecker=typechecker) -class ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): +class SpinToCartesianSpinTransform(NtoNTransform): """ - Transform chirp mass and mass ratio to component masses - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. + Spin to Cartesian spin transformation """ + freq_ref: Float + def __init__( self, name_mapping: tuple[list[str], list[str]], + freq_ref: Float, ): super().__init__(name_mapping) - assert ( - "m_1" in name_mapping[0] - and "m_2" in name_mapping[0] - and "M_c" in name_mapping[1] - and "q" in name_mapping[1] - ) - - def named_transform(x): - Mc, q = m1_m2_to_Mc_q(x["m_1"], x["m_2"]) - return {"M_c": Mc, "q": q} - - self.transform_func = named_transform - - def named_inverse_transform(x): - m1, m2 = Mc_q_to_m1_m2(x["M_c"], x["q"]) - return {"m_1": m1, "m_2": m2} - - self.inverse_transform_func = named_inverse_transform - - -@jaxtyped(typechecker=typechecker) -class ComponentMassesToChirpMassSymmetricMassRatioTransform(BijectiveTransform): - """ - Transform mass ratio to symmetric mass ratio - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - """ + self.freq_ref = freq_ref - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): - super().__init__(name_mapping) assert ( - "m_1" in name_mapping[0] - and "m_2" in name_mapping[0] - and "M_c" in name_mapping[1] - and "eta" in name_mapping[1] + "theta_jn" in name_mapping[0] + and "phi_jl" in name_mapping[0] + and "theta_1" in name_mapping[0] + and "theta_2" in name_mapping[0] + and "phi_12" in name_mapping[0] + and "a_1" in name_mapping[0] + and "a_2" in name_mapping[0] + and "iota" in name_mapping[1] + and "s1_x" in name_mapping[1] + and "s1_y" in name_mapping[1] + and "s1_z" in name_mapping[1] + and "s2_x" in name_mapping[1] + and "s2_y" in name_mapping[1] + and "s2_z" in name_mapping[1] ) def named_transform(x): - Mc, eta = m1_m2_to_Mc_eta(x["m_1"], x["m_2"]) - return {"M_c": Mc, "eta": eta} + iota, s1x, s1y, s1z, s2x, s2y, s2z = spin_to_cartesian_spin( + x["theta_jn"], + x["phi_jl"], + x["theta_1"], + x["theta_2"], + x["phi_12"], + x["a_1"], + x["a_2"], + x["M_c"], + x["q"], + self.freq_ref, + x["phase_c"], + ) + return { + "iota": iota, + "s1_x": s1x, + "s1_y": s1y, + "s1_z": s1z, + "s2_x": s2x, + "s2_y": s2y, + "s2_z": s2z, + } self.transform_func = named_transform - def named_inverse_transform(x): - m1, m2 = Mc_eta_to_m1_m2(x["M_c"], x["q"]) - return {"m_1": m1, "m_2": m2} - - self.inverse_transform_func = named_inverse_transform - - -@jaxtyped(typechecker=typechecker) -class MassRatioToSymmetricMassRatioTransform(BijectiveTransform): - """ - Transform mass ratio to symmetric mass ratio - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - - """ - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): - super().__init__(name_mapping) - assert "q" == name_mapping[0][0] and "eta" == name_mapping[1][0] - - self.transform_func = lambda x: {"eta": q_to_eta(x["q"])} - self.inverse_transform_func = lambda x: {"q": eta_to_q(x["eta"])} - @jaxtyped(typechecker=typechecker) class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): @@ -170,62 +140,24 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform -@jaxtyped(typechecker=typechecker) -class SpinToCartesianSpinTransform(NtoNTransform): - """ - Spin to Cartesian spin transformation - """ - - freq_ref: Float - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - freq_ref: Float, - ): - super().__init__(name_mapping) - - self.freq_ref = freq_ref - - assert ( - "theta_jn" in name_mapping[0] - and "phi_jl" in name_mapping[0] - and "theta_1" in name_mapping[0] - and "theta_2" in name_mapping[0] - and "phi_12" in name_mapping[0] - and "a_1" in name_mapping[0] - and "a_2" in name_mapping[0] - and "iota" in name_mapping[1] - and "s1_x" in name_mapping[1] - and "s1_y" in name_mapping[1] - and "s1_z" in name_mapping[1] - and "s2_x" in name_mapping[1] - and "s2_y" in name_mapping[1] - and "s2_z" in name_mapping[1] - ) +# Pre-made bijective transforms: +ComponentMassesToChirpMassMassRatioTransform = create_bijective_transform( + (["m_1", "m_2"], ["M_c", "q"]), m1_m2_to_Mc_q, Mc_q_to_m1_m2 +) +ChirpMassMassRatioToComponentMassesTransform = reverse_bijective_transform( + ComponentMassesToChirpMassMassRatioTransform +) - def named_transform(x): - iota, s1x, s1y, s1z, s2x, s2y, s2z = spin_to_cartesian_spin( - x["theta_jn"], - x["phi_jl"], - x["theta_1"], - x["theta_2"], - x["phi_12"], - x["a_1"], - x["a_2"], - x["M_c"], - x["q"], - self.freq_ref, - x["phase_c"], - ) - return { - "iota": iota, - "s1_x": s1x, - "s1_y": s1y, - "s1_z": s1z, - "s2_x": s2x, - "s2_y": s2y, - "s2_z": s2z, - } +ComponentMassesToChirpMassSymmetricMassRatioTransform = create_bijective_transform( + (["m_1", "m_2"], ["M_c", "eta"]), m1_m2_to_Mc_eta, Mc_eta_to_m1_m2 +) +ChirpMassSymmetricMassRatioToComponentMassesTransform = reverse_bijective_transform( + ComponentMassesToChirpMassSymmetricMassRatioTransform +) - self.transform_func = named_transform +MassRatioToSymmetricMassRatioTransform = create_bijective_transform( + (["q"], ["eta"]), q_to_eta, eta_to_q +) +SymmetricMassRatioToMassRatioTransform = reverse_bijective_transform( + MassRatioToSymmetricMassRatioTransform +) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 715d49de..44f2837c 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -445,3 +445,54 @@ def __init__( ) for i in range(len(name_mapping[1])) } + + +def create_bijective_transform( + name_mapping: tuple[list[str], list[str]], + transform_func_array: Callable[[Float], Float], + inverse_transform_func_array: Callable[[Float], Float], +) -> BijectiveTransform: + """ + Utility function to create a BijectiveTransform object given a name_mapping and the forward and backward transform functions which take arrays as input, e.g. coming from the utils module. + + Args: + name_mapping (tuple[list[str], list[str]]): The name_mapping to be used in the named transforms. + transform_func_array (Callable[[Float], Float]): The forward function method taking an array as input. + inverse_transform_func_array (Callable[[Float], Float]): The inverse function method taking an array as input. + + Returns: + BijectiveTransform: The BijectiveTransform object. + """ + + def named_transform_func(x_named: dict[str, Float]) -> dict[str, Float]: + x_array = jnp.array([x_named[key] for key in name_mapping[0]]) + y_array = transform_func_array(*x_array) + y_named = dict(zip(name_mapping[1], y_array)) + return y_named + + def named_inverse_transform_func(y_named: dict[str, Float]) -> dict[str, Float]: + y_array = jnp.array([y_named[key] for key in name_mapping[1]]) + x_array = inverse_transform_func_array(*y_array) + x_named = dict(zip(name_mapping[0], x_array)) + return x_named + + new_transform = BijectiveTransform(name_mapping) + new_transform.transform_func = named_transform_func + new_transform.inverse_transform_func = named_inverse_transform_func + + return new_transform + + +def reverse_bijective_transform( + original_transform: BijectiveTransform, +) -> BijectiveTransform: + + reversed_name_mapping = ( + original_transform.name_mapping[1], + original_transform.name_mapping[0], + ) + reversed_transform = BijectiveTransform(name_mapping=reversed_name_mapping) + reversed_transform.transform_func = original_transform.inverse_transform_func + reversed_transform.inverse_transform_func = original_transform.transform_func + + return reversed_transform diff --git a/test/integration/.gitignore b/test/integration/.gitignore new file mode 100644 index 00000000..a7f7ef0e --- /dev/null +++ b/test/integration/.gitignore @@ -0,0 +1,2 @@ +outdir/ +figures/ diff --git a/test/integration/test_GW150914_D.py b/test/integration/test_GW150914_D.py index e1eee9ac..d3c5c0e8 100644 --- a/test/integration/test_GW150914_D.py +++ b/test/integration/test_GW150914_D.py @@ -1,3 +1,7 @@ +import os +os.environ["CUDA_VISIBLE_DEVICES"] = "0" +os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = "0.10" + import jax import jax.numpy as jnp @@ -10,6 +14,8 @@ from jimgw.single_event.transforms import ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, ComponentMassesToChirpMassMassRatioTransform from jimgw.single_event.utils import Mc_q_to_m1_m2 from flowMC.strategy.optimization import optimization_Adam +from flowMC.utils.postprocessing import plot_summary +import optax jax.config.update("jax_enable_x64", True) @@ -62,7 +68,7 @@ ) sample_transforms = [ - ComponentMassesToChirpMassMassRatioTransform(name_mapping=[["m_1", "m_2"], ["M_c", "q"]]), + ComponentMassesToChirpMassMassRatioTransform, BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=M_c_min, original_upper_bound=M_c_max), BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=q_min, original_upper_bound=q_max), BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), @@ -78,7 +84,7 @@ ] likelihood_transforms = [ - ComponentMassesToChirpMassSymmetricMassRatioTransform(name_mapping=[["m_1", "m_2"], ["M_c", "eta"]]), + ComponentMassesToChirpMassSymmetricMassRatioTransform, ] likelihood = TransientLikelihoodFD( @@ -97,10 +103,14 @@ Adam_optimizer = optimization_Adam(n_steps=5, learning_rate=0.01, noise_level=1) -n_epochs = 2 -n_loop_training = 1 -learning_rate = 1e-4 +n_epochs = 20 +n_loop_training = 10 +total_epochs = n_epochs * n_loop_training +start = total_epochs//10 +learning_rate = optax.polynomial_schedule( + 1e-3, 5e-4, 4.0, total_epochs - start, transition_begin=start +) jim = Jim( likelihood, @@ -108,19 +118,19 @@ sample_transforms=sample_transforms, likelihood_transforms=likelihood_transforms, n_loop_training=n_loop_training, - n_loop_production=1, - n_local_steps=5, - n_global_steps=5, - n_chains=4, + n_loop_production=4, + n_local_steps=10, + n_global_steps=1000, + n_chains=500, n_epochs=n_epochs, learning_rate=learning_rate, - n_max_examples=30, - n_flow_samples=100, + n_max_examples=30000, + n_flow_samples=100000, momentum=0.9, - batch_size=100, + batch_size=30000, use_global=True, train_thinning=1, - output_thinning=1, + output_thinning=10, local_sampler_arg=local_sampler_arg, strategies=[Adam_optimizer, "default"], ) @@ -128,3 +138,4 @@ jim.sample(jax.random.PRNGKey(42)) jim.get_samples() jim.print_summary() +plot_summary(jim.sampler) \ No newline at end of file diff --git a/test/integration/test_mass_transforms.py b/test/integration/test_mass_transforms.py new file mode 100644 index 00000000..ec7631cb --- /dev/null +++ b/test/integration/test_mass_transforms.py @@ -0,0 +1,127 @@ +import os +os.environ["CUDA_VISIBLE_DEVICES"] = "2" +os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = "0.10" + +import numpy as np +import matplotlib.pyplot as plt +import corner +import jax +import jax.numpy as jnp +from jaxtyping import Float + +from jimgw.prior import UniformPrior, CombinePrior +from jimgw.single_event.transforms import ChirpMassMassRatioToComponentMassesTransform +from jimgw.base import LikelihoodBase +from jimgw.jim import Jim + +params = {"axes.grid": True, + "text.usetex" : True, + "font.family" : "serif", + "ytick.color" : "black", + "xtick.color" : "black", + "axes.labelcolor" : "black", + "axes.edgecolor" : "black", + "font.serif" : ["Computer Modern Serif"], + "xtick.labelsize": 16, + "ytick.labelsize": 16, + "axes.labelsize": 16, + "legend.fontsize": 16, + "legend.title_fontsize": 16, + "figure.titlesize": 16} + +plt.rcParams.update(params) + +# Improved corner kwargs +default_corner_kwargs = dict(bins=40, + smooth=1., + show_titles=False, + label_kwargs=dict(fontsize=16), + title_kwargs=dict(fontsize=16), + color="blue", + # quantiles=[], + # levels=[0.9], + plot_density=True, + plot_datapoints=False, + fill_contours=True, + max_n_ticks=4, + min_n_ticks=3, + truth_color = "red", + save=False) + +# Likelihood for this test: + +class MyLikelihood(LikelihoodBase): + """Simple toy likelihood: Gaussian centered on the true component masses""" + + true_m1: Float + true_m2: Float + + def __init__(self, + true_m1: Float, + true_m2: Float): + + self.true_m1 = true_m1 + self.true_m2 = true_m2 + + def evaluate(self, params: dict[str, Float], data: dict) -> Float: + m1, m2 = params['m_1'], params['m_2'] + m1_std = 0.1 + m2_std = 0.1 + return -0.5 * (((m1 - self.true_m1) / m1_std)**2 + ((m2 - self.true_m2) / m2_std)**2) + +# Setup +true_m1 = 1.6 +true_m2 = 1.4 +true_mc = (true_m1 * true_m2)**(3/5) / (true_m1 + true_m2)**(1/5) +true_q = true_m2 / true_m1 + +# Priors +eps = 0.5 # half of width of the chirp mass prior +mc_prior = UniformPrior(true_mc - eps, true_mc + eps, parameter_names=['M_c']) +q_prior = UniformPrior(0.125, 1.0, parameter_names=['q']) +combine_prior = CombinePrior([mc_prior, q_prior]) + +# Likelihood and transform +likelihood = MyLikelihood(true_m1, true_m2) +mass_transform = ChirpMassMassRatioToComponentMassesTransform + +print(mass_transform.name_mapping) + +# Other stuff we have to give to Jim to make it work +step = 5e-3 +local_sampler_arg = {"step_size": step * jnp.eye(2)} + +# Jim: +jim = Jim(likelihood, + combine_prior, + likelihood_transforms=[mass_transform], + n_chains = 200, + parameter_names=['M_c', 'q'], + n_loop_training=20, + n_loop_production=5, + local_sampler_arg=local_sampler_arg) + +jim.sample(jax.random.PRNGKey(0)) +jim.print_summary() + +# Go from Mc, q samples to m1, m2 samples +chains_named = jim.get_samples() +m1m2_named = mass_transform.forward(chains_named) +m1, m2 = m1m2_named['m_1'], m1m2_named['m_2'] + +### Prior space: +chains = np.array([chains_named['M_c'], chains_named['q']]).T +chains = np.reshape(chains, (-1, 2)) +corner.corner(chains, truths = np.array([true_mc, true_q]), **default_corner_kwargs) + +plt.savefig("./figures/test_mass_transform_before.png", bbox_inches = 'tight') +plt.close() + +### Transformed space: +chains = np.array([m1, m2]).T +chains = np.reshape(chains, (-1, 2)) + +corner.corner(chains, truths = np.array([true_m1, true_m2]), **default_corner_kwargs) + +plt.savefig("./figures/test_mass_transform_after.png", bbox_inches = 'tight') +plt.close() \ No newline at end of file From 03e76dc1731c793039504b6bf709da79953e17dc Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Sat, 17 Aug 2024 01:52:18 -0700 Subject: [PATCH 188/248] Update extrinsic test --- test/integration/test_extrinsic.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/test/integration/test_extrinsic.py b/test/integration/test_extrinsic.py index 988f6ea7..7cb5bf32 100644 --- a/test/integration/test_extrinsic.py +++ b/test/integration/test_extrinsic.py @@ -1,10 +1,3 @@ -import psutil -p = psutil.Process() -p.cpu_affinity([0]) - -import os -os.environ['CUDA_VISIBLE_DEVICES'] = '0' - from astropy.time import Time import jax @@ -14,7 +7,7 @@ from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior from jimgw.single_event.detector import H1, L1, V1 from jimgw.single_event.likelihood import ZeroLikelihood -from jimgw.transforms import BoundToUnbound +from jimgw.transforms import BoundToUnbound, SingleSidedUnboundTransform from jimgw.single_event.transforms import MassRatioToSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, DistanceToSNRWeightedDistanceTransform, GeocentricArrivalTimeToDetectorArrivalTimeTransform, GeocentricArrivalPhaseToDetectorArrivalPhaseTransform from flowMC.strategy.optimization import optimization_Adam @@ -30,7 +23,7 @@ ifos = [H1, L1, V1] M_c_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) -dL_prior = PowerLawPrior(1.0, 2000.0, 2.0, parameter_names=["d_L"]) +dL_prior = PowerLawPrior(10.0, 200.0, -2.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) iota_prior = SinePrior(parameter_names=["iota"]) @@ -93,7 +86,7 @@ def calc_R_dets(ra, dec, psi, iota): BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = [["phase_det"], ["phase_det_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = [["t_det"], ["t_det_unbounded"]], original_lower_bound=-0.1, original_upper_bound=0.1), - BoundToUnbound(name_mapping = [["d_hat"], ["d_hat_unbounded"]], original_lower_bound=d_hat_min, original_upper_bound=d_hat_max), + SingleSidedUnboundTransform(name_mapping = [["d_hat"], ["d_hat_unbounded"]], original_lower_bound=float(d_hat_min)), ] likelihood_transforms = [] @@ -119,9 +112,9 @@ def calc_R_dets(ra, dec, psi, iota): likelihood_transforms=likelihood_transforms, n_loop_training=n_loop_training, n_loop_production=1, - n_local_steps=5, - n_global_steps=5, - n_chains=4, + n_local_steps=1, + n_global_steps=1, + n_chains=10, n_epochs=n_epochs, learning_rate=learning_rate, n_max_examples=30, From 83b0d14246ed9a71b1900920123813763bca8e8e Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Sun, 18 Aug 2024 00:38:56 -0700 Subject: [PATCH 189/248] Make sure 1d transforms are created correctly --- src/jimgw/transforms.py | 4 ++-- test/integration/test_GW150914_Pv2.py | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 44f2837c..268bfd5c 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -467,13 +467,13 @@ def create_bijective_transform( def named_transform_func(x_named: dict[str, Float]) -> dict[str, Float]: x_array = jnp.array([x_named[key] for key in name_mapping[0]]) y_array = transform_func_array(*x_array) - y_named = dict(zip(name_mapping[1], y_array)) + y_named = dict(zip(name_mapping[1], jnp.atleast_1d(y_array))) return y_named def named_inverse_transform_func(y_named: dict[str, Float]) -> dict[str, Float]: y_array = jnp.array([y_named[key] for key in name_mapping[1]]) x_array = inverse_transform_func_array(*y_array) - x_named = dict(zip(name_mapping[0], x_array)) + x_named = dict(zip(name_mapping[0], jnp.atleast_1d(x_array))) return x_named new_transform = BijectiveTransform(name_mapping) diff --git a/test/integration/test_GW150914_Pv2.py b/test/integration/test_GW150914_Pv2.py index c9d83a5e..e155237d 100644 --- a/test/integration/test_GW150914_Pv2.py +++ b/test/integration/test_GW150914_Pv2.py @@ -1,3 +1,7 @@ +import os +os.environ["CUDA_VISIBLE_DEVICES"] = "0" +os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = "0.10" + import time import jax @@ -12,6 +16,8 @@ from jimgw.single_event.transforms import MassRatioToSymmetricMassRatioTransform, SpinToCartesianSpinTransform from flowMC.strategy.optimization import optimization_Adam +from flowMC.utils.postprocessing import plot_summary + jax.config.update("jax_enable_x64", True) ########################################### @@ -90,7 +96,7 @@ likelihood_transforms = [ SpinToCartesianSpinTransform(name_mapping=[["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a_1", "a_2"], ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"]], freq_ref=20.0), - MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), + MassRatioToSymmetricMassRatioTransform, ] likelihood = TransientLikelihoodFD( @@ -140,3 +146,5 @@ jim.sample(jax.random.PRNGKey(42)) jim.get_samples() jim.print_summary() + +plot_summary(jim.sampler) \ No newline at end of file From 8fe4b5fdcf43c3a2c6430ba868aeddb3e96bbc72 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Mon, 19 Aug 2024 14:52:39 +0200 Subject: [PATCH 190/248] bugfix for single sided transform --- src/jimgw/transforms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 8b4e2d75..f7d4c702 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -435,9 +435,8 @@ def __init__( for i in range(len(name_mapping[0])) } self.inverse_transform_func = lambda x: { - name_mapping[0][i]: jnp.exp( - x[name_mapping[1][i]] + self.original_lower_bound[i] - ) + name_mapping[0][i]: jnp.exp(x[name_mapping[1][i]]) + + self.original_lower_bound[i] for i in range(len(name_mapping[1])) } From a19b556a0db3565b141b7508ddf85f3e4250e8ef Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Mon, 19 Aug 2024 05:59:11 -0700 Subject: [PATCH 191/248] Update test --- test/integration/test_extrinsic.py | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/test/integration/test_extrinsic.py b/test/integration/test_extrinsic.py index 7cb5bf32..55979402 100644 --- a/test/integration/test_extrinsic.py +++ b/test/integration/test_extrinsic.py @@ -23,7 +23,7 @@ ifos = [H1, L1, V1] M_c_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) -dL_prior = PowerLawPrior(10.0, 200.0, -2.0, parameter_names=["d_L"]) +dL_prior = PowerLawPrior(10.0, 2000.0, 2.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) iota_prior = SinePrior(parameter_names=["iota"]) @@ -44,33 +44,7 @@ ] ) -# calculate the d_hat range -@jnp.vectorize -def calc_R_dets(ra, dec, psi, iota): - gmst = ( - Time(gps, format="gps").sidereal_time("apparent", "greenwich").rad - ) - p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 - c_iota_term = jnp.cos(iota) - R_dets2 = 0.0 - for ifo in ifos: - antenna_pattern = ifo.antenna_pattern(ra, dec, psi, gmst) - p_mode_term = p_iota_term * antenna_pattern["p"] - c_mode_term = c_iota_term * antenna_pattern["c"] - R_dets2 += p_mode_term**2 + c_mode_term**2 - - return jnp.sqrt(R_dets2) - -key1, key2, key3, key4 = jax.random.split(jax.random.PRNGKey(1234), 4) -# generate 10000 samples for each -ra_samples = ra_prior.sample(key1, 10000)["ra"] -dec_samples = dec_prior.sample(key2, 10000)["dec"] -psi_samples = psi_prior.sample(key3, 10000)["psi"] -iota_samples = iota_prior.sample(key4, 10000)["iota"] -R_dets_samples = calc_R_dets(ra_samples, dec_samples, psi_samples, iota_samples) - d_hat_min = dL_prior.xmin / jnp.power(M_c_prior.xmax, 5. / 6.) -d_hat_max = dL_prior.xmax / jnp.power(M_c_prior.xmin, 5. / 6.) / jnp.amin(R_dets_samples) sample_transforms = [ # all the user reparametrization transform From 6d2cd9704c10a8345aebfde506ed8bcbeb3e83fe Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Mon, 19 Aug 2024 16:25:39 +0200 Subject: [PATCH 192/248] update distance transform --- src/jimgw/single_event/transforms.py | 36 +++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 4b6ada1f..5d8e688b 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -328,6 +328,8 @@ class DistanceToSNRWeightedDistanceTransform(ConditionalBijectiveTransform): gmst: Float ifos: list[GroundBased2G] + d_L_min: Float + d_L_max: Float def __init__( self, @@ -335,6 +337,8 @@ def __init__( conditional_names: list[str], gps_time: Float, ifos: list[GroundBased2G], + d_L_min: Float, + d_L_max: Float, ): super().__init__(name_mapping, conditional_names) @@ -342,8 +346,10 @@ def __init__( Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad ) self.ifos = ifos + self.d_L_min = d_L_min + self.d_L_max = d_L_max - assert "d_L" in name_mapping[0] and "d_hat" in name_mapping[1] + assert "d_L" in name_mapping[0] and "d_hat_unbounded" in name_mapping[1] assert ( "ra" in conditional_names and "dec" in conditional_names @@ -371,20 +377,38 @@ def named_transform(x): x["M_c"], ) R_dets = _calc_R_dets(x["ra"], x["dec"], x["psi"], x["iota"]) - d_hat = d_L / jnp.power(M_c, 5.0 / 6.0) / R_dets + + scale_factor = 1.0 / jnp.power(M_c, 5.0 / 6.0) / R_dets + d_hat = scale_factor * d_L + + d_hat_min = scale_factor * self.d_L_min + d_hat_max = scale_factor * self.d_L_max + + y = (d_hat - d_hat_min) / (d_hat_max - d_hat_min) + d_hat_unbounded = jnp.log(y / (1.0 - y)) + return { - "d_hat": d_hat, + "d_hat_unbounded": d_hat_unbounded, } self.transform_func = named_transform def named_inverse_transform(x): - d_hat, M_c = ( - x["d_hat"], + d_hat_unbounded, M_c = ( + x["d_hat_unbounded"], x["M_c"], ) R_dets = _calc_R_dets(x["ra"], x["dec"], x["psi"], x["iota"]) - d_L = d_hat * jnp.power(M_c, 5.0 / 6.0) * R_dets + + scale_factor = 1.0 / jnp.power(M_c, 5.0 / 6.0) / R_dets + + d_hat_min = scale_factor * self.d_L_min + d_hat_max = scale_factor * self.d_L_max + + d_hat = (d_hat_max - d_hat_min) / ( + 1.0 + jnp.exp(-d_hat_unbounded) + ) + d_hat_min + d_L = d_hat / scale_factor return { "d_L": d_L, } From 6993dd9072780203bf99046343f62328c63c7848 Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Mon, 19 Aug 2024 08:58:57 -0700 Subject: [PATCH 193/248] Update test --- test/integration/test_extrinsic.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/integration/test_extrinsic.py b/test/integration/test_extrinsic.py index 55979402..a5dc5c7b 100644 --- a/test/integration/test_extrinsic.py +++ b/test/integration/test_extrinsic.py @@ -44,11 +44,10 @@ ] ) -d_hat_min = dL_prior.xmin / jnp.power(M_c_prior.xmax, 5. / 6.) sample_transforms = [ # all the user reparametrization transform - DistanceToSNRWeightedDistanceTransform(name_mapping=[["d_L"], ["d_hat"]], conditional_names=["M_c","ra", "dec", "psi", "iota"], gps_time=gps, ifos=ifos), + DistanceToSNRWeightedDistanceTransform(name_mapping=[["d_L"], ["d_hat_unbounded"]], conditional_names=["M_c","ra", "dec", "psi", "iota"], gps_time=gps, ifos=ifos, d_L_min=dL_prior.xmin, d_L_max=dL_prior.xmax), GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(name_mapping = [["phase_c"], ["phase_det"]], conditional_names=["ra", "dec", "psi", "iota"], gps_time=gps, ifo=ifos[0]), GeocentricArrivalTimeToDetectorArrivalTimeTransform(name_mapping = [["t_c"], ["t_det"]], conditional_names=["ra", "dec"], gps_time=gps, ifo=ifos[0]), SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), @@ -60,7 +59,6 @@ BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = [["phase_det"], ["phase_det_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = [["t_det"], ["t_det_unbounded"]], original_lower_bound=-0.1, original_upper_bound=0.1), - SingleSidedUnboundTransform(name_mapping = [["d_hat"], ["d_hat_unbounded"]], original_lower_bound=float(d_hat_min)), ] likelihood_transforms = [] From ff65fcf2e75a0655c8e33ddfb32c378c84b3dc8e Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Mon, 19 Aug 2024 21:58:57 +0200 Subject: [PATCH 194/248] Update arrival time transform --- src/jimgw/single_event/transforms.py | 58 ++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 5d8e688b..1070adaf 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -196,6 +196,8 @@ class GeocentricArrivalTimeToDetectorArrivalTimeTransform( gmst: Float ifo: GroundBased2G + tc_min: Float + tc_max: Float def __init__( self, @@ -203,6 +205,8 @@ def __init__( conditional_names: list[str], gps_time: Float, ifo: GroundBased2G, + tc_min: Float, + tc_max: Float, ): super().__init__(name_mapping, conditional_names) @@ -210,24 +214,46 @@ def __init__( Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad ) self.ifo = ifo + self.tc_min = tc_min + self.tc_max = tc_max - assert "t_c" in name_mapping[0] and "t_det" in name_mapping[1] + assert "t_c" in name_mapping[0] and "t_det_unbounded" in name_mapping[1] assert "ra" in conditional_names and "dec" in conditional_names + @jnp.vectorize + def time_delay(ra, dec, gmst): + return self.ifo.delay_from_geocenter(ra, dec, gmst) + def named_transform(x): - t_det = x["t_c"] + jnp.vectorize(self.ifo.delay_from_geocenter)( - x["ra"], x["dec"], self.gmst - ) + + time_shift = time_delay(x["ra"], x["dec"], self.gmst) + + t_det = x["t_c"] + time_shift + t_det_min = self.tc_min + time_shift + t_det_max = self.tc_max + time_shift + + y = (t_det - t_det_min) / (t_det_max - t_det_min) + t_det_unbounded = jnp.log(y / (1.0 - y)) return { - "t_det": t_det, + "t_det_unbounded": t_det_unbounded, } self.transform_func = named_transform def named_inverse_transform(x): - t_c = x["t_det"] - jnp.vectorize(self.ifo.delay_from_geocenter)( + + time_shift = jnp.vectorize(self.ifo.delay_from_geocenter)( x["ra"], x["dec"], self.gmst ) + + t_det_min = self.tc_min + time_shift + t_det_max = self.tc_max + time_shift + t_det = (t_det_max - t_det_min) / ( + 1.0 + jnp.exp(-x["t_det_unbounded"]) + ) + t_det_min + + t_c = t_det - time_shift + return { "t_c": t_c, } @@ -328,8 +354,8 @@ class DistanceToSNRWeightedDistanceTransform(ConditionalBijectiveTransform): gmst: Float ifos: list[GroundBased2G] - d_L_min: Float - d_L_max: Float + dL_min: Float + dL_max: Float def __init__( self, @@ -337,8 +363,8 @@ def __init__( conditional_names: list[str], gps_time: Float, ifos: list[GroundBased2G], - d_L_min: Float, - d_L_max: Float, + dL_min: Float, + dL_max: Float, ): super().__init__(name_mapping, conditional_names) @@ -346,8 +372,8 @@ def __init__( Time(gps_time, format="gps").sidereal_time("apparent", "greenwich").rad ) self.ifos = ifos - self.d_L_min = d_L_min - self.d_L_max = d_L_max + self.dL_min = dL_min + self.dL_max = dL_max assert "d_L" in name_mapping[0] and "d_hat_unbounded" in name_mapping[1] assert ( @@ -381,8 +407,8 @@ def named_transform(x): scale_factor = 1.0 / jnp.power(M_c, 5.0 / 6.0) / R_dets d_hat = scale_factor * d_L - d_hat_min = scale_factor * self.d_L_min - d_hat_max = scale_factor * self.d_L_max + d_hat_min = scale_factor * self.dL_min + d_hat_max = scale_factor * self.dL_max y = (d_hat - d_hat_min) / (d_hat_max - d_hat_min) d_hat_unbounded = jnp.log(y / (1.0 - y)) @@ -402,8 +428,8 @@ def named_inverse_transform(x): scale_factor = 1.0 / jnp.power(M_c, 5.0 / 6.0) / R_dets - d_hat_min = scale_factor * self.d_L_min - d_hat_max = scale_factor * self.d_L_max + d_hat_min = scale_factor * self.dL_min + d_hat_max = scale_factor * self.dL_max d_hat = (d_hat_max - d_hat_min) / ( 1.0 + jnp.exp(-d_hat_unbounded) From b98d783db7b16a14c91ca83e7e3cd606697682d7 Mon Sep 17 00:00:00 2001 From: Tsun Ho Pang Date: Mon, 19 Aug 2024 13:04:47 -0700 Subject: [PATCH 195/248] Update test --- test/integration/test_extrinsic.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/integration/test_extrinsic.py b/test/integration/test_extrinsic.py index a5dc5c7b..ff79723e 100644 --- a/test/integration/test_extrinsic.py +++ b/test/integration/test_extrinsic.py @@ -49,7 +49,7 @@ # all the user reparametrization transform DistanceToSNRWeightedDistanceTransform(name_mapping=[["d_L"], ["d_hat_unbounded"]], conditional_names=["M_c","ra", "dec", "psi", "iota"], gps_time=gps, ifos=ifos, d_L_min=dL_prior.xmin, d_L_max=dL_prior.xmax), GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(name_mapping = [["phase_c"], ["phase_det"]], conditional_names=["ra", "dec", "psi", "iota"], gps_time=gps, ifo=ifos[0]), - GeocentricArrivalTimeToDetectorArrivalTimeTransform(name_mapping = [["t_c"], ["t_det"]], conditional_names=["ra", "dec"], gps_time=gps, ifo=ifos[0]), + GeocentricArrivalTimeToDetectorArrivalTimeTransform(name_mapping = [["t_c"], ["t_det_unbounded"]], tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, conditional_names=["ra", "dec"], gps_time=gps, ifo=ifos[0]), SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), # all the bound to unbound transform BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), @@ -58,7 +58,6 @@ BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = [["phase_det"], ["phase_det_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["t_det"], ["t_det_unbounded"]], original_lower_bound=-0.1, original_upper_bound=0.1), ] likelihood_transforms = [] @@ -84,8 +83,8 @@ likelihood_transforms=likelihood_transforms, n_loop_training=n_loop_training, n_loop_production=1, - n_local_steps=1, - n_global_steps=1, + n_local_steps=2, + n_global_steps=2, n_chains=10, n_epochs=n_epochs, learning_rate=learning_rate, From e399a5e9c7609fe1f07ba6951182e5899ac4b692 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Mon, 19 Aug 2024 22:18:31 +0200 Subject: [PATCH 196/248] Fix typo --- test/integration/test_extrinsic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_extrinsic.py b/test/integration/test_extrinsic.py index ff79723e..f0e089fe 100644 --- a/test/integration/test_extrinsic.py +++ b/test/integration/test_extrinsic.py @@ -47,7 +47,7 @@ sample_transforms = [ # all the user reparametrization transform - DistanceToSNRWeightedDistanceTransform(name_mapping=[["d_L"], ["d_hat_unbounded"]], conditional_names=["M_c","ra", "dec", "psi", "iota"], gps_time=gps, ifos=ifos, d_L_min=dL_prior.xmin, d_L_max=dL_prior.xmax), + DistanceToSNRWeightedDistanceTransform(name_mapping=[["d_L"], ["d_hat_unbounded"]], conditional_names=["M_c","ra", "dec", "psi", "iota"], gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(name_mapping = [["phase_c"], ["phase_det"]], conditional_names=["ra", "dec", "psi", "iota"], gps_time=gps, ifo=ifos[0]), GeocentricArrivalTimeToDetectorArrivalTimeTransform(name_mapping = [["t_c"], ["t_det_unbounded"]], tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, conditional_names=["ra", "dec"], gps_time=gps, ifo=ifos[0]), SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), From 583b759de0859d0d2096bc95d7cccfa1d0b6b824 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Tue, 20 Aug 2024 00:43:21 +0200 Subject: [PATCH 197/248] Fix typo --- src/jimgw/single_event/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 1070adaf..084fe368 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -332,7 +332,7 @@ def named_inverse_transform(x): R_det_arg = _calc_R_det_arg( x["ra"], x["dec"], x["psi"], x["iota"], self.gmst ) - phase_c = (-R_det_arg + x["phase_det"]) * 2.0 + phase_c = -R_det_arg + x["phase_det"] * 2.0 return { "phase_c": phase_c % (2.0 * jnp.pi), } From c6605f68996834e5e914a3be229801ea9389978b Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 22 Aug 2024 02:34:35 +0800 Subject: [PATCH 198/248] Update transforms.py --- src/jimgw/transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 715d49de..ac56a1e1 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -89,7 +89,7 @@ def transform(self, x: dict[str, Float]) -> tuple[dict[str, Float], Float]: output_params = self.transform_func(transform_params) jacobian = jax.jacfwd(self.transform_func)(transform_params) jacobian = jnp.array(jax.tree.leaves(jacobian)) - jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) + jacobian = jnp.log(jnp.absolute(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim)))) jax.tree.map( lambda key: x_copy.pop(key), self.name_mapping[0], @@ -126,7 +126,7 @@ def inverse(self, y: dict[str, Float]) -> tuple[dict[str, Float], Float]: output_params = self.inverse_transform_func(transform_params) jacobian = jax.jacfwd(self.inverse_transform_func)(transform_params) jacobian = jnp.array(jax.tree.leaves(jacobian)) - jacobian = jnp.log(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim))) + jacobian = jnp.log(jnp.absolute(jnp.linalg.det(jacobian.reshape(self.n_dim, self.n_dim)))) jax.tree.map( lambda key: y_copy.pop(key), self.name_mapping[1], From 59dd222e8cb1f67dad80e7d4119f7c505bea11d8 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 22 Aug 2024 02:45:23 +0800 Subject: [PATCH 199/248] Update runManager.py --- src/jimgw/single_event/runManager.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index aa8d0dc7..e23c5396 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -71,9 +71,7 @@ class SingleEventRun: str, dict[str, Union[str, float, int, bool]] ] # Transform cannot be included in this way, add it to preset if used often. jim_parameters: dict[str, Union[str, float, int, bool, dict]] - injection_parameters: dict[str, float] = field( - default_factory=lambda: {} - ) + injection_parameters: dict[str, float] injection: bool = False likelihood_parameters: dict[str, Union[str, float, int, bool, PyTree]] = field( default_factory=lambda: {"name": "TransientLikelihoodFD"} @@ -125,9 +123,6 @@ def __init__(self, **kwargs): print("Neither run instance nor path provided.") raise ValueError - if self.run.injection and not self.run.injection_parameters: - raise ValueError("Injection mode requires injection parameters.") - local_prior = self.initialize_prior() local_likelihood = self.initialize_likelihood(local_prior) self.jim = Jim(local_likelihood, local_prior, **self.run.jim_parameters) From 702ee20a6104b8a9561f2ba115809472985c49a0 Mon Sep 17 00:00:00 2001 From: xuyuon <116078673+xuyuon@users.noreply.github.com> Date: Thu, 22 Aug 2024 02:45:58 +0800 Subject: [PATCH 200/248] Update runManager.py --- src/jimgw/single_event/runManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/single_event/runManager.py b/src/jimgw/single_event/runManager.py index e23c5396..3f65166d 100644 --- a/src/jimgw/single_event/runManager.py +++ b/src/jimgw/single_event/runManager.py @@ -71,7 +71,7 @@ class SingleEventRun: str, dict[str, Union[str, float, int, bool]] ] # Transform cannot be included in this way, add it to preset if used often. jim_parameters: dict[str, Union[str, float, int, bool, dict]] - injection_parameters: dict[str, float] + injection_parameters: dict[str, float] injection: bool = False likelihood_parameters: dict[str, Union[str, float, int, bool, PyTree]] = field( default_factory=lambda: {"name": "TransientLikelihoodFD"} From b621dc907eb661a1d1993f39fecac8c8b6d56bc5 Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Wed, 21 Aug 2024 23:24:25 -0700 Subject: [PATCH 201/248] Making the tests lightweight again --- test/integration/test_GW150914_D.py | 31 +++++++------------ .../integration/test_GW150914_D_heterodyne.py | 2 +- test/integration/test_GW150914_Pv2.py | 12 ++----- test/integration/test_mass_transforms.py | 30 +++--------------- 4 files changed, 19 insertions(+), 56 deletions(-) diff --git a/test/integration/test_GW150914_D.py b/test/integration/test_GW150914_D.py index d3c5c0e8..2bb536a2 100644 --- a/test/integration/test_GW150914_D.py +++ b/test/integration/test_GW150914_D.py @@ -95,7 +95,6 @@ post_trigger_duration=2, ) - mass_matrix = jnp.eye(11) mass_matrix = mass_matrix.at[1, 1].set(1e-3) mass_matrix = mass_matrix.at[5, 5].set(1e-3) @@ -103,14 +102,9 @@ Adam_optimizer = optimization_Adam(n_steps=5, learning_rate=0.01, noise_level=1) - -n_epochs = 20 -n_loop_training = 10 -total_epochs = n_epochs * n_loop_training -start = total_epochs//10 -learning_rate = optax.polynomial_schedule( - 1e-3, 5e-4, 4.0, total_epochs - start, transition_begin=start -) +n_epochs = 2 +n_loop_training = 1 +learning_rate = 1e-4 jim = Jim( likelihood, @@ -118,24 +112,23 @@ sample_transforms=sample_transforms, likelihood_transforms=likelihood_transforms, n_loop_training=n_loop_training, - n_loop_production=4, - n_local_steps=10, - n_global_steps=1000, - n_chains=500, + n_loop_production=1, + n_local_steps=5, + n_global_steps=5, + n_chains=4, n_epochs=n_epochs, learning_rate=learning_rate, - n_max_examples=30000, - n_flow_samples=100000, + n_max_examples=30, + n_flow_samples=100, momentum=0.9, - batch_size=30000, + batch_size=100, use_global=True, train_thinning=1, - output_thinning=10, + output_thinning=1, local_sampler_arg=local_sampler_arg, strategies=[Adam_optimizer, "default"], ) jim.sample(jax.random.PRNGKey(42)) jim.get_samples() -jim.print_summary() -plot_summary(jim.sampler) \ No newline at end of file +jim.print_summary() \ No newline at end of file diff --git a/test/integration/test_GW150914_D_heterodyne.py b/test/integration/test_GW150914_D_heterodyne.py index bf97efdb..6d139dc8 100644 --- a/test/integration/test_GW150914_D_heterodyne.py +++ b/test/integration/test_GW150914_D_heterodyne.py @@ -132,4 +132,4 @@ jim.sample(jax.random.PRNGKey(42)) jim.get_samples() -jim.print_summary() +jim.print_summary() \ No newline at end of file diff --git a/test/integration/test_GW150914_Pv2.py b/test/integration/test_GW150914_Pv2.py index e155237d..3d8e9f20 100644 --- a/test/integration/test_GW150914_Pv2.py +++ b/test/integration/test_GW150914_Pv2.py @@ -1,7 +1,3 @@ -import os -os.environ["CUDA_VISIBLE_DEVICES"] = "0" -os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = "0.10" - import time import jax @@ -16,8 +12,6 @@ from jimgw.single_event.transforms import MassRatioToSymmetricMassRatioTransform, SpinToCartesianSpinTransform from flowMC.strategy.optimization import optimization_Adam -from flowMC.utils.postprocessing import plot_summary - jax.config.update("jax_enable_x64", True) ########################################### @@ -96,7 +90,7 @@ likelihood_transforms = [ SpinToCartesianSpinTransform(name_mapping=[["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a_1", "a_2"], ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"]], freq_ref=20.0), - MassRatioToSymmetricMassRatioTransform, + MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), ] likelihood = TransientLikelihoodFD( @@ -145,6 +139,4 @@ jim.sample(jax.random.PRNGKey(42)) jim.get_samples() -jim.print_summary() - -plot_summary(jim.sampler) \ No newline at end of file +jim.print_summary() \ No newline at end of file diff --git a/test/integration/test_mass_transforms.py b/test/integration/test_mass_transforms.py index ec7631cb..f8056d0c 100644 --- a/test/integration/test_mass_transforms.py +++ b/test/integration/test_mass_transforms.py @@ -95,33 +95,11 @@ def evaluate(self, params: dict[str, Float], data: dict) -> Float: jim = Jim(likelihood, combine_prior, likelihood_transforms=[mass_transform], - n_chains = 200, + n_chains = 50, parameter_names=['M_c', 'q'], - n_loop_training=20, - n_loop_production=5, + n_loop_training=2, + n_loop_production=2, local_sampler_arg=local_sampler_arg) jim.sample(jax.random.PRNGKey(0)) -jim.print_summary() - -# Go from Mc, q samples to m1, m2 samples -chains_named = jim.get_samples() -m1m2_named = mass_transform.forward(chains_named) -m1, m2 = m1m2_named['m_1'], m1m2_named['m_2'] - -### Prior space: -chains = np.array([chains_named['M_c'], chains_named['q']]).T -chains = np.reshape(chains, (-1, 2)) -corner.corner(chains, truths = np.array([true_mc, true_q]), **default_corner_kwargs) - -plt.savefig("./figures/test_mass_transform_before.png", bbox_inches = 'tight') -plt.close() - -### Transformed space: -chains = np.array([m1, m2]).T -chains = np.reshape(chains, (-1, 2)) - -corner.corner(chains, truths = np.array([true_m1, true_m2]), **default_corner_kwargs) - -plt.savefig("./figures/test_mass_transform_after.png", bbox_inches = 'tight') -plt.close() \ No newline at end of file +jim.print_summary() \ No newline at end of file From c66c65ca35b925734877850da4f88d023bf26b7d Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Wed, 21 Aug 2024 23:38:12 -0700 Subject: [PATCH 202/248] Reverted some src code and updated tests accordingly --- src/jimgw/single_event/transforms.py | 112 ++++++++++++++++++++--- src/jimgw/transforms.py | 36 -------- test/integration/test_GW150914_D.py | 4 +- test/integration/test_mass_transforms.py | 2 +- 4 files changed, 101 insertions(+), 53 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 49f01457..31d41dfc 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -8,7 +8,6 @@ BijectiveTransform, NtoNTransform, reverse_bijective_transform, - create_bijective_transform, ) from jimgw.single_event.utils import ( m1_m2_to_Mc_q, @@ -139,25 +138,110 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform +@jaxtyped(typechecker=typechecker) +class ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): + """ + Transform chirp mass and mass ratio to component masses + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + assert ( + "m_1" in name_mapping[0] + and "m_2" in name_mapping[0] + and "M_c" in name_mapping[1] + and "q" in name_mapping[1] + ) + + def named_transform(x): + Mc, q = m1_m2_to_Mc_q(x["m_1"], x["m_2"]) + return {"M_c": Mc, "q": q} + + self.transform_func = named_transform + + def named_inverse_transform(x): + m1, m2 = Mc_q_to_m1_m2(x["M_c"], x["q"]) + return {"m_1": m1, "m_2": m2} + + self.inverse_transform_func = named_inverse_transform + + +@jaxtyped(typechecker=typechecker) +class ComponentMassesToChirpMassSymmetricMassRatioTransform(BijectiveTransform): + """ + Transform mass ratio to symmetric mass ratio + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + assert ( + "m_1" in name_mapping[0] + and "m_2" in name_mapping[0] + and "M_c" in name_mapping[1] + and "eta" in name_mapping[1] + ) + + def named_transform(x): + Mc, eta = m1_m2_to_Mc_eta(x["m_1"], x["m_2"]) + return {"M_c": Mc, "eta": eta} + + self.transform_func = named_transform + + def named_inverse_transform(x): + m1, m2 = Mc_eta_to_m1_m2(x["M_c"], x["q"]) + return {"m_1": m1, "m_2": m2} + + self.inverse_transform_func = named_inverse_transform + + +@jaxtyped(typechecker=typechecker) +class MassRatioToSymmetricMassRatioTransform(BijectiveTransform): + """ + Transform mass ratio to symmetric mass ratio + + Parameters + ---------- + name_mapping : tuple[list[str], list[str]] + The name mapping between the input and output dictionary. + + """ + + def __init__( + self, + name_mapping: tuple[list[str], list[str]], + ): + super().__init__(name_mapping) + assert "q" == name_mapping[0][0] and "eta" == name_mapping[1][0] + + self.transform_func = lambda x: {"eta": q_to_eta(x["q"])} + self.inverse_transform_func = lambda x: {"q": eta_to_q(x["eta"])} + -# Pre-made bijective transforms: -ComponentMassesToChirpMassMassRatioTransform = create_bijective_transform( - (["m_1", "m_2"], ["M_c", "q"]), m1_m2_to_Mc_q, Mc_q_to_m1_m2 -) ChirpMassMassRatioToComponentMassesTransform = reverse_bijective_transform( - ComponentMassesToChirpMassMassRatioTransform + ComponentMassesToChirpMassMassRatioTransform(name_mapping=(["m_1", "m_2"], ["M_c", "q"])) ) -ComponentMassesToChirpMassSymmetricMassRatioTransform = create_bijective_transform( - (["m_1", "m_2"], ["M_c", "eta"]), m1_m2_to_Mc_eta, Mc_eta_to_m1_m2 -) ChirpMassSymmetricMassRatioToComponentMassesTransform = reverse_bijective_transform( - ComponentMassesToChirpMassSymmetricMassRatioTransform + ComponentMassesToChirpMassSymmetricMassRatioTransform(name_mapping=(["m_1", "m_2"], ["M_c", "eta"])) ) -MassRatioToSymmetricMassRatioTransform = create_bijective_transform( - (["q"], ["eta"]), q_to_eta, eta_to_q -) SymmetricMassRatioToMassRatioTransform = reverse_bijective_transform( - MassRatioToSymmetricMassRatioTransform + MassRatioToSymmetricMassRatioTransform(name_mapping=(["q"], ["eta"])) ) diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 268bfd5c..768c82c4 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -447,42 +447,6 @@ def __init__( } -def create_bijective_transform( - name_mapping: tuple[list[str], list[str]], - transform_func_array: Callable[[Float], Float], - inverse_transform_func_array: Callable[[Float], Float], -) -> BijectiveTransform: - """ - Utility function to create a BijectiveTransform object given a name_mapping and the forward and backward transform functions which take arrays as input, e.g. coming from the utils module. - - Args: - name_mapping (tuple[list[str], list[str]]): The name_mapping to be used in the named transforms. - transform_func_array (Callable[[Float], Float]): The forward function method taking an array as input. - inverse_transform_func_array (Callable[[Float], Float]): The inverse function method taking an array as input. - - Returns: - BijectiveTransform: The BijectiveTransform object. - """ - - def named_transform_func(x_named: dict[str, Float]) -> dict[str, Float]: - x_array = jnp.array([x_named[key] for key in name_mapping[0]]) - y_array = transform_func_array(*x_array) - y_named = dict(zip(name_mapping[1], jnp.atleast_1d(y_array))) - return y_named - - def named_inverse_transform_func(y_named: dict[str, Float]) -> dict[str, Float]: - y_array = jnp.array([y_named[key] for key in name_mapping[1]]) - x_array = inverse_transform_func_array(*y_array) - x_named = dict(zip(name_mapping[0], jnp.atleast_1d(x_array))) - return x_named - - new_transform = BijectiveTransform(name_mapping) - new_transform.transform_func = named_transform_func - new_transform.inverse_transform_func = named_inverse_transform_func - - return new_transform - - def reverse_bijective_transform( original_transform: BijectiveTransform, ) -> BijectiveTransform: diff --git a/test/integration/test_GW150914_D.py b/test/integration/test_GW150914_D.py index 2bb536a2..9d3ba827 100644 --- a/test/integration/test_GW150914_D.py +++ b/test/integration/test_GW150914_D.py @@ -68,7 +68,7 @@ ) sample_transforms = [ - ComponentMassesToChirpMassMassRatioTransform, + ComponentMassesToChirpMassMassRatioTransform(name_mapping=(["m_1", "m_2"], ["M_c", "q"])), BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=M_c_min, original_upper_bound=M_c_max), BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=q_min, original_upper_bound=q_max), BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), @@ -84,7 +84,7 @@ ] likelihood_transforms = [ - ComponentMassesToChirpMassSymmetricMassRatioTransform, + ComponentMassesToChirpMassSymmetricMassRatioTransform(name_mapping=(["m_1", "m_2"], ["M_c", "eta"])), ] likelihood = TransientLikelihoodFD( diff --git a/test/integration/test_mass_transforms.py b/test/integration/test_mass_transforms.py index f8056d0c..1a85ac0d 100644 --- a/test/integration/test_mass_transforms.py +++ b/test/integration/test_mass_transforms.py @@ -95,7 +95,7 @@ def evaluate(self, params: dict[str, Float], data: dict) -> Float: jim = Jim(likelihood, combine_prior, likelihood_transforms=[mass_transform], - n_chains = 50, + n_chains = 10, parameter_names=['M_c', 'q'], n_loop_training=2, n_loop_production=2, From f0bca2f6dcbe5de38dfc8877131d183d109c8322 Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Wed, 21 Aug 2024 23:38:42 -0700 Subject: [PATCH 203/248] Formatting --- src/jimgw/single_event/transforms.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 31d41dfc..bf43a7d5 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -138,6 +138,7 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform + @jaxtyped(typechecker=typechecker) class ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): """ @@ -235,11 +236,15 @@ def __init__( ChirpMassMassRatioToComponentMassesTransform = reverse_bijective_transform( - ComponentMassesToChirpMassMassRatioTransform(name_mapping=(["m_1", "m_2"], ["M_c", "q"])) + ComponentMassesToChirpMassMassRatioTransform( + name_mapping=(["m_1", "m_2"], ["M_c", "q"]) + ) ) ChirpMassSymmetricMassRatioToComponentMassesTransform = reverse_bijective_transform( - ComponentMassesToChirpMassSymmetricMassRatioTransform(name_mapping=(["m_1", "m_2"], ["M_c", "eta"])) + ComponentMassesToChirpMassSymmetricMassRatioTransform( + name_mapping=(["m_1", "m_2"], ["M_c", "eta"]) + ) ) SymmetricMassRatioToMassRatioTransform = reverse_bijective_transform( From 1d4bb21b82d4b5eb2dcec86c5222ee3111660ba7 Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Tue, 27 Aug 2024 01:21:31 -0700 Subject: [PATCH 204/248] Updated the tests to the new transforms --- test/integration/corner.jpeg | Bin 0 -> 414068 bytes test/integration/diagnostic.jpeg | Bin 0 -> 82859 bytes .../single_event_runrun_manager_summary.txt | 36 ++++++++++++++++++ test/integration/test_GW150914_D.py | 6 +-- .../integration/test_GW150914_D_heterodyne.py | 6 +-- test/integration/test_GW150914_Pv2.py | 4 +- test/integration/test_mass_transforms.py | 5 ++- 7 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 test/integration/corner.jpeg create mode 100644 test/integration/diagnostic.jpeg create mode 100644 test/integration/single_event_runrun_manager_summary.txt diff --git a/test/integration/corner.jpeg b/test/integration/corner.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..08fa8f4eb7ced4f7b6a222d649c850fe7cbf31cc GIT binary patch literal 414068 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:U-8WNe=x?mP)w6k# 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 Pzq=Uz!{0~#`tkn(yPs1z literal 0 HcmV?d00001 diff --git a/test/integration/single_event_runrun_manager_summary.txt b/test/integration/single_event_runrun_manager_summary.txt new file mode 100644 index 00000000..e9782a2a --- /dev/null +++ b/test/integration/single_event_runrun_manager_summary.txt @@ -0,0 +1,36 @@ +Training summary +========== +dec: 1.220 +/- 0.662 +ra: 1.921 +/- 1.016 +psi: 1.620 +/- 0.999 +iota: 1.795 +/- 0.476 +phase_c: 3.246 +/- 1.320 +t_c: -0.025 +/- 0.014 +d_L: 1275.296 +/- 587.710 +s2_z: 0.456 +/- 0.274 +s1_z: -0.647 +/- 0.287 +q: 0.515 +/- 0.104 +M_c: 43.083 +/- 21.050 +Log probability: nan +/- nan +Local acceptance: 0.625 +/- 0.484 +Global acceptance: 0.188 +/- 0.390 +Max loss: 17.043, Min loss: 16.057 +Production summary +========== +dec: 1.214 +/- 0.660 +ra: 1.921 +/- 1.012 +psi: 1.557 +/- 0.996 +iota: 1.739 +/- 0.466 +phase_c: 2.765 +/- 0.979 +t_c: -0.021 +/- 0.013 +d_L: 1253.182 +/- 568.411 +s2_z: 0.425 +/- 0.176 +s1_z: -0.651 +/- 0.273 +q: 0.489 +/- 0.082 +M_c: 42.606 +/- 20.566 +Log probability: nan +/- nan +Local acceptance: 0.750 +/- 0.433 +Global acceptance: 0.062 +/- 0.242 +SNR of detector H1 is 29.763917711763696 +SNR of detector L1 is 51.11730418963541 +network SNR is 59.15124331041875 diff --git a/test/integration/test_GW150914_D.py b/test/integration/test_GW150914_D.py index 9d3ba827..5103e5d8 100644 --- a/test/integration/test_GW150914_D.py +++ b/test/integration/test_GW150914_D.py @@ -68,7 +68,7 @@ ) sample_transforms = [ - ComponentMassesToChirpMassMassRatioTransform(name_mapping=(["m_1", "m_2"], ["M_c", "q"])), + ComponentMassesToChirpMassMassRatioTransform, BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=M_c_min, original_upper_bound=M_c_max), BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=q_min, original_upper_bound=q_max), BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), @@ -78,13 +78,13 @@ BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=0., original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), - SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), + SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), ] likelihood_transforms = [ - ComponentMassesToChirpMassSymmetricMassRatioTransform(name_mapping=(["m_1", "m_2"], ["M_c", "eta"])), + ComponentMassesToChirpMassSymmetricMassRatioTransform, ] likelihood = TransientLikelihoodFD( diff --git a/test/integration/test_GW150914_D_heterodyne.py b/test/integration/test_GW150914_D_heterodyne.py index 6d139dc8..cbac1788 100644 --- a/test/integration/test_GW150914_D_heterodyne.py +++ b/test/integration/test_GW150914_D_heterodyne.py @@ -62,7 +62,7 @@ ) sample_transforms = [ - ComponentMassesToChirpMassMassRatioTransform(name_mapping=[["m_1", "m_2"], ["M_c", "q"]]), + ComponentMassesToChirpMassMassRatioTransform, BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=M_c_min, original_upper_bound=M_c_max), BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=q_min, original_upper_bound=q_max), BoundToUnbound(name_mapping = [["s1_z"], ["s1_z_unbounded"]] , original_lower_bound=-1.0, original_upper_bound=1.0), @@ -72,13 +72,13 @@ BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=0., original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), - SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), + SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), BoundToUnbound(name_mapping = [["zenith"], ["zenith_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = [["azimuth"], ["azimuth_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), ] likelihood_transforms = [ - ComponentMassesToChirpMassSymmetricMassRatioTransform(name_mapping=[["m_1", "m_2"], ["M_c", "eta"]]), + ComponentMassesToChirpMassSymmetricMassRatioTransform, ] likelihood = HeterodynedTransientLikelihoodFD( diff --git a/test/integration/test_GW150914_Pv2.py b/test/integration/test_GW150914_Pv2.py index 3d8e9f20..9892058d 100644 --- a/test/integration/test_GW150914_Pv2.py +++ b/test/integration/test_GW150914_Pv2.py @@ -89,8 +89,8 @@ ] likelihood_transforms = [ - SpinToCartesianSpinTransform(name_mapping=[["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a_1", "a_2"], ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"]], freq_ref=20.0), - MassRatioToSymmetricMassRatioTransform(name_mapping=[["q"], ["eta"]]), + SpinToCartesianSpinTransform(freq_ref=20.0), + MassRatioToSymmetricMassRatioTransform, ] likelihood = TransientLikelihoodFD( diff --git a/test/integration/test_mass_transforms.py b/test/integration/test_mass_transforms.py index 1a85ac0d..65b95244 100644 --- a/test/integration/test_mass_transforms.py +++ b/test/integration/test_mass_transforms.py @@ -1,5 +1,5 @@ import os -os.environ["CUDA_VISIBLE_DEVICES"] = "2" +os.environ["CUDA_VISIBLE_DEVICES"] = "3" os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = "0.10" import numpy as np @@ -85,7 +85,8 @@ def evaluate(self, params: dict[str, Float], data: dict) -> Float: likelihood = MyLikelihood(true_m1, true_m2) mass_transform = ChirpMassMassRatioToComponentMassesTransform -print(mass_transform.name_mapping) +print("Checking mass_transform repr") +print(repr(mass_transform)) # Other stuff we have to give to Jim to make it work step = 5e-3 From 95bc462d67d641ed61df918c99b6f3d006b4c43c Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Tue, 27 Aug 2024 01:22:03 -0700 Subject: [PATCH 205/248] New transforms --- src/jimgw/single_event/transforms.py | 125 ++++++--------------------- src/jimgw/transforms.py | 1 + 2 files changed, 29 insertions(+), 97 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index bf43a7d5..df193d68 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -33,30 +33,16 @@ class SpinToCartesianSpinTransform(NtoNTransform): def __init__( self, - name_mapping: tuple[list[str], list[str]], freq_ref: Float, ): + name_mapping = ( + ["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a_1", "a_2"], + ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"] + ) super().__init__(name_mapping) self.freq_ref = freq_ref - assert ( - "theta_jn" in name_mapping[0] - and "phi_jl" in name_mapping[0] - and "theta_1" in name_mapping[0] - and "theta_2" in name_mapping[0] - and "phi_12" in name_mapping[0] - and "a_1" in name_mapping[0] - and "a_2" in name_mapping[0] - and "iota" in name_mapping[1] - and "s1_x" in name_mapping[1] - and "s1_y" in name_mapping[1] - and "s1_z" in name_mapping[1] - and "s2_x" in name_mapping[1] - and "s2_y" in name_mapping[1] - and "s2_z" in name_mapping[1] - ) - def named_transform(x): iota, s1x, s1y, s1z, s2x, s2y, s2z = spin_to_cartesian_spin( x["theta_jn"], @@ -82,18 +68,13 @@ def named_transform(x): } self.transform_func = named_transform + @jaxtyped(typechecker=typechecker) class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): """ Transform sky frame to detector frame sky position - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - """ gmst: Float @@ -102,10 +83,10 @@ class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): def __init__( self, - name_mapping: tuple[list[str], list[str]], gps_time: Float, ifos: GroundBased2G, ): + name_mapping = (["ra", "dec"], ["zenith", "azimuth"]) super().__init__(name_mapping) self.gmst = ( @@ -115,13 +96,6 @@ def __init__( self.rotation = euler_rotation(delta_x) self.rotation_inv = jnp.linalg.inv(self.rotation) - assert ( - "ra" in name_mapping[0] - and "dec" in name_mapping[0] - and "zenith" in name_mapping[1] - and "azimuth" in name_mapping[1] - ) - def named_transform(x): zenith, azimuth = ra_dec_to_zenith_azimuth( x["ra"], x["dec"], self.gmst, self.rotation @@ -140,27 +114,14 @@ def named_inverse_transform(x): @jaxtyped(typechecker=typechecker) -class ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): +class _ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): """ - Transform chirp mass and mass ratio to component masses - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. + Transform chirp mass and mass ratio to component masses. Instantiated to object below. """ - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): + def __init__(self): + name_mapping = (["m_1", "m_2"], ["M_c", "q"]) super().__init__(name_mapping) - assert ( - "m_1" in name_mapping[0] - and "m_2" in name_mapping[0] - and "M_c" in name_mapping[1] - and "q" in name_mapping[1] - ) def named_transform(x): Mc, q = m1_m2_to_Mc_q(x["m_1"], x["m_2"]) @@ -174,30 +135,16 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform - @jaxtyped(typechecker=typechecker) -class ComponentMassesToChirpMassSymmetricMassRatioTransform(BijectiveTransform): +class _ComponentMassesToChirpMassSymmetricMassRatioTransform(BijectiveTransform): """ - Transform mass ratio to symmetric mass ratio - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - + Transform mass ratio to symmetric mass ratio. Instantiated to object below. """ - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): + def __init__(self): + name_mapping = (["m_1", "m_2"], ["M_c", "eta"]) super().__init__(name_mapping) - assert ( - "m_1" in name_mapping[0] - and "m_2" in name_mapping[0] - and "M_c" in name_mapping[1] - and "eta" in name_mapping[1] - ) + def named_transform(x): Mc, eta = m1_m2_to_Mc_eta(x["m_1"], x["m_2"]) @@ -211,42 +158,26 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform - @jaxtyped(typechecker=typechecker) -class MassRatioToSymmetricMassRatioTransform(BijectiveTransform): +class _MassRatioToSymmetricMassRatioTransform(BijectiveTransform): """ - Transform mass ratio to symmetric mass ratio - - Parameters - ---------- - name_mapping : tuple[list[str], list[str]] - The name mapping between the input and output dictionary. - + Transform mass ratio to symmetric mass ratio. Instantiated to object below. """ - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - ): + def __init__(self): + name_mapping = (["q"], ["eta"]) super().__init__(name_mapping) - assert "q" == name_mapping[0][0] and "eta" == name_mapping[1][0] self.transform_func = lambda x: {"eta": q_to_eta(x["q"])} self.inverse_transform_func = lambda x: {"q": eta_to_q(x["eta"])} + + def __repr__(self): + return f"{self.__class__.__name__[1:]}()" +ComponentMassesToChirpMassMassRatioTransform = _ComponentMassesToChirpMassMassRatioTransform() +ComponentMassesToChirpMassSymmetricMassRatioTransform = _ComponentMassesToChirpMassSymmetricMassRatioTransform() +MassRatioToSymmetricMassRatioTransform = _MassRatioToSymmetricMassRatioTransform() -ChirpMassMassRatioToComponentMassesTransform = reverse_bijective_transform( - ComponentMassesToChirpMassMassRatioTransform( - name_mapping=(["m_1", "m_2"], ["M_c", "q"]) - ) -) - -ChirpMassSymmetricMassRatioToComponentMassesTransform = reverse_bijective_transform( - ComponentMassesToChirpMassSymmetricMassRatioTransform( - name_mapping=(["m_1", "m_2"], ["M_c", "eta"]) - ) -) - -SymmetricMassRatioToMassRatioTransform = reverse_bijective_transform( - MassRatioToSymmetricMassRatioTransform(name_mapping=(["q"], ["eta"])) -) +ChirpMassMassRatioToComponentMassesTransform = reverse_bijective_transform(ComponentMassesToChirpMassMassRatioTransform) +ChirpMassSymmetricMassRatioToComponentMassesTransform = reverse_bijective_transform(ComponentMassesToChirpMassSymmetricMassRatioTransform) +SymmetricMassRatioToMassRatioTransform = reverse_bijective_transform(MassRatioToSymmetricMassRatioTransform) \ No newline at end of file diff --git a/src/jimgw/transforms.py b/src/jimgw/transforms.py index 768c82c4..915802a0 100644 --- a/src/jimgw/transforms.py +++ b/src/jimgw/transforms.py @@ -458,5 +458,6 @@ def reverse_bijective_transform( reversed_transform = BijectiveTransform(name_mapping=reversed_name_mapping) reversed_transform.transform_func = original_transform.inverse_transform_func reversed_transform.inverse_transform_func = original_transform.transform_func + reversed_transform.__repr__ = lambda: f"Reversed{repr(original_transform)}" return reversed_transform From b3898e1b37c74b971dd4d9d2c32fb442db8cb72f Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Tue, 27 Aug 2024 01:23:01 -0700 Subject: [PATCH 206/248] precommit --- src/jimgw/single_event/transforms.py | 29 +++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index df193d68..832c84a9 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -37,7 +37,7 @@ def __init__( ): name_mapping = ( ["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a_1", "a_2"], - ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"] + ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"], ) super().__init__(name_mapping) @@ -68,7 +68,6 @@ def named_transform(x): } self.transform_func = named_transform - @jaxtyped(typechecker=typechecker) @@ -135,6 +134,7 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform + @jaxtyped(typechecker=typechecker) class _ComponentMassesToChirpMassSymmetricMassRatioTransform(BijectiveTransform): """ @@ -145,7 +145,6 @@ def __init__(self): name_mapping = (["m_1", "m_2"], ["M_c", "eta"]) super().__init__(name_mapping) - def named_transform(x): Mc, eta = m1_m2_to_Mc_eta(x["m_1"], x["m_2"]) return {"M_c": Mc, "eta": eta} @@ -158,6 +157,7 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform + @jaxtyped(typechecker=typechecker) class _MassRatioToSymmetricMassRatioTransform(BijectiveTransform): """ @@ -170,14 +170,25 @@ def __init__(self): self.transform_func = lambda x: {"eta": q_to_eta(x["q"])} self.inverse_transform_func = lambda x: {"q": eta_to_q(x["eta"])} - + def __repr__(self): return f"{self.__class__.__name__[1:]}()" -ComponentMassesToChirpMassMassRatioTransform = _ComponentMassesToChirpMassMassRatioTransform() -ComponentMassesToChirpMassSymmetricMassRatioTransform = _ComponentMassesToChirpMassSymmetricMassRatioTransform() + +ComponentMassesToChirpMassMassRatioTransform = ( + _ComponentMassesToChirpMassMassRatioTransform() +) +ComponentMassesToChirpMassSymmetricMassRatioTransform = ( + _ComponentMassesToChirpMassSymmetricMassRatioTransform() +) MassRatioToSymmetricMassRatioTransform = _MassRatioToSymmetricMassRatioTransform() -ChirpMassMassRatioToComponentMassesTransform = reverse_bijective_transform(ComponentMassesToChirpMassMassRatioTransform) -ChirpMassSymmetricMassRatioToComponentMassesTransform = reverse_bijective_transform(ComponentMassesToChirpMassSymmetricMassRatioTransform) -SymmetricMassRatioToMassRatioTransform = reverse_bijective_transform(MassRatioToSymmetricMassRatioTransform) \ No newline at end of file +ChirpMassMassRatioToComponentMassesTransform = reverse_bijective_transform( + ComponentMassesToChirpMassMassRatioTransform +) +ChirpMassSymmetricMassRatioToComponentMassesTransform = reverse_bijective_transform( + ComponentMassesToChirpMassSymmetricMassRatioTransform +) +SymmetricMassRatioToMassRatioTransform = reverse_bijective_transform( + MassRatioToSymmetricMassRatioTransform +) From 45ebe76f163990a5812c4b9d192a341e92ed7e20 Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Wed, 28 Aug 2024 11:53:28 +0200 Subject: [PATCH 207/248] Delete test/integration/corner.jpeg --- test/integration/corner.jpeg | Bin 414068 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/integration/corner.jpeg diff --git a/test/integration/corner.jpeg b/test/integration/corner.jpeg deleted file mode 100644 index 08fa8f4eb7ced4f7b6a222d649c850fe7cbf31cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 414068 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 Date: Wed, 28 Aug 2024 11:53:40 +0200 Subject: [PATCH 208/248] Delete test/integration/diagnostic.jpeg --- test/integration/diagnostic.jpeg | Bin 82859 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/integration/diagnostic.jpeg diff --git a/test/integration/diagnostic.jpeg b/test/integration/diagnostic.jpeg deleted file mode 100644 index e7db0ca7d40290cb2886d50c45a10ec671789868..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82859 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:U-8WNe=x?mP)w6k# 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 Pzq=Uz!{0~#`tkn(yPs1z From 98e9c0b8d99daa6c894e465299b67703b6543c10 Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Wed, 28 Aug 2024 11:53:51 +0200 Subject: [PATCH 209/248] Delete test/integration/single_event_runrun_manager_summary.txt --- .../single_event_runrun_manager_summary.txt | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 test/integration/single_event_runrun_manager_summary.txt diff --git a/test/integration/single_event_runrun_manager_summary.txt b/test/integration/single_event_runrun_manager_summary.txt deleted file mode 100644 index e9782a2a..00000000 --- a/test/integration/single_event_runrun_manager_summary.txt +++ /dev/null @@ -1,36 +0,0 @@ -Training summary -========== -dec: 1.220 +/- 0.662 -ra: 1.921 +/- 1.016 -psi: 1.620 +/- 0.999 -iota: 1.795 +/- 0.476 -phase_c: 3.246 +/- 1.320 -t_c: -0.025 +/- 0.014 -d_L: 1275.296 +/- 587.710 -s2_z: 0.456 +/- 0.274 -s1_z: -0.647 +/- 0.287 -q: 0.515 +/- 0.104 -M_c: 43.083 +/- 21.050 -Log probability: nan +/- nan -Local acceptance: 0.625 +/- 0.484 -Global acceptance: 0.188 +/- 0.390 -Max loss: 17.043, Min loss: 16.057 -Production summary -========== -dec: 1.214 +/- 0.660 -ra: 1.921 +/- 1.012 -psi: 1.557 +/- 0.996 -iota: 1.739 +/- 0.466 -phase_c: 2.765 +/- 0.979 -t_c: -0.021 +/- 0.013 -d_L: 1253.182 +/- 568.411 -s2_z: 0.425 +/- 0.176 -s1_z: -0.651 +/- 0.273 -q: 0.489 +/- 0.082 -M_c: 42.606 +/- 20.566 -Log probability: nan +/- nan -Local acceptance: 0.750 +/- 0.433 -Global acceptance: 0.062 +/- 0.242 -SNR of detector H1 is 29.763917711763696 -SNR of detector L1 is 51.11730418963541 -network SNR is 59.15124331041875 From ad90d099c0b2e41f3f743e0a66d4ad93354eaa1e Mon Sep 17 00:00:00 2001 From: Thibeau Wouters Date: Mon, 2 Sep 2024 06:55:51 -0700 Subject: [PATCH 210/248] Further consolidation as requested --- src/jimgw/single_event/transforms.py | 101 ++++++++------------------- 1 file changed, 31 insertions(+), 70 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 832c84a9..cb60e98b 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -111,77 +111,38 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform +def named_m1_m2_to_Mc_q(x): + Mc, q = m1_m2_to_Mc_q(x["m_1"], x["m_2"]) + return {"M_c": Mc, "q": q} + +def named_Mc_q_to_m1_m2(x): + m1, m2 = Mc_q_to_m1_m2(x["M_c"], x["q"]) + return {"m_1": m1, "m_2": m2} + +ComponentMassesToChirpMassMassRatioTransform = BijectiveTransform((["m_1", "m_2"], ["M_c", "q"])) +ComponentMassesToChirpMassMassRatioTransform.transform_func = named_m1_m2_to_Mc_q +ComponentMassesToChirpMassMassRatioTransform.inverse_transform_func = named_Mc_q_to_m1_m2 + +def named_m1_m2_to_Mc_eta(x): + Mc, eta = m1_m2_to_Mc_eta(x["m_1"], x["m_2"]) + return {"M_c": Mc, "eta": eta} + +def named_Mc_eta_to_m1_m2(x): + m1, m2 = Mc_eta_to_m1_m2(x["M_c"], x["eta"]) + return {"m_1": m1, "m_2": m2} + +ComponentMassesToChirpMassSymmetricMassRatioTransform = BijectiveTransform((["m_1", "m_2"], ["M_c", "eta"])) +ComponentMassesToChirpMassSymmetricMassRatioTransform.transform_func = named_m1_m2_to_Mc_eta +ComponentMassesToChirpMassSymmetricMassRatioTransform.inverse_transform_func = named_Mc_eta_to_m1_m2 + +def named_q_to_eta(x): + return {"eta": q_to_eta(x["q"])} +def named_eta_to_q(x): + return {"q": eta_to_q(x["eta"])} +MassRatioToSymmetricMassRatioTransform = BijectiveTransform((["q"], ["eta"])) +MassRatioToSymmetricMassRatioTransform.transform_func = named_q_to_eta +MassRatioToSymmetricMassRatioTransform.inverse_transform_func = named_eta_to_q -@jaxtyped(typechecker=typechecker) -class _ComponentMassesToChirpMassMassRatioTransform(BijectiveTransform): - """ - Transform chirp mass and mass ratio to component masses. Instantiated to object below. - """ - - def __init__(self): - name_mapping = (["m_1", "m_2"], ["M_c", "q"]) - super().__init__(name_mapping) - - def named_transform(x): - Mc, q = m1_m2_to_Mc_q(x["m_1"], x["m_2"]) - return {"M_c": Mc, "q": q} - - self.transform_func = named_transform - - def named_inverse_transform(x): - m1, m2 = Mc_q_to_m1_m2(x["M_c"], x["q"]) - return {"m_1": m1, "m_2": m2} - - self.inverse_transform_func = named_inverse_transform - - -@jaxtyped(typechecker=typechecker) -class _ComponentMassesToChirpMassSymmetricMassRatioTransform(BijectiveTransform): - """ - Transform mass ratio to symmetric mass ratio. Instantiated to object below. - """ - - def __init__(self): - name_mapping = (["m_1", "m_2"], ["M_c", "eta"]) - super().__init__(name_mapping) - - def named_transform(x): - Mc, eta = m1_m2_to_Mc_eta(x["m_1"], x["m_2"]) - return {"M_c": Mc, "eta": eta} - - self.transform_func = named_transform - - def named_inverse_transform(x): - m1, m2 = Mc_eta_to_m1_m2(x["M_c"], x["q"]) - return {"m_1": m1, "m_2": m2} - - self.inverse_transform_func = named_inverse_transform - - -@jaxtyped(typechecker=typechecker) -class _MassRatioToSymmetricMassRatioTransform(BijectiveTransform): - """ - Transform mass ratio to symmetric mass ratio. Instantiated to object below. - """ - - def __init__(self): - name_mapping = (["q"], ["eta"]) - super().__init__(name_mapping) - - self.transform_func = lambda x: {"eta": q_to_eta(x["q"])} - self.inverse_transform_func = lambda x: {"q": eta_to_q(x["eta"])} - - def __repr__(self): - return f"{self.__class__.__name__[1:]}()" - - -ComponentMassesToChirpMassMassRatioTransform = ( - _ComponentMassesToChirpMassMassRatioTransform() -) -ComponentMassesToChirpMassSymmetricMassRatioTransform = ( - _ComponentMassesToChirpMassSymmetricMassRatioTransform() -) -MassRatioToSymmetricMassRatioTransform = _MassRatioToSymmetricMassRatioTransform() ChirpMassMassRatioToComponentMassesTransform = reverse_bijective_transform( ComponentMassesToChirpMassMassRatioTransform From 7ed4c54efab1e05ae3efbee2362cb491a520acfa Mon Sep 17 00:00:00 2001 From: kazewong Date: Mon, 2 Sep 2024 14:40:51 -0400 Subject: [PATCH 211/248] Move transform test into unit. Note that the current one will fail because of bilby import --- test/{ => unit}/test_transform.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{ => unit}/test_transform.py (100%) diff --git a/test/test_transform.py b/test/unit/test_transform.py similarity index 100% rename from test/test_transform.py rename to test/unit/test_transform.py From 93ea48518182c4cbb0fad99e5e21498790b98dc8 Mon Sep 17 00:00:00 2001 From: kazewong Date: Mon, 2 Sep 2024 15:22:02 -0400 Subject: [PATCH 212/248] Putting transform test in backburner --- test/unit/test_transform.py | 92 ++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/test/unit/test_transform.py b/test/unit/test_transform.py index 5d9fe464..14162f79 100644 --- a/test/unit/test_transform.py +++ b/test/unit/test_transform.py @@ -1,48 +1,48 @@ -import numpy as np -import jax.numpy as jnp +# import numpy as np +# import jax.numpy as jnp -class TestTransform: - def test_sky_location_transform(self): - from bilby.gw.utils import zenith_azimuth_to_ra_dec as bilby_earth_to_sky - from bilby.gw.detector.networks import InterferometerList +# class TestTransform: +# def test_sky_location_transform(self): +# from bilby.gw.utils import zenith_azimuth_to_ra_dec as bilby_earth_to_sky +# from bilby.gw.detector.networks import InterferometerList - from jimgw.single_event.utils import zenith_azimuth_to_ra_dec as jimgw_earth_to_sky - from jimgw.single_event.detector import detector_preset - from astropy.time import Time - - ifos = ["H1", "L1"] - geocent_time = 1000000000 - - import matplotlib.pyplot as plt - - for zenith in np.linspace(0, np.pi, 10): - for azimuth in np.linspace(0, 2*np.pi, 10): - bilby_sky_location = np.array(bilby_earth_to_sky(zenith, azimuth, geocent_time, InterferometerList(ifos))) - jimgw_sky_location = np.array(jimgw_earth_to_sky(zenith, azimuth, Time(geocent_time, format="gps").sidereal_time("apparent", "greenwich").rad, detector_preset[ifos[0]].vertex - detector_preset[ifos[1]].vertex)) - assert np.allclose(bilby_sky_location, jimgw_sky_location, atol=1e-4) - - def test_spin_transform(self): - from bilby.gw.conversion import bilby_to_lalsimulation_spins as bilby_spin_transform - from bilby.gw.conversion import symmetric_mass_ratio_to_mass_ratio, chirp_mass_and_mass_ratio_to_component_masses - - from jimgw.single_event.utils import spin_to_cartesian_spin as jimgw_spin_transform - - for _ in range(100): - thetaJN = jnp.array(np.random.uniform(0, np.pi)) - phiJL = jnp.array(np.random.uniform(0, np.pi)) - theta1 = jnp.array(np.random.uniform(0, np.pi)) - theta2 = jnp.array(np.random.uniform(0, np.pi)) - phi12 = jnp.array(np.random.uniform(0, np.pi)) - chi1 = jnp.array(np.random.uniform(0, 1)) - chi2 = jnp.array(np.random.uniform(0, 1)) - M_c = jnp.array(np.random.uniform(1, 100)) - eta = jnp.array(np.random.uniform(0.1, 0.25)) - fRef = jnp.array(np.random.uniform(10, 1000)) - phiRef = jnp.array(np.random.uniform(0, 2*np.pi)) - - q = symmetric_mass_ratio_to_mass_ratio(eta) - m1, m2 = chirp_mass_and_mass_ratio_to_component_masses(M_c, q) - MsunInkg = 1.9884e30 - bilby_spin = jnp.array(bilby_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, m1*MsunInkg, m2*MsunInkg, fRef, phiRef)) - jimgw_spin = jnp.array(jimgw_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, M_c, eta, fRef, phiRef)) - assert np.allclose(bilby_spin, jimgw_spin, atol=1e-4) +# from jimgw.single_event.utils import zenith_azimuth_to_ra_dec as jimgw_earth_to_sky +# from jimgw.single_event.detector import detector_preset +# from astropy.time import Time + +# ifos = ["H1", "L1"] +# geocent_time = 1000000000 + +# import matplotlib.pyplot as plt + +# for zenith in np.linspace(0, np.pi, 10): +# for azimuth in np.linspace(0, 2*np.pi, 10): +# bilby_sky_location = np.array(bilby_earth_to_sky(zenith, azimuth, geocent_time, InterferometerList(ifos))) +# jimgw_sky_location = np.array(jimgw_earth_to_sky(zenith, azimuth, Time(geocent_time, format="gps").sidereal_time("apparent", "greenwich").rad, detector_preset[ifos[0]].vertex - detector_preset[ifos[1]].vertex)) +# assert np.allclose(bilby_sky_location, jimgw_sky_location, atol=1e-4) + +# def test_spin_transform(self): +# from bilby.gw.conversion import bilby_to_lalsimulation_spins as bilby_spin_transform +# from bilby.gw.conversion import symmetric_mass_ratio_to_mass_ratio, chirp_mass_and_mass_ratio_to_component_masses + +# from jimgw.single_event.utils import spin_to_cartesian_spin as jimgw_spin_transform + +# for _ in range(100): +# thetaJN = jnp.array(np.random.uniform(0, np.pi)) +# phiJL = jnp.array(np.random.uniform(0, np.pi)) +# theta1 = jnp.array(np.random.uniform(0, np.pi)) +# theta2 = jnp.array(np.random.uniform(0, np.pi)) +# phi12 = jnp.array(np.random.uniform(0, np.pi)) +# chi1 = jnp.array(np.random.uniform(0, 1)) +# chi2 = jnp.array(np.random.uniform(0, 1)) +# M_c = jnp.array(np.random.uniform(1, 100)) +# eta = jnp.array(np.random.uniform(0.1, 0.25)) +# fRef = jnp.array(np.random.uniform(10, 1000)) +# phiRef = jnp.array(np.random.uniform(0, 2*np.pi)) + +# q = symmetric_mass_ratio_to_mass_ratio(eta) +# m1, m2 = chirp_mass_and_mass_ratio_to_component_masses(M_c, q) +# MsunInkg = 1.9884e30 +# bilby_spin = jnp.array(bilby_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, m1*MsunInkg, m2*MsunInkg, fRef, phiRef)) +# jimgw_spin = jnp.array(jimgw_spin_transform(thetaJN, phiJL, theta1, theta2, phi12, chi1, chi2, M_c, eta, fRef, phiRef)) +# assert np.allclose(bilby_spin, jimgw_spin, atol=1e-4) From 93b648317074bf07b54dd8ca1fa9d9d2d54a734e Mon Sep 17 00:00:00 2001 From: kazewong Date: Tue, 10 Sep 2024 15:30:42 -0400 Subject: [PATCH 213/248] Updating initial position sampling. Also changing GW150914_IMRPhenomD.py --- example/GW150914.py | 138 --------------------------------- example/GW150914_IMRPhenomD.py | 133 +++++++++++++++++++++++++++++++ src/jimgw/jim.py | 25 +++--- 3 files changed, 146 insertions(+), 150 deletions(-) delete mode 100644 example/GW150914.py create mode 100644 example/GW150914_IMRPhenomD.py diff --git a/example/GW150914.py b/example/GW150914.py deleted file mode 100644 index 559b5b7c..00000000 --- a/example/GW150914.py +++ /dev/null @@ -1,138 +0,0 @@ -import time - -import jax -import jax.numpy as jnp - -from jimgw.jim import Jim -from jimgw.prior import Composite, Unconstrained_Uniform -from jimgw.single_event.detector import H1, L1 -from jimgw.single_event.likelihood import TransientLikelihoodFD -from jimgw.single_event.waveform import RippleIMRPhenomD -from flowMC.strategy.optimization import optimization_Adam - -jax.config.update("jax_enable_x64", True) - -########################################### -########## First we grab data ############# -########################################### - -total_time_start = time.time() - -# first, fetch a 4s segment centered on GW150914 -gps = 1126259462.4 -duration = 4 -post_trigger_duration = 2 -start_pad = duration - post_trigger_duration -end_pad = post_trigger_duration -fmin = 20.0 -fmax = 1024.0 - -ifos = ["H1", "L1"] - -H1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) -L1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) - -Mc_prior = Unconstrained_Uniform(10.0, 80.0, naming=["M_c"]) -q_prior = Unconstrained_Uniform( - 0.125, - 1.0, - naming=["q"], - transforms={"q": ("eta", lambda params: params["q"] / (1 + params["q"]) ** 2)}, -) -s1z_prior = Unconstrained_Uniform(-1.0, 1.0, naming=["s1_z"]) -s2z_prior = Unconstrained_Uniform(-1.0, 1.0, naming=["s2_z"]) -dL_prior = Unconstrained_Uniform(0.0, 2000.0, naming=["d_L"]) -t_c_prior = Unconstrained_Uniform(-0.05, 0.05, naming=["t_c"]) -phase_c_prior = Unconstrained_Uniform(0.0, 2 * jnp.pi, naming=["phase_c"]) -cos_iota_prior = Unconstrained_Uniform( - -1.0, - 1.0, - naming=["cos_iota"], - transforms={ - "cos_iota": ( - "iota", - lambda params: jnp.arccos( - jnp.arcsin(jnp.sin(params["cos_iota"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) -psi_prior = Unconstrained_Uniform(0.0, jnp.pi, naming=["psi"]) -ra_prior = Unconstrained_Uniform(0.0, 2 * jnp.pi, naming=["ra"]) -sin_dec_prior = Unconstrained_Uniform( - -1.0, - 1.0, - naming=["sin_dec"], - transforms={ - "sin_dec": ( - "dec", - lambda params: jnp.arcsin( - jnp.arcsin(jnp.sin(params["sin_dec"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) - -prior = Composite( - [ - Mc_prior, - q_prior, - s1z_prior, - s2z_prior, - dL_prior, - t_c_prior, - phase_c_prior, - cos_iota_prior, - psi_prior, - ra_prior, - sin_dec_prior, - ] -) -likelihood = TransientLikelihoodFD( - [H1, L1], - waveform=RippleIMRPhenomD(), - trigger_time=gps, - duration=4, - post_trigger_duration=2, -) - - -mass_matrix = jnp.eye(11) -mass_matrix = mass_matrix.at[1, 1].set(1e-3) -mass_matrix = mass_matrix.at[5, 5].set(1e-3) -local_sampler_arg = {"step_size": mass_matrix * 3e-3} - -Adam_optimizer = optimization_Adam(n_steps=3000, learning_rate=0.01, noise_level=1) - -import optax -n_epochs = 20 -n_loop_training = 100 -total_epochs = n_epochs * n_loop_training -start = total_epochs//10 -learning_rate = optax.polynomial_schedule( - 1e-3, 1e-4, 4.0, total_epochs - start, transition_begin=start -) - - -jim = Jim( - likelihood, - prior, - n_loop_training=n_loop_training, - n_loop_production=20, - n_local_steps=10, - n_global_steps=1000, - n_chains=500, - n_epochs=n_epochs, - learning_rate=learning_rate, - n_max_examples=30000, - n_flow_samples=100000, - momentum=0.9, - batch_size=30000, - use_global=True, - train_thinning=1, - output_thinning=10, - local_sampler_arg=local_sampler_arg, - strategies=[Adam_optimizer,"default"], -) - -jim.sample(jax.random.PRNGKey(42)) diff --git a/example/GW150914_IMRPhenomD.py b/example/GW150914_IMRPhenomD.py new file mode 100644 index 00000000..7a7a37b3 --- /dev/null +++ b/example/GW150914_IMRPhenomD.py @@ -0,0 +1,133 @@ +import jax +import jax.numpy as jnp + +from jimgw.jim import Jim +from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior +from jimgw.single_event.detector import H1, L1 +from jimgw.single_event.likelihood import TransientLikelihoodFD +from jimgw.single_event.waveform import RippleIMRPhenomD +from jimgw.transforms import BoundToUnbound +from jimgw.single_event.transforms import ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, ComponentMassesToChirpMassMassRatioTransform +from jimgw.single_event.utils import Mc_q_to_m1_m2 +from flowMC.strategy.optimization import optimization_Adam + +jax.config.update("jax_enable_x64", True) + +########################################### +########## First we grab data ############# +########################################### + +# first, fetch a 4s segment centered on GW150914 +gps = 1126259462.4 +duration = 4 +post_trigger_duration = 2 +start_pad = duration - post_trigger_duration +end_pad = post_trigger_duration +fmin = 20.0 +fmax = 1024.0 + +ifos = [H1, L1] + +for ifo in ifos: + ifo.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) + +M_c_min, M_c_max = 10.0, 80.0 +eta_min, eta_max = 0.2, 0.25 +# m_1_prior = UniformPrior(Mc_q_to_m1_m2(M_c_min, q_max)[0], Mc_q_to_m1_m2(M_c_max, q_min)[0], parameter_names=["m_1"]) +# m_2_prior = UniformPrior(Mc_q_to_m1_m2(M_c_min, q_min)[1], Mc_q_to_m1_m2(M_c_max, q_max)[1], parameter_names=["m_2"]) +Mc_prior = UniformPrior(M_c_min, M_c_max, parameter_names=["M_c"]) +eta_prior = UniformPrior(eta_min, eta_max, parameter_names=["eta"]) +s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) +s2z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) +dL_prior = PowerLawPrior(1.0, 2000.0, 2.0, parameter_names=["d_L"]) +t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) +phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) +iota_prior = SinePrior(parameter_names=["iota"]) +psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) +ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) +dec_prior = CosinePrior(parameter_names=["dec"]) + +prior = CombinePrior( + [ + Mc_prior, + eta_prior, + s1z_prior, + s2z_prior, + dL_prior, + t_c_prior, + phase_c_prior, + iota_prior, + psi_prior, + ra_prior, + dec_prior, + ] +) + +sample_transforms = [ + # ComponentMassesToChirpMassMassRatioTransform, + BoundToUnbound(name_mapping = (["M_c"], ["M_c_unbounded"]), original_lower_bound=M_c_min, original_upper_bound=M_c_max), + BoundToUnbound(name_mapping = (["eta"], ["eta_unbounded"]), original_lower_bound=eta_min, original_upper_bound=eta_max), + BoundToUnbound(name_mapping = (["s1_z"], ["s1_z_unbounded"]) , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = (["s2_z"], ["s2_z_unbounded"]) , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = (["d_L"], ["d_L_unbounded"]) , original_lower_bound=1.0, original_upper_bound=2000.0), + BoundToUnbound(name_mapping = (["t_c"], ["t_c_unbounded"]) , original_lower_bound=-0.05, original_upper_bound=0.05), + BoundToUnbound(name_mapping = (["phase_c"], ["phase_c_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["iota"], ["iota_unbounded"]), original_lower_bound=0., original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["psi"], ["psi_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), + BoundToUnbound(name_mapping = (["zenith"], ["zenith_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["azimuth"], ["azimuth_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), +] + +likelihood_transforms = [ + # ComponentMassesToChirpMassSymmetricMassRatioTransform, +] + +likelihood = TransientLikelihoodFD( + ifos, + waveform=RippleIMRPhenomD(), + trigger_time=gps, + duration=4, + post_trigger_duration=2, +) + + +mass_matrix = jnp.eye(11) +mass_matrix = mass_matrix.at[1, 1].set(1e-3) +mass_matrix = mass_matrix.at[5, 5].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 3e-3} + +Adam_optimizer = optimization_Adam(n_steps=3000, learning_rate=0.01, noise_level=1) + +n_epochs = 30 +n_loop_training = 20 +learning_rate = 1e-4 + + +jim = Jim( + likelihood, + prior, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, + n_loop_training=n_loop_training, + n_loop_production=20, + n_local_steps=10, + n_global_steps=1000, + n_chains=500, + n_epochs=n_epochs, + learning_rate=learning_rate, + n_max_examples=30000, + n_flow_samples=100000, + momentum=0.9, + batch_size=30000, + use_global=True, + train_thinning=1, + output_thinning=10, + local_sampler_arg=local_sampler_arg, + strategies=[Adam_optimizer, "default"], + verbose=True +) + +jim.sample(jax.random.PRNGKey(42)) +# jim.get_samples() +# jim.print_summary() \ No newline at end of file diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index fae0bc98..8e6bb0bc 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -104,18 +104,19 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): def sample(self, key: PRNGKeyArray, initial_position: Array = jnp.array([])): if initial_position.size == 0: - initial_guess = [] - for _ in range(self.sampler.n_chains): - flag = True - while flag: - key = jax.random.split(key)[1] - guess = self.prior.sample(key, 1) - for transform in self.sample_transforms: - guess = transform.forward(guess) - guess = jnp.array([i for i in guess.values()]).T[0] - flag = not jnp.all(jnp.isfinite(guess)) - initial_guess.append(guess) - initial_position = jnp.array(initial_guess) + initial_position = jnp.zeros((self.sampler.n_chains, self.prior.n_dim)) + jnp.nan + + while not jax.tree.reduce(jnp.logical_and, jax.tree.map(lambda x: jnp.isfinite(x), initial_position)).all(): + non_finite_index = jnp.any(~jax.tree.reduce(jnp.logical_and, jax.tree.map(lambda x: jnp.isfinite(x), initial_position)),axis=1) + + key, subkey = jax.random.split(key) + guess = self.prior.sample(subkey, self.sampler.n_chains) + for transform in self.sample_transforms: + guess = jax.vmap(transform.forward)(guess) + guess = jnp.array(jax.tree.leaves({key: guess[key] for key in self.parameter_names})).T + finite_guess = jnp.where(jnp.all(jax.tree.map(lambda x: jnp.isfinite(x), guess),axis=1))[0] + common_length = min(len(finite_guess), len(non_finite_index)) + initial_position = initial_position.at[non_finite_index[:common_length]].set(guess[:common_length]) self.sampler.sample(initial_position, None) # type: ignore def maximize_likelihood( From ff12e0b50267e8b72a9c875c0502e96306c7a102 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Mon, 16 Sep 2024 10:07:29 +0800 Subject: [PATCH 214/248] Update transforms.py --- src/jimgw/single_event/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index cb60e98b..0cfae761 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -83,7 +83,7 @@ class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): def __init__( self, gps_time: Float, - ifos: GroundBased2G, + ifos: list[GroundBased2G], ): name_mapping = (["ra", "dec"], ["zenith", "azimuth"]) super().__init__(name_mapping) From aef6124fdb93997d20e906d954646db28f613771 Mon Sep 17 00:00:00 2001 From: kazewong Date: Mon, 16 Sep 2024 08:46:26 -0400 Subject: [PATCH 215/248] minor bug fix on initial sample noon_finite_guess --- example/GW150914_IMRPhenomD.py | 2 +- src/jimgw/jim.py | 2 +- test/integration/test_GW150914_D.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/GW150914_IMRPhenomD.py b/example/GW150914_IMRPhenomD.py index 7a7a37b3..66619ddc 100644 --- a/example/GW150914_IMRPhenomD.py +++ b/example/GW150914_IMRPhenomD.py @@ -117,7 +117,7 @@ n_epochs=n_epochs, learning_rate=learning_rate, n_max_examples=30000, - n_flow_samples=100000, + n_flow_sample=100000, momentum=0.9, batch_size=30000, use_global=True, diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 8e6bb0bc..2f0086ac 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -107,7 +107,7 @@ def sample(self, key: PRNGKeyArray, initial_position: Array = jnp.array([])): initial_position = jnp.zeros((self.sampler.n_chains, self.prior.n_dim)) + jnp.nan while not jax.tree.reduce(jnp.logical_and, jax.tree.map(lambda x: jnp.isfinite(x), initial_position)).all(): - non_finite_index = jnp.any(~jax.tree.reduce(jnp.logical_and, jax.tree.map(lambda x: jnp.isfinite(x), initial_position)),axis=1) + non_finite_index = jnp.where(jnp.any(~jax.tree.reduce(jnp.logical_and, jax.tree.map(lambda x: jnp.isfinite(x), initial_position)),axis=1))[0] key, subkey = jax.random.split(key) guess = self.prior.sample(subkey, self.sampler.n_chains) diff --git a/test/integration/test_GW150914_D.py b/test/integration/test_GW150914_D.py index 5103e5d8..946b0735 100644 --- a/test/integration/test_GW150914_D.py +++ b/test/integration/test_GW150914_D.py @@ -119,7 +119,7 @@ n_epochs=n_epochs, learning_rate=learning_rate, n_max_examples=30, - n_flow_samples=100, + n_flow_sample=100, momentum=0.9, batch_size=100, use_global=True, From 5cf1668b69920d50f1463800781b97601d8616c5 Mon Sep 17 00:00:00 2001 From: Thomas Ng Date: Tue, 17 Sep 2024 08:58:38 +0800 Subject: [PATCH 216/248] Update jim.py --- src/jimgw/jim.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 2f0086ac..66122fe2 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -3,7 +3,6 @@ from flowMC.nfmodel.rqSpline import MaskedCouplingRQSpline from flowMC.proposal.MALA import MALA from flowMC.Sampler import Sampler -from flowMC.utils.EvolutionaryOptimizer import EvolutionaryOptimizer from jaxtyping import Array, Float, PRNGKeyArray from jimgw.base import LikelihoodBase @@ -119,31 +118,6 @@ def sample(self, key: PRNGKeyArray, initial_position: Array = jnp.array([])): initial_position = initial_position.at[non_finite_index[:common_length]].set(guess[:common_length]) self.sampler.sample(initial_position, None) # type: ignore - def maximize_likelihood( - self, - bounds: Float[Array, " n_dim 2"], - set_nwalkers: int = 100, - n_loops: int = 2000, - seed=92348, - ): - key = jax.random.PRNGKey(seed) - set_nwalkers = set_nwalkers - initial_guess = self.prior.sample(key, set_nwalkers) - - def negative_posterior(x: Float[Array, " n_dim"]): - return -self.posterior(x, None) # type: ignore since flowMC does not have typing info, yet - - negative_posterior = jax.jit(jax.vmap(negative_posterior)) - print("Compiling likelihood function") - negative_posterior(initial_guess) - print("Done compiling") - - print("Starting the optimizer") - optimizer = EvolutionaryOptimizer(self.prior.n_dim, verbose=True) - _ = optimizer.optimize(negative_posterior, bounds, n_loops=n_loops) - best_fit = optimizer.get_result()[0] - return best_fit - def print_summary(self, transform: bool = True): """ Generate summary of the run From 3a5cbf71522f21ba4f5ead4508f8f4e1dedbd9f5 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Wed, 18 Sep 2024 21:46:38 +0200 Subject: [PATCH 217/248] remove duplicated SpinToCartesianSpinTransform --- src/jimgw/single_event/transforms.py | 106 +++++++++------------------ test/integration/test_extrinsic.py | 8 +- 2 files changed, 37 insertions(+), 77 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index da7864ba..62a09fd6 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -140,13 +140,13 @@ class GeocentricArrivalTimeToDetectorArrivalTimeTransform( def __init__( self, - name_mapping: tuple[list[str], list[str]], - conditional_names: list[str], gps_time: Float, ifo: GroundBased2G, tc_min: Float, tc_max: Float, ): + name_mapping = [["t_c"], ["t_det_unbounded"]] + conditional_names = ["ra", "dec"] super().__init__(name_mapping, conditional_names) self.gmst = ( @@ -225,11 +225,11 @@ class GeocentricArrivalPhaseToDetectorArrivalPhaseTransform( def __init__( self, - name_mapping: tuple[list[str], list[str]], - conditional_names: list[str], gps_time: Float, ifo: GroundBased2G, ): + name_mapping = [["phase_c"], ["phase_det"]] + conditional_names = ["ra", "dec", "psi", "iota"] super().__init__(name_mapping, conditional_names) self.gmst = ( @@ -305,6 +305,8 @@ def __init__( dL_min: Float, dL_max: Float, ): + name_mapping = [["d_L"], ["d_hat_unbounded"]] + conditional_names = ["M_c", "ra", "dec", "psi", "iota"] super().__init__(name_mapping, conditional_names) self.gmst = ( @@ -381,96 +383,54 @@ def named_inverse_transform(x): self.inverse_transform_func = named_inverse_transform -@jaxtyped(typechecker=typechecker) -class SpinToCartesianSpinTransform(NtoNTransform): - """ - Spin to Cartesian spin transformation - """ - - freq_ref: Float - - def __init__( - self, - name_mapping: tuple[list[str], list[str]], - freq_ref: Float, - ): - super().__init__(name_mapping) - - self.freq_ref = freq_ref - - assert ( - "theta_jn" in name_mapping[0] - and "phi_jl" in name_mapping[0] - and "theta_1" in name_mapping[0] - and "theta_2" in name_mapping[0] - and "phi_12" in name_mapping[0] - and "a_1" in name_mapping[0] - and "a_2" in name_mapping[0] - and "iota" in name_mapping[1] - and "s1_x" in name_mapping[1] - and "s1_y" in name_mapping[1] - and "s1_z" in name_mapping[1] - and "s2_x" in name_mapping[1] - and "s2_y" in name_mapping[1] - and "s2_z" in name_mapping[1] - ) - - def named_transform(x): - iota, s1x, s1y, s1z, s2x, s2y, s2z = spin_to_cartesian_spin( - x["theta_jn"], - x["phi_jl"], - x["theta_1"], - x["theta_2"], - x["phi_12"], - x["a_1"], - x["a_2"], - x["M_c"], - x["q"], - self.freq_ref, - x["phase_c"], - ) - return { - "iota": iota, - "s1_x": s1x, - "s1_y": s1y, - "s1_z": s1z, - "s2_x": s2x, - "s2_y": s2y, - "s2_z": s2z, - } - - self.transform_func = named_transform - - def named_m1_m2_to_Mc_q(x): Mc, q = m1_m2_to_Mc_q(x["m_1"], x["m_2"]) - return {"M_c": Mc, "q": q} - - + return {"M_c": Mc, "q": q} + + def named_Mc_q_to_m1_m2(x): m1, m2 = Mc_q_to_m1_m2(x["M_c"], x["q"]) return {"m_1": m1, "m_2": m2} -ComponentMassesToChirpMassMassRatioTransform = BijectiveTransform((["m_1", "m_2"], ["M_c", "q"])) + +ComponentMassesToChirpMassMassRatioTransform = BijectiveTransform( + (["m_1", "m_2"], ["M_c", "q"]) +) ComponentMassesToChirpMassMassRatioTransform.transform_func = named_m1_m2_to_Mc_q -ComponentMassesToChirpMassMassRatioTransform.inverse_transform_func = named_Mc_q_to_m1_m2 +ComponentMassesToChirpMassMassRatioTransform.inverse_transform_func = ( + named_Mc_q_to_m1_m2 +) + def named_m1_m2_to_Mc_eta(x): Mc, eta = m1_m2_to_Mc_eta(x["m_1"], x["m_2"]) return {"M_c": Mc, "eta": eta} + def named_Mc_eta_to_m1_m2(x): m1, m2 = Mc_eta_to_m1_m2(x["M_c"], x["eta"]) return {"m_1": m1, "m_2": m2} -ComponentMassesToChirpMassSymmetricMassRatioTransform = BijectiveTransform((["m_1", "m_2"], ["M_c", "eta"])) -ComponentMassesToChirpMassSymmetricMassRatioTransform.transform_func = named_m1_m2_to_Mc_eta -ComponentMassesToChirpMassSymmetricMassRatioTransform.inverse_transform_func = named_Mc_eta_to_m1_m2 + +ComponentMassesToChirpMassSymmetricMassRatioTransform = BijectiveTransform( + (["m_1", "m_2"], ["M_c", "eta"]) +) +ComponentMassesToChirpMassSymmetricMassRatioTransform.transform_func = ( + named_m1_m2_to_Mc_eta +) +ComponentMassesToChirpMassSymmetricMassRatioTransform.inverse_transform_func = ( + named_Mc_eta_to_m1_m2 +) + def named_q_to_eta(x): return {"eta": q_to_eta(x["q"])} + + def named_eta_to_q(x): return {"q": eta_to_q(x["eta"])} + + MassRatioToSymmetricMassRatioTransform = BijectiveTransform((["q"], ["eta"])) MassRatioToSymmetricMassRatioTransform.transform_func = named_q_to_eta MassRatioToSymmetricMassRatioTransform.inverse_transform_func = named_eta_to_q diff --git a/test/integration/test_extrinsic.py b/test/integration/test_extrinsic.py index f0e089fe..300f2132 100644 --- a/test/integration/test_extrinsic.py +++ b/test/integration/test_extrinsic.py @@ -47,10 +47,10 @@ sample_transforms = [ # all the user reparametrization transform - DistanceToSNRWeightedDistanceTransform(name_mapping=[["d_L"], ["d_hat_unbounded"]], conditional_names=["M_c","ra", "dec", "psi", "iota"], gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), - GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(name_mapping = [["phase_c"], ["phase_det"]], conditional_names=["ra", "dec", "psi", "iota"], gps_time=gps, ifo=ifos[0]), - GeocentricArrivalTimeToDetectorArrivalTimeTransform(name_mapping = [["t_c"], ["t_det_unbounded"]], tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, conditional_names=["ra", "dec"], gps_time=gps, ifo=ifos[0]), - SkyFrameToDetectorFrameSkyPositionTransform(name_mapping = [["ra", "dec"], ["zenith", "azimuth"]], gps_time=gps, ifos=ifos), + DistanceToSNRWeightedDistanceTransform(gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(gps_time=gps, ifo=ifos[0]), + GeocentricArrivalTimeToDetectorArrivalTimeTransform(tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, gps_time=gps, ifo=ifos[0]), + SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), # all the bound to unbound transform BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), BoundToUnbound(name_mapping = [["iota"], ["iota_unbounded"]], original_lower_bound=0., original_upper_bound=jnp.pi), From c25048f79a3968c1527cbf9aea33357f319eb30d Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Wed, 18 Sep 2024 22:07:05 +0200 Subject: [PATCH 218/248] Minor fix --- src/jimgw/single_event/transforms.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 62a09fd6..d325590b 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -298,8 +298,6 @@ class DistanceToSNRWeightedDistanceTransform(ConditionalBijectiveTransform): def __init__( self, - name_mapping: tuple[list[str], list[str]], - conditional_names: list[str], gps_time: Float, ifos: list[GroundBased2G], dL_min: Float, From 482296291f3f80449b814d0131b19ef3ba5ec52d Mon Sep 17 00:00:00 2001 From: kazewong Date: Fri, 20 Sep 2024 12:25:15 -0400 Subject: [PATCH 219/248] replace vectorize with vmap --- src/jimgw/jim.py | 41 ++++++++++++++++++++-------- src/jimgw/single_event/transforms.py | 13 +++------ 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 2f0086ac..3047c0a4 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -104,19 +104,38 @@ def posterior(self, params: Float[Array, " n_dim"], data: dict): def sample(self, key: PRNGKeyArray, initial_position: Array = jnp.array([])): if initial_position.size == 0: - initial_position = jnp.zeros((self.sampler.n_chains, self.prior.n_dim)) + jnp.nan - - while not jax.tree.reduce(jnp.logical_and, jax.tree.map(lambda x: jnp.isfinite(x), initial_position)).all(): - non_finite_index = jnp.where(jnp.any(~jax.tree.reduce(jnp.logical_and, jax.tree.map(lambda x: jnp.isfinite(x), initial_position)),axis=1))[0] + initial_position = ( + jnp.zeros((self.sampler.n_chains, self.prior.n_dim)) + jnp.nan + ) + + while not jax.tree.reduce( + jnp.logical_and, + jax.tree.map(lambda x: jnp.isfinite(x), initial_position), + ).all(): + non_finite_index = jnp.where( + jnp.any( + ~jax.tree.reduce( + jnp.logical_and, + jax.tree.map(lambda x: jnp.isfinite(x), initial_position), + ), + axis=1, + ) + )[0] key, subkey = jax.random.split(key) guess = self.prior.sample(subkey, self.sampler.n_chains) for transform in self.sample_transforms: guess = jax.vmap(transform.forward)(guess) - guess = jnp.array(jax.tree.leaves({key: guess[key] for key in self.parameter_names})).T - finite_guess = jnp.where(jnp.all(jax.tree.map(lambda x: jnp.isfinite(x), guess),axis=1))[0] + guess = jnp.array( + jax.tree.leaves({key: guess[key] for key in self.parameter_names}) + ).T + finite_guess = jnp.where( + jnp.all(jax.tree.map(lambda x: jnp.isfinite(x), guess), axis=1) + )[0] common_length = min(len(finite_guess), len(non_finite_index)) - initial_position = initial_position.at[non_finite_index[:common_length]].set(guess[:common_length]) + initial_position = initial_position.at[ + non_finite_index[:common_length] + ].set(guess[:common_length]) self.sampler.sample(initial_position, None) # type: ignore def maximize_likelihood( @@ -157,7 +176,7 @@ def print_summary(self, transform: bool = True): training_chain = self.add_name(training_chain) if transform: for sample_transform in reversed(self.sample_transforms): - training_chain = sample_transform.backward(training_chain) + training_chain = jax.vmap(sample_transform.backward)(training_chain) training_log_prob = train_summary["log_prob"] training_local_acceptance = train_summary["local_accs"] training_global_acceptance = train_summary["global_accs"] @@ -167,7 +186,7 @@ def print_summary(self, transform: bool = True): production_chain = self.add_name(production_chain) if transform: for sample_transform in reversed(self.sample_transforms): - production_chain = sample_transform.backward(production_chain) + production_chain = jax.vmap(sample_transform.backward)(production_chain) production_log_prob = production_summary["log_prob"] production_local_acceptance = production_summary["local_accs"] production_global_acceptance = production_summary["global_accs"] @@ -223,10 +242,10 @@ def get_samples(self, training: bool = False) -> dict: else: chains = self.sampler.get_sampler_state(training=False)["chains"] - chains = chains.transpose(2, 0, 1) + chains = chains.reshape(-1, self.prior.n_dim) chains = self.add_name(chains) for sample_transform in reversed(self.sample_transforms): - chains = sample_transform.backward(chains) + chains = jax.vmap(sample_transform.backward)(chains) return chains def plot(self): diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index d325590b..1bf8f3a7 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -145,7 +145,7 @@ def __init__( tc_min: Float, tc_max: Float, ): - name_mapping = [["t_c"], ["t_det_unbounded"]] + name_mapping = (["t_c"], ["t_det_unbounded"]) conditional_names = ["ra", "dec"] super().__init__(name_mapping, conditional_names) @@ -159,7 +159,6 @@ def __init__( assert "t_c" in name_mapping[0] and "t_det_unbounded" in name_mapping[1] assert "ra" in conditional_names and "dec" in conditional_names - @jnp.vectorize def time_delay(ra, dec, gmst): return self.ifo.delay_from_geocenter(ra, dec, gmst) @@ -181,9 +180,7 @@ def named_transform(x): def named_inverse_transform(x): - time_shift = jnp.vectorize(self.ifo.delay_from_geocenter)( - x["ra"], x["dec"], self.gmst - ) + time_shift = self.ifo.delay_from_geocenter(x["ra"], x["dec"], self.gmst) t_det_min = self.tc_min + time_shift t_det_max = self.tc_max + time_shift @@ -228,7 +225,7 @@ def __init__( gps_time: Float, ifo: GroundBased2G, ): - name_mapping = [["phase_c"], ["phase_det"]] + name_mapping = (["phase_c"], ["phase_det"]) conditional_names = ["ra", "dec", "psi", "iota"] super().__init__(name_mapping, conditional_names) @@ -245,7 +242,6 @@ def __init__( and "iota" in conditional_names ) - @jnp.vectorize def _calc_R_det_arg(ra, dec, psi, iota, gmst): p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 c_iota_term = jnp.cos(iota) @@ -303,7 +299,7 @@ def __init__( dL_min: Float, dL_max: Float, ): - name_mapping = [["d_L"], ["d_hat_unbounded"]] + name_mapping = (["d_L"], ["d_hat_unbounded"]) conditional_names = ["M_c", "ra", "dec", "psi", "iota"] super().__init__(name_mapping, conditional_names) @@ -323,7 +319,6 @@ def __init__( and "M_c" in conditional_names ) - @jnp.vectorize def _calc_R_dets(ra, dec, psi, iota): p_iota_term = (1.0 + jnp.cos(iota) ** 2) / 2.0 c_iota_term = jnp.cos(iota) From 617f3e77d594d89545c7c83458d1b3cd7f9ffded Mon Sep 17 00:00:00 2001 From: kazewong Date: Fri, 20 Sep 2024 12:38:04 -0400 Subject: [PATCH 220/248] Try fixing github precommit --- .github/workflows/pre-commit.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index c58fd662..4a7d2c2f 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -25,4 +25,8 @@ jobs: python -m pip install pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi python -m pip install . + - id: changed-files + uses: tj-actions/changed-files@v36 - uses: pre-commit/action@v3.0.0 + with: + extra_args: pip-compile --files ${{ steps.changed-files.outputs.all_changed_files }} From 766c36e5940a02cde644593813c1e0ccd3ec0d9e Mon Sep 17 00:00:00 2001 From: kazewong Date: Fri, 20 Sep 2024 12:39:46 -0400 Subject: [PATCH 221/248] Try fixing github precommit --- .github/workflows/pre-commit.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 4a7d2c2f..30cbfb35 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -25,8 +25,4 @@ jobs: python -m pip install pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi python -m pip install . - - id: changed-files - uses: tj-actions/changed-files@v36 - - uses: pre-commit/action@v3.0.0 - with: - extra_args: pip-compile --files ${{ steps.changed-files.outputs.all_changed_files }} + - uses: pre-commit/action@v3.0.1 From 646e159a89cc48903a26b763c7df7728013b1c2d Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Fri, 20 Sep 2024 12:52:38 -0400 Subject: [PATCH 222/248] black formatting for GW150914_IMRPhenomD --- example/GW150914_IMRPhenomD.py | 84 ++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/example/GW150914_IMRPhenomD.py b/example/GW150914_IMRPhenomD.py index 66619ddc..23d08c7b 100644 --- a/example/GW150914_IMRPhenomD.py +++ b/example/GW150914_IMRPhenomD.py @@ -2,12 +2,22 @@ import jax.numpy as jnp from jimgw.jim import Jim -from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior +from jimgw.prior import ( + CombinePrior, + UniformPrior, + CosinePrior, + SinePrior, + PowerLawPrior, +) from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD from jimgw.transforms import BoundToUnbound -from jimgw.single_event.transforms import ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, ComponentMassesToChirpMassMassRatioTransform +from jimgw.single_event.transforms import ( + ComponentMassesToChirpMassSymmetricMassRatioTransform, + SkyFrameToDetectorFrameSkyPositionTransform, + ComponentMassesToChirpMassMassRatioTransform, +) from jimgw.single_event.utils import Mc_q_to_m1_m2 from flowMC.strategy.optimization import optimization_Adam @@ -65,18 +75,62 @@ sample_transforms = [ # ComponentMassesToChirpMassMassRatioTransform, - BoundToUnbound(name_mapping = (["M_c"], ["M_c_unbounded"]), original_lower_bound=M_c_min, original_upper_bound=M_c_max), - BoundToUnbound(name_mapping = (["eta"], ["eta_unbounded"]), original_lower_bound=eta_min, original_upper_bound=eta_max), - BoundToUnbound(name_mapping = (["s1_z"], ["s1_z_unbounded"]) , original_lower_bound=-1.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = (["s2_z"], ["s2_z_unbounded"]) , original_lower_bound=-1.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = (["d_L"], ["d_L_unbounded"]) , original_lower_bound=1.0, original_upper_bound=2000.0), - BoundToUnbound(name_mapping = (["t_c"], ["t_c_unbounded"]) , original_lower_bound=-0.05, original_upper_bound=0.05), - BoundToUnbound(name_mapping = (["phase_c"], ["phase_c_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = (["iota"], ["iota_unbounded"]), original_lower_bound=0., original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = (["psi"], ["psi_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound( + name_mapping=(["M_c"], ["M_c_unbounded"]), + original_lower_bound=M_c_min, + original_upper_bound=M_c_max, + ), + BoundToUnbound( + name_mapping=(["eta"], ["eta_unbounded"]), + original_lower_bound=eta_min, + original_upper_bound=eta_max, + ), + BoundToUnbound( + name_mapping=(["s1_z"], ["s1_z_unbounded"]), + original_lower_bound=-1.0, + original_upper_bound=1.0, + ), + BoundToUnbound( + name_mapping=(["s2_z"], ["s2_z_unbounded"]), + original_lower_bound=-1.0, + original_upper_bound=1.0, + ), + BoundToUnbound( + name_mapping=(["d_L"], ["d_L_unbounded"]), + original_lower_bound=1.0, + original_upper_bound=2000.0, + ), + BoundToUnbound( + name_mapping=(["t_c"], ["t_c_unbounded"]), + original_lower_bound=-0.05, + original_upper_bound=0.05, + ), + BoundToUnbound( + name_mapping=(["phase_c"], ["phase_c_unbounded"]), + original_lower_bound=0.0, + original_upper_bound=2 * jnp.pi, + ), + BoundToUnbound( + name_mapping=(["iota"], ["iota_unbounded"]), + original_lower_bound=0.0, + original_upper_bound=jnp.pi, + ), + BoundToUnbound( + name_mapping=(["psi"], ["psi_unbounded"]), + original_lower_bound=0.0, + original_upper_bound=jnp.pi, + ), SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), - BoundToUnbound(name_mapping = (["zenith"], ["zenith_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = (["azimuth"], ["azimuth_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound( + name_mapping=(["zenith"], ["zenith_unbounded"]), + original_lower_bound=0.0, + original_upper_bound=jnp.pi, + ), + BoundToUnbound( + name_mapping=(["azimuth"], ["azimuth_unbounded"]), + original_lower_bound=0.0, + original_upper_bound=2 * jnp.pi, + ), ] likelihood_transforms = [ @@ -125,9 +179,9 @@ output_thinning=10, local_sampler_arg=local_sampler_arg, strategies=[Adam_optimizer, "default"], - verbose=True + verbose=True, ) jim.sample(jax.random.PRNGKey(42)) # jim.get_samples() -# jim.print_summary() \ No newline at end of file +# jim.print_summary() From de6ab602e553c8bfc8705f14284f65af4ddc3dbc Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Fri, 20 Sep 2024 13:07:00 -0400 Subject: [PATCH 223/248] Rename IMRPhenomPv2 --- example/GW150914_IMRPhenomPV2.py | 153 ++++++++++++++++++++++++++++ example/GW150914_PV2.py | 165 ------------------------------- 2 files changed, 153 insertions(+), 165 deletions(-) create mode 100644 example/GW150914_IMRPhenomPV2.py delete mode 100644 example/GW150914_PV2.py diff --git a/example/GW150914_IMRPhenomPV2.py b/example/GW150914_IMRPhenomPV2.py new file mode 100644 index 00000000..45895c62 --- /dev/null +++ b/example/GW150914_IMRPhenomPV2.py @@ -0,0 +1,153 @@ +import time + +import jax +import jax.numpy as jnp + +from jimgw.jim import Jim +from jimgw.jim import Jim +from jimgw.prior import ( + CombinePrior, + UniformPrior, + CosinePrior, + SinePrior, + PowerLawPrior, + UniformSpherePrior, +) +from jimgw.single_event.detector import H1, L1 +from jimgw.single_event.likelihood import TransientLikelihoodFD +from jimgw.single_event.waveform import RippleIMRPhenomPv2 +from jimgw.transforms import BoundToUnbound +from jimgw.single_event.transforms import ( + ComponentMassesToChirpMassSymmetricMassRatioTransform, + SkyFrameToDetectorFrameSkyPositionTransform, + ComponentMassesToChirpMassMassRatioTransform, +) +from jimgw.single_event.utils import Mc_q_to_m1_m2 +from flowMC.strategy.optimization import optimization_Adam + +jax.config.update("jax_enable_x64", True) + +########################################### +########## First we grab data ############# +########################################### + +total_time_start = time.time() + +# first, fetch a 4s segment centered on GW150914 +gps = 1126259462.4 +start = gps - 2 +end = gps + 2 +fmin = 20.0 +fmax = 1024.0 + +ifos = ["H1", "L1"] + +H1.load_data(gps, 2, 2, fmin, fmax, psd_pad=16, tukey_alpha=0.2) +L1.load_data(gps, 2, 2, fmin, fmax, psd_pad=16, tukey_alpha=0.2) + +waveform = RippleIMRPhenomPv2(f_ref=20) + +########################################### +########## Set up priors ################## +########################################### + +prior = [] + +# Mass prior +M_c_min, M_c_max = 10.0, 80.0 +eta_min, eta_max = 0.2, 0.25 +Mc_prior = UniformPrior(M_c_min, M_c_max, parameter_names=["M_c"]) +eta_prior = UniformPrior(eta_min, eta_max, parameter_names=["eta"]) + +prior = prior + [Mc_prior, eta_prior] + +# Spin prior +theta_jn_prior = SinePrior(parameter_names=["theta_jn"]) +phi_jl_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_jl"]) +theta_1_prior = SinePrior(parameter_names=["theta_1"]) +theta_2_prior = SinePrior(parameter_names=["theta_2"]) +phi_12_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_12"]) +a_1_prior = UniformPrior(0.0, 1.0, parameter_names=["a_1"]) +a_2_prior = UniformPrior(0.0, 1.0, parameter_names=["a_2"]) + +prior = prior + [ + theta_jn_prior, + phi_jl_prior, + theta_1_prior, + theta_2_prior, + phi_12_prior, + a_1_prior, + a_2_prior, +] + +# Extrinsic prior +dL_prior = PowerLawPrior(1.0, 2000.0, 2.0, parameter_names=["d_L"]) +t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) +phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) +iota_prior = SinePrior(parameter_names=["iota"]) +psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) +ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) +dec_prior = CosinePrior(parameter_names=["dec"]) + +prior = prior + [ + dL_prior, + t_c_prior, + phase_c_prior, + iota_prior, + psi_prior, + ra_prior, + dec_prior, +] + + +prior = CombinePrior(prior) + +likelihood = TransientLikelihoodFD( + [H1, L1], waveform=waveform, trigger_time=gps, duration=4, post_trigger_duration=2 +) + + +mass_matrix = jnp.eye(prior.n_dim) +mass_matrix = mass_matrix.at[1, 1].set(1e-3) +mass_matrix = mass_matrix.at[9, 9].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 1e-3} + +Adam_optimizer = optimization_Adam(n_steps=3000, learning_rate=0.01, noise_level=1) + +# import optax + +# n_epochs = 20 +# n_loop_training = 100 +# total_epochs = n_epochs * n_loop_training +# start = total_epochs // 10 +# learning_rate = optax.polynomial_schedule( +# 1e-3, 1e-4, 4.0, total_epochs - start, transition_begin=start +# ) + +# jim = Jim( +# likelihood, +# prior, +# n_loop_training=n_loop_training, +# n_loop_production=20, +# n_local_steps=10, +# n_global_steps=1000, +# n_chains=500, +# n_epochs=n_epochs, +# learning_rate=learning_rate, +# n_max_examples=30000, +# n_flow_sample=100000, +# momentum=0.9, +# batch_size=30000, +# use_global=True, +# keep_quantile=0.0, +# train_thinning=1, +# output_thinning=10, +# local_sampler_arg=local_sampler_arg, +# # strategies=[Adam_optimizer,"default"], +# ) + +# import numpy as np + +# # chains = np.load('./GW150914_init.npz')['chain'] + +# jim.sample(jax.random.PRNGKey(42)) # ,initial_guess=chains) diff --git a/example/GW150914_PV2.py b/example/GW150914_PV2.py deleted file mode 100644 index 06209ba6..00000000 --- a/example/GW150914_PV2.py +++ /dev/null @@ -1,165 +0,0 @@ -import time - -import jax -import jax.numpy as jnp - -from jimgw.jim import Jim -from jimgw.prior import Composite, Sphere, Unconstrained_Uniform -from jimgw.single_event.detector import H1, L1 -from jimgw.single_event.likelihood import TransientLikelihoodFD -from jimgw.single_event.waveform import RippleIMRPhenomPv2 -from flowMC.strategy.optimization import optimization_Adam - - -jax.config.update("jax_enable_x64", True) - -########################################### -########## First we grab data ############# -########################################### - -total_time_start = time.time() - -# first, fetch a 4s segment centered on GW150914 -gps = 1126259462.4 -start = gps - 2 -end = gps + 2 -fmin = 20.0 -fmax = 1024.0 - -ifos = ["H1", "L1"] - -H1.load_data(gps, 2, 2, fmin, fmax, psd_pad=16, tukey_alpha=0.2) -L1.load_data(gps, 2, 2, fmin, fmax, psd_pad=16, tukey_alpha=0.2) - -waveform = RippleIMRPhenomPv2(f_ref=20) - -########################################### -########## Set up priors ################## -########################################### - -Mc_prior = Unconstrained_Uniform(10.0, 80.0, naming=["M_c"]) -q_prior = Unconstrained_Uniform( - 0.125, - 1.0, - naming=["q"], - transforms={"q": ("eta", lambda params: params["q"] / (1 + params["q"]) ** 2)}, -) -s1_prior = Sphere(naming="s1") -s2_prior = Sphere(naming="s2") -dL_prior = Unconstrained_Uniform(0.0, 2000.0, naming=["d_L"]) -t_c_prior = Unconstrained_Uniform(-0.05, 0.05, naming=["t_c"]) -phase_c_prior = Unconstrained_Uniform(0.0, 2 * jnp.pi, naming=["phase_c"]) -cos_iota_prior = Unconstrained_Uniform( - -1.0, - 1.0, - naming=["cos_iota"], - transforms={ - "cos_iota": ( - "iota", - lambda params: jnp.arccos( - jnp.arcsin(jnp.sin(params["cos_iota"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) -psi_prior = Unconstrained_Uniform(0.0, jnp.pi, naming=["psi"]) -ra_prior = Unconstrained_Uniform(0.0, 2 * jnp.pi, naming=["ra"]) -sin_dec_prior = Unconstrained_Uniform( - -1.0, - 1.0, - naming=["sin_dec"], - transforms={ - "sin_dec": ( - "dec", - lambda params: jnp.arcsin( - jnp.arcsin(jnp.sin(params["sin_dec"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) - -prior = Composite( - [ - Mc_prior, - q_prior, - s1_prior, - s2_prior, - dL_prior, - t_c_prior, - phase_c_prior, - cos_iota_prior, - psi_prior, - ra_prior, - sin_dec_prior, - ], -) - -epsilon = 1e-3 -bounds = jnp.array( - [ - [10.0, 80.0], - [0.125, 1.0], - [0, jnp.pi], - [0, 2 * jnp.pi], - [0.0, 1.0], - [0, jnp.pi], - [0, 2 * jnp.pi], - [0.0, 1.0], - [0.0, 2000], - [-0.05, 0.05], - [0.0, 2 * jnp.pi], - [-1.0, 1.0], - [0.0, jnp.pi], - [0.0, 2 * jnp.pi], - [-1.0, 1.0], - ] -) + jnp.array([[epsilon, -epsilon]]) - -likelihood = TransientLikelihoodFD( - [H1, L1], waveform=waveform, trigger_time=gps, duration=4, post_trigger_duration=2 -) -# likelihood = HeterodynedTransientLikelihoodFD([H1, L1], prior=prior, bounds=bounds, waveform=waveform, trigger_time=gps, duration=4, post_trigger_duration=2) - - -mass_matrix = jnp.eye(prior.n_dim) -mass_matrix = mass_matrix.at[1, 1].set(1e-3) -mass_matrix = mass_matrix.at[9, 9].set(1e-3) -local_sampler_arg = {"step_size": mass_matrix * 1e-3} - -Adam_optimizer = optimization_Adam(n_steps=3000, learning_rate=0.01, noise_level=1, bounds=bounds) - -import optax -n_epochs = 20 -n_loop_training = 100 -total_epochs = n_epochs * n_loop_training -start = total_epochs//10 -learning_rate = optax.polynomial_schedule( - 1e-3, 1e-4, 4.0, total_epochs - start, transition_begin=start -) - -jim = Jim( - likelihood, - prior, - n_loop_training=n_loop_training, - n_loop_production=20, - n_local_steps=10, - n_global_steps=1000, - n_chains=500, - n_epochs=n_epochs, - learning_rate=learning_rate, - n_max_examples=30000, - n_flow_sample=100000, - momentum=0.9, - batch_size=30000, - use_global=True, - keep_quantile=0.0, - train_thinning=1, - output_thinning=10, - local_sampler_arg=local_sampler_arg, - # strategies=[Adam_optimizer,"default"], -) - -import numpy as np -# chains = np.load('./GW150914_init.npz')['chain'] - -jim.sample(jax.random.PRNGKey(42))#,initial_guess=chains) From 41abe69f85abcb9d439eba34082a491e8797b796 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Fri, 20 Sep 2024 13:17:14 -0400 Subject: [PATCH 224/248] Delete redundant spin function --- src/jimgw/single_event/utils.py | 160 -------------------------------- 1 file changed, 160 deletions(-) diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index fb35bf27..e136af02 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -275,166 +275,6 @@ def eta_to_q(eta: Float) -> Float: temp = 1 / eta / 2 - 1 return temp - (temp**2 - 1) ** 0.5 - -def spin_to_cartesian_spin( - thetaJN: Float, - phiJL: Float, - theta1: Float, - theta2: Float, - phi12: Float, - chi1: Float, - chi2: Float, - M_c: Float, - eta: Float, - fRef: Float, - phiRef: Float, -) -> tuple[Float, Float, Float, Float, Float, Float, Float]: - """ - Transforming the spin parameters - - The code is based on the approach used in LALsimulation: - https://lscsoft.docs.ligo.org/lalsuite/lalsimulation/group__lalsimulation__inference.html - - Parameters: - ------- - thetaJN: Float - Zenith angle between the total angular momentum and the line of sight - phiJL: Float - Difference between total and orbital angular momentum azimuthal angles - theta1: Float - Zenith angle between the spin and orbital angular momenta for the primary object - theta2: Float - Zenith angle between the spin and orbital angular momenta for the secondary object - phi12: Float - Difference between the azimuthal angles of the individual spin vector projections - onto the orbital plane - chi1: Float - Primary object aligned spin: - chi2: Float - Secondary object aligned spin: - M_c: Float - The chirp mass - eta: Float - The symmetric mass ratio - fRef: Float - The reference frequency - phiRef: Float - Binary phase at a reference frequency - - Returns: - ------- - iota: Float - Zenith angle between the orbital angular momentum and the line of sight - S1x: Float - The x-component of the primary spin - S1y: Float - The y-component of the primary spin - S1z: Float - The z-component of the primary spin - S2x: Float - The x-component of the secondary spin - S2y: Float - The y-component of the secondary spin - S2z: Float - The z-component of the secondary spin - """ - - def rotate_y(angle, vec): - """ - Rotate the vector (x, y, z) about y-axis - """ - cos_angle = jnp.cos(angle) - sin_angle = jnp.sin(angle) - rotation_matrix = jnp.array( - [[cos_angle, 0, sin_angle], [0, 1, 0], [-sin_angle, 0, cos_angle]] - ) - rotated_vec = jnp.dot(rotation_matrix, vec) - return rotated_vec - - def rotate_z(angle, vec): - """ - Rotate the vector (x, y, z) about z-axis - """ - cos_angle = jnp.cos(angle) - sin_angle = jnp.sin(angle) - rotation_matrix = jnp.array( - [[cos_angle, -sin_angle, 0], [sin_angle, cos_angle, 0], [0, 0, 1]] - ) - rotated_vec = jnp.dot(rotation_matrix, vec) - return rotated_vec - - LNh = jnp.array([0.0, 0.0, 1.0]) - - s1hat = jnp.array( - [ - jnp.sin(theta1) * jnp.cos(phiRef), - jnp.sin(theta1) * jnp.sin(phiRef), - jnp.cos(theta1), - ] - ) - s2hat = jnp.array( - [ - jnp.sin(theta2) * jnp.cos(phi12 + phiRef), - jnp.sin(theta2) * jnp.sin(phi12 + phiRef), - jnp.cos(theta2), - ] - ) - - temp = 1 / eta / 2 - 1 - q = temp - (temp**2 - 1) ** 0.5 - m1, m2 = Mc_q_to_m1m2(M_c, q) - v0 = jnp.cbrt((m1 + m2) * Msun * jnp.pi * fRef) - - Lmag = ((m1 + m2) * (m1 + m2) * eta / v0) * (1.0 + v0 * v0 * (1.5 + eta / 6.0)) - s1 = m1 * m1 * chi1 * s1hat - s2 = m2 * m2 * chi2 * s2hat - J = s1 + s2 + jnp.array([0.0, 0.0, Lmag]) - - Jhat = J / jnp.linalg.norm(J) - theta0 = jnp.arccos(Jhat[2]) - phi0 = jnp.arctan2(Jhat[1], Jhat[0]) - - # Rotation 1: - s1hat = rotate_z(-phi0, s1hat) - s2hat = rotate_z(-phi0, s2hat) - - # Rotation 2: - LNh = rotate_y(-theta0, LNh) - s1hat = rotate_y(-theta0, s1hat) - s2hat = rotate_y(-theta0, s2hat) - - # Rotation 3: - LNh = rotate_z(phiJL - jnp.pi, LNh) - s1hat = rotate_z(phiJL - jnp.pi, s1hat) - s2hat = rotate_z(phiJL - jnp.pi, s2hat) - - # Compute iota - N = jnp.array([0.0, jnp.sin(thetaJN), jnp.cos(thetaJN)]) - iota = jnp.arccos(jnp.dot(N, LNh)) - - thetaLJ = jnp.arccos(LNh[2]) - phiL = jnp.arctan2(LNh[1], LNh[0]) - - # Rotation 4: - s1hat = rotate_z(-phiL, s1hat) - s2hat = rotate_z(-phiL, s2hat) - N = rotate_z(-phiL, N) - - # Rotation 5: - s1hat = rotate_y(-thetaLJ, s1hat) - s2hat = rotate_y(-thetaLJ, s2hat) - N = rotate_y(-thetaLJ, N) - - # Rotation 6: - phiN = jnp.arctan2(N[1], N[0]) - s1hat = rotate_z(jnp.pi / 2.0 - phiN - phiRef, s1hat) - s2hat = rotate_z(jnp.pi / 2.0 - phiN - phiRef, s2hat) - - S1 = s1hat * chi1 - S2 = s2hat * chi2 - return iota, S1[0], S1[1], S1[2], S2[0], S2[1], S2[2] - - def euler_rotation(delta_x: Float[Array, " 3"]): """ Calculate the rotation matrix mapping the vector (0, 0, 1) to delta_x From 84ff0b7df83e73dbe6e3fb0b5e91953baf617c41 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Fri, 20 Sep 2024 13:18:59 -0400 Subject: [PATCH 225/248] update PV2 example --- example/GW150914_IMRPhenomPV2.py | 125 +++++++++++++++++++------------ 1 file changed, 77 insertions(+), 48 deletions(-) diff --git a/example/GW150914_IMRPhenomPV2.py b/example/GW150914_IMRPhenomPV2.py index 45895c62..b47567dc 100644 --- a/example/GW150914_IMRPhenomPV2.py +++ b/example/GW150914_IMRPhenomPV2.py @@ -18,9 +18,10 @@ from jimgw.single_event.waveform import RippleIMRPhenomPv2 from jimgw.transforms import BoundToUnbound from jimgw.single_event.transforms import ( - ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, - ComponentMassesToChirpMassMassRatioTransform, + SpinToCartesianSpinTransform, + MassRatioToSymmetricMassRatioTransform + ) from jimgw.single_event.utils import Mc_q_to_m1_m2 from flowMC.strategy.optimization import optimization_Adam @@ -40,7 +41,7 @@ fmin = 20.0 fmax = 1024.0 -ifos = ["H1", "L1"] +ifos = [H1, L1] H1.load_data(gps, 2, 2, fmin, fmax, psd_pad=16, tukey_alpha=0.2) L1.load_data(gps, 2, 2, fmin, fmax, psd_pad=16, tukey_alpha=0.2) @@ -55,29 +56,30 @@ # Mass prior M_c_min, M_c_max = 10.0, 80.0 -eta_min, eta_max = 0.2, 0.25 +q_min, q_max = 0.125, 1.0 Mc_prior = UniformPrior(M_c_min, M_c_max, parameter_names=["M_c"]) -eta_prior = UniformPrior(eta_min, eta_max, parameter_names=["eta"]) +q_prior = UniformPrior(q_min, q_max, parameter_names=["q"]) -prior = prior + [Mc_prior, eta_prior] +prior = prior + [Mc_prior, q_prior] # Spin prior +a_1_prior = UniformPrior(0.0, 1.0, parameter_names=["a_1"]) +a_2_prior = UniformPrior(0.0, 1.0, parameter_names=["a_2"]) theta_jn_prior = SinePrior(parameter_names=["theta_jn"]) phi_jl_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_jl"]) -theta_1_prior = SinePrior(parameter_names=["theta_1"]) -theta_2_prior = SinePrior(parameter_names=["theta_2"]) +tilt_1_prior = SinePrior(parameter_names=["tilt_1"]) +tilt_2_prior = SinePrior(parameter_names=["tilt_2"]) phi_12_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_12"]) -a_1_prior = UniformPrior(0.0, 1.0, parameter_names=["a_1"]) -a_2_prior = UniformPrior(0.0, 1.0, parameter_names=["a_2"]) + prior = prior + [ + a_1_prior, + a_2_prior, theta_jn_prior, phi_jl_prior, - theta_1_prior, - theta_2_prior, + tilt_1_prior, + tilt_2_prior, phi_12_prior, - a_1_prior, - a_2_prior, ] # Extrinsic prior @@ -99,52 +101,79 @@ dec_prior, ] - prior = CombinePrior(prior) +# Defining Transforms + +sample_transforms = [ + # ComponentMassesToChirpMassMassRatioTransform, + BoundToUnbound(name_mapping = (["M_c"], ["M_c_unbounded"]), original_lower_bound=M_c_min, original_upper_bound=M_c_max), + BoundToUnbound(name_mapping = (["eta"], ["eta_unbounded"]), original_lower_bound=q_min, original_upper_bound=q_max), + BoundToUnbound(name_mapping = (["theta_jn"], ["theta_jn_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["phi_jl"], ["phi_jl_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["theta_1"], ["theta_1_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["theta_2"], ["theta_2_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["phi_12"], ["phi_12_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["a_1"], ["a_1_unbounded"]) , original_lower_bound=0.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = (["a_2"], ["a_2_unbounded"]) , original_lower_bound=0.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = (["d_L"], ["d_L_unbounded"]) , original_lower_bound=10.0, original_upper_bound=2000.0), + BoundToUnbound(name_mapping = (["t_c"], ["t_c_unbounded"]) , original_lower_bound=-0.05, original_upper_bound=0.05), + BoundToUnbound(name_mapping = (["phase_c"], ["phase_c_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["psi"], ["psi_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), + BoundToUnbound(name_mapping = (["zenith"], ["zenith_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["azimuth"], ["azimuth_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), +] + +likelihood_transforms = [ + MassRatioToSymmetricMassRatioTransform, + SpinToCartesianSpinTransform(freq_ref=20.), +] + + likelihood = TransientLikelihoodFD( [H1, L1], waveform=waveform, trigger_time=gps, duration=4, post_trigger_duration=2 ) mass_matrix = jnp.eye(prior.n_dim) -mass_matrix = mass_matrix.at[1, 1].set(1e-3) -mass_matrix = mass_matrix.at[9, 9].set(1e-3) +# mass_matrix = mass_matrix.at[1, 1].set(1e-3) +# mass_matrix = mass_matrix.at[9, 9].set(1e-3) local_sampler_arg = {"step_size": mass_matrix * 1e-3} Adam_optimizer = optimization_Adam(n_steps=3000, learning_rate=0.01, noise_level=1) -# import optax - -# n_epochs = 20 -# n_loop_training = 100 -# total_epochs = n_epochs * n_loop_training -# start = total_epochs // 10 -# learning_rate = optax.polynomial_schedule( -# 1e-3, 1e-4, 4.0, total_epochs - start, transition_begin=start -# ) - -# jim = Jim( -# likelihood, -# prior, -# n_loop_training=n_loop_training, -# n_loop_production=20, -# n_local_steps=10, -# n_global_steps=1000, -# n_chains=500, -# n_epochs=n_epochs, -# learning_rate=learning_rate, -# n_max_examples=30000, -# n_flow_sample=100000, -# momentum=0.9, -# batch_size=30000, -# use_global=True, -# keep_quantile=0.0, -# train_thinning=1, -# output_thinning=10, -# local_sampler_arg=local_sampler_arg, -# # strategies=[Adam_optimizer,"default"], -# ) +import optax + +n_epochs = 20 +n_loop_training = 100 +total_epochs = n_epochs * n_loop_training +start = total_epochs // 10 +learning_rate = optax.polynomial_schedule( + 1e-3, 1e-4, 4.0, total_epochs - start, transition_begin=start +) + +jim = Jim( + likelihood, + prior, + n_loop_training=n_loop_training, + n_loop_production=20, + n_local_steps=10, + n_global_steps=1000, + n_chains=500, + n_epochs=n_epochs, + learning_rate=learning_rate, + n_max_examples=30000, + n_flow_sample=100000, + momentum=0.9, + batch_size=30000, + use_global=True, + keep_quantile=0.0, + train_thinning=1, + output_thinning=10, + local_sampler_arg=local_sampler_arg, + # strategies=[Adam_optimizer,"default"], +) # import numpy as np From c666bcbeb1aab683943f4c1f9a9fb27b908a6f89 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Fri, 20 Sep 2024 13:19:27 -0400 Subject: [PATCH 226/248] update Pv2 example --- example/GW150914_IMRPhenomPV2.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/example/GW150914_IMRPhenomPV2.py b/example/GW150914_IMRPhenomPV2.py index b47567dc..87e23680 100644 --- a/example/GW150914_IMRPhenomPV2.py +++ b/example/GW150914_IMRPhenomPV2.py @@ -175,8 +175,5 @@ # strategies=[Adam_optimizer,"default"], ) -# import numpy as np -# # chains = np.load('./GW150914_init.npz')['chain'] - -# jim.sample(jax.random.PRNGKey(42)) # ,initial_guess=chains) +jim.sample(jax.random.PRNGKey(42)) From 799ffc91a37025d9f5d1f53d4567190becce5c58 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Fri, 20 Sep 2024 13:24:12 -0400 Subject: [PATCH 227/248] update PV2 --- example/GW150914_IMRPhenomPV2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/GW150914_IMRPhenomPV2.py b/example/GW150914_IMRPhenomPV2.py index 87e23680..1263c85b 100644 --- a/example/GW150914_IMRPhenomPV2.py +++ b/example/GW150914_IMRPhenomPV2.py @@ -156,6 +156,8 @@ jim = Jim( likelihood, prior, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, n_loop_training=n_loop_training, n_loop_production=20, n_local_steps=10, From a94586e8bd0ffa4c3baed0b94813789711bb7653 Mon Sep 17 00:00:00 2001 From: kazewong Date: Fri, 20 Sep 2024 13:43:47 -0400 Subject: [PATCH 228/248] Update parameters from theta to tilt in accordence to Lalsuite --- example/GW150914_IMRPhenomPV2.py | 8 ++++---- src/jimgw/single_event/transforms.py | 6 +++--- src/jimgw/single_event/utils.py | 21 +++++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/example/GW150914_IMRPhenomPV2.py b/example/GW150914_IMRPhenomPV2.py index 1263c85b..ef35f5cf 100644 --- a/example/GW150914_IMRPhenomPV2.py +++ b/example/GW150914_IMRPhenomPV2.py @@ -108,11 +108,11 @@ sample_transforms = [ # ComponentMassesToChirpMassMassRatioTransform, BoundToUnbound(name_mapping = (["M_c"], ["M_c_unbounded"]), original_lower_bound=M_c_min, original_upper_bound=M_c_max), - BoundToUnbound(name_mapping = (["eta"], ["eta_unbounded"]), original_lower_bound=q_min, original_upper_bound=q_max), + BoundToUnbound(name_mapping = (["q"], ["q_unbounded"]), original_lower_bound=q_min, original_upper_bound=q_max), BoundToUnbound(name_mapping = (["theta_jn"], ["theta_jn_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = (["phi_jl"], ["phi_jl_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = (["theta_1"], ["theta_1_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = (["theta_2"], ["theta_2_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["tilt_1"], ["tilt_1_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["tilt_2"], ["tilt_2_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = (["phi_12"], ["phi_12_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = (["a_1"], ["a_1_unbounded"]) , original_lower_bound=0.0, original_upper_bound=1.0), BoundToUnbound(name_mapping = (["a_2"], ["a_2_unbounded"]) , original_lower_bound=0.0, original_upper_bound=1.0), @@ -126,8 +126,8 @@ ] likelihood_transforms = [ - MassRatioToSymmetricMassRatioTransform, SpinToCartesianSpinTransform(freq_ref=20.), + MassRatioToSymmetricMassRatioTransform, ] diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 1bf8f3a7..b3062da9 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -37,7 +37,7 @@ def __init__( freq_ref: Float, ): name_mapping = ( - ["theta_jn", "phi_jl", "theta_1", "theta_2", "phi_12", "a_1", "a_2"], + ["theta_jn", "phi_jl", "tilt_1", "tilt_2", "phi_12", "a_1", "a_2"], ["iota", "s1_x", "s1_y", "s1_z", "s2_x", "s2_y", "s2_z"], ) super().__init__(name_mapping) @@ -48,8 +48,8 @@ def named_transform(x): iota, s1x, s1y, s1z, s2x, s2y, s2z = spin_to_cartesian_spin( x["theta_jn"], x["phi_jl"], - x["theta_1"], - x["theta_2"], + x["tilt_1"], + x["tilt_2"], x["phi_12"], x["a_1"], x["a_2"], diff --git a/src/jimgw/single_event/utils.py b/src/jimgw/single_event/utils.py index e136af02..517b2844 100644 --- a/src/jimgw/single_event/utils.py +++ b/src/jimgw/single_event/utils.py @@ -275,6 +275,7 @@ def eta_to_q(eta: Float) -> Float: temp = 1 / eta / 2 - 1 return temp - (temp**2 - 1) ** 0.5 + def euler_rotation(delta_x: Float[Array, " 3"]): """ Calculate the rotation matrix mapping the vector (0, 0, 1) to delta_x @@ -395,8 +396,8 @@ def theta_phi_to_ra_dec(theta: Float, phi: Float, gmst: Float) -> tuple[Float, F def spin_to_cartesian_spin( thetaJN: Float, phiJL: Float, - theta1: Float, - theta2: Float, + tilt1: Float, + tilt2: Float, phi12: Float, chi1: Float, chi2: Float, @@ -417,9 +418,9 @@ def spin_to_cartesian_spin( Zenith angle between the total angular momentum and the line of sight phiJL: Float Difference between total and orbital angular momentum azimuthal angles - theta1: Float + tilt1: Float Zenith angle between the spin and orbital angular momenta for the primary object - theta2: Float + tilt2: Float Zenith angle between the spin and orbital angular momenta for the secondary object phi12: Float Difference between the azimuthal angles of the individual spin vector projections @@ -483,16 +484,16 @@ def rotate_z(angle, vec): s1hat = jnp.array( [ - jnp.sin(theta1) * jnp.cos(phiRef), - jnp.sin(theta1) * jnp.sin(phiRef), - jnp.cos(theta1), + jnp.sin(tilt1) * jnp.cos(phiRef), + jnp.sin(tilt1) * jnp.sin(phiRef), + jnp.cos(tilt1), ] ) s2hat = jnp.array( [ - jnp.sin(theta2) * jnp.cos(phi12 + phiRef), - jnp.sin(theta2) * jnp.sin(phi12 + phiRef), - jnp.cos(theta2), + jnp.sin(tilt2) * jnp.cos(phi12 + phiRef), + jnp.sin(tilt2) * jnp.sin(phi12 + phiRef), + jnp.cos(tilt2), ] ) From 95073104f9b9d0579da5e053bcdae0ed167267e4 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Fri, 20 Sep 2024 13:48:25 -0400 Subject: [PATCH 229/248] update Pv2 --- example/GW150914_IMRPhenomPV2.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/example/GW150914_IMRPhenomPV2.py b/example/GW150914_IMRPhenomPV2.py index ef35f5cf..00a410cc 100644 --- a/example/GW150914_IMRPhenomPV2.py +++ b/example/GW150914_IMRPhenomPV2.py @@ -20,8 +20,10 @@ from jimgw.single_event.transforms import ( SkyFrameToDetectorFrameSkyPositionTransform, SpinToCartesianSpinTransform, - MassRatioToSymmetricMassRatioTransform - + MassRatioToSymmetricMassRatioTransform, + DistanceToSNRWeightedDistanceTransform, + GeocentricArrivalTimeToDetectorArrivalTimeTransform, + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform, ) from jimgw.single_event.utils import Mc_q_to_m1_m2 from flowMC.strategy.optimization import optimization_Adam @@ -106,7 +108,10 @@ # Defining Transforms sample_transforms = [ - # ComponentMassesToChirpMassMassRatioTransform, + DistanceToSNRWeightedDistanceTransform(gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(gps_time=gps, ifo=ifos[0]), + GeocentricArrivalTimeToDetectorArrivalTimeTransform(tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, gps_time=gps, ifo=ifos[0]), + SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), BoundToUnbound(name_mapping = (["M_c"], ["M_c_unbounded"]), original_lower_bound=M_c_min, original_upper_bound=M_c_max), BoundToUnbound(name_mapping = (["q"], ["q_unbounded"]), original_lower_bound=q_min, original_upper_bound=q_max), BoundToUnbound(name_mapping = (["theta_jn"], ["theta_jn_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), @@ -116,11 +121,8 @@ BoundToUnbound(name_mapping = (["phi_12"], ["phi_12_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = (["a_1"], ["a_1_unbounded"]) , original_lower_bound=0.0, original_upper_bound=1.0), BoundToUnbound(name_mapping = (["a_2"], ["a_2_unbounded"]) , original_lower_bound=0.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = (["d_L"], ["d_L_unbounded"]) , original_lower_bound=10.0, original_upper_bound=2000.0), - BoundToUnbound(name_mapping = (["t_c"], ["t_c_unbounded"]) , original_lower_bound=-0.05, original_upper_bound=0.05), - BoundToUnbound(name_mapping = (["phase_c"], ["phase_c_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["phase_det"], ["phase_det_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = (["psi"], ["psi_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), - SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), BoundToUnbound(name_mapping = (["zenith"], ["zenith_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = (["azimuth"], ["azimuth_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), ] From 7dd5145b696bb7e248f5e63ecef510c9207d7d98 Mon Sep 17 00:00:00 2001 From: kazewong Date: Fri, 20 Sep 2024 14:04:45 -0400 Subject: [PATCH 230/248] update pv2 --- example/GW150914_IMRPhenomPV2.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/example/GW150914_IMRPhenomPV2.py b/example/GW150914_IMRPhenomPV2.py index 00a410cc..c87d0fee 100644 --- a/example/GW150914_IMRPhenomPV2.py +++ b/example/GW150914_IMRPhenomPV2.py @@ -88,7 +88,6 @@ dL_prior = PowerLawPrior(1.0, 2000.0, 2.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) -iota_prior = SinePrior(parameter_names=["iota"]) psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) dec_prior = CosinePrior(parameter_names=["dec"]) @@ -97,7 +96,6 @@ dL_prior, t_c_prior, phase_c_prior, - iota_prior, psi_prior, ra_prior, dec_prior, @@ -108,6 +106,7 @@ # Defining Transforms sample_transforms = [ + SpinToCartesianSpinTransform(freq_ref=20.), DistanceToSNRWeightedDistanceTransform(gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(gps_time=gps, ifo=ifos[0]), GeocentricArrivalTimeToDetectorArrivalTimeTransform(tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, gps_time=gps, ifo=ifos[0]), @@ -128,7 +127,6 @@ ] likelihood_transforms = [ - SpinToCartesianSpinTransform(freq_ref=20.), MassRatioToSymmetricMassRatioTransform, ] From 4cf6b2748555590247232c37f521074e29024e89 Mon Sep 17 00:00:00 2001 From: kazewong Date: Sat, 5 Oct 2024 16:55:57 -0400 Subject: [PATCH 231/248] update transform name --- src/jimgw/single_event/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index b3062da9..9297029d 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -25,7 +25,7 @@ @jaxtyped(typechecker=typechecker) -class SpinToCartesianSpinTransform(NtoNTransform): +class PrecessingSpinToCartesianSpinTransform(NtoNTransform): """ Spin to Cartesian spin transformation """ From 66027c333b4a5e136b7e27cf1f9eddbe2bd7d943 Mon Sep 17 00:00:00 2001 From: kazewong Date: Mon, 7 Oct 2024 08:38:28 -0400 Subject: [PATCH 232/248] fix Pv2 prior and transform --- example/GW150914_IMRPhenomPV2.py | 40 +++++++++++--------------- src/jimgw/jim.py | 2 +- src/jimgw/single_event/transforms.py | 42 ++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/example/GW150914_IMRPhenomPV2.py b/example/GW150914_IMRPhenomPV2.py index c87d0fee..c291adbf 100644 --- a/example/GW150914_IMRPhenomPV2.py +++ b/example/GW150914_IMRPhenomPV2.py @@ -19,7 +19,7 @@ from jimgw.transforms import BoundToUnbound from jimgw.single_event.transforms import ( SkyFrameToDetectorFrameSkyPositionTransform, - SpinToCartesianSpinTransform, + SphereSpinToCartesianSpinTransform, MassRatioToSymmetricMassRatioTransform, DistanceToSNRWeightedDistanceTransform, GeocentricArrivalTimeToDetectorArrivalTimeTransform, @@ -65,23 +65,14 @@ prior = prior + [Mc_prior, q_prior] # Spin prior -a_1_prior = UniformPrior(0.0, 1.0, parameter_names=["a_1"]) -a_2_prior = UniformPrior(0.0, 1.0, parameter_names=["a_2"]) -theta_jn_prior = SinePrior(parameter_names=["theta_jn"]) -phi_jl_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_jl"]) -tilt_1_prior = SinePrior(parameter_names=["tilt_1"]) -tilt_2_prior = SinePrior(parameter_names=["tilt_2"]) -phi_12_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_12"]) - +s1_prior = UniformSpherePrior(parameter_names=["s1"]) +s2_prior = UniformSpherePrior(parameter_names=["s2"]) +iota_prior = SinePrior(parameter_names=["iota"]) prior = prior + [ - a_1_prior, - a_2_prior, - theta_jn_prior, - phi_jl_prior, - tilt_1_prior, - tilt_2_prior, - phi_12_prior, + s1_prior, + s2_prior, + iota_prior, ] # Extrinsic prior @@ -106,20 +97,19 @@ # Defining Transforms sample_transforms = [ - SpinToCartesianSpinTransform(freq_ref=20.), DistanceToSNRWeightedDistanceTransform(gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(gps_time=gps, ifo=ifos[0]), GeocentricArrivalTimeToDetectorArrivalTimeTransform(tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, gps_time=gps, ifo=ifos[0]), SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), BoundToUnbound(name_mapping = (["M_c"], ["M_c_unbounded"]), original_lower_bound=M_c_min, original_upper_bound=M_c_max), BoundToUnbound(name_mapping = (["q"], ["q_unbounded"]), original_lower_bound=q_min, original_upper_bound=q_max), - BoundToUnbound(name_mapping = (["theta_jn"], ["theta_jn_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = (["phi_jl"], ["phi_jl_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = (["tilt_1"], ["tilt_1_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = (["tilt_2"], ["tilt_2_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = (["phi_12"], ["phi_12_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = (["a_1"], ["a_1_unbounded"]) , original_lower_bound=0.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = (["a_2"], ["a_2_unbounded"]) , original_lower_bound=0.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = (["s1_phi"], ["s1_phi_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["s2_phi"], ["s2_phi_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["iota"], ["iota_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["s1_theta"], ["s1_theta_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["s2_theta"], ["s2_theta_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["s1_mag"], ["s1_mag_unbounded"]) , original_lower_bound=0.0, original_upper_bound=0.99), + BoundToUnbound(name_mapping = (["s2_mag"], ["s2_mag_unbounded"]) , original_lower_bound=0.0, original_upper_bound=0.99), BoundToUnbound(name_mapping = (["phase_det"], ["phase_det_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), BoundToUnbound(name_mapping = (["psi"], ["psi_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), BoundToUnbound(name_mapping = (["zenith"], ["zenith_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), @@ -128,6 +118,8 @@ likelihood_transforms = [ MassRatioToSymmetricMassRatioTransform, + SphereSpinToCartesianSpinTransform("s1"), + SphereSpinToCartesianSpinTransform("s2"), ] diff --git a/src/jimgw/jim.py b/src/jimgw/jim.py index 7f16532d..805e9268 100644 --- a/src/jimgw/jim.py +++ b/src/jimgw/jim.py @@ -217,7 +217,7 @@ def get_samples(self, training: bool = False) -> dict: chains = self.sampler.get_sampler_state(training=False)["chains"] chains = chains.reshape(-1, self.prior.n_dim) - chains = self.add_name(chains) + chains = jax.vmap(self.add_name)(chains) for sample_transform in reversed(self.sample_transforms): chains = jax.vmap(sample_transform.backward)(chains) return chains diff --git a/src/jimgw/single_event/transforms.py b/src/jimgw/single_event/transforms.py index 9297029d..e9983f06 100644 --- a/src/jimgw/single_event/transforms.py +++ b/src/jimgw/single_event/transforms.py @@ -71,6 +71,48 @@ def named_transform(x): self.transform_func = named_transform +@jaxtyped(typechecker=typechecker) +class SphereSpinToCartesianSpinTransform(BijectiveTransform): + """ + Spin to Cartesian spin transformation + """ + + def __init__( + self, + label: str, + ): + name_mapping = ( + [label + "_mag", label + "_theta", label + "_phi"], + [label + "_x", label + "_y", label + "_z"], + ) + super().__init__(name_mapping) + + def named_transform(x): + mag, theta, phi = x[label + "_mag"], x[label + "_theta"], x[label + "_phi"] + x = mag * jnp.sin(theta) * jnp.cos(phi) + y = mag * jnp.sin(theta) * jnp.sin(phi) + z = mag * jnp.cos(theta) + return { + label + "_x": x, + label + "_y": y, + label + "_z": z, + } + + def named_inverse_transform(x): + x, y, z = x[label + "_x"], x[label + "_y"], x[label + "_z"] + mag = jnp.sqrt(x**2 + y**2 + z**2) + theta = jnp.arccos(z / mag) + phi = jnp.arctan2(y, x) + return { + label + "_mag": mag, + label + "_theta": theta, + label + "_phi": phi, + } + + self.transform_func = named_transform + self.inverse_transform_func = named_inverse_transform + + @jaxtyped(typechecker=typechecker) class SkyFrameToDetectorFrameSkyPositionTransform(BijectiveTransform): """ From d69c05a7d1ac3916afbce1842ea80f6169285e78 Mon Sep 17 00:00:00 2001 From: kazewong Date: Sat, 12 Oct 2024 22:42:07 -0400 Subject: [PATCH 233/248] move prior functions to the right place. Also add magnitude options to UniformSphere --- src/jimgw/prior.py | 19 ++++++++++++++++-- src/jimgw/single_event/prior.py | 35 --------------------------------- 2 files changed, 17 insertions(+), 37 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 5227ffa5..3db3f122 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -331,7 +331,7 @@ class UniformSpherePrior(CombinePrior): def __repr__(self): return f"UniformSpherePrior(parameter_names={self.parameter_names})" - def __init__(self, parameter_names: list[str], **kwargs): + def __init__(self, parameter_names: list[str], max_mag: float = 1.0, **kwargs): self.parameter_names = parameter_names assert self.n_dim == 1, "UniformSpherePrior only takes the name of the vector" self.parameter_names = [ @@ -341,7 +341,7 @@ def __init__(self, parameter_names: list[str], **kwargs): ] super().__init__( [ - UniformPrior(0.0, 1.0, [self.parameter_names[0]]), + UniformPrior(0.0, max_mag, [self.parameter_names[0]]), SinePrior([self.parameter_names[1]]), UniformPrior(0.0, 2 * jnp.pi, [self.parameter_names[2]]), ] @@ -397,6 +397,21 @@ def __init__( ], ) +def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: + if prior.composite: + if isinstance(prior.base_prior, list): + for subprior in prior.base_prior: + output = trace_prior_parent(subprior, output) + elif isinstance(prior.base_prior, Prior): + output = trace_prior_parent(prior.base_prior, output) + else: + output.append(prior) + + return output + + + + # ====================== Things below may need rework ====================== diff --git a/src/jimgw/single_event/prior.py b/src/jimgw/single_event/prior.py index 51a754eb..76ca6376 100644 --- a/src/jimgw/single_event/prior.py +++ b/src/jimgw/single_event/prior.py @@ -11,28 +11,6 @@ ) -@jaxtyped(typechecker=typechecker) -class UniformSpherePrior(CombinePrior): - - def __repr__(self): - return f"UniformSpherePrior(parameter_names={self.parameter_names})" - - def __init__(self, parameter_names: list[str], **kwargs): - self.parameter_names = parameter_names - assert self.n_dim == 1, "UniformSpherePrior only takes the name of the vector" - self.parameter_names = [ - f"{self.parameter_names[0]}_mag", - f"{self.parameter_names[0]}_theta", - f"{self.parameter_names[0]}_phi", - ] - super().__init__( - [ - UniformPrior(0.0, 1.0, [self.parameter_names[0]]), - SinePrior([self.parameter_names[1]]), - UniformPrior(0.0, 2 * jnp.pi, [self.parameter_names[2]]), - ] - ) - @jaxtyped(typechecker=typechecker) class UniformComponentChirpMassPrior(PowerLawPrior): @@ -50,19 +28,6 @@ def __init__(self, xmin: float, xmax: float): super().__init__(xmin, xmax, 1.0, ["M_c"]) -def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: - if prior.composite: - if isinstance(prior.base_prior, list): - for subprior in prior.base_prior: - output = trace_prior_parent(subprior, output) - elif isinstance(prior.base_prior, Prior): - output = trace_prior_parent(prior.base_prior, output) - else: - output.append(prior) - - return output - - # ====================== Things below may need rework ====================== From d062ee111e58a765256e347a68e91a0d5f580047 Mon Sep 17 00:00:00 2001 From: kazewong Date: Sat, 12 Oct 2024 22:43:05 -0400 Subject: [PATCH 234/248] format --- src/jimgw/prior.py | 4 +-- src/jimgw/single_event/likelihood.py | 41 ++++++++++++++++++++-------- src/jimgw/single_event/prior.py | 6 ---- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/jimgw/prior.py b/src/jimgw/prior.py index 3db3f122..87406ca8 100644 --- a/src/jimgw/prior.py +++ b/src/jimgw/prior.py @@ -397,6 +397,7 @@ def __init__( ], ) + def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: if prior.composite: if isinstance(prior.base_prior, list): @@ -410,9 +411,6 @@ def trace_prior_parent(prior: Prior, output: list[Prior] = []) -> list[Prior]: return output - - - # ====================== Things below may need rework ====================== # @jaxtyped(typechecker=typechecker) diff --git a/src/jimgw/single_event/likelihood.py b/src/jimgw/single_event/likelihood.py index 9e775b33..96e11e62 100644 --- a/src/jimgw/single_event/likelihood.py +++ b/src/jimgw/single_event/likelihood.py @@ -584,18 +584,35 @@ def y(x): ) key = jax.random.PRNGKey(0) - initial_position = [] - for _ in range(popsize): - flag = True - while flag: - key = jax.random.split(key)[1] - guess = prior.sample(key, 1) - for transform in sample_transforms: - guess = transform.forward(guess) - guess = jnp.array([i for i in guess.values()]).T[0] - flag = not jnp.all(jnp.isfinite(guess)) - initial_position.append(guess) - initial_position = jnp.array(initial_position) + initial_position = jnp.zeros((popsize, prior.n_dim)) + jnp.nan + while not jax.tree.reduce( + jnp.logical_and, jax.tree.map(lambda x: jnp.isfinite(x), initial_position) + ).all(): + non_finite_index = jnp.where( + jnp.any( + ~jax.tree.reduce( + jnp.logical_and, + jax.tree.map(lambda x: jnp.isfinite(x), initial_position), + ), + axis=1, + ) + )[0] + + key, subkey = jax.random.split(key) + guess = prior.sample(subkey, popsize) + for transform in sample_transforms: + guess = jax.vmap(transform.forward)(guess) + guess = jnp.array( + jax.tree.leaves({key: guess[key] for key in parameter_names}) + ).T + finite_guess = jnp.where( + jnp.all(jax.tree.map(lambda x: jnp.isfinite(x), guess), axis=1) + )[0] + common_length = min(len(finite_guess), len(non_finite_index)) + initial_position = initial_position.at[ + non_finite_index[:common_length] + ].set(guess[:common_length]) + rng_key, optimized_positions, summary = optimizer.optimize( jax.random.PRNGKey(12094), y, initial_position ) diff --git a/src/jimgw/single_event/prior.py b/src/jimgw/single_event/prior.py index 76ca6376..194262f0 100644 --- a/src/jimgw/single_event/prior.py +++ b/src/jimgw/single_event/prior.py @@ -1,17 +1,11 @@ -import jax.numpy as jnp from beartype import beartype as typechecker from jaxtyping import jaxtyped from jimgw.prior import ( - Prior, - CombinePrior, - UniformPrior, PowerLawPrior, - SinePrior, ) - @jaxtyped(typechecker=typechecker) class UniformComponentChirpMassPrior(PowerLawPrior): """ From c5957b451011621516602ca21c0fe6d09a41fc6b Mon Sep 17 00:00:00 2001 From: kazewong Date: Sat, 12 Oct 2024 22:43:32 -0400 Subject: [PATCH 235/248] Add GW170817 example --- example/GW170817_IMRPhenomPV2.py | 190 +++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 example/GW170817_IMRPhenomPV2.py diff --git a/example/GW170817_IMRPhenomPV2.py b/example/GW170817_IMRPhenomPV2.py new file mode 100644 index 00000000..55bd211b --- /dev/null +++ b/example/GW170817_IMRPhenomPV2.py @@ -0,0 +1,190 @@ +import time + +import jax +import jax.numpy as jnp + +from jimgw.jim import Jim +from jimgw.jim import Jim +from jimgw.prior import ( + CombinePrior, + UniformPrior, + CosinePrior, + SinePrior, + PowerLawPrior, + UniformSpherePrior, +) +from jimgw.single_event.detector import H1, L1, V1 +from jimgw.single_event.likelihood import TransientLikelihoodFD, HeterodynedTransientLikelihoodFD +from jimgw.single_event.waveform import RippleIMRPhenomPv2 +from jimgw.transforms import BoundToUnbound +from jimgw.single_event.transforms import ( + SkyFrameToDetectorFrameSkyPositionTransform, + SphereSpinToCartesianSpinTransform, + MassRatioToSymmetricMassRatioTransform, + DistanceToSNRWeightedDistanceTransform, + GeocentricArrivalTimeToDetectorArrivalTimeTransform, + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform, +) +from jimgw.single_event.utils import Mc_q_to_m1_m2 +from flowMC.strategy.optimization import optimization_Adam + +jax.config.update("jax_enable_x64", True) + +########################################### +########## First we grab data ############# +########################################### + +total_time_start = time.time() + +# first, fetch a 4s segment centered on GW150914 + +gps = 1187008882.43 +trigger_time = gps +fmin = 20 +fmax = 2048 +minimum_frequency = fmin +maximum_frequency = fmax +duration = 128 +post_trigger_duration = 2 +epoch = duration - post_trigger_duration +f_ref = fmin + +ifos = [H1, L1, V1] + + +tukey_alpha = 2 / (duration / 2) +H1.load_data( + gps, duration, 2, fmin, fmax, psd_pad=duration + 16, tukey_alpha=tukey_alpha +) +L1.load_data( + gps, duration, 2, fmin, fmax, psd_pad=duration + 16, tukey_alpha=tukey_alpha +) +V1.load_data( + gps, duration, 2, fmin, fmax, psd_pad=duration + 16, tukey_alpha=tukey_alpha +) + + +waveform = RippleIMRPhenomPv2(f_ref=f_ref) + +########################################### +########## Set up priors ################## +########################################### + +prior = [] + +# Mass prior +M_c_min, M_c_max = 1.18, 1.21 +q_min, q_max = 0.125, 1.0 +Mc_prior = UniformPrior(M_c_min, M_c_max, parameter_names=["M_c"]) +q_prior = UniformPrior(q_min, q_max, parameter_names=["q"]) + +prior = prior + [Mc_prior, q_prior] + +# Spin prior +s1_prior = UniformSpherePrior(parameter_names=["s1"], max_mag = 0.05) +s2_prior = UniformSpherePrior(parameter_names=["s2"], max_mag = 0.05) +iota_prior = SinePrior(parameter_names=["iota"]) + +prior = prior + [ + s1_prior, + s2_prior, + iota_prior, +] + +# Extrinsic prior +dL_prior = PowerLawPrior(1.0, 75.0, 2.0, parameter_names=["d_L"]) +t_c_prior = UniformPrior(-0.1, 0.1, parameter_names=["t_c"]) +phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) +psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) +ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) +dec_prior = CosinePrior(parameter_names=["dec"]) + +prior = prior + [ + dL_prior, + t_c_prior, + phase_c_prior, + psi_prior, + ra_prior, + dec_prior, +] + +prior = CombinePrior(prior) + +# Defining Transforms + +sample_transforms = [ + DistanceToSNRWeightedDistanceTransform(gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(gps_time=gps, ifo=ifos[0]), + GeocentricArrivalTimeToDetectorArrivalTimeTransform(tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, gps_time=gps, ifo=ifos[0]), + SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), + BoundToUnbound(name_mapping = (["M_c"], ["M_c_unbounded"]), original_lower_bound=M_c_min, original_upper_bound=M_c_max), + BoundToUnbound(name_mapping = (["q"], ["q_unbounded"]), original_lower_bound=q_min, original_upper_bound=q_max), + BoundToUnbound(name_mapping = (["s1_phi"], ["s1_phi_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["s2_phi"], ["s2_phi_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["iota"], ["iota_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["s1_theta"], ["s1_theta_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["s2_theta"], ["s2_theta_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["s1_mag"], ["s1_mag_unbounded"]) , original_lower_bound=0.0, original_upper_bound=0.05), + BoundToUnbound(name_mapping = (["s2_mag"], ["s2_mag_unbounded"]) , original_lower_bound=0.0, original_upper_bound=0.05), + BoundToUnbound(name_mapping = (["phase_det"], ["phase_det_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["psi"], ["psi_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["zenith"], ["zenith_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["azimuth"], ["azimuth_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), +] + +likelihood_transforms = [ + MassRatioToSymmetricMassRatioTransform, + SphereSpinToCartesianSpinTransform("s1"), + SphereSpinToCartesianSpinTransform("s2"), +] + + +#likelihood = TransientLikelihoodFD( +# [H1, L1, V1], waveform=waveform, trigger_time=trigger_time, duration=duration, post_trigger_duration=post_trigger_duration +#) + +likelihood = HeterodynedTransientLikelihoodFD(ifos, waveform=waveform, n_bins = 1000, trigger_time=trigger_time, duration=duration, post_trigger_duration=post_trigger_duration, prior = prior, sample_transforms = sample_transforms, likelihood_transforms = likelihood_transforms, popsize = 10, n_steps = 50) + +mass_matrix = jnp.eye(prior.n_dim) +# mass_matrix = mass_matrix.at[1, 1].set(1e-3) +# mass_matrix = mass_matrix.at[9, 9].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 1e-3} + +Adam_optimizer = optimization_Adam(n_steps=3000, learning_rate=0.01, noise_level=1) + +import optax + +n_epochs = 20 +n_loop_training = 100 +total_epochs = n_epochs * n_loop_training +start = total_epochs // 10 +learning_rate = optax.polynomial_schedule( + 1e-3, 1e-4, 4.0, total_epochs - start, transition_begin=start +) + +jim = Jim( + likelihood, + prior, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, + n_loop_training=n_loop_training, + n_loop_production=20, + n_local_steps=10, + n_global_steps=1000, + n_chains=500, + n_epochs=n_epochs, + learning_rate=learning_rate, + n_max_examples=30000, + n_flow_sample=100000, + momentum=0.9, + batch_size=30000, + use_global=True, + keep_quantile=0.0, + train_thinning=1, + output_thinning=10, + local_sampler_arg=local_sampler_arg, + # strategies=[Adam_optimizer,"default"], +) + + +jim.sample(jax.random.PRNGKey(42)) From f9080a2727fa4294e82210bacaed5a804e2b26ab Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Wed, 16 Oct 2024 13:40:53 -0400 Subject: [PATCH 236/248] Use uv for package management --- pyproject.toml | 23 +- setup.cfg | 31 - uv.lock | 1480 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1497 insertions(+), 37 deletions(-) delete mode 100644 setup.cfg create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml index 006e01c4..8480b244 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,17 @@ -[build-system] -requires = ["setuptools","wheel"] -build-backend = "setuptools.build_meta" - -[tool.pyright] -reportIncompatibleMethodOverride = "warning" \ No newline at end of file +[project] +name = "jim" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "astropy>=6.1.4", + "beartype>=0.19.0", + "corner>=2.2.2", + "flowmc>=0.3.4", + "gwpy>=3.0.10", + "jax>=0.4.34", + "jaxtyping>=0.2.34", + "ripplegw>=0.0.9", + "typed-argument-parser>=1.10.1", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e2fbd986..00000000 --- a/setup.cfg +++ /dev/null @@ -1,31 +0,0 @@ -[metadata] -name = jimGW -version = 0.1.1 -author = Kaze Wong -author_email = kazewong.physics@gmail.com -url = https://github.com/kazewong/jim -description = Gravitatioanl wave data analysis tool in Jax -long_description = file: README.md -long_description_content_type = text/markdown -keywords = sampling, inference, machine learning, normalizing, autodiff, jax -license = MIT - -[options] -packages_dir= - =src -packages = find: -install_requires = - jax>=0.4.12 - jaxlib>=0.4.12 - flowMC>=0.3.4 - ripplegw - gwpy - corner - astropy - typed-argument-parser - jaxtyping>=0.2.31 - beartype -python_requires = >=3.9 - -[options.packages.find] -where=src diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..fecaeb66 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1480 @@ +version = 1 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version < '3.12'", + "python_full_version == '3.12.*'", + "python_full_version >= '3.13'", +] + +[[package]] +name = "absl-py" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/8f/fc001b92ecc467cc32ab38398bd0bfb45df46e7523bf33c2ad22a505f06e/absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff", size = 118055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/ad/e0d3c824784ff121c03cc031f944bc7e139a8f1870ffd2845cc2dd76f6c4/absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", size = 133706 }, +] + +[[package]] +name = "astropy" +version = "6.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astropy-iers-data" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyerfa" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/25/adf5ee49f3990fc5cde050cce9c6f0a74a78af4eb18bd3aec8f1eecd1a26/astropy-6.1.4.tar.gz", hash = "sha256:361558e2b093a99bebe69f1fd47fac86a192607a4c16ed39ba0a800b2ab60c34", size = 7057481 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/2b/50e0fe0d750d87573d7bd0fd8f0d37210c6f04fc924c29995945ed5d0def/astropy-6.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:601d8a9a8d44f45064d2d8cc963cecf3949d3fbc10c9b6412c241cdc0c686b2c", size = 6527451 }, + { url = "https://files.pythonhosted.org/packages/15/0b/2ca63201730c3cb1335b370ee8800ae6ba16fbfd6e47958771d7b667bdb1/astropy-6.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfc23b9e5e899f66f1377dc1116c247e6c7d0f92623ca115ad084a297414de03", size = 6405709 }, + { url = "https://files.pythonhosted.org/packages/9b/10/e98e7e985ac50ff75c73a925ce3403aebc6c08307ace54e56981ccd3efe8/astropy-6.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52683072d106162ca124e09f2f132de58ca9756e04c25ebd45d56cbdd6feb8f7", size = 10146911 }, + { url = "https://files.pythonhosted.org/packages/d3/8f/5cffeea42c7cde892aa4e0209ec1b3b989bbb7b5c5fc711bce049468d465/astropy-6.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50ab8d8097df76e33b56ab429d07240df6f273ccd267949ff99f8df79c7fcc42", size = 10206852 }, + { url = "https://files.pythonhosted.org/packages/ea/22/d44fee1a76eb93f47eefc11e38c9f8969c39dfe17c6a7b0365d9c506022e/astropy-6.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:81152825e80a03562cf2b95164c0ae7f56cd3521208a13221b3afa683c1469a6", size = 10242130 }, + { url = "https://files.pythonhosted.org/packages/fd/2a/0541025e01e5247ca3ab7994f14ce78f7eb5f4aa0f8c84899e88ddd8dee3/astropy-6.1.4-cp311-cp311-win32.whl", hash = "sha256:af49e0a80ee6243727f449a6d8c99e711b2a4fdd1701d92c42964cd3e01a3490", size = 6268705 }, + { url = "https://files.pythonhosted.org/packages/5e/26/95178806d3ec81c256e59ca602102249d5996bf75448c99d00a3a09f01b6/astropy-6.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:b2fa9e1031eb83bee2d0b29f3e4d85a9d8b38ecb1b759a0ffbb8145fe8685d0e", size = 6393199 }, + { url = "https://files.pythonhosted.org/packages/0f/f6/6906f3adf452b829776577462ab0450835723fae7fa3a1d4bc09350f053a/astropy-6.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cf4ce31973dd18da522bb8f0fac91685ab1fcf21ca81251c79b09ef27d2b0d8f", size = 6531832 }, + { url = "https://files.pythonhosted.org/packages/a9/c5/4946b949b1adfdef30f393cd9bc05dcec8cbe3b860e868a6f805ad231156/astropy-6.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d4d10d964bfd110751fd47132af5227807d2beca85669b3d00d29cf5bae167d", size = 6411341 }, + { url = "https://files.pythonhosted.org/packages/98/c9/e18849886c84177f84c81204b93ae013b390ba17c2668efb8b758b88f16d/astropy-6.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26c6cc099a312aaf0e473a4ea2ac4279f79ef53c8413e8259758d0f5930b4745", size = 10174046 }, + { url = "https://files.pythonhosted.org/packages/8c/23/dec61952031ba6e8c3e22a2027febccab4b97a96bc967cc3ed0a17cfd0df/astropy-6.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3085e201e9d4bd223a543f7e2c0810dc873ea6a81bbdf06b5a987aa10e09bce", size = 10254180 }, + { url = "https://files.pythonhosted.org/packages/95/6b/d26ccd59f2d78e9141b0fea7f7d8a7ff783a5ae365a9dc6b9b5a44ec9ef4/astropy-6.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1598720ac43875e602f1fe9766133ab67d50164047b5ed18f5e35d2fa69e914", size = 10273538 }, + { url = "https://files.pythonhosted.org/packages/03/57/18616baa4c525a032ecd6404a736582420b9f059532ab9f6a6227e2712fd/astropy-6.1.4-cp312-cp312-win32.whl", hash = "sha256:59576f354772c448300438acc910b5a37b9d15ddfaa5c942746a7253a6c7765f", size = 6268393 }, + { url = "https://files.pythonhosted.org/packages/67/59/ba3a3e9ef57021c5081fd79f3cafddc67dd2503678b40e9d5c72381ac254/astropy-6.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:27496654ae2e672fa13eee9200caa776f47fbdce9ece55adcfe1d7e37e80341e", size = 6392567 }, + { url = "https://files.pythonhosted.org/packages/19/88/30f91cd3f33a101d496704ba3696d30c17bec3ec05134610dca6d70d3079/astropy-6.1.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:024cbba2b20d6abf50395669a010b1c026a0b966ba2292addd354163b0b44e55", size = 6524601 }, + { url = "https://files.pythonhosted.org/packages/57/35/07b36c4d11e1edee72708449cc4062bf6209e517388fb42d219940afcfc7/astropy-6.1.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aeec3aa6c12f613b2609dfbf9bac355b5e64446e67643637ee449bdcf583f5e2", size = 6404147 }, + { url = "https://files.pythonhosted.org/packages/d9/44/15cfbc421d1dec01defc49bfcd3310aea6ee14100ae3a42e6e4b14bbddfb/astropy-6.1.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1ed2dfb1551b2f140ca636c0156ab6a6a13f4202b7f3bc06fa739058f311d18", size = 10103041 }, + { url = "https://files.pythonhosted.org/packages/61/bd/66d543d5e9d31889d48a265c6512eeb0e5bd97c6493fc244b88bc76ff61b/astropy-6.1.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1a48e1dbd8072b3ef8a94334bc5344bf33bfd2b1b0236ca02eaa2cc3ca4b90", size = 10190934 }, + { url = "https://files.pythonhosted.org/packages/1b/73/6d6fd362ff5e3e8e333f126f8163abc7b3c4415473faa6f41bfcd7a323b3/astropy-6.1.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b23cb881cf1fa0795b92ab2d86b04339a0a38b336e3a391fd050b6caf695b01b", size = 10215470 }, + { url = "https://files.pythonhosted.org/packages/ba/23/bda94ccae81d90c8642defa0777424abfef23fe9103128fb3e37bcec3168/astropy-6.1.4-cp313-cp313-win32.whl", hash = "sha256:b28752d9515dac72358bce58214ef4bae89bfc4af548c98f6052ff91b38e93ee", size = 6266143 }, + { url = "https://files.pythonhosted.org/packages/ce/2c/10889bf3a672c470769e79274a3c7c54ba56d51e52a78a8612cd46ccd0ff/astropy-6.1.4-cp313-cp313-win_amd64.whl", hash = "sha256:048ae0883db33f94dea03800211d2e4a0aacaf8d0f0640196815d9d1298b7449", size = 6389682 }, +] + +[[package]] +name = "astropy-iers-data" +version = "0.2024.10.14.0.32.55" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/99/f9c18b1234b7a093be0b6833e77f712a22906996315f5032cfdca5c05980/astropy_iers_data-0.2024.10.14.0.32.55.tar.gz", hash = "sha256:efe7e5b26fa064df08fcda528ea910c79137625208ccc4073addf4f0eb9289a0", size = 1881292 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/68/9af5fb22fc04f54f8a27f6a90abe48707eeaa7c564f4f5e547cb3eecfa11/astropy_iers_data-0.2024.10.14.0.32.55-py3-none-any.whl", hash = "sha256:1a7c8e12bfe2af5605fd46dd9f8cb7fc9e9a78328cd231fdf6753b1814efe597", size = 1933242 }, +] + +[[package]] +name = "beartype" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/e1/00515b97afa3993b4a314e4bc168fbde0917fd5845435cb6f16a19770746/beartype-0.19.0.tar.gz", hash = "sha256:de42dfc1ba5c3710fde6c3002e3bd2cad236ed4d2aabe876345ab0b4234a6573", size = 1294480 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/69/f6db6e4cb2fe2f887dead40b76caa91af4844cb647dd2c7223bb010aa416/beartype-0.19.0-py3-none-any.whl", hash = "sha256:33b2694eda0daf052eb2aff623ed9a8a586703bbf0a90bbc475a83bbf427f699", size = 1039760 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", size = 193339 }, + { url = "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", size = 124366 }, + { url = "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", size = 118874 }, + { url = "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", size = 138243 }, + { url = "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", size = 148676 }, + { url = "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", size = 141289 }, + { url = "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", size = 142585 }, + { url = "https://files.pythonhosted.org/packages/3b/a0/a68980ab8a1f45a36d9745d35049c1af57d27255eff8c907e3add84cf68f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", size = 144408 }, + { url = "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", size = 139076 }, + { url = "https://files.pythonhosted.org/packages/fb/9d/9c13753a5a6e0db4a0a6edb1cef7aee39859177b64e1a1e748a6e3ba62c2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", size = 146874 }, + { url = "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", size = 150871 }, + { url = "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", size = 148546 }, + { url = "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", size = 143048 }, + { url = "https://files.pythonhosted.org/packages/01/f8/38842422988b795220eb8038745d27a675ce066e2ada79516c118f291f07/charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", size = 94389 }, + { url = "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", size = 101752 }, + { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, + { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, + { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, + { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, + { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, + { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, + { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, + { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, + { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, + { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, + { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, + { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, +] + +[[package]] +name = "chex" +version = "0.1.87" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "jax" }, + { name = "jaxlib" }, + { name = "numpy" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "toolz" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/8a/857474810b64ab135a0c3e594b0453c7f39f140757c4cd26a32bccadcbc4/chex-0.1.87.tar.gz", hash = "sha256:0096d89cc8d898bb521ef4bfbf5c24549022b0e5b301f529ab57238896fe6c5d", size = 90063 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/dd/c1ff2eb8fbf95a8ca804abb1cc3ce70b283ee7b4bc653c3abac245670400/chex-0.1.87-py3-none-any.whl", hash = "sha256:ce536475661fd96d21be0c1728ecdbedd03f8ff950c662dfc338c92ea782cb16", size = 99369 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "contourpy" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/f6/31a8f28b4a2a4fa0e01085e542f3081ab0588eff8e589d39d775172c9792/contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4", size = 13464370 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/1f/9375917786cb39270b0ee6634536c0e22abf225825602688990d8f5c6c19/contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad", size = 266356 }, + { url = "https://files.pythonhosted.org/packages/05/46/9256dd162ea52790c127cb58cfc3b9e3413a6e3478917d1f811d420772ec/contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49", size = 250915 }, + { url = "https://files.pythonhosted.org/packages/e1/5d/3056c167fa4486900dfbd7e26a2fdc2338dc58eee36d490a0ed3ddda5ded/contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66", size = 310443 }, + { url = "https://files.pythonhosted.org/packages/ca/c2/1a612e475492e07f11c8e267ea5ec1ce0d89971be496c195e27afa97e14a/contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081", size = 348548 }, + { url = "https://files.pythonhosted.org/packages/45/cf/2c2fc6bb5874158277b4faf136847f0689e1b1a1f640a36d76d52e78907c/contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1", size = 319118 }, + { url = "https://files.pythonhosted.org/packages/03/33/003065374f38894cdf1040cef474ad0546368eea7e3a51d48b8a423961f8/contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d", size = 323162 }, + { url = "https://files.pythonhosted.org/packages/42/80/e637326e85e4105a802e42959f56cff2cd39a6b5ef68d5d9aee3ea5f0e4c/contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c", size = 1265396 }, + { url = "https://files.pythonhosted.org/packages/7c/3b/8cbd6416ca1bbc0202b50f9c13b2e0b922b64be888f9d9ee88e6cfabfb51/contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb", size = 1324297 }, + { url = "https://files.pythonhosted.org/packages/4d/2c/021a7afaa52fe891f25535506cc861c30c3c4e5a1c1ce94215e04b293e72/contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c", size = 171808 }, + { url = "https://files.pythonhosted.org/packages/8d/2f/804f02ff30a7fae21f98198828d0857439ec4c91a96e20cf2d6c49372966/contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67", size = 217181 }, + { url = "https://files.pythonhosted.org/packages/c9/92/8e0bbfe6b70c0e2d3d81272b58c98ac69ff1a4329f18c73bd64824d8b12e/contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f", size = 267838 }, + { url = "https://files.pythonhosted.org/packages/e3/04/33351c5d5108460a8ce6d512307690b023f0cfcad5899499f5c83b9d63b1/contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6", size = 251549 }, + { url = "https://files.pythonhosted.org/packages/51/3d/aa0fe6ae67e3ef9f178389e4caaaa68daf2f9024092aa3c6032e3d174670/contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639", size = 303177 }, + { url = "https://files.pythonhosted.org/packages/56/c3/c85a7e3e0cab635575d3b657f9535443a6f5d20fac1a1911eaa4bbe1aceb/contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c", size = 341735 }, + { url = "https://files.pythonhosted.org/packages/dd/8d/20f7a211a7be966a53f474bc90b1a8202e9844b3f1ef85f3ae45a77151ee/contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06", size = 314679 }, + { url = "https://files.pythonhosted.org/packages/6e/be/524e377567defac0e21a46e2a529652d165fed130a0d8a863219303cee18/contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09", size = 320549 }, + { url = "https://files.pythonhosted.org/packages/0f/96/fdb2552a172942d888915f3a6663812e9bc3d359d53dafd4289a0fb462f0/contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd", size = 1263068 }, + { url = "https://files.pythonhosted.org/packages/2a/25/632eab595e3140adfa92f1322bf8915f68c932bac468e89eae9974cf1c00/contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35", size = 1322833 }, + { url = "https://files.pythonhosted.org/packages/73/e3/69738782e315a1d26d29d71a550dbbe3eb6c653b028b150f70c1a5f4f229/contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb", size = 172681 }, + { url = "https://files.pythonhosted.org/packages/0c/89/9830ba00d88e43d15e53d64931e66b8792b46eb25e2050a88fec4a0df3d5/contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b", size = 218283 }, + { url = "https://files.pythonhosted.org/packages/53/a1/d20415febfb2267af2d7f06338e82171824d08614084714fb2c1dac9901f/contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3", size = 267879 }, + { url = "https://files.pythonhosted.org/packages/aa/45/5a28a3570ff6218d8bdfc291a272a20d2648104815f01f0177d103d985e1/contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7", size = 251573 }, + { url = "https://files.pythonhosted.org/packages/39/1c/d3f51540108e3affa84f095c8b04f0aa833bb797bc8baa218a952a98117d/contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84", size = 303184 }, + { url = "https://files.pythonhosted.org/packages/00/56/1348a44fb6c3a558c1a3a0cd23d329d604c99d81bf5a4b58c6b71aab328f/contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0", size = 340262 }, + { url = "https://files.pythonhosted.org/packages/2b/23/00d665ba67e1bb666152131da07e0f24c95c3632d7722caa97fb61470eca/contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b", size = 313806 }, + { url = "https://files.pythonhosted.org/packages/5a/42/3cf40f7040bb8362aea19af9a5fb7b32ce420f645dd1590edcee2c657cd5/contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da", size = 319710 }, + { url = "https://files.pythonhosted.org/packages/05/32/f3bfa3fc083b25e1a7ae09197f897476ee68e7386e10404bdf9aac7391f0/contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14", size = 1264107 }, + { url = "https://files.pythonhosted.org/packages/1c/1e/1019d34473a736664f2439542b890b2dc4c6245f5c0d8cdfc0ccc2cab80c/contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8", size = 1322458 }, + { url = "https://files.pythonhosted.org/packages/22/85/4f8bfd83972cf8909a4d36d16b177f7b8bdd942178ea4bf877d4a380a91c/contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294", size = 172643 }, + { url = "https://files.pythonhosted.org/packages/cc/4a/fb3c83c1baba64ba90443626c228ca14f19a87c51975d3b1de308dd2cf08/contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087", size = 218301 }, + { url = "https://files.pythonhosted.org/packages/76/65/702f4064f397821fea0cb493f7d3bc95a5d703e20954dce7d6d39bacf378/contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8", size = 278972 }, + { url = "https://files.pythonhosted.org/packages/80/85/21f5bba56dba75c10a45ec00ad3b8190dbac7fd9a8a8c46c6116c933e9cf/contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b", size = 263375 }, + { url = "https://files.pythonhosted.org/packages/0a/64/084c86ab71d43149f91ab3a4054ccf18565f0a8af36abfa92b1467813ed6/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973", size = 307188 }, + { url = "https://files.pythonhosted.org/packages/3d/ff/d61a4c288dc42da0084b8d9dc2aa219a850767165d7d9a9c364ff530b509/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18", size = 345644 }, + { url = "https://files.pythonhosted.org/packages/ca/aa/00d2313d35ec03f188e8f0786c2fc61f589306e02fdc158233697546fd58/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8", size = 317141 }, + { url = "https://files.pythonhosted.org/packages/8d/6a/b5242c8cb32d87f6abf4f5e3044ca397cb1a76712e3fa2424772e3ff495f/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6", size = 323469 }, + { url = "https://files.pythonhosted.org/packages/6f/a6/73e929d43028a9079aca4bde107494864d54f0d72d9db508a51ff0878593/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2", size = 1260894 }, + { url = "https://files.pythonhosted.org/packages/2b/1e/1e726ba66eddf21c940821df8cf1a7d15cb165f0682d62161eaa5e93dae1/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927", size = 1314829 }, +] + +[[package]] +name = "corner" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/12/b738e24c8241c9be1566009d6d6fe78f26933e9d17fa4d4ac72eff43ae74/corner-2.2.2.tar.gz", hash = "sha256:4bc79f3b6778c270103f0926e64ef2606c48c3b6f92daf5382fc4babf5d608d1", size = 19906 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/c0/dca9f4801daa879f3bd483299e4f3829fc73a405641ebd12888d21cf98ec/corner-2.2.2-py3-none-any.whl", hash = "sha256:e7577cdb59cfa304effa243b0c7ac0e3777030d3dc2f2e217a387e87a47074bb", size = 15860 }, +] + +[[package]] +name = "cryptography" +version = "43.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/ba/0664727028b37e249e73879348cc46d45c5c1a2a2e81e8166462953c5755/cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", size = 686927 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/28/b92c98a04ba762f8cdeb54eba5c4c84e63cac037a7c5e70117d337b15ad6/cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", size = 6223222 }, + { url = "https://files.pythonhosted.org/packages/33/13/1193774705783ba364121aa2a60132fa31a668b8ababd5edfa1662354ccd/cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", size = 3794751 }, + { url = "https://files.pythonhosted.org/packages/5e/4b/39bb3c4c8cfb3e94e736b8d8859ce5c81536e91a1033b1d26770c4249000/cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", size = 3981827 }, + { url = "https://files.pythonhosted.org/packages/ce/dc/1471d4d56608e1013237af334b8a4c35d53895694fbb73882d1c4fd3f55e/cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", size = 3780034 }, + { url = "https://files.pythonhosted.org/packages/ad/43/7a9920135b0d5437cc2f8f529fa757431eb6a7736ddfadfdee1cc5890800/cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", size = 3993407 }, + { url = "https://files.pythonhosted.org/packages/cc/42/9ab8467af6c0b76f3d9b8f01d1cf25b9c9f3f2151f4acfab888d21c55a72/cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", size = 3886457 }, + { url = "https://files.pythonhosted.org/packages/a4/65/430509e31700286ec02868a2457d2111d03ccefc20349d24e58d171ae0a7/cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", size = 4081499 }, + { url = "https://files.pythonhosted.org/packages/bb/18/a04b6467e6e09df8c73b91dcee8878f4a438a43a3603dc3cd6f8003b92d8/cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", size = 2616504 }, + { url = "https://files.pythonhosted.org/packages/cc/73/0eacbdc437202edcbdc07f3576ed8fb8b0ab79d27bf2c5d822d758a72faa/cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", size = 3067456 }, + { url = "https://files.pythonhosted.org/packages/8a/b6/bc54b371f02cffd35ff8dc6baba88304d7cf8e83632566b4b42e00383e03/cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", size = 6225263 }, + { url = "https://files.pythonhosted.org/packages/00/0e/8217e348a1fa417ec4c78cd3cdf24154f5e76fd7597343a35bd403650dfd/cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", size = 3794368 }, + { url = "https://files.pythonhosted.org/packages/3d/ed/38b6be7254d8f7251fde8054af597ee8afa14f911da67a9410a45f602fc3/cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", size = 3981750 }, + { url = "https://files.pythonhosted.org/packages/64/f3/b7946c3887cf7436f002f4cbb1e6aec77b8d299b86be48eeadfefb937c4b/cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", size = 3778925 }, + { url = "https://files.pythonhosted.org/packages/ac/7e/ebda4dd4ae098a0990753efbb4b50954f1d03003846b943ea85070782da7/cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", size = 3993152 }, + { url = "https://files.pythonhosted.org/packages/43/f6/feebbd78a3e341e3913846a3bb2c29d0b09b1b3af1573c6baabc2533e147/cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", size = 3886392 }, + { url = "https://files.pythonhosted.org/packages/bd/4c/ab0b9407d5247576290b4fd8abd06b7f51bd414f04eef0f2800675512d61/cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", size = 4082606 }, + { url = "https://files.pythonhosted.org/packages/05/36/e532a671998d6fcfdb9122da16434347a58a6bae9465e527e450e0bc60a5/cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", size = 2617948 }, + { url = "https://files.pythonhosted.org/packages/b3/c6/c09cee6968add5ff868525c3815e5dccc0e3c6e89eec58dc9135d3c40e88/cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", size = 3070445 }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, +] + +[[package]] +name = "dateparser" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "regex" }, + { name = "tzlocal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/b2/f6b29ab17d7959eb1a0a5c64f5011dc85051ad4e25e401cbddcc515db00f/dateparser-1.2.0.tar.gz", hash = "sha256:7975b43a4222283e0ae15be7b4999d08c9a70e2d378ac87385b1ccf2cffbbb30", size = 307260 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/29/db12aa4dda81580be1999824a689bd52aa40061fc12c9ccdc3feab5ea718/dateparser-1.2.0-py2.py3-none-any.whl", hash = "sha256:0b21ad96534e562920a0083e97fd45fa959882d4162acc358705144520a35830", size = 294995 }, +] + +[[package]] +name = "docstring-parser" +version = "0.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/08/12/9c22a58c0b1e29271051222d8906257616da84135af9ed167c9e28f85cb3/docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e", size = 26565 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/7c/e9fcff7623954d86bdc17782036cbf715ecab1bec4847c008557affe1ca8/docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637", size = 36533 }, +] + +[[package]] +name = "dotmap" +version = "1.3.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/68/c186606e4f2bf731abd18044ea201e70c3c244bf468f41368820d197fca5/dotmap-1.3.30.tar.gz", hash = "sha256:5821a7933f075fb47563417c0e92e0b7c031158b4c9a6a7e56163479b658b368", size = 12391 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/f9/976d6813c160d6c89196d81e9466dca1503d20e609d8751f3536daf37ec6/dotmap-1.3.30-py3-none-any.whl", hash = "sha256:bd9fa15286ea2ad899a4d1dc2445ed85a1ae884a42effb87c89a6ecce71243c6", size = 11464 }, +] + +[[package]] +name = "dqsegdb2" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "igwn-auth-utils" }, + { name = "ligo-segments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/aa/b4bb24735f0b4f167b7ce3f68ec3d6682cd49ea90130b73b98c544774372/dqsegdb2-1.2.1.tar.gz", hash = "sha256:6a222dbb7024de6de845ba5ad3583859cecf8ed5ce584ff5db6f01581b821f81", size = 35041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/7e/097ec2c8da84074bafae6ceca298b638eacf82510484b0d03ea7c8f182a2/dqsegdb2-1.2.1-py3-none-any.whl", hash = "sha256:4a6eb2ad66bcd596f7eebd18229674b3ae262a54480c75ac135755b0a5e8f783", size = 25089 }, +] + +[[package]] +name = "equinox" +version = "0.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jax" }, + { name = "jaxtyping" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/48/ed7ba0849bea18b8be49f471b8e597ca6e5975f75ed05c3be84c1e0ef7c4/equinox-0.11.7.tar.gz", hash = "sha256:96e0216a9d822ec4b1465b0cbfbab14a36fb7e7d62c55f521287db3aaaa251be", size = 139909 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/3f/2fa4627a902a38cd836aa8f7e5a0753114bb23d6b4d2a73784dd9f5ff2e6/equinox-0.11.7-py3-none-any.whl", hash = "sha256:1177354e1795061fbad71535fe54096d8887dc059b4ab7d400fea2d47dfaa283", size = 178416 }, +] + +[[package]] +name = "etils" +version = "1.9.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/e0/d8e99c24e7c55a9cb6a405fa502c059f77ed789f916bffbcaa8e1cc65f2d/etils-1.9.4.tar.gz", hash = "sha256:fad950414f0a1ca58c70c70915b0014f9953dd9bcf8aa951a0f75ff9becbeb24", size = 103161 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/35/7f8fcc9c23a504cf09e2795164eeb39a39ade1b2c7c8724ee207b2019ae6/etils-1.9.4-py3-none-any.whl", hash = "sha256:4387e7a4911a3b5cc4b92b99a9211386d176b43bae1dac8e2fe345fc2cb95e4b", size = 164341 }, +] + +[package.optional-dependencies] +epath = [ + { name = "fsspec" }, + { name = "importlib-resources" }, + { name = "typing-extensions" }, + { name = "zipp" }, +] +epy = [ + { name = "typing-extensions" }, +] + +[[package]] +name = "evosax" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chex" }, + { name = "dotmap" }, + { name = "flax" }, + { name = "jax" }, + { name = "jaxlib" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/1c/42c78cae403ee993a99f6f5645cb7202111beac80a5fd2dd2f31c52f823e/evosax-0.1.6.tar.gz", hash = "sha256:bc3dd032191f2f878b08127e0c88d270d08fc95e80b379509b14dc265f29484b", size = 196800 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/f8/a58890d615e548de5003a38636d8986e226805bbcb27aab206e1c8430f57/evosax-0.1.6-py3-none-any.whl", hash = "sha256:195145d68c67f5f1de9c580b6f248f624ac9d089de628e67665f7b3616d638e4", size = 240393 }, +] + +[[package]] +name = "flax" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jax" }, + { name = "msgpack" }, + { name = "numpy" }, + { name = "optax" }, + { name = "orbax-checkpoint" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "tensorstore" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/21/ee8b1fb88974e768bcf281cb83c76875c736aa8a3981ce133f685738e945/flax-0.9.0.tar.gz", hash = "sha256:8b7f361eed0f5324e81f9dc8d02ea53da5f993d7c2e37e7aa5b37d3f6331dd53", size = 3073134 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/e8/e0aa0c81a4b2c14bcaf7566d865039d4ae39ed604b8ba90708f8faedbda5/flax-0.9.0-py3-none-any.whl", hash = "sha256:12cd8f7162165ddd56877fb1cd9a4fcb47a31569e4c5343eeb59a36369fa2cfe", size = 780735 }, +] + +[[package]] +name = "flowmc" +version = "0.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "corner" }, + { name = "equinox" }, + { name = "evosax" }, + { name = "jax" }, + { name = "jaxlib" }, + { name = "optax" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/bf/18ecaceaa43989123413b3ebfa0e9dbc9c29759ab748938f6d1fe44cae80/flowmc-0.3.4.tar.gz", hash = "sha256:ad64011efdb58ff624b176bc0853aee549fdeb37e5a7a7081580eb5009b2945f", size = 29392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/93/8ed7610e900278a6cfc8edf1ada5fa13032bec88296b27ee7aa57273005d/flowMC-0.3.4-py3-none-any.whl", hash = "sha256:b65c2cc892c07056ea51f1056a4ef4df8d8075b924a76f242efb5b37654716bf", size = 37730 }, +] + +[[package]] +name = "fonttools" +version = "4.54.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/1d/70b58e342e129f9c0ce030029fb4b2b0670084bbbfe1121d008f6a1e361c/fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285", size = 3463867 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/2c/8b5d82fe2d9c7f260fb73121418f5e07d4e38c329ea3886a5b0e55586113/fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20", size = 2768112 }, + { url = "https://files.pythonhosted.org/packages/37/2e/f94118b92f7b6a9ec93840101b64bfdd09f295b266133857e8e852a5c35c/fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2", size = 2254739 }, + { url = "https://files.pythonhosted.org/packages/45/4b/8a32f56a13e78256192f77d6b65583c43538c7955f5420887bb574b91ddf/fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7", size = 4879772 }, + { url = "https://files.pythonhosted.org/packages/96/13/748b7f7239893ff0796de11074b0ad8aa4c3da2d9f4d79a128b0b16147f3/fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07", size = 4927686 }, + { url = "https://files.pythonhosted.org/packages/7c/82/91bc5a378b4a0593fa90ea706f68ce7e9e871c6873e0d91e134d107758db/fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8", size = 4890789 }, + { url = "https://files.pythonhosted.org/packages/ea/ca/82be5d4f8b78405cdb3f7f3f1316af5e8db93216121f19da9f684a35beee/fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a", size = 5061351 }, + { url = "https://files.pythonhosted.org/packages/da/2f/fd6e1b01c80c473c3ac52492dcf8d26cdf5f4a89b4f30875ecfbda55e7ff/fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc", size = 2166210 }, + { url = "https://files.pythonhosted.org/packages/63/f1/3a081cd047d83b5966cb0d7ef3fea929ee6eddeb94d8fbfdb2a19bd60cc7/fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6", size = 2211946 }, + { url = "https://files.pythonhosted.org/packages/27/b6/f9d365932dcefefdcc794985f8846471e60932070c557e0f66ed195fccec/fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d", size = 2761873 }, + { url = "https://files.pythonhosted.org/packages/67/9d/cfbfe36e5061a8f68b154454ba2304eb01f40d4ba9b63e41d9058909baed/fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08", size = 2251828 }, + { url = "https://files.pythonhosted.org/packages/90/41/5573e074739efd9227dd23647724f01f6f07ad062fe09d02e91c5549dcf7/fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263", size = 4792544 }, + { url = "https://files.pythonhosted.org/packages/08/07/aa85cc62abcc940b25d14b542cf585eebf4830032a7f6a1395d696bb3231/fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab", size = 4875892 }, + { url = "https://files.pythonhosted.org/packages/47/23/c5726c2615446c498a976bed21c35a242a97eee39930a2655d616ca885cc/fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d", size = 4769822 }, + { url = "https://files.pythonhosted.org/packages/8f/7b/87f7f7d35e0732ac67422dfa6f05e2b568fb6ca2dcd7f3e4f500293cfd75/fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714", size = 5029455 }, + { url = "https://files.pythonhosted.org/packages/e0/09/241aa498587889576838aa73c78d22b70ce06970807a5475d372baa7ccb7/fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac", size = 2154411 }, + { url = "https://files.pythonhosted.org/packages/b9/0a/a57caaff3bc880779317cb157e5b49dc47fad54effe027016abd355b0651/fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e", size = 2200412 }, + { url = "https://files.pythonhosted.org/packages/05/3d/cc515cae84a11d696f2cb7c139a90997b15f02e2e97ec09a5d79302cbcd7/fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff", size = 2749174 }, + { url = "https://files.pythonhosted.org/packages/03/03/05d4b22d1a674d066380657f60bbc0eda2d206446912e676d1a33a206878/fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb", size = 2246267 }, + { url = "https://files.pythonhosted.org/packages/52/c3/bb6086adb675e8b0963a7dbb7769e7118c95b687dd318cd660aefd4b4c8c/fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a", size = 4855090 }, + { url = "https://files.pythonhosted.org/packages/80/a1/d7192b6a104e3f9ea8e5b1c3463a6240399f0fa826a782eff636cbe0495a/fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c", size = 5005449 }, + { url = "https://files.pythonhosted.org/packages/5a/6c/ecfd5c6cd8c9006e85b128d073af26bb263e8aa47506374cb14b25bcf65f/fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58", size = 2152496 }, + { url = "https://files.pythonhosted.org/packages/63/da/f7a1d837de419e3d4cccbd0dbf53c7399f610f65ceb9bcbf2480f3ae7950/fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d", size = 2197257 }, + { url = "https://files.pythonhosted.org/packages/57/5e/de2e6e51cb6894f2f2bc2641f6c845561361b622e96df3cca04df77222c9/fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd", size = 1096920 }, +] + +[[package]] +name = "fsspec" +version = "2024.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/7c/12b0943011daaaa9c35c2a2e22e5eb929ac90002f08f1259d69aedad84de/fsspec-2024.9.0.tar.gz", hash = "sha256:4b0afb90c2f21832df142f292649035d80b421f60a9e1c027802e5a0da2b04e8", size = 286206 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/a0/6aaea0c2fbea2f89bfd5db25fb1e3481896a423002ebe4e55288907a97a3/fsspec-2024.9.0-py3-none-any.whl", hash = "sha256:a0947d552d8a6efa72cc2c730b12c41d043509156966cca4fb157b0f2a0c574b", size = 179253 }, +] + +[[package]] +name = "gwdatafind" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "igwn-auth-utils" }, + { name = "ligo-segments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/10/9f1b9100f59e2ca4a85dad8a21942d0702d756f4b80a433c728be4a871d2/gwdatafind-1.2.0.tar.gz", hash = "sha256:8f74942e66cdb9a53030da29069110b3cb30afc2a034790957786028fb09f451", size = 40381 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/61/5020eff070e04b1e07c7cf8bed63705aa705011e057cbb839e9a31367bdd/gwdatafind-1.2.0-py3-none-any.whl", hash = "sha256:58c505ee188c1186ff81b3de5f946f289179a4f8c334f7eb45d07dd70a71bd2c", size = 45529 }, +] + +[[package]] +name = "gwosc" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/d1/82de4365b8d18abfb4930d6d5aa772e3c2375b44c44598aaac4150e1dc8a/gwosc-0.7.1.tar.gz", hash = "sha256:5328223410081731ba4ef6f3be9f13ac4b3b9a43397fa04c1f50ddeb59895816", size = 35423 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/86/b2d5499eb119268fdfd5bc11147de1028082433431dbfa464e19f8921027/gwosc-0.7.1-py3-none-any.whl", hash = "sha256:4cb7598f9aaf8749c032e8913c723a391784a52127397989c9f733f8c3f99558", size = 27285 }, +] + +[[package]] +name = "gwpy" +version = "3.0.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astropy" }, + { name = "dateparser" }, + { name = "dqsegdb2" }, + { name = "gwdatafind" }, + { name = "gwosc" }, + { name = "h5py" }, + { name = "ligo-segments" }, + { name = "ligotimegps" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "scipy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/d5/82a45dcaf285aaf04550c77a4d06abeaff2695c018be2901ddc116b60ce5/gwpy-3.0.10.tar.gz", hash = "sha256:073b4d8ec14e3db32c7cff74df2248f31d378fb095089e7ceb241cfb34f6e8fd", size = 1540393 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/1b/fee8e2007913613d7494b8591b5a62d3dee389f88b2425be22a9576c98ea/gwpy-3.0.10-py3-none-any.whl", hash = "sha256:348070a88b6b2acefe6b40ee541e0a6100c89604c5def62e55fd31417b82076b", size = 1395063 }, +] + +[[package]] +name = "h5py" +version = "3.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/0c/5c2b0a88158682aeafb10c1c2b735df5bc31f165bfe192f2ee9f2a23b5f1/h5py-3.12.1.tar.gz", hash = "sha256:326d70b53d31baa61f00b8aa5f95c2fcb9621a3ee8365d770c551a13dbbcbfdf", size = 411457 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/61/c463dc5fc02fbe019566d067a9d18746cd3c664f29c9b8b3c3f9ed025365/h5py-3.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ccd9006d92232727d23f784795191bfd02294a4f2ba68708825cb1da39511a93", size = 3410828 }, + { url = "https://files.pythonhosted.org/packages/95/9d/eb91a9076aa998bb2179d6b1788055ea09cdf9d6619cd967f1d3321ed056/h5py-3.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad8a76557880aed5234cfe7279805f4ab5ce16b17954606cca90d578d3e713ef", size = 2872586 }, + { url = "https://files.pythonhosted.org/packages/b0/62/e2b1f9723ff713e3bd3c16dfeceec7017eadc21ef063d8b7080c0fcdc58a/h5py-3.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1473348139b885393125126258ae2d70753ef7e9cec8e7848434f385ae72069e", size = 5273038 }, + { url = "https://files.pythonhosted.org/packages/e1/89/118c3255d6ff2db33b062ec996a762d99ae50c21f54a8a6047ae8eda1b9f/h5py-3.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:018a4597f35092ae3fb28ee851fdc756d2b88c96336b8480e124ce1ac6fb9166", size = 5452688 }, + { url = "https://files.pythonhosted.org/packages/1d/4d/cbd3014eb78d1e449b29beba1f3293a841aa8086c6f7968c383c2c7ff076/h5py-3.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:3fdf95092d60e8130ba6ae0ef7a9bd4ade8edbe3569c13ebbaf39baefffc5ba4", size = 3006095 }, + { url = "https://files.pythonhosted.org/packages/d4/e1/ea9bfe18a3075cdc873f0588ff26ce394726047653557876d7101bf0c74e/h5py-3.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06a903a4e4e9e3ebbc8b548959c3c2552ca2d70dac14fcfa650d9261c66939ed", size = 3372538 }, + { url = "https://files.pythonhosted.org/packages/0d/74/1009b663387c025e8fa5f3ee3cf3cd0d99b1ad5c72eeb70e75366b1ce878/h5py-3.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b3b8f3b48717e46c6a790e3128d39c61ab595ae0a7237f06dfad6a3b51d5351", size = 2868104 }, + { url = "https://files.pythonhosted.org/packages/af/52/c604adc06280c15a29037d4aa79a24fe54d8d0b51085e81ed24b2fa995f7/h5py-3.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:050a4f2c9126054515169c49cb900949814987f0c7ae74c341b0c9f9b5056834", size = 5194606 }, + { url = "https://files.pythonhosted.org/packages/fa/63/eeaacff417b393491beebabb8a3dc5342950409eb6d7b39d437289abdbae/h5py-3.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4b41d1019322a5afc5082864dfd6359f8935ecd37c11ac0029be78c5d112c9", size = 5413256 }, + { url = "https://files.pythonhosted.org/packages/86/f7/bb465dcb92ca3521a15cbe1031f6d18234dbf1fb52a6796a00bfaa846ebf/h5py-3.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4d51919110a030913201422fb07987db4338eba5ec8c5a15d6fab8e03d443fc", size = 2993055 }, + { url = "https://files.pythonhosted.org/packages/23/1c/ecdd0efab52c24f2a9bf2324289828b860e8dd1e3c5ada3cf0889e14fdc1/h5py-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:513171e90ed92236fc2ca363ce7a2fc6f2827375efcbb0cc7fbdd7fe11fecafc", size = 3346239 }, + { url = "https://files.pythonhosted.org/packages/93/cd/5b6f574bf3e318bbe305bc93ba45181676550eb44ba35e006d2e98004eaa/h5py-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59400f88343b79655a242068a9c900001a34b63e3afb040bd7cdf717e440f653", size = 2843416 }, + { url = "https://files.pythonhosted.org/packages/8a/4f/b74332f313bfbe94ba03fff784219b9db385e6139708e55b11490149f90a/h5py-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e465aee0ec353949f0f46bf6c6f9790a2006af896cee7c178a8c3e5090aa32", size = 5154390 }, + { url = "https://files.pythonhosted.org/packages/1a/57/93ea9e10a6457ea8d3b867207deb29a527e966a08a84c57ffd954e32152a/h5py-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba51c0c5e029bb5420a343586ff79d56e7455d496d18a30309616fdbeed1068f", size = 5378244 }, + { url = "https://files.pythonhosted.org/packages/50/51/0bbf3663062b2eeee78aa51da71e065f8a0a6e3cb950cc7020b4444999e6/h5py-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:52ab036c6c97055b85b2a242cb540ff9590bacfda0c03dd0cf0661b311f522f8", size = 2979760 }, +] + +[[package]] +name = "humanize" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/40/64a912b9330786df25e58127194d4a5a7441f818b400b155e748a270f924/humanize-4.11.0.tar.gz", hash = "sha256:e66f36020a2d5a974c504bd2555cf770621dbdbb6d82f94a6857c0b1ea2608be", size = 80374 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/75/4bc3e242ad13f2e6c12e0b0401ab2c5e5c6f0d7da37ec69bc808e24e0ccb/humanize-4.11.0-py3-none-any.whl", hash = "sha256:b53caaec8532bcb2fff70c8826f904c35943f8cecaca29d272d9df38092736c0", size = 128055 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "igwn-auth-utils" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "requests" }, + { name = "safe-netrc" }, + { name = "scitokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/25/41df00da93af467c6653808ff04143c6da7cfdf5cae8e90fe0652b77b04b/igwn_auth_utils-1.1.1.tar.gz", hash = "sha256:be787dc31227c3b497f44d88440eb81a6e069225c57c66647a420d93fbe79476", size = 31950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/7d/07b9b5e6421362a27db4fdeac60e211fc07d47b94c2085bcfc2cd76192ae/igwn_auth_utils-1.1.1-py3-none-any.whl", hash = "sha256:f995d79214afbcb05823d46b33a9fd96cfa7734431a2ca5beeddb09c0452da83", size = 26712 }, +] + +[[package]] +name = "importlib-resources" +version = "6.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/be/f3e8c6081b684f176b761e6a2fef02a0be939740ed6f54109a2951d806f3/importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065", size = 43372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/6a/4604f9ae2fa62ef47b9de2fa5ad599589d28c9fd1d335f32759813dfa91e/importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717", size = 36115 }, +] + +[[package]] +name = "jax" +version = "0.4.34" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaxlib" }, + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "opt-einsum" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/6a/cacfcdf77841a4562e555ef35e0dbc5f8ca79c9f1010aaa4cf3973e79c69/jax-0.4.34.tar.gz", hash = "sha256:44196854f40c5f9cea3142824b9f1051f85afc3fcf7593ec5479fc8db01c58db", size = 1848472 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f3/c499d358dd7f267a63d7d38ef54aadad82e28d2c28bafff15360c3091946/jax-0.4.34-py3-none-any.whl", hash = "sha256:b957ca1fc91f7343f91a186af9f19c7f342c946f95a8c11c7f1e5cdfe2e58d9e", size = 2144294 }, +] + +[[package]] +name = "jaxlib" +version = "0.4.34" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "scipy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/14/00a3385532d72ab51bd8e9f8c3e19a2e257667955565e9fc10236771dd06/jaxlib-0.4.34-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8ee3f93836e53c86556ccd9449a4ea43516ee05184d031a71dd692e81259f7d9", size = 87420889 }, + { url = "https://files.pythonhosted.org/packages/66/78/d1535ee73fe505dc6c8831c19c4846afdce7df5acefb9f8ee885aa73d700/jaxlib-0.4.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c9d3adcae43a33aad4332be9c2aedc5ef751d1e755f917a5afb30c7872eacaa8", size = 67635880 }, + { url = "https://files.pythonhosted.org/packages/aa/06/3e09e794acf308e170905d732eca0d041449503c47505cc22e8ef78a989d/jaxlib-0.4.34-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:571ef03259835458111596a71a2f4a6fabf4ec34595df4cea555035362ac5bf0", size = 69421901 }, + { url = "https://files.pythonhosted.org/packages/c7/d0/6bc81c0b1d507f403e6085ce76a429e6d7f94749d742199252e299dd1424/jaxlib-0.4.34-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:3bcfa639ca3cfaf86c8ceebd5fc0d47300fd98a078014a1d0cc03133e1523d5f", size = 86114491 }, + { url = "https://files.pythonhosted.org/packages/9d/5d/7e71019af5f6fdebe6c10eab97d01f44b931d94609330da9e142cb155f8c/jaxlib-0.4.34-cp311-cp311-win_amd64.whl", hash = "sha256:133070d4fec5525ffea4dc72956398c1cf647a04dcb37f8a935ee82af78d9965", size = 55241262 }, + { url = "https://files.pythonhosted.org/packages/bc/42/5038983664494dfb50f8669a662d965d7ea62f9250e40d8cd36dcf9ac3dd/jaxlib-0.4.34-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:c7b3e724a30426a856070aba0192b5d199e95b4411070e7ad96ad8b196877b10", size = 87473956 }, + { url = "https://files.pythonhosted.org/packages/87/2e/8a75d3107c019c370c50c01acc205da33f9d6fba830950401a772a8e9f6d/jaxlib-0.4.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:096f0ca309d41fa692a9d1f2f9baab1c5c8ca0749876ebb3f748e738a27c7ff4", size = 67650276 }, + { url = "https://files.pythonhosted.org/packages/af/09/cceae2d251a506b4297679d10ee9f5e905a6b992b0687d553c9470ffd1db/jaxlib-0.4.34-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:1a30771d85fa77f9ab8f18e63240f455ab3a3f87660ed7b8d5eea6ceecbe5c1e", size = 69431284 }, + { url = "https://files.pythonhosted.org/packages/e7/0d/4faf839e3c8ce2a5b615df64427be3e870899c72c0ebfb5859348150aba1/jaxlib-0.4.34-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:48272e9034ff868d4328cf0055a07882fd2be93f59dfb6283af7de491f9d1290", size = 86151183 }, + { url = "https://files.pythonhosted.org/packages/a4/bc/a38f99071fca6cc31ae949e508a23b0de5de559da594443bb625a1adb8f3/jaxlib-0.4.34-cp312-cp312-win_amd64.whl", hash = "sha256:901cb4040ed24eae40071d8114ea8d10dff436277fa74a1a5b9e7206f641151c", size = 55278745 }, + { url = "https://files.pythonhosted.org/packages/21/4e/fab0606683af7aa9284a32d2b188ff132cffb0ee3ea04d941a547eb776d1/jaxlib-0.4.34-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:72e22e99a5dc890a64443c3fc12f13f20091f578c405a76de077ba42b4c62cd7", size = 87474367 }, + { url = "https://files.pythonhosted.org/packages/3e/1b/709be16d543a3db5b471ee5e7d089c57484c386b08499923e43bd8da5d0b/jaxlib-0.4.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c303f5acaf6c56ce5ff133a923c9b6247bdebedde15bd2c893c24be4d8f71306", size = 67651281 }, + { url = "https://files.pythonhosted.org/packages/85/9e/f3801096cd4a2c764af7a1f6b683c769706602ea72b27ec35bacfcc4cd4f/jaxlib-0.4.34-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:7be673a876ebd1aef440fb7e3ebaf99a91abeb550c9728c644b7d7c7b5d7c108", size = 69432987 }, + { url = "https://files.pythonhosted.org/packages/e6/79/61301f55b24c3a898ef9bc4e13600b66e3f838623fc6f87648ac1ccbca01/jaxlib-0.4.34-cp313-cp313-manylinux2014_x86_64.whl", hash = "sha256:87f25a477cd279840e53718403f97092eba0e8a945fcab47bcf435b6f9119dda", size = 86152550 }, + { url = "https://files.pythonhosted.org/packages/16/b0/e682d02126e0062b58dec0f0851048592396f74c24b4a4412dce4ddbbadb/jaxlib-0.4.34-cp313-cp313-win_amd64.whl", hash = "sha256:6b43a974c5d91a19912d138f2658dd8dbb7d30dcdff5c961d896c673e872b611", size = 55279410 }, +] + +[[package]] +name = "jaxtyping" +version = "0.2.34" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/cb/580f3ec30b9e52b79c0288c033a1f0a90378f3b820707a2e8894c076481b/jaxtyping-0.2.34.tar.gz", hash = "sha256:eed9a3458ec8726c84ea5457ebde53c964f65d2c22c0ec40d0555ae3fed5bbaf", size = 32688 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/ae/220537f80eb82ae43a299de31edb2a91a28b8c5fb8046e9ff853ec7763cd/jaxtyping-0.2.34-py3-none-any.whl", hash = "sha256:2f81fb6d1586e497a6ea2d28c06dcab37b108a096cbb36ea3fe4fa2e1c1f32e5", size = 42426 }, +] + +[[package]] +name = "jim" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "astropy" }, + { name = "beartype" }, + { name = "corner" }, + { name = "flowmc" }, + { name = "gwpy" }, + { name = "jax" }, + { name = "jaxtyping" }, + { name = "ripplegw" }, + { name = "typed-argument-parser" }, +] + +[package.metadata] +requires-dist = [ + { name = "astropy", specifier = ">=6.1.4" }, + { name = "beartype", specifier = ">=0.19.0" }, + { name = "corner", specifier = ">=2.2.2" }, + { name = "flowmc", specifier = ">=0.3.4" }, + { name = "gwpy" }, + { name = "jax", specifier = ">=0.4.34" }, + { name = "jaxtyping", specifier = ">=0.2.34" }, + { name = "ripplegw", specifier = ">=0.0.9" }, + { name = "typed-argument-parser", specifier = ">=1.10.1" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/77429fa0a58f941d6e1c58da9efe08597d2e86bf2b2cce6626834f49d07b/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54", size = 122442 }, + { url = "https://files.pythonhosted.org/packages/e5/20/8c75caed8f2462d63c7fd65e16c832b8f76cda331ac9e615e914ee80bac9/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95", size = 65762 }, + { url = "https://files.pythonhosted.org/packages/f4/98/fe010f15dc7230f45bc4cf367b012d651367fd203caaa992fd1f5963560e/kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935", size = 64319 }, + { url = "https://files.pythonhosted.org/packages/8b/1b/b5d618f4e58c0675654c1e5051bcf42c776703edb21c02b8c74135541f60/kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb", size = 1334260 }, + { url = "https://files.pythonhosted.org/packages/b8/01/946852b13057a162a8c32c4c8d2e9ed79f0bb5d86569a40c0b5fb103e373/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02", size = 1426589 }, + { url = "https://files.pythonhosted.org/packages/70/d1/c9f96df26b459e15cf8a965304e6e6f4eb291e0f7a9460b4ad97b047561e/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51", size = 1541080 }, + { url = "https://files.pythonhosted.org/packages/d3/73/2686990eb8b02d05f3de759d6a23a4ee7d491e659007dd4c075fede4b5d0/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052", size = 1470049 }, + { url = "https://files.pythonhosted.org/packages/a7/4b/2db7af3ed3af7c35f388d5f53c28e155cd402a55432d800c543dc6deb731/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18", size = 1426376 }, + { url = "https://files.pythonhosted.org/packages/05/83/2857317d04ea46dc5d115f0df7e676997bbd968ced8e2bd6f7f19cfc8d7f/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545", size = 2222231 }, + { url = "https://files.pythonhosted.org/packages/0d/b5/866f86f5897cd4ab6d25d22e403404766a123f138bd6a02ecb2cdde52c18/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b", size = 2368634 }, + { url = "https://files.pythonhosted.org/packages/c1/ee/73de8385403faba55f782a41260210528fe3273d0cddcf6d51648202d6d0/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36", size = 2329024 }, + { url = "https://files.pythonhosted.org/packages/a1/e7/cd101d8cd2cdfaa42dc06c433df17c8303d31129c9fdd16c0ea37672af91/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3", size = 2468484 }, + { url = "https://files.pythonhosted.org/packages/e1/72/84f09d45a10bc57a40bb58b81b99d8f22b58b2040c912b7eb97ebf625bf2/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523", size = 2284078 }, + { url = "https://files.pythonhosted.org/packages/d2/d4/71828f32b956612dc36efd7be1788980cb1e66bfb3706e6dec9acad9b4f9/kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d", size = 46645 }, + { url = "https://files.pythonhosted.org/packages/a1/65/d43e9a20aabcf2e798ad1aff6c143ae3a42cf506754bcb6a7ed8259c8425/kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b", size = 56022 }, + { url = "https://files.pythonhosted.org/packages/35/b3/9f75a2e06f1b4ca00b2b192bc2b739334127d27f1d0625627ff8479302ba/kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376", size = 48536 }, + { url = "https://files.pythonhosted.org/packages/97/9c/0a11c714cf8b6ef91001c8212c4ef207f772dd84540104952c45c1f0a249/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2", size = 121808 }, + { url = "https://files.pythonhosted.org/packages/f2/d8/0fe8c5f5d35878ddd135f44f2af0e4e1d379e1c7b0716f97cdcb88d4fd27/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a", size = 65531 }, + { url = "https://files.pythonhosted.org/packages/80/c5/57fa58276dfdfa612241d640a64ca2f76adc6ffcebdbd135b4ef60095098/kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee", size = 63894 }, + { url = "https://files.pythonhosted.org/packages/8b/e9/26d3edd4c4ad1c5b891d8747a4f81b1b0aba9fb9721de6600a4adc09773b/kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640", size = 1369296 }, + { url = "https://files.pythonhosted.org/packages/b6/67/3f4850b5e6cffb75ec40577ddf54f7b82b15269cc5097ff2e968ee32ea7d/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f", size = 1461450 }, + { url = "https://files.pythonhosted.org/packages/52/be/86cbb9c9a315e98a8dc6b1d23c43cffd91d97d49318854f9c37b0e41cd68/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483", size = 1579168 }, + { url = "https://files.pythonhosted.org/packages/0f/00/65061acf64bd5fd34c1f4ae53f20b43b0a017a541f242a60b135b9d1e301/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258", size = 1507308 }, + { url = "https://files.pythonhosted.org/packages/21/e4/c0b6746fd2eb62fe702118b3ca0cb384ce95e1261cfada58ff693aeec08a/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e", size = 1464186 }, + { url = "https://files.pythonhosted.org/packages/0a/0f/529d0a9fffb4d514f2782c829b0b4b371f7f441d61aa55f1de1c614c4ef3/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107", size = 2247877 }, + { url = "https://files.pythonhosted.org/packages/d1/e1/66603ad779258843036d45adcbe1af0d1a889a07af4635f8b4ec7dccda35/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948", size = 2404204 }, + { url = "https://files.pythonhosted.org/packages/8d/61/de5fb1ca7ad1f9ab7970e340a5b833d735df24689047de6ae71ab9d8d0e7/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038", size = 2352461 }, + { url = "https://files.pythonhosted.org/packages/ba/d2/0edc00a852e369827f7e05fd008275f550353f1f9bcd55db9363d779fc63/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383", size = 2501358 }, + { url = "https://files.pythonhosted.org/packages/84/15/adc15a483506aec6986c01fb7f237c3aec4d9ed4ac10b756e98a76835933/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520", size = 2314119 }, + { url = "https://files.pythonhosted.org/packages/36/08/3a5bb2c53c89660863a5aa1ee236912269f2af8762af04a2e11df851d7b2/kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b", size = 46367 }, + { url = "https://files.pythonhosted.org/packages/19/93/c05f0a6d825c643779fc3c70876bff1ac221f0e31e6f701f0e9578690d70/kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb", size = 55884 }, + { url = "https://files.pythonhosted.org/packages/d2/f9/3828d8f21b6de4279f0667fb50a9f5215e6fe57d5ec0d61905914f5b6099/kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a", size = 48528 }, + { url = "https://files.pythonhosted.org/packages/c4/06/7da99b04259b0f18b557a4effd1b9c901a747f7fdd84cf834ccf520cb0b2/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e", size = 121913 }, + { url = "https://files.pythonhosted.org/packages/97/f5/b8a370d1aa593c17882af0a6f6755aaecd643640c0ed72dcfd2eafc388b9/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6", size = 65627 }, + { url = "https://files.pythonhosted.org/packages/2a/fc/6c0374f7503522539e2d4d1b497f5ebad3f8ed07ab51aed2af988dd0fb65/kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750", size = 63888 }, + { url = "https://files.pythonhosted.org/packages/bf/3e/0b7172793d0f41cae5c923492da89a2ffcd1adf764c16159ca047463ebd3/kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d", size = 1369145 }, + { url = "https://files.pythonhosted.org/packages/77/92/47d050d6f6aced2d634258123f2688fbfef8ded3c5baf2c79d94d91f1f58/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379", size = 1461448 }, + { url = "https://files.pythonhosted.org/packages/9c/1b/8f80b18e20b3b294546a1adb41701e79ae21915f4175f311a90d042301cf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c", size = 1578750 }, + { url = "https://files.pythonhosted.org/packages/a4/fe/fe8e72f3be0a844f257cadd72689c0848c6d5c51bc1d60429e2d14ad776e/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34", size = 1507175 }, + { url = "https://files.pythonhosted.org/packages/39/fa/cdc0b6105d90eadc3bee525fecc9179e2b41e1ce0293caaf49cb631a6aaf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1", size = 1463963 }, + { url = "https://files.pythonhosted.org/packages/6e/5c/0c03c4e542720c6177d4f408e56d1c8315899db72d46261a4e15b8b33a41/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f", size = 2248220 }, + { url = "https://files.pythonhosted.org/packages/3d/ee/55ef86d5a574f4e767df7da3a3a7ff4954c996e12d4fbe9c408170cd7dcc/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b", size = 2404463 }, + { url = "https://files.pythonhosted.org/packages/0f/6d/73ad36170b4bff4825dc588acf4f3e6319cb97cd1fb3eb04d9faa6b6f212/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27", size = 2352842 }, + { url = "https://files.pythonhosted.org/packages/0b/16/fa531ff9199d3b6473bb4d0f47416cdb08d556c03b8bc1cccf04e756b56d/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a", size = 2501635 }, + { url = "https://files.pythonhosted.org/packages/78/7e/aa9422e78419db0cbe75fb86d8e72b433818f2e62e2e394992d23d23a583/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee", size = 2314556 }, + { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364 }, + { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887 }, + { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530 }, +] + +[[package]] +name = "ligo-segments" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/60/8de5c89e4e5fc760649cee0c773418ecc920c3dae21ac5656fd3e5e21a9d/ligo-segments-1.4.0.tar.gz", hash = "sha256:e072a844713c5b02efdcaf5bfe4c3a8cd9ef225b08cfd3202a4e185e0f71f5dc", size = 51015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/db/b2de07a032a766d34480e93e6d4ecda5a4d651bb6a5e3286c2624d6c181d/ligo_segments-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:da82ae2b839bad0027e0a492b3673344e62360747d41194a265563e6c2903c7d", size = 50007 }, + { url = "https://files.pythonhosted.org/packages/b5/37/c962f26408ce45271a5a3aaa918f7beae74a8e8a6f00bbe5fdf77fd778ca/ligo_segments-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:601be6d92e52bdebbb5a82b608ed1ceb781a0ea86b3e5333d61af77575b32664", size = 50555 }, +] + +[[package]] +name = "ligotimegps" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/2e/cef2ee4c4f3f1f04566e3e7e9343811a74f6e9a0bc6ef4711248f132e3bb/ligotimegps-2.0.1.tar.gz", hash = "sha256:88626c02ad9a464d1242a1147b40074792f424bafa2ab013eee629c7d1b6469c", size = 35191 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/b6/6d6d0585fa2ae936a9f5d411b1f0fbe9fcb0aca0c51a775aa4f8f95fdf5e/ligotimegps-2.0.1-py2.py3-none-any.whl", hash = "sha256:da8c1289ba1310337ef5177e7936e25ce47d4e8e6a269cbdd5e9abfc5b5db490", size = 19930 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "matplotlib" +version = "3.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/d8/3d7f706c69e024d4287c1110d74f7dabac91d9843b99eadc90de9efc8869/matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92", size = 36088381 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/c2/f9d7fe80a8fcce9bb128d1381c6fe41a8d286d7e18395e273002e8e0fa34/matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772", size = 7902925 }, + { url = "https://files.pythonhosted.org/packages/28/ba/8be09886eb56ac04a218a1dc3fa728a5c4cac60b019b4f1687885166da00/matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41", size = 7773193 }, + { url = "https://files.pythonhosted.org/packages/e6/9a/5991972a560db3ab621312a7ca5efec339ae2122f25901c0846865c4b72f/matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f", size = 8202378 }, + { url = "https://files.pythonhosted.org/packages/01/75/6c7ce560e95714a10fcbb3367d1304975a1a3e620f72af28921b796403f3/matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447", size = 8314361 }, + { url = "https://files.pythonhosted.org/packages/6e/49/dc7384c6c092958e0b75e754efbd9e52500154939c3d715789cee9fb8a53/matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e", size = 9091428 }, + { url = "https://files.pythonhosted.org/packages/8b/ce/15b0bb2fb29b3d46211d8ca740b96b5232499fc49200b58b8d571292c9a6/matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7", size = 7829377 }, + { url = "https://files.pythonhosted.org/packages/82/de/54f7f38ce6de79cb77d513bb3eaa4e0b1031e9fd6022214f47943fa53a88/matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9", size = 7892511 }, + { url = "https://files.pythonhosted.org/packages/35/3e/5713b84a02b24b2a4bd4d6673bfc03017e6654e1d8793ece783b7ed4d484/matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d", size = 7769370 }, + { url = "https://files.pythonhosted.org/packages/5b/bd/c404502aa1824456d2862dd6b9b0c1917761a51a32f7f83ff8cf94b6d117/matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7", size = 8193260 }, + { url = "https://files.pythonhosted.org/packages/27/75/de5b9cd67648051cae40039da0c8cbc497a0d99acb1a1f3d087cd66d27b7/matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c", size = 8306310 }, + { url = "https://files.pythonhosted.org/packages/de/e3/2976e4e54d7ee76eaf54b7639fdc10a223d05c2bdded7045233e9871e469/matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e", size = 9086717 }, + { url = "https://files.pythonhosted.org/packages/d2/92/c2b9464a0562feb6ae780bdc152364810862e07ef5e6affa2b7686028db2/matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3", size = 7832805 }, + { url = "https://files.pythonhosted.org/packages/5c/7f/8932eac316b32f464b8f9069f151294dcd892c8fbde61fe8bcd7ba7f7f7e/matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9", size = 7893012 }, + { url = "https://files.pythonhosted.org/packages/90/89/9db9db3dd0ff3e2c49e452236dfe29e60b5586a88f8928ca1d153d0da8b5/matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa", size = 7769810 }, + { url = "https://files.pythonhosted.org/packages/67/26/d2661cdc2e1410b8929c5f12dfd521e4528abfed1b3c3d5a28ac48258b43/matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b", size = 8193779 }, + { url = "https://files.pythonhosted.org/packages/95/70/4839eaa672bf4eacc98ebc8d23633e02b6daf39e294e7433c4ab11a689be/matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413", size = 8306260 }, + { url = "https://files.pythonhosted.org/packages/88/62/7b263b2cb2724b45d3a4f9c8c6137696cc3ef037d44383fb01ac2a9555c2/matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b", size = 9086073 }, + { url = "https://files.pythonhosted.org/packages/b0/6d/3572fe243c74112fef120f0bc86f5edd21f49b60e8322fc7f6a01fe945dd/matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49", size = 7833041 }, + { url = "https://files.pythonhosted.org/packages/03/8f/9d505be3eb2f40ec731674fb6b47d10cc3147bbd6a9ea7a08c8da55415c6/matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03", size = 7933657 }, + { url = "https://files.pythonhosted.org/packages/5d/68/44b458b9794bcff2a66921f8c9a8110a50a0bb099bd5f7cabb428a1dc765/matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30", size = 7799276 }, + { url = "https://files.pythonhosted.org/packages/47/79/8486d4ddcaaf676314b5fb58e8fe19d1a6210a443a7c31fa72d4215fcb87/matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51", size = 8221027 }, + { url = "https://files.pythonhosted.org/packages/56/62/72a472181578c3d035dcda0d0fa2e259ba2c4cb91132588a348bb705b70d/matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c", size = 8329097 }, + { url = "https://files.pythonhosted.org/packages/01/8a/760f7fce66b39f447ad160800619d0bd5d0936d2b4633587116534a4afe0/matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e", size = 9093770 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/79/717c5e22ad25d63ce3acdfe8ff8d64bdedec18914256c59b838218708b16/ml_dtypes-0.5.0.tar.gz", hash = "sha256:3e7d3a380fe73a63c884f06136f8baa7a5249cc8e9fdec677997dd78549f8128", size = 699367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/29/8968fd7ee026c0d04c553fb1ce1cd67f9da668cd567d62c0cdc995ce989e/ml_dtypes-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60275f2b51b56834e840c4809fca840565f9bf8e9a73f6d8c94f5b5935701215", size = 736792 }, + { url = "https://files.pythonhosted.org/packages/19/93/14896596644dad2e041ac5ca7237e6233c484f7defa186ff88b18ee6110b/ml_dtypes-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76942f6aeb5c40766d5ea62386daa4148e6a54322aaf5b53eae9e7553240222f", size = 4392038 }, + { url = "https://files.pythonhosted.org/packages/89/65/ffdbf3489b0ba2213674ea347fad3a11747be64d2d23d888f9e5abe80a18/ml_dtypes-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e7534392682c3098bc7341648c650864207169c654aed83143d7a19c67ae06f", size = 4499448 }, + { url = "https://files.pythonhosted.org/packages/bf/31/058b9bcf9a81abd51623985add78711a915e4b0f6045baa5f9a0b41eb039/ml_dtypes-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:dc74fd9995513d33eac63d64e436240f5494ec74d522a9f0920194942fc3d2d7", size = 211916 }, + { url = "https://files.pythonhosted.org/packages/1c/b7/a067839f6e435785f34b09d96938dccb3a5d9502037de243cb84a2eb3f23/ml_dtypes-0.5.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d4b1a70a3e5219790d6b55b9507606fc4e02911d1497d16c18dd721eb7efe7d0", size = 750226 }, + { url = "https://files.pythonhosted.org/packages/31/75/bf571247bb3dbea73aa33ccae57ce322b9688003cfee2f68d303ab7b987b/ml_dtypes-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a988bac6572630e1e9c2edd9b1277b4eefd1c86209e52b0d061b775ac33902ff", size = 4420139 }, + { url = "https://files.pythonhosted.org/packages/6f/d3/1321715a95e856d4ef4fba24e4351cf5e4c89d459ad132a8cba5fe257d72/ml_dtypes-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a38df8df61194aeaae1ab7579075779b4ad32cd1cffd012c28be227fa7f2a70a", size = 4471130 }, + { url = "https://files.pythonhosted.org/packages/00/3a/40c40b78a7eb456837817bfa2c5bc442db59aefdf21c5ecb94700037813d/ml_dtypes-0.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:afa08343069874a30812871d639f9c02b4158ace065601406a493a8511180c02", size = 213187 }, + { url = "https://files.pythonhosted.org/packages/b3/4a/18f670a2703e771a6775fbc354208e597ff062a88efb0cecc220a282210b/ml_dtypes-0.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d3b3db9990c3840986a0e70524e122cfa32b91139c3653df76121ba7776e015f", size = 753345 }, + { url = "https://files.pythonhosted.org/packages/ed/c6/358d85e274e22d53def0c85f3cbe0933475fa3cf6922e9dca66eb25cb22f/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04fde367b2fe901b1d47234426fe8819909bd1dd862a5adb630f27789c20599", size = 4424962 }, + { url = "https://files.pythonhosted.org/packages/4c/b4/d766586e24e7a073333c8eb8bd9275f3c6fe0569b509ae7b1699d4f00c74/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54415257f00eb44fbcc807454efac3356f75644f1cbfc2d4e5522a72ae1dacab", size = 4475201 }, + { url = "https://files.pythonhosted.org/packages/14/87/30323ad2e52f56262019a4493fe5f5e71067c5561ce7e2f9c75de520f5e8/ml_dtypes-0.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:cb5cc7b25acabd384f75bbd78892d0c724943f3e2e1986254665a1aa10982e07", size = 213195 }, +] + +[[package]] +name = "msgpack" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/d0/7555686ae7ff5731205df1012ede15dd9d927f6227ea151e901c7406af4f/msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e", size = 167260 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/5e/a4c7154ba65d93be91f2f1e55f90e76c5f91ccadc7efc4341e6f04c8647f/msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7", size = 150803 }, + { url = "https://files.pythonhosted.org/packages/60/c2/687684164698f1d51c41778c838d854965dd284a4b9d3a44beba9265c931/msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa", size = 84343 }, + { url = "https://files.pythonhosted.org/packages/42/ae/d3adea9bb4a1342763556078b5765e666f8fdf242e00f3f6657380920972/msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701", size = 81408 }, + { url = "https://files.pythonhosted.org/packages/dc/17/6313325a6ff40ce9c3207293aee3ba50104aed6c2c1559d20d09e5c1ff54/msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6", size = 396096 }, + { url = "https://files.pythonhosted.org/packages/a8/a1/ad7b84b91ab5a324e707f4c9761633e357820b011a01e34ce658c1dda7cc/msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59", size = 403671 }, + { url = "https://files.pythonhosted.org/packages/bb/0b/fd5b7c0b308bbf1831df0ca04ec76fe2f5bf6319833646b0a4bd5e9dc76d/msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0", size = 387414 }, + { url = "https://files.pythonhosted.org/packages/f0/03/ff8233b7c6e9929a1f5da3c7860eccd847e2523ca2de0d8ef4878d354cfa/msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e", size = 383759 }, + { url = "https://files.pythonhosted.org/packages/1f/1b/eb82e1fed5a16dddd9bc75f0854b6e2fe86c0259c4353666d7fab37d39f4/msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6", size = 394405 }, + { url = "https://files.pythonhosted.org/packages/90/2e/962c6004e373d54ecf33d695fb1402f99b51832631e37c49273cc564ffc5/msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5", size = 396041 }, + { url = "https://files.pythonhosted.org/packages/f8/20/6e03342f629474414860c48aeffcc2f7f50ddaf351d95f20c3f1c67399a8/msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88", size = 68538 }, + { url = "https://files.pythonhosted.org/packages/aa/c4/5a582fc9a87991a3e6f6800e9bb2f3c82972912235eb9539954f3e9997c7/msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788", size = 74871 }, + { url = "https://files.pythonhosted.org/packages/e1/d6/716b7ca1dbde63290d2973d22bbef1b5032ca634c3ff4384a958ec3f093a/msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d", size = 152421 }, + { url = "https://files.pythonhosted.org/packages/70/da/5312b067f6773429cec2f8f08b021c06af416bba340c912c2ec778539ed6/msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2", size = 85277 }, + { url = "https://files.pythonhosted.org/packages/28/51/da7f3ae4462e8bb98af0d5bdf2707f1b8c65a0d4f496e46b6afb06cbc286/msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420", size = 82222 }, + { url = "https://files.pythonhosted.org/packages/33/af/dc95c4b2a49cff17ce47611ca9ba218198806cad7796c0b01d1e332c86bb/msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2", size = 392971 }, + { url = "https://files.pythonhosted.org/packages/f1/54/65af8de681fa8255402c80eda2a501ba467921d5a7a028c9c22a2c2eedb5/msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39", size = 401403 }, + { url = "https://files.pythonhosted.org/packages/97/8c/e333690777bd33919ab7024269dc3c41c76ef5137b211d776fbb404bfead/msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f", size = 385356 }, + { url = "https://files.pythonhosted.org/packages/57/52/406795ba478dc1c890559dd4e89280fa86506608a28ccf3a72fbf45df9f5/msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247", size = 383028 }, + { url = "https://files.pythonhosted.org/packages/e7/69/053b6549bf90a3acadcd8232eae03e2fefc87f066a5b9fbb37e2e608859f/msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c", size = 391100 }, + { url = "https://files.pythonhosted.org/packages/23/f0/d4101d4da054f04274995ddc4086c2715d9b93111eb9ed49686c0f7ccc8a/msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b", size = 394254 }, + { url = "https://files.pythonhosted.org/packages/1c/12/cf07458f35d0d775ff3a2dc5559fa2e1fcd06c46f1ef510e594ebefdca01/msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b", size = 69085 }, + { url = "https://files.pythonhosted.org/packages/73/80/2708a4641f7d553a63bc934a3eb7214806b5b39d200133ca7f7afb0a53e8/msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f", size = 75347 }, + { url = "https://files.pythonhosted.org/packages/c8/b0/380f5f639543a4ac413e969109978feb1f3c66e931068f91ab6ab0f8be00/msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf", size = 151142 }, + { url = "https://files.pythonhosted.org/packages/c8/ee/be57e9702400a6cb2606883d55b05784fada898dfc7fd12608ab1fdb054e/msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330", size = 84523 }, + { url = "https://files.pythonhosted.org/packages/7e/3a/2919f63acca3c119565449681ad08a2f84b2171ddfcff1dba6959db2cceb/msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734", size = 81556 }, + { url = "https://files.pythonhosted.org/packages/7c/43/a11113d9e5c1498c145a8925768ea2d5fce7cbab15c99cda655aa09947ed/msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e", size = 392105 }, + { url = "https://files.pythonhosted.org/packages/2d/7b/2c1d74ca6c94f70a1add74a8393a0138172207dc5de6fc6269483519d048/msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca", size = 399979 }, + { url = "https://files.pythonhosted.org/packages/82/8c/cf64ae518c7b8efc763ca1f1348a96f0e37150061e777a8ea5430b413a74/msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915", size = 383816 }, + { url = "https://files.pythonhosted.org/packages/69/86/a847ef7a0f5ef3fa94ae20f52a4cacf596a4e4a010197fbcc27744eb9a83/msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d", size = 380973 }, + { url = "https://files.pythonhosted.org/packages/aa/90/c74cf6e1126faa93185d3b830ee97246ecc4fe12cf9d2d31318ee4246994/msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434", size = 387435 }, + { url = "https://files.pythonhosted.org/packages/7a/40/631c238f1f338eb09f4acb0f34ab5862c4e9d7eda11c1b685471a4c5ea37/msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c", size = 399082 }, + { url = "https://files.pythonhosted.org/packages/e9/1b/fa8a952be252a1555ed39f97c06778e3aeb9123aa4cccc0fd2acd0b4e315/msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc", size = 69037 }, + { url = "https://files.pythonhosted.org/packages/b6/bc/8bd826dd03e022153bfa1766dcdec4976d6c818865ed54223d71f07862b3/msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f", size = 75140 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + +[[package]] +name = "numpy" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/d1/8a730ea07f4a37d94f9172f4ce1d81064b7a64766b460378be278952de75/numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c", size = 18878063 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/9c/9a6ec3ae89cd0648d419781284308f2956d2a61d932b5ac9682c956a171b/numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe", size = 21154845 }, + { url = "https://files.pythonhosted.org/packages/02/69/9f05c4ecc75fabf297b17743996371b4c3dfc4d92e15c5c38d8bb3db8d74/numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1", size = 13789409 }, + { url = "https://files.pythonhosted.org/packages/34/4e/f95c99217bf77bbfaaf660d693c10bd0dc03b6032d19316d316088c9e479/numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f", size = 5352097 }, + { url = "https://files.pythonhosted.org/packages/06/13/f5d87a497c16658e9af8920449b0b5692b469586b8231340c672962071c5/numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4", size = 6891195 }, + { url = "https://files.pythonhosted.org/packages/6c/89/691ac07429ac061b344d5e37fa8e94be51a6017734aea15f2d9d7c6d119a/numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a", size = 13895153 }, + { url = "https://files.pythonhosted.org/packages/23/69/538317f0d925095537745f12aced33be1570bbdc4acde49b33748669af96/numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1", size = 16338306 }, + { url = "https://files.pythonhosted.org/packages/af/03/863fe7062c2106d3c151f7df9353f2ae2237c1dd6900f127a3eb1f24cb1b/numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2", size = 16710893 }, + { url = "https://files.pythonhosted.org/packages/70/77/0ad9efe25482009873f9660d29a40a8c41a6f0e8b541195e3c95c70684c5/numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146", size = 14398048 }, + { url = "https://files.pythonhosted.org/packages/3e/0f/e785fe75544db9f2b0bb1c181e13ceff349ce49753d807fd9672916aa06d/numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c", size = 6533458 }, + { url = "https://files.pythonhosted.org/packages/d4/96/450054662295125af861d48d2c4bc081dadcf1974a879b2104613157aa62/numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9", size = 12870896 }, + { url = "https://files.pythonhosted.org/packages/a0/7d/554a6838f37f3ada5a55f25173c619d556ae98092a6e01afb6e710501d70/numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b", size = 20848077 }, + { url = "https://files.pythonhosted.org/packages/b0/29/cb48a402ea879e645b16218718f3f7d9588a77d674a9dcf22e4c43487636/numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db", size = 13493242 }, + { url = "https://files.pythonhosted.org/packages/56/44/f899b0581766c230da42f751b7b8896d096640b19b312164c267e48d36cb/numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1", size = 5089219 }, + { url = "https://files.pythonhosted.org/packages/79/8f/b987070d45161a7a4504afc67ed38544ed2c0ed5576263599a0402204a9c/numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426", size = 6620167 }, + { url = "https://files.pythonhosted.org/packages/c4/a7/af3329fda3c3ec31d9b650e42bbcd3422fc62a765cbb1405fde4177a0996/numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0", size = 13604905 }, + { url = "https://files.pythonhosted.org/packages/9b/b4/e3c7e6fab0f77fff6194afa173d1f2342073d91b1d3b4b30b17c3fb4407a/numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df", size = 16041825 }, + { url = "https://files.pythonhosted.org/packages/e9/50/6828e66a78aa03147c111f84d55f33ce2dde547cb578d6744a3b06a0124b/numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366", size = 16409541 }, + { url = "https://files.pythonhosted.org/packages/bf/72/66af7916d9c3c6dbfbc8acdd4930c65461e1953374a2bc43d00f948f004a/numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142", size = 14081134 }, + { url = "https://files.pythonhosted.org/packages/dc/5a/59a67d84f33fe00ae74f0b5b69dd4f93a586a4aba7f7e19b54b2133db038/numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550", size = 6237784 }, + { url = "https://files.pythonhosted.org/packages/4c/79/73735a6a5dad6059c085f240a4e74c9270feccd2bc66e4d31b5ca01d329c/numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e", size = 12568254 }, + { url = "https://files.pythonhosted.org/packages/16/72/716fa1dbe92395a9a623d5049203ff8ddb0cfce65b9df9117c3696ccc011/numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d", size = 20834690 }, + { url = "https://files.pythonhosted.org/packages/1e/fb/3e85a39511586053b5c6a59a643879e376fae22230ebfef9cfabb0e032e2/numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf", size = 13507474 }, + { url = "https://files.pythonhosted.org/packages/35/eb/5677556d9ba13436dab51e129f98d4829d95cd1b6bd0e199c14485a4bdb9/numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e", size = 5074742 }, + { url = "https://files.pythonhosted.org/packages/3e/c5/6c5ef5ba41b65a7e51bed50dbf3e1483eb578055633dd013e811a28e96a1/numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3", size = 6606787 }, + { url = "https://files.pythonhosted.org/packages/08/ac/f2f29dd4fd325b379c7dc932a0ebab22f0e031dbe80b2f6019b291a3a544/numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8", size = 13601333 }, + { url = "https://files.pythonhosted.org/packages/44/26/63f5f4e5089654dfb858f4892215ed968cd1a68e6f4a83f9961f84f855cb/numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a", size = 16038090 }, + { url = "https://files.pythonhosted.org/packages/1d/21/015e0594de9c3a8d5edd24943d2bd23f102ec71aec026083f822f86497e2/numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98", size = 16410865 }, + { url = "https://files.pythonhosted.org/packages/df/01/c1bcf9e6025d79077fbf3f3ee503b50aa7bfabfcd8f4b54f5829f4c00f3f/numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe", size = 14078077 }, + { url = "https://files.pythonhosted.org/packages/ba/06/db9d127d63bd11591770ba9f3d960f8041e0f895184b9351d4b1b5b56983/numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a", size = 6234904 }, + { url = "https://files.pythonhosted.org/packages/a9/96/9f61f8f95b6e0ea0aa08633b704c75d1882bdcb331bdf8bfd63263b25b00/numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445", size = 12561910 }, + { url = "https://files.pythonhosted.org/packages/36/b8/033f627821784a48e8f75c218033471eebbaacdd933f8979c79637a1b44b/numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5", size = 20857719 }, + { url = "https://files.pythonhosted.org/packages/96/46/af5726fde5b74ed83f2f17a73386d399319b7ed4d51279fb23b721d0816d/numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0", size = 13518826 }, + { url = "https://files.pythonhosted.org/packages/db/6e/8ce677edf36da1c4dae80afe5529f47690697eb55b4864673af260ccea7b/numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17", size = 5115036 }, + { url = "https://files.pythonhosted.org/packages/6a/ba/3cce44fb1b8438042c11847048812a776f75ee0e7070179c22e4cfbf420c/numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6", size = 6628641 }, + { url = "https://files.pythonhosted.org/packages/59/c8/e722998720ccbd35ffbcf1d1b8ed0aa2304af88d3f1c38e06ebf983599b3/numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8", size = 13574803 }, + { url = "https://files.pythonhosted.org/packages/7c/8e/fc1fdd83a55476765329ac2913321c4aed5b082a7915095628c4ca30ea72/numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35", size = 16021174 }, + { url = "https://files.pythonhosted.org/packages/2a/b6/a790742aa88067adb4bd6c89a946778c1417d4deaeafce3ca928f26d4c52/numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62", size = 16400117 }, + { url = "https://files.pythonhosted.org/packages/48/6f/129e3c17e3befe7fefdeaa6890f4c4df3f3cf0831aa053802c3862da67aa/numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a", size = 14066202 }, +] + +[[package]] +name = "opt-einsum" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932 }, +] + +[[package]] +name = "optax" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "chex" }, + { name = "etils", extra = ["epy"] }, + { name = "jax" }, + { name = "jaxlib" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/5f/e8b09028b37a8c1c159359e59469f3504b550910d472d8ee59543b1735d9/optax-0.2.3.tar.gz", hash = "sha256:ec7ab925440b0c5a512e1f24fba0fb3e7d760a7fd5d2496d7a691e9d37da01d9", size = 205212 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/8b/7032a6788205e9da398a8a33e1030ee9a22bd9289126e5afed9aac33bcde/optax-0.2.3-py3-none-any.whl", hash = "sha256:083e603dcd731d7e74d99f71c12f77937dd53f79001b4c09c290e4f47dd2e94f", size = 289647 }, +] + +[[package]] +name = "orbax-checkpoint" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "etils", extra = ["epath", "epy"] }, + { name = "humanize" }, + { name = "jax" }, + { name = "msgpack" }, + { name = "nest-asyncio" }, + { name = "numpy" }, + { name = "protobuf" }, + { name = "pyyaml" }, + { name = "tensorstore" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/5a/e07d3b2a9dacc6fe882a255080d4af3ac180bc190fd8ce22ab64cf0bfe26/orbax_checkpoint-0.7.0.tar.gz", hash = "sha256:f5a59babbf86fdafacddcfd2fb1c6d45b4fa0685b38a87a4598a5702bb70a657", size = 201557 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/63/45b63b51b320d104f21cb7f2a5d0ae2b37558e24296c02d33521a291ad87/orbax_checkpoint-0.7.0-py3-none-any.whl", hash = "sha256:0469030dd70729f7416981712a9ea8a82bd02c65ca82c933675c9e3ed4763f9b", size = 279660 }, +] + +[[package]] +name = "packaging" +version = "24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, +] + +[[package]] +name = "pillow" +version = "11.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/26/0d95c04c868f6bdb0c447e3ee2de5564411845e36a858cfd63766bc7b563/pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", size = 46737780 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/eb/f7e21b113dd48a9c97d364e0915b3988c6a0b6207652f5a92372871b7aa4/pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc", size = 3154705 }, + { url = "https://files.pythonhosted.org/packages/25/b3/2b54a1d541accebe6bd8b1358b34ceb2c509f51cb7dcda8687362490da5b/pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a", size = 2979222 }, + { url = "https://files.pythonhosted.org/packages/20/12/1a41eddad8265c5c19dda8fb6c269ce15ee25e0b9f8f26286e6202df6693/pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3", size = 4190220 }, + { url = "https://files.pythonhosted.org/packages/a9/9b/8a8c4d07d77447b7457164b861d18f5a31ae6418ef5c07f6f878fa09039a/pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5", size = 4291399 }, + { url = "https://files.pythonhosted.org/packages/fc/e4/130c5fab4a54d3991129800dd2801feeb4b118d7630148cd67f0e6269d4c/pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b", size = 4202709 }, + { url = "https://files.pythonhosted.org/packages/39/63/b3fc299528d7df1f678b0666002b37affe6b8751225c3d9c12cf530e73ed/pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa", size = 4372556 }, + { url = "https://files.pythonhosted.org/packages/c6/a6/694122c55b855b586c26c694937d36bb8d3b09c735ff41b2f315c6e66a10/pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306", size = 4287187 }, + { url = "https://files.pythonhosted.org/packages/ba/a9/f9d763e2671a8acd53d29b1e284ca298bc10a595527f6be30233cdb9659d/pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9", size = 4418468 }, + { url = "https://files.pythonhosted.org/packages/6e/0e/b5cbad2621377f11313a94aeb44ca55a9639adabcaaa073597a1925f8c26/pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5", size = 2249249 }, + { url = "https://files.pythonhosted.org/packages/dc/83/1470c220a4ff06cd75fc609068f6605e567ea51df70557555c2ab6516b2c/pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291", size = 2566769 }, + { url = "https://files.pythonhosted.org/packages/52/98/def78c3a23acee2bcdb2e52005fb2810ed54305602ec1bfcfab2bda6f49f/pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9", size = 2254611 }, + { url = "https://files.pythonhosted.org/packages/1c/a3/26e606ff0b2daaf120543e537311fa3ae2eb6bf061490e4fea51771540be/pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923", size = 3147642 }, + { url = "https://files.pythonhosted.org/packages/4f/d5/1caabedd8863526a6cfa44ee7a833bd97f945dc1d56824d6d76e11731939/pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903", size = 2978999 }, + { url = "https://files.pythonhosted.org/packages/d9/ff/5a45000826a1aa1ac6874b3ec5a856474821a1b59d838c4f6ce2ee518fe9/pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4", size = 4196794 }, + { url = "https://files.pythonhosted.org/packages/9d/21/84c9f287d17180f26263b5f5c8fb201de0f88b1afddf8a2597a5c9fe787f/pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f", size = 4300762 }, + { url = "https://files.pythonhosted.org/packages/84/39/63fb87cd07cc541438b448b1fed467c4d687ad18aa786a7f8e67b255d1aa/pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9", size = 4210468 }, + { url = "https://files.pythonhosted.org/packages/7f/42/6e0f2c2d5c60f499aa29be14f860dd4539de322cd8fb84ee01553493fb4d/pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7", size = 4381824 }, + { url = "https://files.pythonhosted.org/packages/31/69/1ef0fb9d2f8d2d114db982b78ca4eeb9db9a29f7477821e160b8c1253f67/pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6", size = 4296436 }, + { url = "https://files.pythonhosted.org/packages/44/ea/dad2818c675c44f6012289a7c4f46068c548768bc6c7f4e8c4ae5bbbc811/pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc", size = 4429714 }, + { url = "https://files.pythonhosted.org/packages/af/3a/da80224a6eb15bba7a0dcb2346e2b686bb9bf98378c0b4353cd88e62b171/pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6", size = 2249631 }, + { url = "https://files.pythonhosted.org/packages/57/97/73f756c338c1d86bb802ee88c3cab015ad7ce4b838f8a24f16b676b1ac7c/pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47", size = 2567533 }, + { url = "https://files.pythonhosted.org/packages/0b/30/2b61876e2722374558b871dfbfcbe4e406626d63f4f6ed92e9c8e24cac37/pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25", size = 2254890 }, + { url = "https://files.pythonhosted.org/packages/63/24/e2e15e392d00fcf4215907465d8ec2a2f23bcec1481a8ebe4ae760459995/pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699", size = 3147300 }, + { url = "https://files.pythonhosted.org/packages/43/72/92ad4afaa2afc233dc44184adff289c2e77e8cd916b3ddb72ac69495bda3/pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38", size = 2978742 }, + { url = "https://files.pythonhosted.org/packages/9e/da/c8d69c5bc85d72a8523fe862f05ababdc52c0a755cfe3d362656bb86552b/pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2", size = 4194349 }, + { url = "https://files.pythonhosted.org/packages/cd/e8/686d0caeed6b998351d57796496a70185376ed9c8ec7d99e1d19ad591fc6/pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2", size = 4298714 }, + { url = "https://files.pythonhosted.org/packages/ec/da/430015cec620d622f06854be67fd2f6721f52fc17fca8ac34b32e2d60739/pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527", size = 4208514 }, + { url = "https://files.pythonhosted.org/packages/44/ae/7e4f6662a9b1cb5f92b9cc9cab8321c381ffbee309210940e57432a4063a/pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa", size = 4380055 }, + { url = "https://files.pythonhosted.org/packages/74/d5/1a807779ac8a0eeed57f2b92a3c32ea1b696e6140c15bd42eaf908a261cd/pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f", size = 4296751 }, + { url = "https://files.pythonhosted.org/packages/38/8c/5fa3385163ee7080bc13026d59656267daaaaf3c728c233d530e2c2757c8/pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb", size = 4430378 }, + { url = "https://files.pythonhosted.org/packages/ca/1d/ad9c14811133977ff87035bf426875b93097fb50af747793f013979facdb/pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798", size = 2249588 }, + { url = "https://files.pythonhosted.org/packages/fb/01/3755ba287dac715e6afdb333cb1f6d69740a7475220b4637b5ce3d78cec2/pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de", size = 2567509 }, + { url = "https://files.pythonhosted.org/packages/c0/98/2c7d727079b6be1aba82d195767d35fcc2d32204c7a5820f822df5330152/pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84", size = 2254791 }, + { url = "https://files.pythonhosted.org/packages/eb/38/998b04cc6f474e78b563716b20eecf42a2fa16a84589d23c8898e64b0ffd/pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b", size = 3150854 }, + { url = "https://files.pythonhosted.org/packages/13/8e/be23a96292113c6cb26b2aa3c8b3681ec62b44ed5c2bd0b258bd59503d3c/pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003", size = 2982369 }, + { url = "https://files.pythonhosted.org/packages/97/8a/3db4eaabb7a2ae8203cd3a332a005e4aba00067fc514aaaf3e9721be31f1/pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2", size = 4333703 }, + { url = "https://files.pythonhosted.org/packages/28/ac/629ffc84ff67b9228fe87a97272ab125bbd4dc462745f35f192d37b822f1/pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a", size = 4412550 }, + { url = "https://files.pythonhosted.org/packages/d6/07/a505921d36bb2df6868806eaf56ef58699c16c388e378b0dcdb6e5b2fb36/pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8", size = 4461038 }, + { url = "https://files.pythonhosted.org/packages/d6/b9/fb620dd47fc7cc9678af8f8bd8c772034ca4977237049287e99dda360b66/pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8", size = 2253197 }, + { url = "https://files.pythonhosted.org/packages/df/86/25dde85c06c89d7fc5db17940f07aae0a56ac69aa9ccb5eb0f09798862a8/pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904", size = 2572169 }, + { url = "https://files.pythonhosted.org/packages/51/85/9c33f2517add612e17f3381aee7c4072779130c634921a756c97bc29fb49/pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", size = 2256828 }, +] + +[[package]] +name = "protobuf" +version = "5.28.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/a4/4579a61de526e19005ceeb93e478b61d77aa38c8a85ad958ff16a9906549/protobuf-5.28.2.tar.gz", hash = "sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0", size = 422494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/30/231764750e0987755b7b8d66771f161e5f002e165d27b72154c776dbabf7/protobuf-5.28.2-cp310-abi3-win32.whl", hash = "sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d", size = 419662 }, + { url = "https://files.pythonhosted.org/packages/7d/46/3fdf7462160135aee6a530f1ec66665b5b4132fa2e1002ab971bc6ec2589/protobuf-5.28.2-cp310-abi3-win_amd64.whl", hash = "sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132", size = 431479 }, + { url = "https://files.pythonhosted.org/packages/37/45/d2a760580f8f2ed2825ba44cb370e0a4011ddef85e728f46ea3dd565a8a5/protobuf-5.28.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7", size = 414736 }, + { url = "https://files.pythonhosted.org/packages/e6/23/ed718dc18e6a561445ece1e7a17d2dda0c634ad9cf663102b47f10005d8f/protobuf-5.28.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f", size = 316518 }, + { url = "https://files.pythonhosted.org/packages/23/08/a1ce0415a115c2b703bfa798f06f0e43ca91dbe29d6180bf86a9287b15e2/protobuf-5.28.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f", size = 316605 }, + { url = "https://files.pythonhosted.org/packages/9b/55/f24e3b801d2e108c48aa2b1b59bb791b5cffba89465cbbf66fc98de89270/protobuf-5.28.2-py3-none-any.whl", hash = "sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece", size = 169566 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pyerfa" +version = "2.0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/aa/a5f67966decfe415684e2505bb0b3dcaadb8c6ccc6421813b1ab34451060/pyerfa-2.0.1.4.tar.gz", hash = "sha256:acb8a6713232ea35c04bc6e40ac4e461dfcc817d395ef2a3c8051c1a33249dd3", size = 817421 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/29/daec96895624ec32db7a32ad3738463aac52bedb2a8b07bffcebf76c9f64/pyerfa-2.0.1.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ff112353944bf705342741f2fe41674f97154a302b0295eaef7381af92ad2b3a", size = 341799 }, + { url = "https://files.pythonhosted.org/packages/f9/4c/0460d2c1d82ae1c651a6e5354e64dfe4566bcb124d6d915eff039706dfd9/pyerfa-2.0.1.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:900b266a3862baa9560d6b1b184dcc14e0e76d550ff70d32336d3989b2ed18ca", size = 329332 }, + { url = "https://files.pythonhosted.org/packages/15/da/d588ed186140c775a3117c40e596c8ff09c80ebe4978051d9516cf0bb01b/pyerfa-2.0.1.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:610d2bc314e140d876b93b1287c7c81685434873c8700cc3e1596193f77d1071", size = 692783 }, + { url = "https://files.pythonhosted.org/packages/b3/1c/023031381b4cbc619eae5328d536735ec468218a2e92f45560bab9bb59a3/pyerfa-2.0.1.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e4508dd7ffd7b27b7f67168643764454887e990ca9e4584824f0e3ab5884c0f", size = 738706 }, + { url = "https://files.pythonhosted.org/packages/9e/b2/d4d916fd2e4cb1bc47e9698b31deed2798fdb99c0a8385871a7aac78b149/pyerfa-2.0.1.4-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:83a44ba84ebfc3244412ecbf1065c087c382da84f1c3eee1f2a0638d9046ac96", size = 749207 }, + { url = "https://files.pythonhosted.org/packages/f4/f3/9b857e3dea224d76e242bd2d4cfb686dc5d357d9f3de4f22e6958a2b52b9/pyerfa-2.0.1.4-cp39-abi3-win32.whl", hash = "sha256:46d3bed0ac666f08d8364b34a00b8c6595358d6c4f4532da8d13fac0e5227baa", size = 339975 }, + { url = "https://files.pythonhosted.org/packages/dd/cc/fdce417aa810a6abddf699948d3e1a57b85e12380222d5093d1b0756fb1f/pyerfa-2.0.1.4-cp39-abi3-win_amd64.whl", hash = "sha256:bc3cf45967ac1af77a777deb050fb08bbc75256dd97ca6005e4d385358b7af40", size = 347108 }, +] + +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + +[[package]] +name = "pyjwt" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 }, +] + +[[package]] +name = "pyparsing" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/d5/e5aeee5387091148a19e1145f63606619cb5f20b83fccb63efae6474e7b2/pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c", size = 920984 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/ec/2eb3cd785efd67806c46c13a17339708ddc346cbb684eade7a6e6f79536a/pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84", size = 106921 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "pytz" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "regex" +version = "2024.9.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/38/148df33b4dbca3bd069b963acab5e0fa1a9dbd6820f8c322d0dd6faeff96/regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd", size = 399403 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/a1/d526b7b6095a0019aa360948c143aacfeb029919c898701ce7763bbe4c15/regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df", size = 482483 }, + { url = "https://files.pythonhosted.org/packages/32/d9/bfdd153179867c275719e381e1e8e84a97bd186740456a0dcb3e7125c205/regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268", size = 287442 }, + { url = "https://files.pythonhosted.org/packages/33/c4/60f3370735135e3a8d673ddcdb2507a8560d0e759e1398d366e43d000253/regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad", size = 284561 }, + { url = "https://files.pythonhosted.org/packages/b1/51/91a5ebdff17f9ec4973cb0aa9d37635efec1c6868654bbc25d1543aca4ec/regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679", size = 791779 }, + { url = "https://files.pythonhosted.org/packages/07/4a/022c5e6f0891a90cd7eb3d664d6c58ce2aba48bff107b00013f3d6167069/regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4", size = 832605 }, + { url = "https://files.pythonhosted.org/packages/ac/1c/3793990c8c83ca04e018151ddda83b83ecc41d89964f0f17749f027fc44d/regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664", size = 818556 }, + { url = "https://files.pythonhosted.org/packages/e9/5c/8b385afbfacb853730682c57be56225f9fe275c5bf02ac1fc88edbff316d/regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50", size = 792808 }, + { url = "https://files.pythonhosted.org/packages/9b/8b/a4723a838b53c771e9240951adde6af58c829fb6a6a28f554e8131f53839/regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199", size = 781115 }, + { url = "https://files.pythonhosted.org/packages/83/5f/031a04b6017033d65b261259c09043c06f4ef2d4eac841d0649d76d69541/regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4", size = 778155 }, + { url = "https://files.pythonhosted.org/packages/fd/cd/4660756070b03ce4a66663a43f6c6e7ebc2266cc6b4c586c167917185eb4/regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd", size = 784614 }, + { url = "https://files.pythonhosted.org/packages/93/8d/65b9bea7df120a7be8337c415b6d256ba786cbc9107cebba3bf8ff09da99/regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f", size = 853744 }, + { url = "https://files.pythonhosted.org/packages/96/a7/fba1eae75eb53a704475baf11bd44b3e6ccb95b316955027eb7748f24ef8/regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96", size = 855890 }, + { url = "https://files.pythonhosted.org/packages/45/14/d864b2db80a1a3358534392373e8a281d95b28c29c87d8548aed58813910/regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1", size = 781887 }, + { url = "https://files.pythonhosted.org/packages/4d/a9/bfb29b3de3eb11dc9b412603437023b8e6c02fb4e11311863d9bf62c403a/regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9", size = 261644 }, + { url = "https://files.pythonhosted.org/packages/c7/ab/1ad2511cf6a208fde57fafe49829cab8ca018128ab0d0b48973d8218634a/regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf", size = 274033 }, + { url = "https://files.pythonhosted.org/packages/6e/92/407531450762bed778eedbde04407f68cbd75d13cee96c6f8d6903d9c6c1/regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7", size = 483590 }, + { url = "https://files.pythonhosted.org/packages/8e/a2/048acbc5ae1f615adc6cba36cc45734e679b5f1e4e58c3c77f0ed611d4e2/regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231", size = 288175 }, + { url = "https://files.pythonhosted.org/packages/8a/ea/909d8620329ab710dfaf7b4adee41242ab7c9b95ea8d838e9bfe76244259/regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d", size = 284749 }, + { url = "https://files.pythonhosted.org/packages/ca/fa/521eb683b916389b4975337873e66954e0f6d8f91bd5774164a57b503185/regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64", size = 795181 }, + { url = "https://files.pythonhosted.org/packages/28/db/63047feddc3280cc242f9c74f7aeddc6ee662b1835f00046f57d5630c827/regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42", size = 835842 }, + { url = "https://files.pythonhosted.org/packages/e3/94/86adc259ff8ec26edf35fcca7e334566c1805c7493b192cb09679f9c3dee/regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766", size = 823533 }, + { url = "https://files.pythonhosted.org/packages/29/52/84662b6636061277cb857f658518aa7db6672bc6d1a3f503ccd5aefc581e/regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a", size = 797037 }, + { url = "https://files.pythonhosted.org/packages/c3/2a/cd4675dd987e4a7505f0364a958bc41f3b84942de9efaad0ef9a2646681c/regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9", size = 784106 }, + { url = "https://files.pythonhosted.org/packages/6f/75/3ea7ec29de0bbf42f21f812f48781d41e627d57a634f3f23947c9a46e303/regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d", size = 782468 }, + { url = "https://files.pythonhosted.org/packages/d3/67/15519d69b52c252b270e679cb578e22e0c02b8dd4e361f2b04efcc7f2335/regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822", size = 790324 }, + { url = "https://files.pythonhosted.org/packages/9c/71/eff77d3fe7ba08ab0672920059ec30d63fa7e41aa0fb61c562726e9bd721/regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0", size = 860214 }, + { url = "https://files.pythonhosted.org/packages/81/11/e1bdf84a72372e56f1ea4b833dd583b822a23138a616ace7ab57a0e11556/regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a", size = 859420 }, + { url = "https://files.pythonhosted.org/packages/ea/75/9753e9dcebfa7c3645563ef5c8a58f3a47e799c872165f37c55737dadd3e/regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a", size = 787333 }, + { url = "https://files.pythonhosted.org/packages/bc/4e/ba1cbca93141f7416624b3ae63573e785d4bc1834c8be44a8f0747919eca/regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776", size = 262058 }, + { url = "https://files.pythonhosted.org/packages/6e/16/efc5f194778bf43e5888209e5cec4b258005d37c613b67ae137df3b89c53/regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009", size = 273526 }, + { url = "https://files.pythonhosted.org/packages/93/0a/d1c6b9af1ff1e36832fe38d74d5c5bab913f2bdcbbd6bc0e7f3ce8b2f577/regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784", size = 483376 }, + { url = "https://files.pythonhosted.org/packages/a4/42/5910a050c105d7f750a72dcb49c30220c3ae4e2654e54aaaa0e9bc0584cb/regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36", size = 288112 }, + { url = "https://files.pythonhosted.org/packages/8d/56/0c262aff0e9224fa7ffce47b5458d373f4d3e3ff84e99b5ff0cb15e0b5b2/regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92", size = 284608 }, + { url = "https://files.pythonhosted.org/packages/b9/54/9fe8f9aec5007bbbbce28ba3d2e3eaca425f95387b7d1e84f0d137d25237/regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86", size = 795337 }, + { url = "https://files.pythonhosted.org/packages/b2/e7/6b2f642c3cded271c4f16cc4daa7231be544d30fe2b168e0223724b49a61/regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85", size = 835848 }, + { url = "https://files.pythonhosted.org/packages/cd/9e/187363bdf5d8c0e4662117b92aa32bf52f8f09620ae93abc7537d96d3311/regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963", size = 823503 }, + { url = "https://files.pythonhosted.org/packages/f8/10/601303b8ee93589f879664b0cfd3127949ff32b17f9b6c490fb201106c4d/regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6", size = 797049 }, + { url = "https://files.pythonhosted.org/packages/ef/1c/ea200f61ce9f341763f2717ab4daebe4422d83e9fd4ac5e33435fd3a148d/regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802", size = 784144 }, + { url = "https://files.pythonhosted.org/packages/d8/5c/d2429be49ef3292def7688401d3deb11702c13dcaecdc71d2b407421275b/regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29", size = 782483 }, + { url = "https://files.pythonhosted.org/packages/12/d9/cbc30f2ff7164f3b26a7760f87c54bf8b2faed286f60efd80350a51c5b99/regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8", size = 790320 }, + { url = "https://files.pythonhosted.org/packages/19/1d/43ed03a236313639da5a45e61bc553c8d41e925bcf29b0f8ecff0c2c3f25/regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84", size = 860435 }, + { url = "https://files.pythonhosted.org/packages/34/4f/5d04da61c7c56e785058a46349f7285ae3ebc0726c6ea7c5c70600a52233/regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554", size = 859571 }, + { url = "https://files.pythonhosted.org/packages/12/7f/8398c8155a3c70703a8e91c29532558186558e1aea44144b382faa2a6f7a/regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8", size = 787398 }, + { url = "https://files.pythonhosted.org/packages/58/3a/f5903977647a9a7e46d5535e9e96c194304aeeca7501240509bde2f9e17f/regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8", size = 262035 }, + { url = "https://files.pythonhosted.org/packages/ff/80/51ba3a4b7482f6011095b3a036e07374f64de180b7d870b704ed22509002/regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f", size = 273510 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rich" +version = "13.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/9e/1784d15b057b0075e5136445aaea92d23955aad2c93eaede673718a40d95/rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c", size = 222843 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/91/5474b84e505a6ccc295b2d322d90ff6aa0746745717839ee0c5fb4fdcceb/rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1", size = 242117 }, +] + +[[package]] +name = "ripplegw" +version = "0.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jax" }, + { name = "jaxlib" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/68/0a8d7851a44c8ed6bcdd353337d2dc4e0a891e7cc2895f7fe9bbe17cbbd3/ripplegw-0.0.9.tar.gz", hash = "sha256:f0514364a1f5c530294f0c46e0efde01fecf05e8ee7bd04db256f44e0428de55", size = 452325 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/1c/bff356a2a6d8ce804d36778d35e8492e8309cd0f824b4f2669f408b16a3c/ripplegw-0.0.9-py3-none-any.whl", hash = "sha256:cd87ff6b3adb8d0651fc2bbbf7b4a21abc2c5bdc0680c8ec9c7b63d6d744b01a", size = 906662 }, +] + +[[package]] +name = "safe-netrc" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/f1/fb3c8ccd4a8963d7400cfd80100d5ae7fd95d49ace3a6722736bb4c1c1a6/safe-netrc-1.0.1.tar.gz", hash = "sha256:1dcc7263b4d9ce72e0109d8e2bc9ba89c8056ccc618d26c8c94802c6fd753720", size = 10846 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/2a/b5b09ff2781c7ea694b337f65f52a00319396578c049af8890ff9b4b8232/safe_netrc-1.0.1-py3-none-any.whl", hash = "sha256:5f0dd6a5e304b1da3be220f15efedbf09e50779fe90462143c228c781b9d8218", size = 10891 }, +] + +[[package]] +name = "scipy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/ab/070ccfabe870d9f105b04aee1e2860520460ef7ca0213172abfe871463b9/scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675", size = 39076999 }, + { url = "https://files.pythonhosted.org/packages/a7/c5/02ac82f9bb8f70818099df7e86c3ad28dae64e1347b421d8e3adf26acab6/scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2", size = 29894570 }, + { url = "https://files.pythonhosted.org/packages/ed/05/7f03e680cc5249c4f96c9e4e845acde08eb1aee5bc216eff8a089baa4ddb/scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617", size = 23103567 }, + { url = "https://files.pythonhosted.org/packages/5e/fc/9f1413bef53171f379d786aabc104d4abeea48ee84c553a3e3d8c9f96a9c/scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8", size = 25499102 }, + { url = "https://files.pythonhosted.org/packages/c2/4b/b44bee3c2ddc316b0159b3d87a3d467ef8d7edfd525e6f7364a62cd87d90/scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37", size = 35586346 }, + { url = "https://files.pythonhosted.org/packages/93/6b/701776d4bd6bdd9b629c387b5140f006185bd8ddea16788a44434376b98f/scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2", size = 41165244 }, + { url = "https://files.pythonhosted.org/packages/06/57/e6aa6f55729a8f245d8a6984f2855696c5992113a5dc789065020f8be753/scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2", size = 42817917 }, + { url = "https://files.pythonhosted.org/packages/ea/c2/5ecadc5fcccefaece775feadcd795060adf5c3b29a883bff0e678cfe89af/scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94", size = 44781033 }, + { url = "https://files.pythonhosted.org/packages/c0/04/2bdacc8ac6387b15db6faa40295f8bd25eccf33f1f13e68a72dc3c60a99e/scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d", size = 39128781 }, + { url = "https://files.pythonhosted.org/packages/c8/53/35b4d41f5fd42f5781dbd0dd6c05d35ba8aa75c84ecddc7d44756cd8da2e/scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07", size = 29939542 }, + { url = "https://files.pythonhosted.org/packages/66/67/6ef192e0e4d77b20cc33a01e743b00bc9e68fb83b88e06e636d2619a8767/scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5", size = 23148375 }, + { url = "https://files.pythonhosted.org/packages/f6/32/3a6dedd51d68eb7b8e7dc7947d5d841bcb699f1bf4463639554986f4d782/scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc", size = 25578573 }, + { url = "https://files.pythonhosted.org/packages/f0/5a/efa92a58dc3a2898705f1dc9dbaf390ca7d4fba26d6ab8cfffb0c72f656f/scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310", size = 35319299 }, + { url = "https://files.pythonhosted.org/packages/8e/ee/8a26858ca517e9c64f84b4c7734b89bda8e63bec85c3d2f432d225bb1886/scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066", size = 40849331 }, + { url = "https://files.pythonhosted.org/packages/a5/cd/06f72bc9187840f1c99e1a8750aad4216fc7dfdd7df46e6280add14b4822/scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1", size = 42544049 }, + { url = "https://files.pythonhosted.org/packages/aa/7d/43ab67228ef98c6b5dd42ab386eae2d7877036970a0d7e3dd3eb47a0d530/scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f", size = 44521212 }, + { url = "https://files.pythonhosted.org/packages/50/ef/ac98346db016ff18a6ad7626a35808f37074d25796fd0234c2bb0ed1e054/scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79", size = 39091068 }, + { url = "https://files.pythonhosted.org/packages/b9/cc/70948fe9f393b911b4251e96b55bbdeaa8cca41f37c26fd1df0232933b9e/scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e", size = 29875417 }, + { url = "https://files.pythonhosted.org/packages/3b/2e/35f549b7d231c1c9f9639f9ef49b815d816bf54dd050da5da1c11517a218/scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73", size = 23084508 }, + { url = "https://files.pythonhosted.org/packages/3f/d6/b028e3f3e59fae61fb8c0f450db732c43dd1d836223a589a8be9f6377203/scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e", size = 25503364 }, + { url = "https://files.pythonhosted.org/packages/a7/2f/6c142b352ac15967744d62b165537a965e95d557085db4beab2a11f7943b/scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d", size = 35292639 }, + { url = "https://files.pythonhosted.org/packages/56/46/2449e6e51e0d7c3575f289f6acb7f828938eaab8874dbccfeb0cd2b71a27/scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e", size = 40798288 }, + { url = "https://files.pythonhosted.org/packages/32/cd/9d86f7ed7f4497c9fd3e39f8918dd93d9f647ba80d7e34e4946c0c2d1a7c/scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06", size = 42524647 }, + { url = "https://files.pythonhosted.org/packages/f5/1b/6ee032251bf4cdb0cc50059374e86a9f076308c1512b61c4e003e241efb7/scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84", size = 44469524 }, +] + +[[package]] +name = "scitokens" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyjwt" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/13/13372ff4f4b6335819d2592b7f10ff1e56513261558b2a4162f6d6cba971/scitokens-1.8.1.tar.gz", hash = "sha256:f255383d9c7402b3fcd20d5ed26a6b407b4be8bec6f282d0af29b6275382b54d", size = 48564 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/17/916811af05e98d86b347f3a9d9c2d490ef9e5c75dc5980842bf946a70ba3/scitokens-1.8.1-py3-none-any.whl", hash = "sha256:a5455d85969cd7c7b341ed8691ea89e0e446bd414a734f8443752081b049b46c", size = 31190 }, +] + +[[package]] +name = "setuptools" +version = "75.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/37/b31be7e4b9f13b59cde9dcaeff112d401d49e0dc5b37ed4a9fc8fb12f409/setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec", size = 1350308 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/2d/90165d51ecd38f9a02c6832198c13a4e48652485e2ccf863ebb942c531b6/setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8", size = 1249825 }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "tensorstore" +version = "0.1.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/51/d9389280f077c1cb8ecaea2790a752a57f43edeb3b26ddf2d5ef2bb0a642/tensorstore-0.1.66.tar.gz", hash = "sha256:b77ee47da8a1b3d6fd03e23a8f853a2a666037f03e21546b4eb2b4cf43e13a96", size = 6533222 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/c5/093b09cbebe80590e681cb0bd4d1917a5ee8f198a0fd2ded63a0860f4df3/tensorstore-0.1.66-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:356e2a7fa256060c9da5d88cac6d9384d99887ab0df819c61f1dbe298ab56085", size = 15809330 }, + { url = "https://files.pythonhosted.org/packages/e2/3c/0508fb7778174c78bf98a423eee5448544249fad43947d8b324521d97e28/tensorstore-0.1.66-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16c4474b10c90b27d422ccffd31f78ed7c2f2976330ac80a4b53a73fd8f041d9", size = 13927590 }, + { url = "https://files.pythonhosted.org/packages/20/d4/c394eaa8ee519082aed59e8d3e6f8256890eb4b0e76c81e2b741af35c442/tensorstore-0.1.66-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb3d9bc9156adaf2212953d941efd993b57a8da7053b93f512c1c313dfdddb89", size = 13979944 }, + { url = "https://files.pythonhosted.org/packages/cc/87/f86a03fdd3f39521eb2a3a2c1e0d90d3e6b776cf8c84f8dae65c43104e3e/tensorstore-0.1.66-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a150257280f580f73b3c037a8e60c56a51e5c81d4bc3a1a90cab0e00a292dddd", size = 15305741 }, + { url = "https://files.pythonhosted.org/packages/7f/bb/3c2312d05b1bce3b07b1e18bdb7a8f295594db9ab82816a07edf0cf01263/tensorstore-0.1.66-cp311-cp311-win_amd64.whl", hash = "sha256:afcfa90c5dfc969e2286a30df97b91588486f77b232793abdd7c2cfd20df29d5", size = 11982284 }, + { url = "https://files.pythonhosted.org/packages/55/cc/c7462525d077ae94ea574b28f75e424f8c1be91dbc3cef9b0a17c89637d2/tensorstore-0.1.66-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:3bd37baa5983b9707c2b5e63301a2091aac3c21fef9891b5fce67597f9cfd61e", size = 15844587 }, + { url = "https://files.pythonhosted.org/packages/6d/43/e7745b21c31a2a9e36f3b062200dcc803a68d5526f093d5b87acbe472cff/tensorstore-0.1.66-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0de07f34b181023f9179b1e2f7cb255dfad300a4d3f18639d5c4cc955ae88b6b", size = 13955417 }, + { url = "https://files.pythonhosted.org/packages/a1/a6/2306a87375cb7e5fe3bdc465b0050d43802565a0a59398920a43d56ac09c/tensorstore-0.1.66-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ca2a389b8b274f3a8bef907469bc07f700001ee8ae0bc63a5164135c5afa8ee", size = 13968002 }, + { url = "https://files.pythonhosted.org/packages/70/66/69aaeac9dc02de80917acacfc74ac71af3b44cb7dfa1c84a559f57e2b7be/tensorstore-0.1.66-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4047f37ea0fbf665e25e918ad9d178fb2b3513203654ae10d8853d14e10a107", size = 15295727 }, + { url = "https://files.pythonhosted.org/packages/25/c8/bc42b40489833c70d61ff90edf6ca47eaa401b2f7d66b964b95b6060639b/tensorstore-0.1.66-cp312-cp312-win_amd64.whl", hash = "sha256:c974926fb2f3048fa3d36fc2fdf572403c7b70191193e83c5af78fdc46910b62", size = 11984388 }, +] + +[[package]] +name = "toolz" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/0b/d80dfa675bf592f636d1ea0b835eab4ec8df6e9415d8cfd766df54456123/toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02", size = 66790 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236", size = 56383 }, +] + +[[package]] +name = "tqdm" +version = "4.66.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351 }, +] + +[[package]] +name = "typed-argument-parser" +version = "1.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docstring-parser" }, + { name = "packaging" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/f0/b986539d89938ccd2ed7fd3a5f0f11250229e47b3bf7a7525786f36d2a3f/typed_argument_parser-1.10.1.tar.gz", hash = "sha256:117c2ef9ad6536d36f84f65eb7be035244c841fea34bd5abf2301a01c8a1ec89", size = 228573 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/07/0055bb513de2b7cf23b2bfc7eeeb6a6ae6ff7b287e2a62ab80cf403d61e9/typed_argument_parser-1.10.1-py3-none-any.whl", hash = "sha256:152cad218c84f23d9d4cbbfcb153a1a53fa0b330f0f7a4b85190dd4cb86f0c0a", size = 30407 }, +] + +[[package]] +name = "typeguard" +version = "2.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/38/c61bfcf62a7b572b5e9363a802ff92559cb427ee963048e1442e3aef7490/typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4", size = 40604 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/bb/d43e5c75054e53efce310e79d63df0ac3f25e34c926be5dffb7d283fb2a8/typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1", size = 17605 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, +] + +[[package]] +name = "tzdata" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, +] + +[[package]] +name = "tzlocal" +version = "5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/d3/c19d65ae67636fe63953b20c2e4a8ced4497ea232c43ff8d01db16de8dc0/tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e", size = 30201 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/3f/c4c51c55ff8487f2e6d0e618dba917e3c3ee2caae6cf0fbb59c9b1876f2e/tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8", size = 17859 }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, +] + +[[package]] +name = "zipp" +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200 }, +] From 6d89b185f0cece4241affb6e4fe16fe07b896e29 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Wed, 16 Oct 2024 13:59:19 -0400 Subject: [PATCH 237/248] update docs --- pyproject.toml | 21 +- uv.lock | 979 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 994 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8480b244..9c38c988 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,13 @@ [project] -name = "jim" -version = "0.1.0" -description = "Add your description here" +name = "jimGW" +version = "0.2.0" +description = "Gravitatioanl wave data analysis tool in Jax" readme = "README.md" requires-python = ">=3.11" +authors = [ + {name = "Kaze W. K. Wong", email = "kazewong.physics@gmail.com"}, +] +license = {file = "LICENSE"} dependencies = [ "astropy>=6.1.4", "beartype>=0.19.0", @@ -15,3 +19,14 @@ dependencies = [ "ripplegw>=0.0.9", "typed-argument-parser>=1.10.1", ] + +[project.optional-dependencies] +docs = [ + "mkdocs-gen-files==0.5.0", + "mkdocs-jupyter==0.24.2", + "mkdocs-literate-nav==0.6.0", + "mkdocs-material==9.1.18", + "mkdocs==1.4.3", + "mkdocstrings[python]==0.22.0", + "pymdown-extensions==10.1", +] diff --git a/uv.lock b/uv.lock index fecaeb66..752a32a7 100644 --- a/uv.lock +++ b/uv.lock @@ -15,6 +15,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/ad/e0d3c824784ff121c03cc031f944bc7e139a8f1870ffd2845cc2dd76f6c4/absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", size = 133706 }, ] +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, +] + [[package]] name = "astropy" version = "6.1.4" @@ -60,6 +69,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/68/9af5fb22fc04f54f8a27f6a90abe48707eeaa7c564f4f5e547cb3eecfa11/astropy_iers_data-0.2024.10.14.0.32.55-py3-none-any.whl", hash = "sha256:1a7c8e12bfe2af5605fd46dd9f8cb7fc9e9a78328cd231fdf6753b1814efe597", size = 1933242 }, ] +[[package]] +name = "asttokens" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/1d/f03bcb60c4a3212e15f99a56085d93093a497718adf828d050b9d675da81/asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0", size = 62284 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/86/4736ac618d82a20d87d2f92ae19441ebc7ac9e7a581d7e58bbe79233b24a/asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24", size = 27764 }, +] + +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, +] + [[package]] name = "beartype" version = "0.19.0" @@ -69,6 +99,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/69/f6db6e4cb2fe2f887dead40b76caa91af4844cb647dd2c7223bb010aa416/beartype-0.19.0-py3-none-any.whl", hash = "sha256:33b2694eda0daf052eb2aff623ed9a8a586703bbf0a90bbc475a83bbf427f699", size = 1039760 }, ] +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, +] + +[[package]] +name = "bleach" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/10/77f32b088738f40d4f5be801daa5f327879eadd4562f36a2b5ab975ae571/bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe", size = 202119 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/63/da7237f805089ecc28a3f36bca6a21c31fcbc2eb380f3b8f1be3312abd14/bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6", size = 162750 }, +] + [[package]] name = "certifi" version = "2024.8.30" @@ -195,6 +250,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/dd/c1ff2eb8fbf95a8ca804abb1cc3ce70b283ee7b4bc653c3abac245670400/chex-0.1.87-py3-none-any.whl", hash = "sha256:ce536475661fd96d21be0c1728ecdbedd03f8ff950c662dfc338c92ea782cb16", size = 99369 }, ] +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -204,6 +271,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "comm" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, +] + [[package]] name = "contourpy" version = "1.3.0" @@ -318,6 +397,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/29/db12aa4dda81580be1999824a689bd52aa40061fc12c9ccdc3feab5ea718/dateparser-1.2.0-py2.py3-none-any.whl", hash = "sha256:0b21ad96534e562920a0083e97fd45fa959882d4162acc358705144520a35830", size = 294995 }, ] +[[package]] +name = "debugpy" +version = "1.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/00/5a8b5dc8f52617c5e41845e26290ebea1ba06377cc08155b6d245c27b386/debugpy-1.8.7.zip", hash = "sha256:18b8f731ed3e2e1df8e9cdaa23fb1fc9c24e570cd0081625308ec51c82efe42e", size = 4957835 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/0a/4a4516ef4c07891542cb25620085507cab3c6b23a42b5630c17788fff83e/debugpy-1.8.7-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:caf528ff9e7308b74a1749c183d6808ffbedbb9fb6af78b033c28974d9b8831f", size = 2204794 }, + { url = "https://files.pythonhosted.org/packages/46/6f/2bb0bba20b8b74b7c341379dd99275cf6aa7722c1948fa99728716aad1b9/debugpy-1.8.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cba1d078cf2e1e0b8402e6bda528bf8fda7ccd158c3dba6c012b7897747c41a0", size = 3122160 }, + { url = "https://files.pythonhosted.org/packages/c0/ce/833351375cef971f0caa63fa82adf3f6949ad85410813026a4a436083a71/debugpy-1.8.7-cp311-cp311-win32.whl", hash = "sha256:171899588bcd412151e593bd40d9907133a7622cd6ecdbdb75f89d1551df13c2", size = 5078675 }, + { url = "https://files.pythonhosted.org/packages/7d/e1/e9ac2d546143a4defbaa2e609e173c912fb989cdfb5385c9771770a6bf5c/debugpy-1.8.7-cp311-cp311-win_amd64.whl", hash = "sha256:6e1c4ffb0c79f66e89dfd97944f335880f0d50ad29525dc792785384923e2211", size = 5102927 }, + { url = "https://files.pythonhosted.org/packages/59/4b/9f52ca1a799601a10cd2673503658bd8c8ecc4a7a43302ee29cf062474ec/debugpy-1.8.7-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:4d27d842311353ede0ad572600c62e4bcd74f458ee01ab0dd3a1a4457e7e3706", size = 2529803 }, + { url = "https://files.pythonhosted.org/packages/80/79/8bba39190d2ea17840925d287f1c6c3a7c60b58f5090444e9ecf176c540f/debugpy-1.8.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c1fd62ae0356e194f3e7b7a92acd931f71fe81c4b3be2c17a7b8a4b546ec2", size = 4170911 }, + { url = "https://files.pythonhosted.org/packages/3b/19/5b3d312936db8eb281310fa27903459328ed722d845d594ba5feaeb2f0b3/debugpy-1.8.7-cp312-cp312-win32.whl", hash = "sha256:2f729228430ef191c1e4df72a75ac94e9bf77413ce5f3f900018712c9da0aaca", size = 5195476 }, + { url = "https://files.pythonhosted.org/packages/9f/49/ad20b29f8c921fd5124530d3d39b8f2077efd51b71339a2eff02bba693e9/debugpy-1.8.7-cp312-cp312-win_amd64.whl", hash = "sha256:45c30aaefb3e1975e8a0258f5bbd26cd40cde9bfe71e9e5a7ac82e79bad64e39", size = 5235031 }, + { url = "https://files.pythonhosted.org/packages/41/95/29b247518d0a6afdb5249f5d05743c9c5bfaf4bd13a85b81cb5e1dc65837/debugpy-1.8.7-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:d050a1ec7e925f514f0f6594a1e522580317da31fbda1af71d1530d6ea1f2b40", size = 2517557 }, + { url = "https://files.pythonhosted.org/packages/4d/93/026e2000a0740e2f54b198f8dc317accf3a70b6524b2b15fa8e6eca74414/debugpy-1.8.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f4349a28e3228a42958f8ddaa6333d6f8282d5edaea456070e48609c5983b7", size = 4162703 }, + { url = "https://files.pythonhosted.org/packages/c3/92/a48e653b19a171434290ecdc5935b7a292a65488139c5271d6d0eceeb0f1/debugpy-1.8.7-cp313-cp313-win32.whl", hash = "sha256:11ad72eb9ddb436afb8337891a986302e14944f0f755fd94e90d0d71e9100bba", size = 5195220 }, + { url = "https://files.pythonhosted.org/packages/4e/b3/dc3c5527edafcd1a6d0f8c4ecc6c5c9bc431f77340cf4193328e98f0ac38/debugpy-1.8.7-cp313-cp313-win_amd64.whl", hash = "sha256:2efb84d6789352d7950b03d7f866e6d180284bc02c7e12cb37b489b7083d81aa", size = 5235333 }, + { url = "https://files.pythonhosted.org/packages/51/b1/a0866521c71a6ae3d3ca320e74835163a4671b1367ba360a55a0a51e5a91/debugpy-1.8.7-py2.py3-none-any.whl", hash = "sha256:57b00de1c8d2c84a61b90880f7e5b6deaf4c312ecbde3a0e8912f2a56c4ac9ae", size = 5210683 }, +] + +[[package]] +name = "decorator" +version = "5.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", size = 35016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + [[package]] name = "docstring-parser" version = "0.16" @@ -402,6 +520,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/24/f8/a58890d615e548de5003a38636d8986e226805bbcb27aab206e1c8430f57/evosax-0.1.6-py3-none-any.whl", hash = "sha256:195145d68c67f5f1de9c580b6f248f624ac9d089de628e67665f7b3616d638e4", size = 240393 }, ] +[[package]] +name = "executing" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/7d45f492c2c4a0e8e0fad57d081a7c8a0286cdd86372b070cca1ec0caa1e/executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab", size = 977485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/fd/afcd0496feca3276f509df3dbd5dae726fcc756f1a08d9e25abe1733f962/executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf", size = 25805 }, +] + +[[package]] +name = "fastjsonschema" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/3f/3ad5e7be13b4b8b55f4477141885ab2364f65d5f6ad5f7a9daffd634d066/fastjsonschema-2.20.0.tar.gz", hash = "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23", size = 373056 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/ca/086311cdfc017ec964b2436fe0c98c1f4efcb7e4c328956a22456e497655/fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a", size = 23543 }, +] + [[package]] name = "flax" version = "0.9.0" @@ -480,6 +616,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/a0/6aaea0c2fbea2f89bfd5db25fb1e3481896a423002ebe4e55288907a97a3/fsspec-2024.9.0-py3-none-any.whl", hash = "sha256:a0947d552d8a6efa72cc2c730b12c41d043509156966cca4fb157b0f2a0c574b", size = 179253 }, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, +] + +[[package]] +name = "griffe" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/65/f708fd15b91182e8928f5bf335255028c22f5d8a181e8819f3fa8f0230f4/griffe-1.4.1.tar.gz", hash = "sha256:911a201b01dc92e08c0e84c38a301e9da5ec067f00e7d9f2e39bc24dbfa3c176", size = 381239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/fc/570a1e503e19be24c5642ea8b93f23e3eef1dfa930e761cab72dedc2c2db/griffe-1.4.1-py3-none-any.whl", hash = "sha256:84295ee0b27743bd880aea75632830ef02ded65d16124025e4c263bb826ab645", size = 126956 }, +] + [[package]] name = "gwdatafind" version = "1.2.0" @@ -598,6 +758,51 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/6a/4604f9ae2fa62ef47b9de2fa5ad599589d28c9fd1d335f32759813dfa91e/importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717", size = 36115 }, ] +[[package]] +name = "ipykernel" +version = "6.29.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "platform_system == 'Darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173 }, +] + +[[package]] +name = "ipython" +version = "8.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/21/48db7d9dd622b9692575004c7c98f85f5629428f58596c59606d36c51b58/ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a", size = 5495762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/3a/5d8680279ada9571de8469220069d27024ee47624af534e537c9ff49a450/ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35", size = 819456 }, +] + [[package]] name = "jax" version = "0.4.34" @@ -654,8 +859,20 @@ wheels = [ ] [[package]] -name = "jim" -version = "0.1.0" +name = "jedi" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/99/99b493cec4bf43176b678de30f81ed003fd6a647a301b9c927280c600f0a/jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd", size = 1227821 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/9f/bc63f0f0737ad7a60800bfd472a4836661adae21f9c2535f3957b1e54ceb/jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0", size = 1569361 }, +] + +[[package]] +name = "jimgw" +version = "0.2.0" source = { virtual = "." } dependencies = [ { name = "astropy" }, @@ -669,19 +886,131 @@ dependencies = [ { name = "typed-argument-parser" }, ] +[package.optional-dependencies] +docs = [ + { name = "mkdocs" }, + { name = "mkdocs-gen-files" }, + { name = "mkdocs-jupyter" }, + { name = "mkdocs-literate-nav" }, + { name = "mkdocs-material" }, + { name = "mkdocstrings", extra = ["python"] }, + { name = "pymdown-extensions" }, +] + [package.metadata] requires-dist = [ { name = "astropy", specifier = ">=6.1.4" }, { name = "beartype", specifier = ">=0.19.0" }, { name = "corner", specifier = ">=2.2.2" }, { name = "flowmc", specifier = ">=0.3.4" }, - { name = "gwpy" }, + { name = "gwpy", specifier = ">=3.0.10" }, { name = "jax", specifier = ">=0.4.34" }, { name = "jaxtyping", specifier = ">=0.2.34" }, + { name = "mkdocs", marker = "extra == 'docs'", specifier = "==1.4.3" }, + { name = "mkdocs-gen-files", marker = "extra == 'docs'", specifier = "==0.5.0" }, + { name = "mkdocs-jupyter", marker = "extra == 'docs'", specifier = "==0.24.2" }, + { name = "mkdocs-literate-nav", marker = "extra == 'docs'", specifier = "==0.6.0" }, + { name = "mkdocs-material", marker = "extra == 'docs'", specifier = "==9.1.18" }, + { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'", specifier = "==0.22.0" }, + { name = "pymdown-extensions", marker = "extra == 'docs'", specifier = "==10.1" }, { name = "ripplegw", specifier = ">=0.0.9" }, { name = "typed-argument-parser", specifier = ">=1.10.1" }, ] +[[package]] +name = "jinja2" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, +] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/11/b56381fa6c3f4cc5d2cf54a7dbf98ad9aa0b339ef7a601d6053538b079a7/jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9", size = 87629 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, +] + +[[package]] +name = "jupytext" +version = "1.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/ba/81097573072b165772b71298c339d5da46dfeec53c1c354ce282109967ea/jupytext-1.16.4.tar.gz", hash = "sha256:28e33f46f2ce7a41fb9d677a4a2c95327285579b64ca104437c4b9eb1e4174e9", size = 3724368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/a3/285eb1e79dbbd8e9513a3bb1bb2bb3d4c7c22c8a92efb8449baface0b864/jupytext-1.16.4-py3-none-any.whl", hash = "sha256:76989d2690e65667ea6fb411d8056abe7cd0437c07bd774660b83d62acf9490a", size = 153540 }, +] + [[package]] name = "kiwisolver" version = "1.4.7" @@ -760,6 +1089,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/b6/6d6d0585fa2ae936a9f5d411b1f0fbe9fcb0aca0c51a775aa4f8f95fdf5e/ligotimegps-2.0.1-py2.py3-none-any.whl", hash = "sha256:da8c1289ba1310337ef5177e7936e25ce47d4e8e6a269cbdd5e9abfc5b5db490", size = 19930 }, ] +[[package]] +name = "markdown" +version = "3.3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/58/79df20de6e67a83f0d0bbfe6c19bb82adf68cdf362885257eb01099f930a/Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874", size = 324130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/df/ca72f352e15b6f8ce32b74af029f1189abffb906f7c137501ffe69c98a65/Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621", size = 97778 }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -772,6 +1110,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, ] +[[package]] +name = "markupsafe" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/d2/38ff920762f2247c3af5cbbbbc40756f575d9692d381d7c520f45deb9b8f/markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344", size = 20249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/af/2f5d88a7fc7226bd34c6e15f6061246ad8cff979da9f19d11bdd0addd8e2/MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad", size = 14387 }, + { url = "https://files.pythonhosted.org/packages/8d/43/fd588ef5d192308c5e05974bac659bf6ae29c202b7ea2c4194bcf01eacee/MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583", size = 12410 }, + { url = "https://files.pythonhosted.org/packages/58/26/78f161d602fb03804118905e5faacafc0ec592bbad71aaee62537529813a/MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7", size = 24006 }, + { url = "https://files.pythonhosted.org/packages/ae/1d/7d5ec8bcfd9c2db235d720fa51d818b7e2abc45250ce5f53dd6cb60409ca/MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b", size = 23303 }, + { url = "https://files.pythonhosted.org/packages/26/ce/703ca3b03a709e3bd1fbffa407789e56b9fa664456538092617dd665fc1d/MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3", size = 23205 }, + { url = "https://files.pythonhosted.org/packages/88/60/40be0493decabc2344b12d3a709fd6ccdd15a5ebaee1e8d878315d107ad3/MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50", size = 23684 }, + { url = "https://files.pythonhosted.org/packages/6d/f8/8fd52a66e8f62a9add62b4a0b5a3ab4092027437f2ef027f812d94ae91cf/MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915", size = 23472 }, + { url = "https://files.pythonhosted.org/packages/d4/0b/998b17b9e06ea45ad1646fea586f1b83d02dfdb14d47dd2fd81fba5a08c9/MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91", size = 23388 }, + { url = "https://files.pythonhosted.org/packages/5a/57/b6b7aa23b2e26d68d601718f8ce3161fbdaf967b31752c7dec52bef828c9/MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635", size = 15106 }, + { url = "https://files.pythonhosted.org/packages/fc/b5/20cb1d714596acb553c810009c8004c809823947da63e13c19a7decfcb6c/MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf", size = 15542 }, + { url = "https://files.pythonhosted.org/packages/45/6d/72ed58d42a12bd9fc288dbff6dd8d03ea973a232ac0538d7f88d105b5251/MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4", size = 14322 }, + { url = "https://files.pythonhosted.org/packages/86/f5/241238f89cdd6461ac9f521af8389f9a48fab97e4f315c69e9e0d52bc919/MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5", size = 12380 }, + { url = "https://files.pythonhosted.org/packages/27/94/79751928bca5841416d8ca02e22198672e021d5c7120338e2a6e3771f8fc/MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346", size = 24099 }, + { url = "https://files.pythonhosted.org/packages/10/6e/1b8070bbfc467429c7983cd5ffd4ec57e1d501763d974c7caaa0a9a79f4c/MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729", size = 23249 }, + { url = "https://files.pythonhosted.org/packages/66/50/9389ae6cdff78d7481a2a2641830b5eb1d1f62177550e73355a810a889c9/MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc", size = 23149 }, + { url = "https://files.pythonhosted.org/packages/16/02/5dddff5366fde47133186efb847fa88bddef85914bbe623e25cfeccb3517/MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9", size = 23864 }, + { url = "https://files.pythonhosted.org/packages/f3/f1/700ee6655561cfda986e03f7afc309e3738918551afa7dedd99225586227/MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b", size = 23440 }, + { url = "https://files.pythonhosted.org/packages/fb/3e/d26623ac7f16709823b4c80e0b4a1c9196eeb46182a6c1d47b5e0c8434f4/MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38", size = 23610 }, + { url = "https://files.pythonhosted.org/packages/51/04/1f8da0810c39cb9fcff96b6baed62272c97065e9cf11471965a161439e20/MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa", size = 15113 }, + { url = "https://files.pythonhosted.org/packages/eb/24/a36dc37365bdd358b1e583cc40475593e36ab02cb7da6b3d0b9c05b0da7a/MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f", size = 15611 }, + { url = "https://files.pythonhosted.org/packages/b1/60/4572a8aa1beccbc24b133aa0670781a5d2697f4fa3fecf0a87b46383174b/MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772", size = 14325 }, + { url = "https://files.pythonhosted.org/packages/38/42/849915b99a765ec104bfd07ee933de5fc9c58fa9570efa7db81717f495d8/MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da", size = 12373 }, + { url = "https://files.pythonhosted.org/packages/ef/82/4caaebd963c6d60b28e4445f38841d24f8b49bc10594a09956c9d73bfc08/MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a", size = 24059 }, + { url = "https://files.pythonhosted.org/packages/20/15/6b319be2f79fcfa3173f479d69f4e950b5c9b642db4f22cf73ae5ade745f/MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c", size = 23211 }, + { url = "https://files.pythonhosted.org/packages/9d/3f/8963bdf4962feb2154475acb7dc350f04217b5e0be7763a39b432291e229/MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd", size = 23095 }, + { url = "https://files.pythonhosted.org/packages/af/93/f770bc70953d32de0c6ce4bcb76271512123a1ead91aaef625a020c5bfaf/MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7", size = 23901 }, + { url = "https://files.pythonhosted.org/packages/11/92/1e5a33aa0a1190161238628fb68eb1bc5e67b56a5c89f0636328704b463a/MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd", size = 23463 }, + { url = "https://files.pythonhosted.org/packages/0d/fe/657efdfe385d2a3a701f2c4fcc9577c63c438aeefdd642d0d956c4ecd225/MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5", size = 23569 }, + { url = "https://files.pythonhosted.org/packages/cf/24/587dea40304046ace60f846cedaebc0d33d967a3ce46c11395a10e7a78ba/MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c", size = 15117 }, + { url = "https://files.pythonhosted.org/packages/32/8f/d8961d633f26a011b4fe054f3bfff52f673423b8c431553268741dfb089e/MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f", size = 15613 }, + { url = "https://files.pythonhosted.org/packages/9e/93/d6367ffbcd0c5c371370767f768eaa32af60bc411245b8517e383c6a2b12/MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a", size = 14563 }, + { url = "https://files.pythonhosted.org/packages/4a/37/f813c3835747dec08fe19ac9b9eced01fdf93a4b3e626521675dc7f423a9/MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d", size = 12505 }, + { url = "https://files.pythonhosted.org/packages/72/bf/800b4d1580298ca91ccd6c95915bbd147142dad1b8cf91d57b93b28670dd/MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396", size = 25358 }, + { url = "https://files.pythonhosted.org/packages/fd/78/26e209abc8f0a379f031f0acc151231974e5b153d7eda5759d17d8f329f2/MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453", size = 23797 }, + { url = "https://files.pythonhosted.org/packages/09/e1/918496a9390891756efee818880e71c1bbaf587f4dc8ede3f3852357310a/MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4", size = 23743 }, + { url = "https://files.pythonhosted.org/packages/cd/c6/26f576cd58d6c2decd9045e4e3f3c5dbc01ea6cb710916e7bbb6ebd95b6b/MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8", size = 25076 }, + { url = "https://files.pythonhosted.org/packages/b5/fa/10b24fb3b0e15fe5389dc88ecc6226ede08297e0ba7130610efbe0cdfb27/MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984", size = 24037 }, + { url = "https://files.pythonhosted.org/packages/c8/81/4b3f5537d9f6cc4f5c80d6c4b78af9a5247fd37b5aba95807b2cbc336b9a/MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a", size = 24015 }, + { url = "https://files.pythonhosted.org/packages/5f/07/8e8dcecd53216c5e01a51e84c32a2bce166690ed19c184774b38cd41921d/MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b", size = 15213 }, + { url = "https://files.pythonhosted.org/packages/0d/87/4c364e0f109eea2402079abecbe33fef4f347b551a11423d1f4e187ea497/MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295", size = 15741 }, +] + [[package]] name = "matplotlib" version = "3.9.2" @@ -814,6 +1200,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/8a/760f7fce66b39f447ad160800619d0bd5d0936d2b4633587116534a4afe0/matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e", size = 9093770 }, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -823,6 +1233,165 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, +] + +[[package]] +name = "mistune" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/c8/f0173fe3bf85fd891aee2e7bcd8207dfe26c2c683d727c5a6cc3aec7b628/mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8", size = 90840 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/74/c95adcdf032956d9ef6c89a9b8a5152bf73915f8c633f3e3d88d06bd699c/mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205", size = 47958 }, +] + +[[package]] +name = "mkdocs" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mergedeep" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/ef/49b4427e5eec761b77a3c3c421d3fd63010e2798b7401dc0fa2b875ef6b5/mkdocs-1.4.3.tar.gz", hash = "sha256:5955093bbd4dd2e9403c5afaf57324ad8b04f16886512a3ee6ef828956481c57", size = 3624951 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/7a/5ed794942ace9d00bb77a8036c64c999cda6ebaab57e9b8a6ec1aa5fc900/mkdocs-1.4.3-py3-none-any.whl", hash = "sha256:6ee46d309bda331aac915cd24aab882c179a933bd9e77b80ce7d2eaaa3f689dd", size = 3654464 }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/ae/0f1154c614d6a8b8a36fff084e5b82af3a15f7d2060cf0dcdb1c53297a71/mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f", size = 40262 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f", size = 16522 }, +] + +[[package]] +name = "mkdocs-gen-files" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/85/2d634462fd59136197d3126ca431ffb666f412e3db38fd5ce3a60566303e/mkdocs_gen_files-0.5.0.tar.gz", hash = "sha256:4c7cf256b5d67062a788f6b1d035e157fc1a9498c2399be9af5257d4ff4d19bc", size = 7539 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/0f/1e55b3fd490ad2cecb6e7b31892d27cb9fc4218ec1dab780440ba8579e74/mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea", size = 8380 }, +] + +[[package]] +name = "mkdocs-jupyter" +version = "0.24.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "jupytext" }, + { name = "mkdocs" }, + { name = "mkdocs-material" }, + { name = "nbconvert" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/6d/e2367bdd3662a2a1cf15d61ad11548394fbef55a393fbdcceddbb59e4deb/mkdocs_jupyter-0.24.2.tar.gz", hash = "sha256:5e0c109d535d48797230719b6941f4d08de95a7a3c95bf158662c412fc15cb2e", size = 4324061 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/51/1b4da5919e7bb028969129a5ff631e56215f3e110bb2af68bfe157209854/mkdocs_jupyter-0.24.2-py3-none-any.whl", hash = "sha256:5295a34b1d0bd0a7688a857323eaf0319d83c8a14179b2651709b8ced6eae7db", size = 3894677 }, +] + +[[package]] +name = "mkdocs-literate-nav" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/91/75eb48eafdbe609ca80d6c271ad5bc4edc75cffde4d1d70604f2a4c2145a/mkdocs_literate_nav-0.6.0.tar.gz", hash = "sha256:81ccbea18163ae8e10bd0bd39237fe70c32a1f2dff6c170779f5d52dd98a0470", size = 16342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/e0/4c04aa187e4c0a3f1218681e7389106fad8f25ecaae90b1e68636f04d15b/mkdocs_literate_nav-0.6.0-py3-none-any.whl", hash = "sha256:8c1b84714e5974da5e44e011ec0069275ae7647270c13a679662cf6ffce675a4", size = 13082 }, +] + +[[package]] +name = "mkdocs-material" +version = "9.1.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/64/d2b6cfc3cb7584f4e2270fbe90be6448d1e410075a9b7d1b79c166a6eaee/mkdocs_material-9.1.18.tar.gz", hash = "sha256:981dd39979723d4cda7cfc77bbbe5e54922d5761a7af23fb8ba9edb52f114b13", size = 3693508 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/e9/bdb7488c44adef405afb7343b55f71660f6de4b6e06e02cad123e4473370/mkdocs_material-9.1.18-py3-none-any.whl", hash = "sha256:5bcf8fb79ac2f253c0ffe93fa181cba87718c6438f459dc4180ac7418cc9a450", size = 7846316 }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 }, +] + +[[package]] +name = "mkdocstrings" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/89/39b7da1cd3d7bc9d3626a2030349443276bd4c8428b676b010ffb96ec9be/mkdocstrings-0.22.0.tar.gz", hash = "sha256:82a33b94150ebb3d4b5c73bab4598c3e21468c79ec072eff6931c8f3bfc38256", size = 30419 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/26/5816407b5dd51821a3d23f53bdbd013ab1878b6246e520dc014d200ee1d2/mkdocstrings-0.22.0-py3-none-any.whl", hash = "sha256:2d4095d461554ff6a778fdabdca3c00c468c2f1459d469f7a7f622a2b23212ba", size = 26747 }, +] + +[package.optional-dependencies] +python = [ + { name = "mkdocstrings-python" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe" }, + { name = "markdown" }, + { name = "mkdocstrings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/47/9d771f91359e5377cf19385a056aa4da43324995d72b1fac9d77c840d423/mkdocstrings_python-1.9.1.tar.gz", hash = "sha256:077188fa43eab3b689826b15da7da6753501224b2482e4eca3ce4412ce3b71cb", size = 34013 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/e1/1498edc8323a2967dea48bd9366df861507dbafcd36a58f59db86fec615f/mkdocstrings_python-1.9.1-py3-none-any.whl", hash = "sha256:bf2406ed37ff19c9f8e0acc9d72c41953fb789bfb4ae10eb00ee17e537eeb220", size = 58554 }, +] + [[package]] name = "ml-dtypes" version = "0.5.0" @@ -896,6 +1465,62 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, ] +[[package]] +name = "nbclient" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/d2/39bc36604f24bccd44d374ac34769bc58c53a1da5acd1e83f0165aa4940e/nbclient-0.10.0.tar.gz", hash = "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09", size = 62246 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/e8/00517a23d3eeaed0513e718fbc94aab26eaa1758f5690fc8578839791c79/nbclient-0.10.0-py3-none-any.whl", hash = "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f", size = 25318 }, +] + +[[package]] +name = "nbconvert" +version = "7.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach" }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "tinycss2" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/e8/ba521a033b21132008e520c28ceb818f9f092da5f0261e94e509401b29f9/nbconvert-7.16.4.tar.gz", hash = "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4", size = 854422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/bb/bb5b6a515d1584aa2fd89965b11db6632e4bdc69495a52374bcc36e56cfa/nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3", size = 257388 }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, +] + [[package]] name = "nest-asyncio" version = "1.6.0" @@ -1008,6 +1633,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, ] +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + [[package]] name = "pillow" version = "11.0.0" @@ -1057,6 +1712,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/85/9c33f2517add612e17f3381aee7c4072779130c634921a756c97bc29fb49/pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", size = 2256828 }, ] +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/4f/feb5e137aff82f7c7f3248267b97451da3644f6cdc218edfe549fb354127/prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90", size = 424684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, +] + [[package]] name = "protobuf" version = "5.28.2" @@ -1071,6 +1747,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/55/f24e3b801d2e108c48aa2b1b59bb791b5cffba89465cbbf66fc98de89270/protobuf-5.28.2-py3-none-any.whl", hash = "sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece", size = 169566 }, ] +[[package]] +name = "psutil" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/8c6872f7372eb6a6b2e4708b88419fb46b857f7a2e1892966b851cc79fc9/psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2", size = 508067 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/37/f8da2fbd29690b3557cca414c1949f92162981920699cd62095a984983bf/psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0", size = 250961 }, + { url = "https://files.pythonhosted.org/packages/35/56/72f86175e81c656a01c4401cd3b1c923f891b31fbcebe98985894176d7c9/psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0", size = 287478 }, + { url = "https://files.pythonhosted.org/packages/19/74/f59e7e0d392bc1070e9a70e2f9190d652487ac115bb16e2eff6b22ad1d24/psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd", size = 290455 }, + { url = "https://files.pythonhosted.org/packages/cd/5f/60038e277ff0a9cc8f0c9ea3d0c5eb6ee1d2470ea3f9389d776432888e47/psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132", size = 292046 }, + { url = "https://files.pythonhosted.org/packages/8b/20/2ff69ad9c35c3df1858ac4e094f20bd2374d33c8643cf41da8fd7cdcb78b/psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d", size = 253560 }, + { url = "https://files.pythonhosted.org/packages/73/44/561092313ae925f3acfaace6f9ddc4f6a9c748704317bad9c8c8f8a36a79/psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3", size = 257399 }, + { url = "https://files.pythonhosted.org/packages/7c/06/63872a64c312a24fb9b4af123ee7007a306617da63ff13bcc1432386ead7/psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0", size = 251988 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + [[package]] name = "pycparser" version = "2.22" @@ -1116,6 +1825,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 }, ] +[[package]] +name = "pymdown-extensions" +version = "10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/3b/b0a111b226ccf26429d0b2e0fc64f5208c6b57ad20696caef5717b8472a7/pymdown_extensions-10.1.tar.gz", hash = "sha256:508009b211373058debb8247e168de4cbcb91b1bff7b5e961b2c3e864e00b195", size = 783894 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/55/740e1c6f1d3a598b6d2d3afc9691d154596f1b7a50f7a7288526b8818529/pymdown_extensions-10.1-py3-none-any.whl", hash = "sha256:ef25dbbae530e8f67575d222b75ff0649b1e841e22c2ae9a20bad9472c2207dc", size = 240804 }, +] + [[package]] name = "pyparsing" version = "3.2.0" @@ -1146,6 +1868,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, ] +[[package]] +name = "pywin32" +version = "308" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156 }, + { url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559 }, + { url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495 }, + { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, + { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, + { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, + { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -1181,6 +1919,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 }, +] + +[[package]] +name = "pyzmq" +version = "26.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/05/bed626b9f7bb2322cdbbf7b4bd8f54b1b617b0d2ab2d3547d6e39428a48e/pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f", size = 271975 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/20/de7442172f77f7c96299a0ac70e7d4fb78cd51eca67aa2cf552b66c14196/pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218", size = 1340639 }, + { url = "https://files.pythonhosted.org/packages/98/4d/5000468bd64c7910190ed0a6c76a1ca59a68189ec1f007c451dc181a22f4/pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4", size = 1008710 }, + { url = "https://files.pythonhosted.org/packages/e1/bf/c67fd638c2f9fbbab8090a3ee779370b97c82b84cc12d0c498b285d7b2c0/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef", size = 673129 }, + { url = "https://files.pythonhosted.org/packages/86/94/99085a3f492aa538161cbf27246e8886ff850e113e0c294a5b8245f13b52/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317", size = 910107 }, + { url = "https://files.pythonhosted.org/packages/31/1d/346809e8a9b999646d03f21096428453465b1bca5cd5c64ecd048d9ecb01/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf", size = 867960 }, + { url = "https://files.pythonhosted.org/packages/ab/68/6fb6ae5551846ad5beca295b7bca32bf0a7ce19f135cb30e55fa2314e6b6/pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e", size = 869204 }, + { url = "https://files.pythonhosted.org/packages/0f/f9/18417771dee223ccf0f48e29adf8b4e25ba6d0e8285e33bcbce078070bc3/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37", size = 1203351 }, + { url = "https://files.pythonhosted.org/packages/e0/46/f13e67fe0d4f8a2315782cbad50493de6203ea0d744610faf4d5f5b16e90/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3", size = 1514204 }, + { url = "https://files.pythonhosted.org/packages/50/11/ddcf7343b7b7a226e0fc7b68cbf5a5bb56291fac07f5c3023bb4c319ebb4/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6", size = 1414339 }, + { url = "https://files.pythonhosted.org/packages/01/14/1c18d7d5b7be2708f513f37c61bfadfa62161c10624f8733f1c8451b3509/pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4", size = 576928 }, + { url = "https://files.pythonhosted.org/packages/3b/1b/0a540edd75a41df14ec416a9a500b9fec66e554aac920d4c58fbd5756776/pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5", size = 642317 }, + { url = "https://files.pythonhosted.org/packages/98/77/1cbfec0358078a4c5add529d8a70892db1be900980cdb5dd0898b3d6ab9d/pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003", size = 543834 }, + { url = "https://files.pythonhosted.org/packages/28/2f/78a766c8913ad62b28581777ac4ede50c6d9f249d39c2963e279524a1bbe/pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9", size = 1343105 }, + { url = "https://files.pythonhosted.org/packages/b7/9c/4b1e2d3d4065be715e007fe063ec7885978fad285f87eae1436e6c3201f4/pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52", size = 1008365 }, + { url = "https://files.pythonhosted.org/packages/4f/ef/5a23ec689ff36d7625b38d121ef15abfc3631a9aecb417baf7a4245e4124/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08", size = 665923 }, + { url = "https://files.pythonhosted.org/packages/ae/61/d436461a47437d63c6302c90724cf0981883ec57ceb6073873f32172d676/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5", size = 903400 }, + { url = "https://files.pythonhosted.org/packages/47/42/fc6d35ecefe1739a819afaf6f8e686f7f02a4dd241c78972d316f403474c/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae", size = 860034 }, + { url = "https://files.pythonhosted.org/packages/07/3b/44ea6266a6761e9eefaa37d98fabefa112328808ac41aa87b4bbb668af30/pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711", size = 860579 }, + { url = "https://files.pythonhosted.org/packages/38/6f/4df2014ab553a6052b0e551b37da55166991510f9e1002c89cab7ce3b3f2/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6", size = 1196246 }, + { url = "https://files.pythonhosted.org/packages/38/9d/ee240fc0c9fe9817f0c9127a43238a3e28048795483c403cc10720ddef22/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3", size = 1507441 }, + { url = "https://files.pythonhosted.org/packages/85/4f/01711edaa58d535eac4a26c294c617c9a01f09857c0ce191fd574d06f359/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b", size = 1406498 }, + { url = "https://files.pythonhosted.org/packages/07/18/907134c85c7152f679ed744e73e645b365f3ad571f38bdb62e36f347699a/pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7", size = 575533 }, + { url = "https://files.pythonhosted.org/packages/ce/2c/a6f4a20202a4d3c582ad93f95ee78d79bbdc26803495aec2912b17dbbb6c/pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a", size = 637768 }, + { url = "https://files.pythonhosted.org/packages/5f/0e/eb16ff731632d30554bf5af4dbba3ffcd04518219d82028aea4ae1b02ca5/pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b", size = 540675 }, + { url = "https://files.pythonhosted.org/packages/04/a7/0f7e2f6c126fe6e62dbae0bc93b1bd3f1099cf7fea47a5468defebe3f39d/pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726", size = 1006564 }, + { url = "https://files.pythonhosted.org/packages/31/b6/a187165c852c5d49f826a690857684333a6a4a065af0a6015572d2284f6a/pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3", size = 1340447 }, + { url = "https://files.pythonhosted.org/packages/68/ba/f4280c58ff71f321602a6e24fd19879b7e79793fb8ab14027027c0fb58ef/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50", size = 665485 }, + { url = "https://files.pythonhosted.org/packages/77/b5/c987a5c53c7d8704216f29fc3d810b32f156bcea488a940e330e1bcbb88d/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb", size = 903484 }, + { url = "https://files.pythonhosted.org/packages/29/c9/07da157d2db18c72a7eccef8e684cefc155b712a88e3d479d930aa9eceba/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187", size = 859981 }, + { url = "https://files.pythonhosted.org/packages/43/09/e12501bd0b8394b7d02c41efd35c537a1988da67fc9c745cae9c6c776d31/pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b", size = 860334 }, + { url = "https://files.pythonhosted.org/packages/eb/ff/f5ec1d455f8f7385cc0a8b2acd8c807d7fade875c14c44b85c1bddabae21/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18", size = 1196179 }, + { url = "https://files.pythonhosted.org/packages/ec/8a/bb2ac43295b1950fe436a81fc5b298be0b96ac76fb029b514d3ed58f7b27/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115", size = 1507668 }, + { url = "https://files.pythonhosted.org/packages/a9/49/dbc284ebcfd2dca23f6349227ff1616a7ee2c4a35fe0a5d6c3deff2b4fed/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e", size = 1406539 }, + { url = "https://files.pythonhosted.org/packages/00/68/093cdce3fe31e30a341d8e52a1ad86392e13c57970d722c1f62a1d1a54b6/pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5", size = 575567 }, + { url = "https://files.pythonhosted.org/packages/92/ae/6cc4657148143412b5819b05e362ae7dd09fb9fe76e2a539dcff3d0386bc/pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad", size = 637551 }, + { url = "https://files.pythonhosted.org/packages/6c/67/fbff102e201688f97c8092e4c3445d1c1068c2f27bbd45a578df97ed5f94/pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797", size = 540378 }, + { url = "https://files.pythonhosted.org/packages/3f/fe/2d998380b6e0122c6c4bdf9b6caf490831e5f5e2d08a203b5adff060c226/pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a", size = 1007378 }, + { url = "https://files.pythonhosted.org/packages/4a/f4/30d6e7157f12b3a0390bde94d6a8567cdb88846ed068a6e17238a4ccf600/pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc", size = 1329532 }, + { url = "https://files.pythonhosted.org/packages/82/86/3fe917870e15ee1c3ad48229a2a64458e36036e64b4afa9659045d82bfa8/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5", size = 653242 }, + { url = "https://files.pythonhosted.org/packages/50/2d/242e7e6ef6c8c19e6cb52d095834508cd581ffb925699fd3c640cdc758f1/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672", size = 888404 }, + { url = "https://files.pythonhosted.org/packages/ac/11/7270566e1f31e4ea73c81ec821a4b1688fd551009a3d2bab11ec66cb1e8f/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797", size = 845858 }, + { url = "https://files.pythonhosted.org/packages/91/d5/72b38fbc69867795c8711bdd735312f9fef1e3d9204e2f63ab57085434b9/pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386", size = 847375 }, + { url = "https://files.pythonhosted.org/packages/dd/9a/10ed3c7f72b4c24e719c59359fbadd1a27556a28b36cdf1cd9e4fb7845d5/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306", size = 1183489 }, + { url = "https://files.pythonhosted.org/packages/72/2d/8660892543fabf1fe41861efa222455811adac9f3c0818d6c3170a1153e3/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6", size = 1492932 }, + { url = "https://files.pythonhosted.org/packages/7b/d6/32fd69744afb53995619bc5effa2a405ae0d343cd3e747d0fbc43fe894ee/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0", size = 1392485 }, +] + +[[package]] +name = "referencing" +version = "0.35.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/73ca1f8e72fff6fa52119dbd185f73a907b1989428917b24cff660129b6d/referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", size = 62991 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/59/2056f61236782a2c86b33906c025d4f4a0b17be0161b63b70fd9e8775d36/referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de", size = 26684 }, +] + [[package]] name = "regex" version = "2024.9.11" @@ -1276,6 +2095,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/1c/bff356a2a6d8ce804d36778d35e8492e8309cd0f824b4f2669f408b16a3c/ripplegw-0.0.9-py3-none-any.whl", hash = "sha256:cd87ff6b3adb8d0651fc2bbbf7b4a21abc2c5bdc0680c8ec9c7b63d6d744b01a", size = 906662 }, ] +[[package]] +name = "rpds-py" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/64/b693f262791b818880d17268f3f8181ef799b0d187f6f731b1772e05a29a/rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", size = 25814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/2a/191374c52d7be0b056cc2a04d718d2244c152f915d4a8d2db2aacc526189/rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", size = 318369 }, + { url = "https://files.pythonhosted.org/packages/0e/6a/2c9fdcc6d235ac0d61ec4fd9981184689c3e682abd05e3caa49bccb9c298/rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", size = 311303 }, + { url = "https://files.pythonhosted.org/packages/d2/b2/725487d29633f64ef8f9cbf4729111a0b61702c8f8e94db1653930f52cce/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", size = 366424 }, + { url = "https://files.pythonhosted.org/packages/7a/8c/668195ab9226d01b7cf7cd9e59c1c0be1df05d602df7ec0cf46f857dcf59/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", size = 368359 }, + { url = "https://files.pythonhosted.org/packages/52/28/356f6a39c1adeb02cf3e5dd526f5e8e54e17899bef045397abcfbf50dffa/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", size = 394886 }, + { url = "https://files.pythonhosted.org/packages/a2/65/640fb1a89080a8fb6f4bebd3dafb65a2edba82e2e44c33e6eb0f3e7956f1/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", size = 432416 }, + { url = "https://files.pythonhosted.org/packages/a7/e8/85835077b782555d6b3416874b702ea6ebd7db1f145283c9252968670dd5/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", size = 354819 }, + { url = "https://files.pythonhosted.org/packages/4f/87/1ac631e923d65cbf36fbcfc6eaa702a169496de1311e54be142f178e53ee/rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", size = 373282 }, + { url = "https://files.pythonhosted.org/packages/e4/ce/cb316f7970189e217b998191c7cf0da2ede3d5437932c86a7210dc1e9994/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", size = 541540 }, + { url = "https://files.pythonhosted.org/packages/90/d7/4112d7655ec8aff168ecc91d4ceb51c557336edde7e6ccf6463691a2f253/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", size = 547640 }, + { url = "https://files.pythonhosted.org/packages/ab/44/4f61d64dfed98cc71623f3a7fcb612df636a208b4b2c6611eaa985e130a9/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", size = 525555 }, + { url = "https://files.pythonhosted.org/packages/35/f2/a862d81eacb21f340d584cd1c749c289979f9a60e9229f78bffc0418a199/rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", size = 199338 }, + { url = "https://files.pythonhosted.org/packages/cc/ec/77d0674f9af4872919f3738018558dd9d37ad3f7ad792d062eadd4af7cba/rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", size = 213585 }, + { url = "https://files.pythonhosted.org/packages/89/b7/f9682c5cc37fcc035f4a0fc33c1fe92ec9cbfdee0cdfd071cf948f53e0df/rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", size = 321468 }, + { url = "https://files.pythonhosted.org/packages/b8/ad/fc82be4eaceb8d444cb6fc1956ce972b3a0795104279de05e0e4131d0a47/rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", size = 313062 }, + { url = "https://files.pythonhosted.org/packages/0e/1c/6039e80b13a08569a304dc13476dc986352dca4598e909384db043b4e2bb/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", size = 370168 }, + { url = "https://files.pythonhosted.org/packages/dc/c9/5b9aa35acfb58946b4b785bc8e700ac313669e02fb100f3efa6176a83e81/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", size = 371376 }, + { url = "https://files.pythonhosted.org/packages/7b/dd/0e0dbeb70d8a5357d2814764d467ded98d81d90d3570de4fb05ec7224f6b/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", size = 397200 }, + { url = "https://files.pythonhosted.org/packages/e4/da/a47d931eb688ccfd77a7389e45935c79c41e8098d984d87335004baccb1d/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", size = 426824 }, + { url = "https://files.pythonhosted.org/packages/0f/f7/a59a673594e6c2ff2dbc44b00fd4ecdec2fc399bb6a7bd82d612699a0121/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", size = 357967 }, + { url = "https://files.pythonhosted.org/packages/5f/61/3ba1905396b2cb7088f9503a460b87da33452da54d478cb9241f6ad16d00/rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", size = 378905 }, + { url = "https://files.pythonhosted.org/packages/08/31/6d0df9356b4edb0a3a077f1ef714e25ad21f9f5382fc490c2383691885ea/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", size = 546348 }, + { url = "https://files.pythonhosted.org/packages/ae/15/d33c021de5cb793101df9961c3c746dfc476953dbbf5db337d8010dffd4e/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", size = 553152 }, + { url = "https://files.pythonhosted.org/packages/70/2d/5536d28c507a4679179ab15aa0049440e4d3dd6752050fa0843ed11e9354/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", size = 528807 }, + { url = "https://files.pythonhosted.org/packages/e3/62/7ebe6ec0d3dd6130921f8cffb7e34afb7f71b3819aa0446a24c5e81245ec/rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", size = 200993 }, + { url = "https://files.pythonhosted.org/packages/ec/2f/b938864d66b86a6e4acadefdc56de75ef56f7cafdfd568a6464605457bd5/rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", size = 214458 }, + { url = "https://files.pythonhosted.org/packages/99/32/43b919a0a423c270a838ac2726b1c7168b946f2563fd99a51aaa9692d00f/rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", size = 321465 }, + { url = "https://files.pythonhosted.org/packages/58/a9/c4d899cb28e9e47b0ff12462e8f827381f243176036f17bef9c1604667f2/rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", size = 312900 }, + { url = "https://files.pythonhosted.org/packages/8f/90/9e51670575b5dfaa8c823369ef7d943087bfb73d4f124a99ad6ef19a2b26/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", size = 370973 }, + { url = "https://files.pythonhosted.org/packages/fc/c1/523f2a03f853fc0d4c1acbef161747e9ab7df0a8abf6236106e333540921/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", size = 370890 }, + { url = "https://files.pythonhosted.org/packages/51/ca/2458a771f16b0931de4d384decbe43016710bc948036c8f4562d6e063437/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", size = 397174 }, + { url = "https://files.pythonhosted.org/packages/00/7d/6e06807f6305ea2408b364efb0eef83a6e21b5e7b5267ad6b473b9a7e416/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", size = 426449 }, + { url = "https://files.pythonhosted.org/packages/8c/d1/6c9e65260a819a1714510a7d69ac1d68aa23ee9ce8a2d9da12187263c8fc/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", size = 357698 }, + { url = "https://files.pythonhosted.org/packages/5d/fb/ecea8b5286d2f03eec922be7173a03ed17278944f7c124348f535116db15/rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", size = 378530 }, + { url = "https://files.pythonhosted.org/packages/e3/e3/ac72f858957f52a109c588589b73bd2fad4a0fc82387fb55fb34aeb0f9cd/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", size = 545753 }, + { url = "https://files.pythonhosted.org/packages/b2/a4/a27683b519d5fc98e4390a3b130117d80fd475c67aeda8aac83c0e8e326a/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", size = 552443 }, + { url = "https://files.pythonhosted.org/packages/a1/ed/c074d248409b4432b1ccb2056974175fa0af2d1bc1f9c21121f80a358fa3/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", size = 528380 }, + { url = "https://files.pythonhosted.org/packages/d5/bd/04caf938895d2d78201e89c0c8a94dfd9990c34a19ff52fb01d0912343e3/rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", size = 200540 }, + { url = "https://files.pythonhosted.org/packages/95/cc/109eb8b9863680411ae703664abacaa035820c7755acc9686d5dd02cdd2e/rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", size = 214111 }, +] + [[package]] name = "safe-netrc" version = "1.0.1" @@ -1352,6 +2218,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, ] +[[package]] +name = "soupsieve" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + [[package]] name = "tensorstore" version = "0.1.66" @@ -1374,6 +2263,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/c8/bc42b40489833c70d61ff90edf6ca47eaa401b2f7d66b964b95b6060639b/tensorstore-0.1.66-cp312-cp312-win_amd64.whl", hash = "sha256:c974926fb2f3048fa3d36fc2fdf572403c7b70191193e83c5af78fdc46910b62", size = 11984388 }, ] +[[package]] +name = "tinycss2" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/6f/38d2335a2b70b9982d112bb177e3dbe169746423e33f718bf5e9c7b3ddd3/tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d", size = 67360 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/4d/0db5b8a613d2a59bbc29bc5bb44a2f8070eb9ceab11c50d477502a8a0092/tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7", size = 22532 }, +] + [[package]] name = "toolz" version = "1.0.0" @@ -1383,6 +2284,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236", size = 56383 }, ] +[[package]] +name = "tornado" +version = "6.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/66/398ac7167f1c7835406888a386f6d0d26ee5dbf197d8a571300be57662d3/tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9", size = 500623 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/d9/c33be3c1a7564f7d42d87a8d186371a75fd142097076767a5c27da941fef/tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8", size = 435924 }, + { url = "https://files.pythonhosted.org/packages/2e/0f/721e113a2fac2f1d7d124b3279a1da4c77622e104084f56119875019ffab/tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14", size = 433883 }, + { url = "https://files.pythonhosted.org/packages/13/cf/786b8f1e6fe1c7c675e79657448178ad65e41c1c9765ef82e7f6f765c4c5/tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4", size = 437224 }, + { url = "https://files.pythonhosted.org/packages/e4/8e/a6ce4b8d5935558828b0f30f3afcb2d980566718837b3365d98e34f6067e/tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842", size = 436597 }, + { url = "https://files.pythonhosted.org/packages/22/d4/54f9d12668b58336bd30defe0307e6c61589a3e687b05c366f804b7faaf0/tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3", size = 436797 }, + { url = "https://files.pythonhosted.org/packages/cf/3f/2c792e7afa7dd8b24fad7a2ed3c2f24a5ec5110c7b43a64cb6095cc106b8/tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f", size = 437516 }, + { url = "https://files.pythonhosted.org/packages/71/63/c8fc62745e669ac9009044b889fc531b6f88ac0f5f183cac79eaa950bb23/tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4", size = 436958 }, + { url = "https://files.pythonhosted.org/packages/94/d4/f8ac1f5bd22c15fad3b527e025ce219bd526acdbd903f52053df2baecc8b/tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698", size = 436882 }, + { url = "https://files.pythonhosted.org/packages/4b/3e/a8124c21cc0bbf144d7903d2a0cadab15cadaf683fa39a0f92bc567f0d4d/tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d", size = 438092 }, + { url = "https://files.pythonhosted.org/packages/d9/2f/3f2f05e84a7aff787a96d5fb06821323feb370fe0baed4db6ea7b1088f32/tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7", size = 438532 }, +] + [[package]] name = "tqdm" version = "4.66.5" @@ -1395,6 +2314,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351 }, ] +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + [[package]] name = "typed-argument-parser" version = "1.10.1" @@ -1470,6 +2398,51 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, ] +[[package]] +name = "watchdog" +version = "5.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/48/a86139aaeab2db0a2482676f64798d8ac4d2dbb457523f50ab37bf02ce2c/watchdog-5.0.3.tar.gz", hash = "sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176", size = 129556 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/34/946f08602f8b8e6af45bc725e4a8013975a34883ab5570bd0d827a4c9829/watchdog-5.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818", size = 96650 }, + { url = "https://files.pythonhosted.org/packages/96/2b/b84e35d49e8b0bad77e5d086fc1e2c6c833bbfe74d53144cfe8b26117eff/watchdog-5.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490", size = 88653 }, + { url = "https://files.pythonhosted.org/packages/d5/3f/41b5d77c10f450b79921c17b7d0b416616048867bfe63acaa072a619a0cb/watchdog-5.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e", size = 89286 }, + { url = "https://files.pythonhosted.org/packages/1c/9b/8b206a928c188fdeb7b12e1c795199534cd44bdef223b8470129016009dd/watchdog-5.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8", size = 96739 }, + { url = "https://files.pythonhosted.org/packages/e1/26/129ca9cd0f8016672f37000010c2fedc0b86816e894ebdc0af9bb04a6439/watchdog-5.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926", size = 88708 }, + { url = "https://files.pythonhosted.org/packages/8f/b3/5e10ec32f0c429cdb55b1369066d6e83faf9985b3a53a4e37bb5c5e29aa0/watchdog-5.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e", size = 89309 }, + { url = "https://files.pythonhosted.org/packages/54/c4/49af4ab00bcfb688e9962eace2edda07a2cf89b9699ea536da48e8585cff/watchdog-5.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7", size = 96740 }, + { url = "https://files.pythonhosted.org/packages/96/a4/b24de77cc9ae424c1687c9d4fb15aa560d7d7b28ba559aca72f781d0202b/watchdog-5.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906", size = 88711 }, + { url = "https://files.pythonhosted.org/packages/a4/71/3f2e9fe8403386b99d788868955b3a790f7a09721501a7e1eb58f514ffaa/watchdog-5.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1", size = 89319 }, + { url = "https://files.pythonhosted.org/packages/60/33/7cb71c9df9a77b6927ee5f48d25e1de5562ce0fa7e0c56dcf2b0472e64a2/watchdog-5.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91", size = 79335 }, + { url = "https://files.pythonhosted.org/packages/f6/91/320bc1496cf951a3cf93a7ffd18a581f0792c304be963d943e0e608c2919/watchdog-5.0.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c", size = 79334 }, + { url = "https://files.pythonhosted.org/packages/8b/2c/567c5e042ed667d3544c43d48a65cf853450a2d2a9089d9523a65f195e94/watchdog-5.0.3-py3-none-manylinux2014_i686.whl", hash = "sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c", size = 79333 }, + { url = "https://files.pythonhosted.org/packages/c3/f0/64059fe162ef3274662e67bbdea6c45b3cd53e846d5bd1365fcdc3dc1d15/watchdog-5.0.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221", size = 79334 }, + { url = "https://files.pythonhosted.org/packages/f6/d9/19b7d02965be2801e2d0f6f4bde23e4ae172620071b65430fa0c2f8441ac/watchdog-5.0.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05", size = 79333 }, + { url = "https://files.pythonhosted.org/packages/cb/a1/5393ac6d0b095d3a44946b09258e9b5f22cb2fb67bcfa419dd868478826c/watchdog-5.0.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97", size = 79332 }, + { url = "https://files.pythonhosted.org/packages/a0/58/edec25190b6403caf4426dd418234f2358a106634b7d6aa4aec6939b104f/watchdog-5.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7", size = 79334 }, + { url = "https://files.pythonhosted.org/packages/97/69/cfb2d17ba8aabc73be2e2d03c8c319b1f32053a02c4b571852983aa24ff2/watchdog-5.0.3-py3-none-win32.whl", hash = "sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49", size = 79320 }, + { url = "https://files.pythonhosted.org/packages/91/b4/2b5b59358dadfa2c8676322f955b6c22cde4937602f40490e2f7403e548e/watchdog-5.0.3-py3-none-win_amd64.whl", hash = "sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9", size = 79325 }, + { url = "https://files.pythonhosted.org/packages/38/b8/0aa69337651b3005f161f7f494e59188a1d8d94171666900d26d29d10f69/watchdog-5.0.3-py3-none-win_ia64.whl", hash = "sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45", size = 79324 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, +] + [[package]] name = "zipp" version = "3.20.2" From efb1fe1204e020c61b23fb9b1c7ff3eedb679a1f Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Wed, 16 Oct 2024 15:52:28 -0400 Subject: [PATCH 238/248] Nuke outdated examples --- example/GW150914_TaylorF2.py | 155 --------------- example/GW150914_heterodyne.py | 158 --------------- example/GW170817.py | 104 ---------- example/GW170817_PhenomD_NRTv2.py | 298 ----------------------------- example/GW170817_TaylorF2.py | 294 ---------------------------- example/GW170817_heterodyne.py | 157 --------------- example/Single_event_runManager.py | 97 ---------- example/gen_injection_config.py | 73 ------- example/make_ppPlot.py | 68 ------- 9 files changed, 1404 deletions(-) delete mode 100644 example/GW150914_TaylorF2.py delete mode 100644 example/GW150914_heterodyne.py delete mode 100644 example/GW170817.py delete mode 100644 example/GW170817_PhenomD_NRTv2.py delete mode 100644 example/GW170817_TaylorF2.py delete mode 100644 example/GW170817_heterodyne.py delete mode 100644 example/Single_event_runManager.py delete mode 100644 example/gen_injection_config.py delete mode 100644 example/make_ppPlot.py diff --git a/example/GW150914_TaylorF2.py b/example/GW150914_TaylorF2.py deleted file mode 100644 index 2520b794..00000000 --- a/example/GW150914_TaylorF2.py +++ /dev/null @@ -1,155 +0,0 @@ -import psutil -p = psutil.Process() -p.cpu_affinity([0]) -import os -os.environ["CUDA_VISIBLE_DEVICES"] = "2" -os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = "0.10" - -import time - -import jax -import jax.numpy as jnp -import optax - -from jimgw.jim import Jim -from jimgw.prior import Composite, Unconstrained_Uniform -from jimgw.single_event.detector import H1, L1 -from jimgw.single_event.likelihood import TransientLikelihoodFD -from jimgw.single_event.waveform import RippleTaylorF2 -from flowMC.strategy.optimization import optimization_Adam - -jax.config.update("jax_enable_x64", True) - -########################################### -########## First we grab data ############# -########################################### - -total_time_start = time.time() - -# first, fetch a 4s segment centered on GW150914 -gps = 1126259462.4 -duration = 4 -post_trigger_duration = 2 -start_pad = duration - post_trigger_duration -end_pad = post_trigger_duration -fmin = 20.0 -fmax = 1024.0 - -ifos = ["H1", "L1"] - -H1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) -L1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) - -Mc_prior = Unconstrained_Uniform(10.0, 80.0, naming=["M_c"]) -q_prior = Unconstrained_Uniform( - 0.125, - 1.0, - naming=["q"], - transforms={"q": ("eta", lambda params: params["q"] / (1 + params["q"]) ** 2)}, -) -s1z_prior = Unconstrained_Uniform(-1.0, 1.0, naming=["s1_z"]) -s2z_prior = Unconstrained_Uniform(-1.0, 1.0, naming=["s2_z"]) -lambda1_prior = Unconstrained_Uniform(0.0, 5000.0, naming=["lambda_1"]) -lambda2_prior = Unconstrained_Uniform(0.0, 5000.0, naming=["lambda_2"]) -dL_prior = Unconstrained_Uniform(0.0, 2000.0, naming=["d_L"]) -t_c_prior = Unconstrained_Uniform(-0.05, 0.05, naming=["t_c"]) -phase_c_prior = Unconstrained_Uniform(0.0, 2 * jnp.pi, naming=["phase_c"]) -cos_iota_prior = Unconstrained_Uniform( - -1.0, - 1.0, - naming=["cos_iota"], - transforms={ - "cos_iota": ( - "iota", - lambda params: jnp.arccos( - jnp.arcsin(jnp.sin(params["cos_iota"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) -psi_prior = Unconstrained_Uniform(0.0, jnp.pi, naming=["psi"]) -ra_prior = Unconstrained_Uniform(0.0, 2 * jnp.pi, naming=["ra"]) -sin_dec_prior = Unconstrained_Uniform( - -1.0, - 1.0, - naming=["sin_dec"], - transforms={ - "sin_dec": ( - "dec", - lambda params: jnp.arcsin( - jnp.arcsin(jnp.sin(params["sin_dec"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) - -prior = Composite( - [ - Mc_prior, - q_prior, - s1z_prior, - s2z_prior, - lambda1_prior, - lambda2_prior, - dL_prior, - t_c_prior, - phase_c_prior, - cos_iota_prior, - psi_prior, - ra_prior, - sin_dec_prior, - ] -) -likelihood = TransientLikelihoodFD( - [H1, L1], - waveform=RippleTaylorF2(), - trigger_time=gps, - duration=4, - post_trigger_duration=2, -) - -n_dim = 13 -mass_matrix = jnp.eye(n_dim) -mass_matrix = mass_matrix.at[0,0].set(1e-5) -mass_matrix = mass_matrix.at[1,1].set(1e-4) -mass_matrix = mass_matrix.at[2,2].set(1e-3) -mass_matrix = mass_matrix.at[3,3].set(1e-3) -mass_matrix = mass_matrix.at[7,7].set(1e-5) -mass_matrix = mass_matrix.at[11,11].set(1e-2) -mass_matrix = mass_matrix.at[12,12].set(1e-2) -local_sampler_arg = {"step_size": mass_matrix * 1e-3} - -# Build the learning rate scheduler - -n_loop_training = 100 -n_epochs = 100 -total_epochs = n_epochs * n_loop_training -start = int(total_epochs / 10) -start_lr = 1e-3 -end_lr = 1e-5 -power = 4.0 -schedule_fn = optax.polynomial_schedule( - start_lr, end_lr, power, total_epochs-start, transition_begin=start) - -jim = Jim( - likelihood, - prior, - n_loop_training=n_loop_training, - n_loop_production=20, - n_local_steps=10, - n_global_steps=1000, - n_chains=1000, - n_epochs=n_epochs, - learning_rate=schedule_fn, - n_max_examples=30000, - n_flow_samples=100000, - momentum=0.9, - batch_size=30000, - use_global=True, - train_thinning=20, - output_thinning=50, - local_sampler_arg=local_sampler_arg, -) - -jim.sample(jax.random.PRNGKey(24)) -jim.print_summary() diff --git a/example/GW150914_heterodyne.py b/example/GW150914_heterodyne.py deleted file mode 100644 index c1faed03..00000000 --- a/example/GW150914_heterodyne.py +++ /dev/null @@ -1,158 +0,0 @@ -import time - -import jax -import jax.numpy as jnp - -from jimgw.jim import Jim -from jimgw.prior import Composite, Unconstrained_Uniform -from jimgw.single_event.detector import H1, L1 -from jimgw.single_event.likelihood import ( - HeterodynedTransientLikelihoodFD, - TransientLikelihoodFD, -) -from jimgw.single_event.waveform import RippleIMRPhenomD -from flowMC.strategy.optimization import optimization_Adam - -jax.config.update("jax_enable_x64", True) - -########################################### -########## First we grab data ############# -########################################### - -total_time_start = time.time() - -# first, fetch a 4s segment centered on GW150914 -gps = 1126259462.4 -duration = 4 -post_trigger_duration = 2 -start_pad = duration - post_trigger_duration -end_pad = post_trigger_duration -fmin = 20.0 -fmax = 1024.0 - -ifos = ["H1", "L1"] - -H1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) -L1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) - -Mc_prior = Unconstrained_Uniform(10.0, 80.0, naming=["M_c"]) -q_prior = Unconstrained_Uniform( - 0.125, - 1.0, - naming=["q"], - transforms={"q": ("eta", lambda params: params["q"] / (1 + params["q"]) ** 2)}, -) -s1z_prior = Unconstrained_Uniform(-1.0, 1.0, naming=["s1_z"]) -s2z_prior = Unconstrained_Uniform(-1.0, 1.0, naming=["s2_z"]) -dL_prior = Unconstrained_Uniform(0.0, 2000.0, naming=["d_L"]) -t_c_prior = Unconstrained_Uniform(-0.05, 0.05, naming=["t_c"]) -phase_c_prior = Unconstrained_Uniform(0.0, 2 * jnp.pi, naming=["phase_c"]) -cos_iota_prior = Unconstrained_Uniform( - -1.0, - 1.0, - naming=["cos_iota"], - transforms={ - "cos_iota": ( - "iota", - lambda params: jnp.arccos( - jnp.arcsin(jnp.sin(params["cos_iota"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) -psi_prior = Unconstrained_Uniform(0.0, jnp.pi, naming=["psi"]) -ra_prior = Unconstrained_Uniform(0.0, 2 * jnp.pi, naming=["ra"]) -sin_dec_prior = Unconstrained_Uniform( - -1.0, - 1.0, - naming=["sin_dec"], - transforms={ - "sin_dec": ( - "dec", - lambda params: jnp.arcsin( - jnp.arcsin(jnp.sin(params["sin_dec"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) - -prior = Composite( - [ - Mc_prior, - q_prior, - s1z_prior, - s2z_prior, - dL_prior, - t_c_prior, - phase_c_prior, - cos_iota_prior, - psi_prior, - ra_prior, - sin_dec_prior, - ] -) - -bounds = jnp.array( - [ - [10.0, 80.0], - [0.125, 1.0], - [-1.0, 1.0], - [-1.0, 1.0], - [0.0, 2000.0], - [-0.05, 0.05], - [0.0, 2 * jnp.pi], - [-1.0, 1.0], - [0.0, jnp.pi], - [0.0, 2 * jnp.pi], - [-1.0, 1.0], - ] -) - -likelihood = HeterodynedTransientLikelihoodFD( - [H1, L1], - prior=prior, - bounds=bounds, - waveform=RippleIMRPhenomD(), - trigger_time=gps, - duration=duration, - post_trigger_duration=post_trigger_duration, - n_steps=3000, -) - -mass_matrix = jnp.eye(11) -mass_matrix = mass_matrix.at[1, 1].set(1e-3) -mass_matrix = mass_matrix.at[5, 5].set(1e-3) -local_sampler_arg = {"step_size": mass_matrix * 3e-3} - -Adam_optimizer = optimization_Adam(n_steps=3000, learning_rate=0.01, noise_level=1) -import optax -n_epochs = 20 -n_loop_training = 100 -total_epochs = n_epochs * n_loop_training -start = total_epochs//10 -learning_rate = optax.polynomial_schedule( - 1e-3, 1e-4, 4.0, total_epochs - start, transition_begin=start -) - -jim = Jim( - likelihood, - prior, - n_loop_training=n_loop_training, - n_loop_production=20, - n_local_steps=10, - n_global_steps=1000, - n_chains=500, - n_epochs=n_epochs, - learning_rate=learning_rate, - n_max_examples=30000, - n_flow_sample=100000, - momentum=0.9, - batch_size=30000, - use_global=True, - keep_quantile=0.0, - train_thinning=1, - output_thinning=10, - local_sampler_arg=local_sampler_arg, - # strategies=[Adam_optimizer,"default"], -) -jim.sample(jax.random.PRNGKey(42)) diff --git a/example/GW170817.py b/example/GW170817.py deleted file mode 100644 index bf4c6710..00000000 --- a/example/GW170817.py +++ /dev/null @@ -1,104 +0,0 @@ -import time - -import jax -import jax.numpy as jnp -from gwosc.datasets import event_gps - -from jimgw.jim import Jim -from jimgw.prior import Uniform -from jimgw.single_event.detector import H1, L1, V1 -from jimgw.single_event.likelihood import HeterodynedTransientLikelihoodFD -from jimgw.single_event.waveform import RippleIMRPhenomD - -jax.config.update("jax_enable_x64", True) - -########################################### -########## First we grab data ############# -########################################### - -total_time_start = time.time() - -gps = event_gps("GW170817") -duration = 128 -post_trigger_duration = 32 -start_pad = duration - post_trigger_duration -end_pad = post_trigger_duration -fmin = 20.0 -fmax = 1024.0 - -ifos = ["H1", "L1"]#, "V1"] - -H1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=4*duration, tukey_alpha=0.05, gwpy_kwargs={"version": 2, "cache": False}) -L1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=4*duration, tukey_alpha=0.05, gwpy_kwargs={"version": 2, "cache": False}) -# V1.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.05) - -prior = Uniform( - xmin=[1.18, 0.125, -0.3, -0.3, 1., -0.1, 0.0, -1, 0.0, 0.0, -1.0], - xmax=[1.21, 1.0, 0.3, 0.3, 75., 0.1, 2 * jnp.pi, 1.0, jnp.pi, 2 * jnp.pi, 1.0], - naming=[ - "M_c", - "q", - "s1_z", - "s2_z", - "d_L", - "t_c", - "phase_c", - "cos_iota", - "psi", - "ra", - "sin_dec", - ], - transforms={ - "q": ("eta", lambda params: params["q"] / (1 + params["q"]) ** 2), - "cos_iota": ( - "iota", - lambda params: jnp.arccos( - jnp.arcsin(jnp.sin(params["cos_iota"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ), - "sin_dec": ( - "dec", - lambda params: jnp.arcsin( - jnp.arcsin(jnp.sin(params["sin_dec"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ), - }, # sin and arcsin are periodize cos_iota and sin_dec -) - -likelihood = HeterodynedTransientLikelihoodFD( - [H1], - prior=prior, - bounds=[prior.xmin, prior.xmax], - waveform=RippleIMRPhenomD(), - trigger_time=gps, - duration=duration, - post_trigger_duration=post_trigger_duration, - n_loops=1000 -) - -# mass_matrix = jnp.eye(11) -# mass_matrix = mass_matrix.at[1, 1].set(1e-3) -# mass_matrix = mass_matrix.at[5, 5].set(1e-3) -# local_sampler_arg = {"step_size": mass_matrix * 3e-3} - -# jim = Jim( -# likelihood, -# prior, -# n_loop_training=100, -# n_loop_production=10, -# n_local_steps=150, -# n_global_steps=150, -# n_chains=500, -# n_epochs=50, -# learning_rate=0.001, -# max_samples=45000, -# momentum=0.9, -# batch_size=50000, -# use_global=True, -# keep_quantile=0.0, -# train_thinning=1, -# output_thinning=10, -# local_sampler_arg=local_sampler_arg, -# ) - -# jim.sample(jax.random.PRNGKey(42)) diff --git a/example/GW170817_PhenomD_NRTv2.py b/example/GW170817_PhenomD_NRTv2.py deleted file mode 100644 index 6d1d6adb..00000000 --- a/example/GW170817_PhenomD_NRTv2.py +++ /dev/null @@ -1,298 +0,0 @@ -import psutil -p = psutil.Process() -p.cpu_affinity([0]) -import os -os.environ["CUDA_VISIBLE_DEVICES"] = "3" -os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = "0.10" -from jimgw.jim import Jim -from jimgw.single_event.detector import H1, L1, V1 -from jimgw.single_event.likelihood import HeterodynedTransientLikelihoodFD -from jimgw.single_event.waveform import RippleIMRPhenomD_NRTidalv2 -from jimgw.prior import Uniform, Composite -import jax.numpy as jnp -import jax -import time -import numpy as np -jax.config.update("jax_enable_x64", True) -import shutil -import numpy as np -import matplotlib.pyplot as plt -import optax -print(f"Devices found by Jax: {jax.devices()}") - -import utils_plotting as utils - -################ -### PREAMBLE ### -################ - -data_path = "/home/thibeau.wouters/gw-datasets/GW170817/" # this is on the CIT cluster # TODO: has to be shared externally! - -start_runtime = time.time() - -############ -### BODY ### -############ - -### Data definitions - -gps = 1187008882.43 -trigger_time = gps -fmin = 20 -fmax = 2048 -minimum_frequency = fmin -maximum_frequency = fmax -duration = 128 -post_trigger_duration = 2 -epoch = duration - post_trigger_duration -f_ref = fmin - -### Getting detector data - -# This is our preprocessed data obtained from the TXT files at the GWOSC website (the GWF gave me NaNs?) -H1.frequencies = jnp.array(np.genfromtxt(f'{data_path}H1_freq.txt')) -H1_data_re, H1_data_im = jnp.array(np.genfromtxt(f'{data_path}H1_data_re.txt')), jnp.array(np.genfromtxt(f'{data_path}H1_data_im.txt')) -H1.data = H1_data_re + 1j * H1_data_im - -L1.frequencies = jnp.array(np.genfromtxt(f'{data_path}L1_freq.txt')) -L1_data_re, L1_data_im = jnp.array(np.genfromtxt(f'{data_path}L1_data_re.txt')), jnp.array(np.genfromtxt(f'{data_path}L1_data_im.txt')) -L1.data = L1_data_re + 1j * L1_data_im - -V1.frequencies = jnp.array(np.genfromtxt(f'{data_path}V1_freq.txt')) -V1_data_re, V1_data_im = jnp.array(np.genfromtxt(f'{data_path}V1_data_re.txt')), jnp.array(np.genfromtxt(f'{data_path}V1_data_im.txt')) -V1.data = V1_data_re + 1j * V1_data_im - -# Load the PSD - -H1.psd = H1.load_psd(H1.frequencies, psd_file = data_path + "GW170817-IMRD_data0_1187008882-43_generation_data_dump.pickle_H1_psd.txt") -L1.psd = L1.load_psd(L1.frequencies, psd_file = data_path + "GW170817-IMRD_data0_1187008882-43_generation_data_dump.pickle_L1_psd.txt") -V1.psd = V1.load_psd(V1.frequencies, psd_file = data_path + "GW170817-IMRD_data0_1187008882-43_generation_data_dump.pickle_V1_psd.txt") - -### Define priors - -# Internal parameters -Mc_prior = Uniform(1.18, 1.21, naming=["M_c"]) -q_prior = Uniform( - 0.125, - 1.0, - naming=["q"], - transforms={"q": ("eta", lambda params: params["q"] / (1 + params["q"]) ** 2)}, -) -s1z_prior = Uniform(-0.05, 0.05, naming=["s1_z"]) -s2z_prior = Uniform(-0.05, 0.05, naming=["s2_z"]) -lambda_1_prior = Uniform(0.0, 5000.0, naming=["lambda_1"]) -lambda_2_prior = Uniform(0.0, 5000.0, naming=["lambda_2"]) -dL_prior = Uniform(1.0, 75.0, naming=["d_L"]) -t_c_prior = Uniform(-0.1, 0.1, naming=["t_c"]) -phase_c_prior = Uniform(0.0, 2 * jnp.pi, naming=["phase_c"]) -cos_iota_prior = Uniform( - -1.0, - 1.0, - naming=["cos_iota"], - transforms={ - "cos_iota": ( - "iota", - lambda params: jnp.arccos( - jnp.arcsin(jnp.sin(params["cos_iota"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) -psi_prior = Uniform(0.0, jnp.pi, naming=["psi"]) -ra_prior = Uniform(0.0, 2 * jnp.pi, naming=["ra"]) -sin_dec_prior = Uniform( - -1.0, - 1.0, - naming=["sin_dec"], - transforms={ - "sin_dec": ( - "dec", - lambda params: jnp.arcsin( - jnp.arcsin(jnp.sin(params["sin_dec"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) - -prior_list = [ - Mc_prior, - q_prior, - s1z_prior, - s2z_prior, - lambda_1_prior, - lambda_2_prior, - dL_prior, - t_c_prior, - phase_c_prior, - cos_iota_prior, - psi_prior, - ra_prior, - sin_dec_prior, - ] - -prior = Composite(prior_list) - -# The following only works if every prior has xmin and xmax property, which is OK for Uniform and Powerlaw -bounds = jnp.array([[p.xmin, p.xmax] for p in prior.priors]) - -### Create likelihood object - -# For simplicity, we put here a set of reference parameters found by the optimizer -ref_params = { - 'M_c': 1.1975896, - 'eta': 0.2461001, - 's1_z': -0.01890608, - 's2_z': 0.04888488, - 'lambda_1': 791.04366468, - 'lambda_2': 891.04366468, - 'd_L': 16.06331818, - 't_c': 0.00193536, - 'phase_c': 5.88649652, - 'iota': 1.93095421, - 'psi': 1.59687217, - 'ra': 3.39736826, - 'dec': -0.34000186 -} - -# Number of bins to use for relative binning -n_bins = 500 - -waveform = RippleIMRPhenomD_NRTidalv2(f_ref=f_ref) -reference_waveform = RippleIMRPhenomD_NRTidalv2(f_ref=f_ref, no_taper=True) - -likelihood = HeterodynedTransientLikelihoodFD([H1, L1, V1], - prior=prior, - bounds=bounds, - waveform=waveform, - trigger_time=gps, - duration=duration, - n_bins=n_bins, - ref_params=ref_params, - reference_waveform=reference_waveform) - -# Local sampler args - -eps = 1e-3 -n_dim = 13 -mass_matrix = jnp.eye(n_dim) -mass_matrix = mass_matrix.at[0,0].set(1e-5) -mass_matrix = mass_matrix.at[1,1].set(1e-4) -mass_matrix = mass_matrix.at[2,2].set(1e-3) -mass_matrix = mass_matrix.at[3,3].set(1e-3) -mass_matrix = mass_matrix.at[7,7].set(1e-5) -mass_matrix = mass_matrix.at[11,11].set(1e-2) -mass_matrix = mass_matrix.at[12,12].set(1e-2) -local_sampler_arg = {"step_size": mass_matrix * eps} - -# Build the learning rate scheduler (if used) - -n_loop_training = 300 -n_epochs = 50 -total_epochs = n_epochs * n_loop_training -start = int(total_epochs / 10) -start_lr = 1e-3 -end_lr = 1e-5 -power = 4.0 -schedule_fn = optax.polynomial_schedule( - start_lr, end_lr, power, total_epochs-start, transition_begin=start) - -scheduler_str = f"polynomial_schedule({start_lr}, {end_lr}, {power}, {total_epochs-start}, {start})" - -## Choose between fixed learning rate - or - the above scheduler here -# learning_rate = schedule_fn -learning_rate = 0.001 - -print(f"Learning rate: {learning_rate}") - -# Create jim object - -outdir_name = "./outdir/" - -jim = Jim( - likelihood, - prior, - n_loop_training=n_loop_training, - n_loop_production=20, - n_local_steps=100, - n_global_steps=1000, - n_chains=1000, - n_epochs=n_epochs, - learning_rate=schedule_fn, - max_samples=50000, - momentum=0.9, - batch_size=50000, - use_global=True, - keep_quantile=0.0, - train_thinning=10, - output_thinning=30, - local_sampler_arg=local_sampler_arg, - outdir_name=outdir_name -) - -### Heavy computation begins -jim.sample(jax.random.PRNGKey(41)) -### Heavy computation ends - -# === Show results, save output === - -# Print a summary to screen: -jim.print_summary() -outdir = outdir_name - -# Save and plot the results of the run -# - training phase - -name = outdir + f'results_training.npz' -print(f"Saving samples to {name}") -state = jim.Sampler.get_sampler_state(training=True) -chains, log_prob, local_accs, global_accs, loss_vals = state["chains"], state[ - "log_prob"], state["local_accs"], state["global_accs"], state["loss_vals"] -local_accs = jnp.mean(local_accs, axis=0) -global_accs = jnp.mean(global_accs, axis=0) -np.savez(name, log_prob=log_prob, local_accs=local_accs, - global_accs=global_accs, loss_vals=loss_vals) - -utils.plot_accs(local_accs, "Local accs (training)", - "local_accs_training", outdir) -utils.plot_accs(global_accs, "Global accs (training)", - "global_accs_training", outdir) -utils.plot_loss_vals(loss_vals, "Loss", "loss_vals", outdir) -utils.plot_log_prob(log_prob, "Log probability (training)", - "log_prob_training", outdir) - -# - production phase -name = outdir + f'results_production.npz' -state = jim.Sampler.get_sampler_state(training=False) -chains, log_prob, local_accs, global_accs = state["chains"], state[ - "log_prob"], state["local_accs"], state["global_accs"] -local_accs = jnp.mean(local_accs, axis=0) -global_accs = jnp.mean(global_accs, axis=0) -np.savez(name, chains=chains, log_prob=log_prob, - local_accs=local_accs, global_accs=global_accs) - -utils.plot_accs(local_accs, "Local accs (production)", - "local_accs_production", outdir) -utils.plot_accs(global_accs, "Global accs (production)", - "global_accs_production", outdir) -utils.plot_log_prob(log_prob, "Log probability (production)", - "log_prob_production", outdir) - -# Plot the chains as corner plots -utils.plot_chains(chains, "chains_production", outdir, truths=None) - -# Save the NF and show a plot of samples from the flow -print("Saving the NF") -jim.Sampler.save_flow(outdir + "nf_model") - -# Final steps - - -print("Finished successfully") - -end_runtime = time.time() -runtime = end_runtime - start_runtime -print(f"Time taken: {runtime} seconds ({(runtime)/60} minutes)") - -print(f"Saving runtime") -with open(outdir + 'runtime.txt', 'w') as file: - file.write(str(runtime)) \ No newline at end of file diff --git a/example/GW170817_TaylorF2.py b/example/GW170817_TaylorF2.py deleted file mode 100644 index 499f2d6d..00000000 --- a/example/GW170817_TaylorF2.py +++ /dev/null @@ -1,294 +0,0 @@ -import psutil -p = psutil.Process() -p.cpu_affinity([0]) -import os -os.environ["CUDA_VISIBLE_DEVICES"] = "2" -os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = "0.10" -from jimgw.jim import Jim -from jimgw.single_event.detector import H1, L1, V1 -from jimgw.single_event.likelihood import HeterodynedTransientLikelihoodFD -from jimgw.single_event.waveform import RippleTaylorF2 -from jimgw.prior import Uniform, Composite -import jax.numpy as jnp -import jax -import time -import numpy as np -jax.config.update("jax_enable_x64", True) -import shutil -import numpy as np -import matplotlib.pyplot as plt -import optax -print(f"Devices found by Jax: {jax.devices()}") - -import utils_plotting as utils - -################ -### PREAMBLE ### -################ - -data_path = "/home/thibeau.wouters/gw-datasets/GW170817/" # this is on the CIT cluster # TODO: has to be shared externally! - -start_runtime = time.time() - -############ -### BODY ### -############ - -### Data definitions - -gps = 1187008882.43 -trigger_time = gps -fmin = 20 -fmax = 2048 -minimum_frequency = fmin -maximum_frequency = fmax -duration = 128 -post_trigger_duration = 2 -epoch = duration - post_trigger_duration -f_ref = fmin - -### Getting detector data - -# This is our preprocessed data obtained from the TXT files at the GWOSC website (the GWF gave me NaNs?) -H1.frequencies = jnp.array(np.genfromtxt(f'{data_path}H1_freq.txt')) -H1_data_re, H1_data_im = jnp.array(np.genfromtxt(f'{data_path}H1_data_re.txt')), jnp.array(np.genfromtxt(f'{data_path}H1_data_im.txt')) -H1.data = H1_data_re + 1j * H1_data_im - -L1.frequencies = jnp.array(np.genfromtxt(f'{data_path}L1_freq.txt')) -L1_data_re, L1_data_im = jnp.array(np.genfromtxt(f'{data_path}L1_data_re.txt')), jnp.array(np.genfromtxt(f'{data_path}L1_data_im.txt')) -L1.data = L1_data_re + 1j * L1_data_im - -V1.frequencies = jnp.array(np.genfromtxt(f'{data_path}V1_freq.txt')) -V1_data_re, V1_data_im = jnp.array(np.genfromtxt(f'{data_path}V1_data_re.txt')), jnp.array(np.genfromtxt(f'{data_path}V1_data_im.txt')) -V1.data = V1_data_re + 1j * V1_data_im - -# Load the PSD - -H1.psd = H1.load_psd(H1.frequencies, psd_file = data_path + "GW170817-IMRD_data0_1187008882-43_generation_data_dump.pickle_H1_psd.txt") -L1.psd = L1.load_psd(L1.frequencies, psd_file = data_path + "GW170817-IMRD_data0_1187008882-43_generation_data_dump.pickle_L1_psd.txt") -V1.psd = V1.load_psd(V1.frequencies, psd_file = data_path + "GW170817-IMRD_data0_1187008882-43_generation_data_dump.pickle_V1_psd.txt") - -### Define priors - -# Internal parameters -Mc_prior = Uniform(1.18, 1.21, naming=["M_c"]) -q_prior = Uniform( - 0.125, - 1.0, - naming=["q"], - transforms={"q": ("eta", lambda params: params["q"] / (1 + params["q"]) ** 2)}, -) -s1z_prior = Uniform(-0.05, 0.05, naming=["s1_z"]) -s2z_prior = Uniform(-0.05, 0.05, naming=["s2_z"]) -lambda_1_prior = Uniform(0.0, 5000.0, naming=["lambda_1"]) -lambda_2_prior = Uniform(0.0, 5000.0, naming=["lambda_2"]) -dL_prior = Uniform(1.0, 75.0, naming=["d_L"]) -t_c_prior = Uniform(-0.1, 0.1, naming=["t_c"]) -phase_c_prior = Uniform(0.0, 2 * jnp.pi, naming=["phase_c"]) -cos_iota_prior = Uniform( - -1.0, - 1.0, - naming=["cos_iota"], - transforms={ - "cos_iota": ( - "iota", - lambda params: jnp.arccos( - jnp.arcsin(jnp.sin(params["cos_iota"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) -psi_prior = Uniform(0.0, jnp.pi, naming=["psi"]) -ra_prior = Uniform(0.0, 2 * jnp.pi, naming=["ra"]) -sin_dec_prior = Uniform( - -1.0, - 1.0, - naming=["sin_dec"], - transforms={ - "sin_dec": ( - "dec", - lambda params: jnp.arcsin( - jnp.arcsin(jnp.sin(params["sin_dec"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) - -prior_list = [ - Mc_prior, - q_prior, - s1z_prior, - s2z_prior, - lambda_1_prior, - lambda_2_prior, - dL_prior, - t_c_prior, - phase_c_prior, - cos_iota_prior, - psi_prior, - ra_prior, - sin_dec_prior, - ] - -prior = Composite(prior_list) - -# The following only works if every prior has xmin and xmax property, which is OK for Uniform and Powerlaw -bounds = jnp.array([[p.xmin, p.xmax] for p in prior.priors]) - -### Create likelihood object - -# For simplicity, we put here a set of reference parameters found by the optimizer -ref_params = { - 'M_c': 1.19793583, - 'eta': 0.24794374, - 's1_z': 0.00220637, - 's2_z': 0.05, - 'lambda_1': 105.12916663, - 'lambda_2': 0.0, - 'd_L': 45.41592353, - 't_c': 0.00220588, - 'phase_c': 5.76822606, - 'iota': 2.46158044, - 'psi': 2.09118099, - 'ra': 5.03335133, - 'dec': 0.01679998 -} - -# Number of bins to use for relative binning -n_bins = 500 - -waveform = RippleTaylorF2(f_ref=f_ref) -likelihood = HeterodynedTransientLikelihoodFD([H1, L1, V1], - prior=prior, - bounds=bounds, - waveform=waveform, - trigger_time=gps, - duration=duration, - n_bins=n_bins, - ref_params=ref_params) - -# Local sampler args - -eps = 1e-3 -n_dim = 13 -mass_matrix = jnp.eye(n_dim) -mass_matrix = mass_matrix.at[0,0].set(1e-5) -mass_matrix = mass_matrix.at[1,1].set(1e-4) -mass_matrix = mass_matrix.at[2,2].set(1e-3) -mass_matrix = mass_matrix.at[3,3].set(1e-3) -mass_matrix = mass_matrix.at[7,7].set(1e-5) -mass_matrix = mass_matrix.at[11,11].set(1e-2) -mass_matrix = mass_matrix.at[12,12].set(1e-2) -local_sampler_arg = {"step_size": mass_matrix * eps} - -# Build the learning rate scheduler (if used) - -n_loop_training = 300 -n_epochs = 50 -total_epochs = n_epochs * n_loop_training -start = int(total_epochs / 10) -start_lr = 1e-3 -end_lr = 1e-5 -power = 4.0 -schedule_fn = optax.polynomial_schedule( - start_lr, end_lr, power, total_epochs-start, transition_begin=start) - -scheduler_str = f"polynomial_schedule({start_lr}, {end_lr}, {power}, {total_epochs-start}, {start})" - -## Choose between fixed learning rate - or - the above scheduler here -# learning_rate = schedule_fn -learning_rate = 0.001 - -print(f"Learning rate: {learning_rate}") - -# Create jim object - -outdir_name = "./outdir_TF2/" -jim = Jim( - likelihood, - prior, - n_loop_training=n_loop_training, - n_loop_production=20, - n_local_steps=100, - n_global_steps=1000, - n_chains=1000, - n_epochs=n_epochs, - learning_rate=schedule_fn, - max_samples=50000, - momentum=0.9, - batch_size=50000, - use_global=True, - keep_quantile=0.0, - train_thinning=10, - output_thinning=30, - local_sampler_arg=local_sampler_arg, - outdir_name=outdir_name -) - -### Heavy computation begins -jim.sample(jax.random.PRNGKey(43)) -### Heavy computation ends - -# === Show results, save output === - -# Print a summary to screen: -jim.print_summary() -outdir = outdir_name - -# Save and plot the results of the run -# - training phase - -name = outdir + f'results_training.npz' -print(f"Saving samples to {name}") -state = jim.Sampler.get_sampler_state(training=True) -chains, log_prob, local_accs, global_accs, loss_vals = state["chains"], state[ - "log_prob"], state["local_accs"], state["global_accs"], state["loss_vals"] -local_accs = jnp.mean(local_accs, axis=0) -global_accs = jnp.mean(global_accs, axis=0) -np.savez(name, log_prob=log_prob, local_accs=local_accs, - global_accs=global_accs, loss_vals=loss_vals) - -utils.plot_accs(local_accs, "Local accs (training)", - "local_accs_training", outdir) -utils.plot_accs(global_accs, "Global accs (training)", - "global_accs_training", outdir) -utils.plot_loss_vals(loss_vals, "Loss", "loss_vals", outdir) -utils.plot_log_prob(log_prob, "Log probability (training)", - "log_prob_training", outdir) - -# - production phase -name = outdir + f'results_production.npz' -state = jim.Sampler.get_sampler_state(training=False) -chains, log_prob, local_accs, global_accs = state["chains"], state[ - "log_prob"], state["local_accs"], state["global_accs"] -local_accs = jnp.mean(local_accs, axis=0) -global_accs = jnp.mean(global_accs, axis=0) -np.savez(name, chains=chains, log_prob=log_prob, - local_accs=local_accs, global_accs=global_accs) - -utils.plot_accs(local_accs, "Local accs (production)", - "local_accs_production", outdir) -utils.plot_accs(global_accs, "Global accs (production)", - "global_accs_production", outdir) -utils.plot_log_prob(log_prob, "Log probability (production)", - "log_prob_production", outdir) - -# Plot the chains as corner plots -utils.plot_chains(chains, "chains_production", outdir, truths=None) - -# Save the NF and show a plot of samples from the flow -print("Saving the NF") -jim.Sampler.save_flow(outdir + "nf_model") - -# Final steps - - -print("Finished successfully") - -end_runtime = time.time() -runtime = end_runtime - start_runtime -print(f"Time taken: {runtime} seconds ({(runtime)/60} minutes)") - -print(f"Saving runtime") -with open(outdir + 'runtime.txt', 'w') as file: - file.write(str(runtime)) \ No newline at end of file diff --git a/example/GW170817_heterodyne.py b/example/GW170817_heterodyne.py deleted file mode 100644 index 37aaeec1..00000000 --- a/example/GW170817_heterodyne.py +++ /dev/null @@ -1,157 +0,0 @@ -import time - -import jax -import jax.numpy as jnp - -from jimgw.jim import Jim -from jimgw.prior import Composite, PowerLaw, Uniform -from jimgw.single_event.detector import H1, L1, V1 -from jimgw.single_event.likelihood import HeterodynedTransientLikelihoodFD -from jimgw.single_event.waveform import RippleIMRPhenomD - -jax.config.update("jax_enable_x64", True) - - -### Fetching the data - -total_time_start = time.time() -gps = 1187008882.43 -trigger_time = gps -fmin = 20 -fmax = 2048 -minimum_frequency = fmin -maximum_frequency = fmax -T = 128 -duration = T -post_trigger_duration = 2 -epoch = duration - post_trigger_duration -f_ref = fmin - -### Getting ifos and overwriting with above data - -tukey_alpha = 2 / (duration / 2) -H1.load_data( - gps, duration, 2, fmin, fmax, psd_pad=duration + 16, tukey_alpha=tukey_alpha -) -L1.load_data( - gps, duration, 2, fmin, fmax, psd_pad=duration + 16, tukey_alpha=tukey_alpha -) -V1.load_data( - gps, duration, 2, fmin, fmax, psd_pad=duration + 16, tukey_alpha=tukey_alpha -) - -### Define priors - -# Internal parameters -Mc_prior = Uniform(1.18, 1.21, naming=["M_c"]) -q_prior = Uniform( - 0.125, - 1.0, - naming=["q"], - transforms={"q": ("eta", lambda params: params["q"] / (1 + params["q"]) ** 2)}, -) -s1z_prior = Uniform(-0.05, 0.05, naming=["s1_z"]) -s2z_prior = Uniform(-0.05, 0.05, naming=["s2_z"]) - -# External parameters -dL_prior = PowerLaw(1.0, 75.0, 2.0, naming=["d_L"]) -t_c_prior = Uniform(-0.1, 0.1, naming=["t_c"]) -phase_c_prior = Uniform(0.0, 2 * jnp.pi, naming=["phase_c"]) -cos_iota_prior = Uniform( - -1.0, - 1.0, - naming=["cos_iota"], - transforms={ - "cos_iota": ( - "iota", - lambda params: jnp.arccos( - jnp.arcsin(jnp.sin(params["cos_iota"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) -psi_prior = Uniform(0.0, jnp.pi, naming=["psi"]) -ra_prior = Uniform(0.0, 2 * jnp.pi, naming=["ra"]) -sin_dec_prior = Uniform( - -1.0, - 1.0, - naming=["sin_dec"], - transforms={ - "sin_dec": ( - "dec", - lambda params: jnp.arcsin( - jnp.arcsin(jnp.sin(params["sin_dec"] / 2 * jnp.pi)) * 2 / jnp.pi - ), - ) - }, -) - -prior = Composite( - [ - Mc_prior, - q_prior, - s1z_prior, - s2z_prior, - dL_prior, - t_c_prior, - phase_c_prior, - cos_iota_prior, - psi_prior, - ra_prior, - sin_dec_prior, - ] -) - -# The following only works if every prior has xmin and xmax property, which is OK for Uniform and Powerlaw -bounds = jnp.array([[p.xmin, p.xmax] for p in prior.priors]) - -### Create likelihood object -likelihood = HeterodynedTransientLikelihoodFD( - [H1, L1, V1], - prior=prior, - bounds=bounds, - waveform=RippleIMRPhenomD(), - trigger_time=gps, - duration=T, - n_bins=500, -) - -### Create sampler and jim objects -eps = 3e-2 -n_dim = 11 -mass_matrix = jnp.eye(n_dim) -mass_matrix = mass_matrix.at[0, 0].set(1e-5) -mass_matrix = mass_matrix.at[1, 1].set(1e-4) -mass_matrix = mass_matrix.at[2, 2].set(1e-3) -mass_matrix = mass_matrix.at[3, 3].set(1e-3) -mass_matrix = mass_matrix.at[5, 5].set(1e-5) -mass_matrix = mass_matrix.at[9, 9].set(1e-2) -mass_matrix = mass_matrix.at[10, 10].set(1e-2) -local_sampler_arg = {"step_size": mass_matrix * eps} - -outdir_name = "./outdir/" - -jim = Jim( - likelihood, - prior, - n_loop_training=200, - n_loop_production=20, - n_local_steps=200, - n_global_steps=200, - n_chains=1000, - n_epochs=300, - learning_rate=0.001, - n_max_examples=50000, - momentum=0.9, - batch_size=50000, - use_global=True, - keep_quantile=0.0, - train_thinning=10, - output_thinning=30, - n_loops_maximize_likelihood=2000, - local_sampler_arg=local_sampler_arg, - outdir_name=outdir_name, -) - -jim.sample(jax.random.PRNGKey(42)) -jim.print_summary() diff --git a/example/Single_event_runManager.py b/example/Single_event_runManager.py deleted file mode 100644 index 5c678b22..00000000 --- a/example/Single_event_runManager.py +++ /dev/null @@ -1,97 +0,0 @@ - -import jax -import jax.numpy as jnp - -from jimgw.single_event.runManager import (SingleEventPERunManager, - SingleEventRun) - -jax.config.update("jax_enable_x64", True) - -mass_matrix = jnp.eye(11) -mass_matrix = mass_matrix.at[1, 1].set(1e-3) -mass_matrix = mass_matrix.at[5, 5].set(1e-3) -mass_matrix = mass_matrix * 3e-3 -local_sampler_arg = {"step_size": mass_matrix} -bounds = jnp.array( - [ - [10.0, 40.0], - [0.125, 1.0], - [-1.0, 1.0], - [-1.0, 1.0], - [0.0, 2000.0], - [-0.05, 0.05], - [0.0, 2 * jnp.pi], - [-1.0, 1.0], - [0.0, jnp.pi], - [0.0, 2 * jnp.pi], - [-1.0, 1.0], - ] -) - -run = SingleEventRun( - seed=0, - detectors=["H1", "L1"], - priors={ - "M_c": {"name": "Unconstrained_Uniform", "xmin": 10.0, "xmax": 80.0}, - "q": {"name": "MassRatio"}, - "s1_z": {"name": "Unconstrained_Uniform", "xmin": -1.0, "xmax": 1.0}, - "s2_z": {"name": "Unconstrained_Uniform", "xmin": -1.0, "xmax": 1.0}, - "d_L": {"name": "Unconstrained_Uniform", "xmin": 0.0, "xmax": 2000.0}, - "t_c": {"name": "Unconstrained_Uniform", "xmin": -0.05, "xmax": 0.05}, - "phase_c": {"name": "Unconstrained_Uniform", "xmin": 0.0, "xmax": 2 * jnp.pi}, - "cos_iota": {"name": "CosIota"}, - "psi": {"name": "Unconstrained_Uniform", "xmin": 0.0, "xmax": jnp.pi}, - "ra": {"name": "Unconstrained_Uniform", "xmin": 0.0, "xmax": 2 * jnp.pi}, - "sin_dec": {"name": "SinDec"}, - }, - waveform_parameters={"name": "RippleIMRPhenomD", "f_ref": 20.0}, - jim_parameters={ - "n_loop_training": 10, - "n_loop_production": 10, - "n_local_steps": 150, - "n_global_steps": 150, - "n_chains": 500, - "n_epochs": 50, - "learning_rate": 0.001, - "n_max_examples": 45000, - "momentum": 0.9, - "batch_size": 50000, - "use_global": True, - "keep_quantile": 0.0, - "train_thinning": 1, - "output_thinning": 10, - "local_sampler_arg": local_sampler_arg, - }, - likelihood_parameters={"name": "HeterodynedTransientLikelihoodFD", "bounds": bounds}, - injection=True, - injection_parameters={ - "M_c": 28.6, - "eta": 0.24, - "s1_z": 0.05, - "s2_z": 0.05, - "d_L": 440.0, - "t_c": 0.0, - "phase_c": 0.0, - "iota": 0.5, - "psi": 0.7, - "ra": 1.2, - "dec": 0.3, - }, - data_parameters={ - "trigger_time": 1126259462.4, - "duration": 4, - "post_trigger_duration": 2, - "f_min": 20.0, - "f_max": 1024.0, - "tukey_alpha": 0.2, - "f_sampling": 4096.0, - }, -) - -run_manager = SingleEventPERunManager(run=run) -run_manager.jim.sample(jax.random.PRNGKey(42)) - -# plot the corner plot and diagnostic plot -run_manager.plot_corner() -run_manager.plot_diagnostic() - diff --git a/example/gen_injection_config.py b/example/gen_injection_config.py deleted file mode 100644 index 1c200a58..00000000 --- a/example/gen_injection_config.py +++ /dev/null @@ -1,73 +0,0 @@ -import numpy as np - - -def Mc_eta_to_ms(m): - Mchirp, eta = m - M = Mchirp / (eta ** (3 / 5)) - m2 = (M - np.sqrt(M ** 2 - 4 * M ** 2 * eta)) / 2 - m1 = M - m2 - return m1, m2 - -prior_range = np.array([[10,50],[0.5,1],[-0.5,0.5],[-0.5,0.5],[300,2000],[-0.5,0.5],[0,2*np.pi],[-1,1],[0,np.pi],[0,2*np.pi],[-1,1]]) - -N_config = 3000 - -mc = np.random.uniform(prior_range[0,0],prior_range[0,1],N_config) -q = np.random.uniform(prior_range[1,0],prior_range[1,1],N_config) -eta = q/(1+q)**2 -m1,m2 = Mc_eta_to_ms(np.stack([mc,eta])) -chi1 = np.random.uniform(prior_range[2,0],prior_range[2,1],N_config) -chi2 = np.random.uniform(prior_range[3,0],prior_range[3,1],N_config) -dist_mpc = np.random.uniform(prior_range[4,0],prior_range[4,1],N_config) -tc = np.random.uniform(prior_range[5,0],prior_range[5,1],N_config) -phic = np.random.uniform(prior_range[6,0],prior_range[6,1],N_config) -cos_inclination = np.random.uniform(prior_range[7,0],prior_range[7,1],N_config) -inclination = np.arccos(cos_inclination) -polarization_angle = np.random.uniform(prior_range[8,0],prior_range[8,1],N_config) -ra = np.random.uniform(prior_range[9,0],prior_range[9,1],N_config) -sin_dec = np.random.uniform(prior_range[10,0],prior_range[10,1],N_config) -#dec = np.arcsin(sin_dec) - -directory = '/mnt/home/wwong/ceph/GWProject/JaxGW/RealtimePE/ppPlots/configs/' - -for i in range(N_config): - f = open(directory+"injection_config_"+str(i)+".yaml","w") - - f.write('output_path: /mnt/home/wwong/ceph/GWProject/JaxGW/RealtimePE/ppPlots/injection_'+str(i)+'\n') - f.write('downsample_factor: 10\n') - f.write('seed: '+str(np.random.randint(low=0,high=10000))+'\n') - f.write('f_sampling: 2048\n') - f.write('duration: 16\n') - f.write('fmin: 30\n') - f.write('ifos:\n') - f.write(' - H1\n') - f.write(' - L1\n') - f.write(' - V1\n') - - f.write("m1: "+str(m1[i])+"\n") - f.write("m2: "+str(m2[i])+"\n") - f.write("chi1: "+str(chi1[i])+"\n") - f.write("chi2: "+str(chi2[i])+"\n") - f.write("dist_mpc: "+str(dist_mpc[i])+"\n") - f.write("tc: "+str(tc[i])+"\n") - f.write("phic: "+str(phic[i])+"\n") - f.write("inclination: "+str(inclination[i])+"\n") - f.write("polarization_angle: "+str(polarization_angle[i])+"\n") - f.write("ra: "+str(ra[i])+"\n") - f.write("dec: "+str(dec[i])+"\n") - f.write("heterodyne_bins: 1001\n") - - f.write("n_dim: 11\n") - f.write("n_chains: 1000\n") - f.write("n_loop_training: 20\n") - f.write("n_loop_production: 20\n") - f.write("n_local_steps: 200\n") - f.write("n_global_steps: 200\n") - f.write("learning_rate: 0.001\n") - f.write("max_samples: 50000\n") - f.write("momentum: 0.9\n") - f.write("num_epochs: 240\n") - f.write("batch_size: 50000\n") - f.write("stepsize: 0.01\n") - - f.close() diff --git a/example/make_ppPlot.py b/example/make_ppPlot.py deleted file mode 100644 index 3a8c1ad1..00000000 --- a/example/make_ppPlot.py +++ /dev/null @@ -1,68 +0,0 @@ -import arviz as az -import numpy as np -from scipy.interpolate import interp1d -from scipy.optimize import minimize_scalar - -q_axis = np.linspace(0.1,1,100) -eta = q_axis/(1+q_axis)**2 -q_interp = interp1d(eta,q_axis) - -def get_all_quantile(filename): - data = np.load(filename) - - chains = data['chains'] - true_param = data['true_param'] - true_param[1] = q_interp(true_param[1]) - true_param[7] = np.cos(true_param[7]) - true_param[10] = np.sin(true_param[10]) - - def compute_percentile(value,data): - return np.where(data Date: Wed, 16 Oct 2024 16:29:13 -0400 Subject: [PATCH 239/248] update pyproject dependency --- mkdocs.yml | 4 +- pyproject.toml | 14 +++--- uv.lock | 113 +++++++++++++++++++++++++++++++++++-------------- 3 files changed, 90 insertions(+), 41 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 6ece4db8..1aab9612 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,13 +55,13 @@ plugins: - mkdocstrings: handlers: python: - setup_commands: + setup_commands: - import pytkdocs_tweaks - pytkdocs_tweaks.main() - import jaxtyping - jaxtyping.set_array_name_format("array") - optional: + optional: inherited_members: true # Allow looking up inherited methods show_root_heading: true # actually display anything at all... show_root_full_path: true # display "diffrax.asdf" not just "asdf" diff --git a/pyproject.toml b/pyproject.toml index 9c38c988..681fc007 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,11 +22,11 @@ dependencies = [ [project.optional-dependencies] docs = [ - "mkdocs-gen-files==0.5.0", - "mkdocs-jupyter==0.24.2", - "mkdocs-literate-nav==0.6.0", - "mkdocs-material==9.1.18", - "mkdocs==1.4.3", - "mkdocstrings[python]==0.22.0", - "pymdown-extensions==10.1", + "mkdocs-gen-files", + "mkdocs-jupyter", + "mkdocs-literate-nav", + "mkdocs-material", + "mkdocs", + "mkdocstrings[python]", + "pymdown-extensions", ] diff --git a/uv.lock b/uv.lock index 752a32a7..5b641d86 100644 --- a/uv.lock +++ b/uv.lock @@ -90,6 +90,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, ] +[[package]] +name = "babel" +version = "2.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, +] + [[package]] name = "beartype" version = "0.19.0" @@ -906,13 +915,14 @@ requires-dist = [ { name = "gwpy", specifier = ">=3.0.10" }, { name = "jax", specifier = ">=0.4.34" }, { name = "jaxtyping", specifier = ">=0.2.34" }, - { name = "mkdocs", marker = "extra == 'docs'", specifier = "==1.4.3" }, - { name = "mkdocs-gen-files", marker = "extra == 'docs'", specifier = "==0.5.0" }, - { name = "mkdocs-jupyter", marker = "extra == 'docs'", specifier = "==0.24.2" }, - { name = "mkdocs-literate-nav", marker = "extra == 'docs'", specifier = "==0.6.0" }, - { name = "mkdocs-material", marker = "extra == 'docs'", specifier = "==9.1.18" }, - { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'", specifier = "==0.22.0" }, - { name = "pymdown-extensions", marker = "extra == 'docs'", specifier = "==10.1" }, + { name = "jimgw", marker = "extra == 'docs'" }, + { name = "mkdocs", marker = "extra == 'docs'" }, + { name = "mkdocs-gen-files", marker = "extra == 'docs'" }, + { name = "mkdocs-jupyter", marker = "extra == 'docs'" }, + { name = "mkdocs-literate-nav", marker = "extra == 'docs'" }, + { name = "mkdocs-material", marker = "extra == 'docs'" }, + { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'" }, + { name = "pymdown-extensions", marker = "extra == 'docs'" }, { name = "ripplegw", specifier = ">=0.0.9" }, { name = "typed-argument-parser", specifier = ">=1.10.1" }, ] @@ -1091,11 +1101,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.3.7" +version = "3.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/58/79df20de6e67a83f0d0bbfe6c19bb82adf68cdf362885257eb01099f930a/Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874", size = 324130 } +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/df/ca72f352e15b6f8ce32b74af029f1189abffb906f7c137501ffe69c98a65/Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621", size = 97778 }, + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, ] [[package]] @@ -1253,7 +1263,7 @@ wheels = [ [[package]] name = "mkdocs" -version = "1.4.3" +version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1261,15 +1271,18 @@ dependencies = [ { name = "ghp-import" }, { name = "jinja2" }, { name = "markdown" }, + { name = "markupsafe" }, { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, { name = "packaging" }, + { name = "pathspec" }, { name = "pyyaml" }, { name = "pyyaml-env-tag" }, { name = "watchdog" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/ef/49b4427e5eec761b77a3c3c421d3fd63010e2798b7401dc0fa2b875ef6b5/mkdocs-1.4.3.tar.gz", hash = "sha256:5955093bbd4dd2e9403c5afaf57324ad8b04f16886512a3ee6ef828956481c57", size = 3624951 } +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/7a/5ed794942ace9d00bb77a8036c64c999cda6ebaab57e9b8a6ec1aa5fc900/mkdocs-1.4.3-py3-none-any.whl", hash = "sha256:6ee46d309bda331aac915cd24aab882c179a933bd9e77b80ce7d2eaaa3f689dd", size = 3654464 }, + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 }, ] [[package]] @@ -1298,9 +1311,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/0f/1e55b3fd490ad2cecb6e7b31892d27cb9fc4218ec1dab780440ba8579e74/mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea", size = 8380 }, ] +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, +] + [[package]] name = "mkdocs-jupyter" -version = "0.24.2" +version = "0.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ipykernel" }, @@ -1310,41 +1337,43 @@ dependencies = [ { name = "nbconvert" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/6d/e2367bdd3662a2a1cf15d61ad11548394fbef55a393fbdcceddbb59e4deb/mkdocs_jupyter-0.24.2.tar.gz", hash = "sha256:5e0c109d535d48797230719b6941f4d08de95a7a3c95bf158662c412fc15cb2e", size = 4324061 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/23/6ffb8d2fd2117aa860a04c6fe2510b21bc3c3c085907ffdd851caba53152/mkdocs_jupyter-0.25.1.tar.gz", hash = "sha256:0e9272ff4947e0ec683c92423a4bfb42a26477c103ab1a6ab8277e2dcc8f7afe", size = 1626747 } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/51/1b4da5919e7bb028969129a5ff631e56215f3e110bb2af68bfe157209854/mkdocs_jupyter-0.24.2-py3-none-any.whl", hash = "sha256:5295a34b1d0bd0a7688a857323eaf0319d83c8a14179b2651709b8ced6eae7db", size = 3894677 }, + { url = "https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl", hash = "sha256:3f679a857609885d322880e72533ef5255561bbfdb13cfee2a1e92ef4d4ad8d8", size = 1456197 }, ] [[package]] name = "mkdocs-literate-nav" -version = "0.6.0" +version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/91/75eb48eafdbe609ca80d6c271ad5bc4edc75cffde4d1d70604f2a4c2145a/mkdocs_literate_nav-0.6.0.tar.gz", hash = "sha256:81ccbea18163ae8e10bd0bd39237fe70c32a1f2dff6c170779f5d52dd98a0470", size = 16342 } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f9/c48a04f3cf484f8016a343c1d7d99c3a1ef01dbb33ceabb1d02e0ecabda7/mkdocs_literate_nav-0.6.1.tar.gz", hash = "sha256:78a7ab6d878371728acb0cdc6235c9b0ffc6e83c997b037f4a5c6ff7cef7d759", size = 16437 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/e0/4c04aa187e4c0a3f1218681e7389106fad8f25ecaae90b1e68636f04d15b/mkdocs_literate_nav-0.6.0-py3-none-any.whl", hash = "sha256:8c1b84714e5974da5e44e011ec0069275ae7647270c13a679662cf6ffce675a4", size = 13082 }, + { url = "https://files.pythonhosted.org/packages/51/3b/e00d839d3242844c77e248f9572dd34644a04300839a60fe7d6bf652ab19/mkdocs_literate_nav-0.6.1-py3-none-any.whl", hash = "sha256:e70bdc4a07050d32da79c0b697bd88e9a104cf3294282e9cb20eec94c6b0f401", size = 13182 }, ] [[package]] name = "mkdocs-material" -version = "9.1.18" +version = "9.5.41" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "babel" }, { name = "colorama" }, { name = "jinja2" }, { name = "markdown" }, { name = "mkdocs" }, { name = "mkdocs-material-extensions" }, + { name = "paginate" }, { name = "pygments" }, { name = "pymdown-extensions" }, { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/64/d2b6cfc3cb7584f4e2270fbe90be6448d1e410075a9b7d1b79c166a6eaee/mkdocs_material-9.1.18.tar.gz", hash = "sha256:981dd39979723d4cda7cfc77bbbe5e54922d5761a7af23fb8ba9edb52f114b13", size = 3693508 } +sdist = { url = "https://files.pythonhosted.org/packages/ab/56/c58fe932fe0b3e70b065f1ce5759672c81ae91d00b720023ab8cd580b7a8/mkdocs_material-9.5.41.tar.gz", hash = "sha256:30fa5d459b4b8130848ecd8e1c908878345d9d8268f7ddbc31eebe88d462d97b", size = 3963765 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/e9/bdb7488c44adef405afb7343b55f71660f6de4b6e06e02cad123e4473370/mkdocs_material-9.1.18-py3-none-any.whl", hash = "sha256:5bcf8fb79ac2f253c0ffe93fa181cba87718c6438f459dc4180ac7418cc9a450", size = 7846316 }, + { url = "https://files.pythonhosted.org/packages/38/bd/92771ccb61285dacfe852f96c08601f9b4830a3e1647ccc9726588b3340b/mkdocs_material-9.5.41-py3-none-any.whl", hash = "sha256:990bc138c33342b5b73e7545915ebc0136e501bfbd8e365735144f5120891d83", size = 8672461 }, ] [[package]] @@ -1358,19 +1387,21 @@ wheels = [ [[package]] name = "mkdocstrings" -version = "0.22.0" +version = "0.26.2" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "click" }, { name = "jinja2" }, { name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }, { name = "mkdocs-autorefs" }, + { name = "platformdirs" }, { name = "pymdown-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/89/39b7da1cd3d7bc9d3626a2030349443276bd4c8428b676b010ffb96ec9be/mkdocstrings-0.22.0.tar.gz", hash = "sha256:82a33b94150ebb3d4b5c73bab4598c3e21468c79ec072eff6931c8f3bfc38256", size = 30419 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/76/0475d10d27f3384df3a6ddfdf4a4fdfef83766f77cd4e327d905dc956c15/mkdocstrings-0.26.2.tar.gz", hash = "sha256:34a8b50f1e6cfd29546c6c09fbe02154adfb0b361bb758834bf56aa284ba876e", size = 92512 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/26/5816407b5dd51821a3d23f53bdbd013ab1878b6246e520dc014d200ee1d2/mkdocstrings-0.22.0-py3-none-any.whl", hash = "sha256:2d4095d461554ff6a778fdabdca3c00c468c2f1459d469f7a7f622a2b23212ba", size = 26747 }, + { url = "https://files.pythonhosted.org/packages/80/b6/4ee320d7c313da3774eff225875eb278f7e6bb26a9cd8e680b8dbc38fdea/mkdocstrings-0.26.2-py3-none-any.whl", hash = "sha256:1248f3228464f3b8d1a15bd91249ce1701fe3104ac517a5f167a0e01ca850ba5", size = 29716 }, ] [package.optional-dependencies] @@ -1380,16 +1411,16 @@ python = [ [[package]] name = "mkdocstrings-python" -version = "1.9.1" +version = "1.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, - { name = "markdown" }, + { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/47/9d771f91359e5377cf19385a056aa4da43324995d72b1fac9d77c840d423/mkdocstrings_python-1.9.1.tar.gz", hash = "sha256:077188fa43eab3b689826b15da7da6753501224b2482e4eca3ce4412ce3b71cb", size = 34013 } +sdist = { url = "https://files.pythonhosted.org/packages/61/19/7b186a49a957611270d6c4fc156face8748cf98680a40c00b5b0b7008fe1/mkdocstrings_python-1.12.1.tar.gz", hash = "sha256:60d6a5ca912c9af4ad431db6d0111ce9f79c6c48d33377dde6a05a8f5f48d792", size = 168014 } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/e1/1498edc8323a2967dea48bd9366df861507dbafcd36a58f59db86fec615f/mkdocstrings_python-1.9.1-py3-none-any.whl", hash = "sha256:bf2406ed37ff19c9f8e0acc9d72c41953fb789bfb4ae10eb00ee17e537eeb220", size = 58554 }, + { url = "https://files.pythonhosted.org/packages/65/e8/3cf3467fb8e31f68bfc8a2bfd5f4891c1eaa584b0c62b76c783d24d1901d/mkdocstrings_python-1.12.1-py3-none-any.whl", hash = "sha256:205244488199c9aa2a39787ad6a0c862d39b74078ea9aa2be817bc972399563f", size = 111657 }, ] [[package]] @@ -1633,6 +1664,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, ] +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, +] + [[package]] name = "pandocfilters" version = "1.5.1" @@ -1651,6 +1691,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, ] +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + [[package]] name = "pexpect" version = "4.9.0" @@ -1827,15 +1876,15 @@ wheels = [ [[package]] name = "pymdown-extensions" -version = "10.1" +version = "10.11.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/3b/b0a111b226ccf26429d0b2e0fc64f5208c6b57ad20696caef5717b8472a7/pymdown_extensions-10.1.tar.gz", hash = "sha256:508009b211373058debb8247e168de4cbcb91b1bff7b5e961b2c3e864e00b195", size = 783894 } +sdist = { url = "https://files.pythonhosted.org/packages/f4/71/2730a20e9e3752393d78998347f8b1085ef9c417646ea9befbeef221e3c4/pymdown_extensions-10.11.2.tar.gz", hash = "sha256:bc8847ecc9e784a098efd35e20cba772bc5a1b529dfcef9dc1972db9021a1049", size = 830241 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/55/740e1c6f1d3a598b6d2d3afc9691d154596f1b7a50f7a7288526b8818529/pymdown_extensions-10.1-py3-none-any.whl", hash = "sha256:ef25dbbae530e8f67575d222b75ff0649b1e841e22c2ae9a20bad9472c2207dc", size = 240804 }, + { url = "https://files.pythonhosted.org/packages/c2/35/c0edf199257ef0a7d407d29cd51c4e70d1dad4370a5f44deb65a7a5475e2/pymdown_extensions-10.11.2-py3-none-any.whl", hash = "sha256:41cdde0a77290e480cf53892f5c5e50921a7ee3e5cd60ba91bf19837b33badcf", size = 259044 }, ] [[package]] From 87e0824aa8ada3423f65f71afa70fc57323fe83a Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Wed, 16 Oct 2024 17:03:16 -0400 Subject: [PATCH 240/248] Fixing GW150914 Pv2 test --- test/integration/test_GW150914_Pv2.py | 137 ++++++++++++++++---------- 1 file changed, 86 insertions(+), 51 deletions(-) diff --git a/test/integration/test_GW150914_Pv2.py b/test/integration/test_GW150914_Pv2.py index 9892058d..2b54ed3c 100644 --- a/test/integration/test_GW150914_Pv2.py +++ b/test/integration/test_GW150914_Pv2.py @@ -4,12 +4,31 @@ import jax.numpy as jnp from jimgw.jim import Jim -from jimgw.prior import CombinePrior, UniformPrior, CosinePrior, SinePrior, PowerLawPrior +from jimgw.jim import Jim +from jimgw.prior import ( + CombinePrior, + UniformPrior, + CosinePrior, + SinePrior, + PowerLawPrior, + UniformSpherePrior, +) from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD -from jimgw.single_event.waveform import RippleIMRPhenomD +from jimgw.single_event.waveform import RippleIMRPhenomPv2 from jimgw.transforms import BoundToUnbound -from jimgw.single_event.transforms import MassRatioToSymmetricMassRatioTransform, SpinToCartesianSpinTransform +from jimgw.single_event.transforms import ( + SkyFrameToDetectorFrameSkyPositionTransform, + SphereSpinToCartesianSpinTransform, + MassRatioToSymmetricMassRatioTransform, + DistanceToSNRWeightedDistanceTransform, + GeocentricArrivalTimeToDetectorArrivalTimeTransform, + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform, + ComponentMassesToChirpMassMassRatioTransform, +) +from jimgw.single_event.prior import ( + ChirpMassMassRatioBoundedUniformComponentPrior +) from flowMC.strategy.optimization import optimization_Adam jax.config.update("jax_enable_x64", True) @@ -34,75 +53,91 @@ for ifo in ifos: ifo.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) -Mc_prior = UniformPrior(10.0, 80.0, parameter_names=["M_c"]) -q_prior = UniformPrior(0.125, 1., parameter_names=["q"]) -theta_jn_prior = SinePrior(parameter_names=["theta_jn"]) -phi_jl_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_jl"]) -theta_1_prior = SinePrior(parameter_names=["theta_1"]) -theta_2_prior = SinePrior(parameter_names=["theta_2"]) -phi_12_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phi_12"]) -a_1_prior = UniformPrior(0.0, 1.0, parameter_names=["a_1"]) -a_2_prior = UniformPrior(0.0, 1.0, parameter_names=["a_2"]) -dL_prior = PowerLawPrior(10.0, 2000.0, 2.0, parameter_names=["d_L"]) +prior = [] + +# Mass prior +M_c_min, M_c_max = 10.0, 80.0 +q_min, q_max = 0.125, 1.0 +m1m2_prior = ChirpMassMassRatioBoundedUniformComponentPrior( + q_min=q_min, + q_max=q_max, + M_c_min=M_c_min, + M_c_max=M_c_max, +) + +prior = prior + [m1m2_prior] + +# Spin prior +s1_prior = UniformSpherePrior(parameter_names=["s1"]) +s2_prior = UniformSpherePrior(parameter_names=["s2"]) +iota_prior = SinePrior(parameter_names=["iota"]) + +prior = prior + [ + s1_prior, + s2_prior, + iota_prior, +] + +# Extrinsic prior +dL_prior = PowerLawPrior(1.0, 2000.0, 2.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) dec_prior = CosinePrior(parameter_names=["dec"]) -prior = CombinePrior( - [ - Mc_prior, - q_prior, - theta_jn_prior, - phi_jl_prior, - theta_1_prior, - theta_2_prior, - phi_12_prior, - a_1_prior, - a_2_prior, - dL_prior, - t_c_prior, - phase_c_prior, - psi_prior, - ra_prior, - dec_prior, - ] -) +prior = prior + [ + dL_prior, + t_c_prior, + phase_c_prior, + psi_prior, + ra_prior, + dec_prior, +] + +prior = CombinePrior(prior) + +# Defining Transforms sample_transforms = [ - BoundToUnbound(name_mapping = [["M_c"], ["M_c_unbounded"]], original_lower_bound=10.0, original_upper_bound=80.0), - BoundToUnbound(name_mapping = [["q"], ["q_unbounded"]], original_lower_bound=0.125, original_upper_bound=1.), - BoundToUnbound(name_mapping = [["theta_jn"], ["theta_jn_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["phi_jl"], ["phi_jl_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["theta_1"], ["theta_1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["theta_2"], ["theta_2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["phi_12"], ["phi_12_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["a_1"], ["a_1_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = [["a_2"], ["a_2_unbounded"]] , original_lower_bound=0.0, original_upper_bound=1.0), - BoundToUnbound(name_mapping = [["d_L"], ["d_L_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2000.0), - BoundToUnbound(name_mapping = [["t_c"], ["t_c_unbounded"]] , original_lower_bound=-0.05, original_upper_bound=0.05), - BoundToUnbound(name_mapping = [["phase_c"], ["phase_c_unbounded"]] , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["psi"], ["psi_unbounded"]], original_lower_bound=0.0, original_upper_bound=jnp.pi), - BoundToUnbound(name_mapping = [["ra"], ["ra_unbounded"]], original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), - BoundToUnbound(name_mapping = [["dec"], ["dec_unbounded"]],original_lower_bound=-jnp.pi / 2, original_upper_bound=jnp.pi / 2) + ComponentMassesToChirpMassMassRatioTransform, + DistanceToSNRWeightedDistanceTransform(gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(gps_time=gps, ifo=ifos[0]), + GeocentricArrivalTimeToDetectorArrivalTimeTransform(tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, gps_time=gps, ifo=ifos[0]), + SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), + BoundToUnbound(name_mapping = (["M_c"], ["M_c_unbounded"]), original_lower_bound=M_c_min, original_upper_bound=M_c_max), + BoundToUnbound(name_mapping = (["q"], ["q_unbounded"]), original_lower_bound=q_min, original_upper_bound=q_max), + BoundToUnbound(name_mapping = (["s1_phi"], ["s1_phi_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["s2_phi"], ["s2_phi_unbounded"]) , original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["iota"], ["iota_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["s1_theta"], ["s1_theta_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["s2_theta"], ["s2_theta_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["s1_mag"], ["s1_mag_unbounded"]) , original_lower_bound=0.0, original_upper_bound=0.99), + BoundToUnbound(name_mapping = (["s2_mag"], ["s2_mag_unbounded"]) , original_lower_bound=0.0, original_upper_bound=0.99), + BoundToUnbound(name_mapping = (["phase_det"], ["phase_det_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["psi"], ["psi_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["zenith"], ["zenith_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["azimuth"], ["azimuth_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), ] likelihood_transforms = [ - SpinToCartesianSpinTransform(freq_ref=20.0), + ComponentMassesToChirpMassMassRatioTransform, MassRatioToSymmetricMassRatioTransform, + SphereSpinToCartesianSpinTransform("s1"), + SphereSpinToCartesianSpinTransform("s2"), ] likelihood = TransientLikelihoodFD( ifos, - waveform=RippleIMRPhenomD(), + waveform=RippleIMRPhenomPv2(), trigger_time=gps, duration=4, post_trigger_duration=2, ) -mass_matrix = jnp.eye(15) +n_dim = sum([ind_prior.n_dim for ind_prior in prior.base_prior]) +mass_matrix = jnp.eye(n_dim) mass_matrix = mass_matrix.at[1, 1].set(1e-3) mass_matrix = mass_matrix.at[9, 9].set(1e-3) local_sampler_arg = {"step_size": mass_matrix * 3e-3} @@ -139,4 +174,4 @@ jim.sample(jax.random.PRNGKey(42)) jim.get_samples() -jim.print_summary() \ No newline at end of file +jim.print_summary() From 54cb2e12770aa17e0eb03c70848df3e17dc8493d Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Wed, 16 Oct 2024 17:06:42 -0400 Subject: [PATCH 241/248] Update GW150914 PhenomD example --- example/GW150914_IMRPhenomD.py | 186 +++++++++++++++------------------ 1 file changed, 83 insertions(+), 103 deletions(-) diff --git a/example/GW150914_IMRPhenomD.py b/example/GW150914_IMRPhenomD.py index 23d08c7b..9a9b9e09 100644 --- a/example/GW150914_IMRPhenomD.py +++ b/example/GW150914_IMRPhenomD.py @@ -1,6 +1,9 @@ +import time + import jax import jax.numpy as jnp +from jimgw.jim import Jim from jimgw.jim import Jim from jimgw.prior import ( CombinePrior, @@ -8,15 +11,19 @@ CosinePrior, SinePrior, PowerLawPrior, + UniformSpherePrior, ) from jimgw.single_event.detector import H1, L1 from jimgw.single_event.likelihood import TransientLikelihoodFD from jimgw.single_event.waveform import RippleIMRPhenomD from jimgw.transforms import BoundToUnbound from jimgw.single_event.transforms import ( - ComponentMassesToChirpMassSymmetricMassRatioTransform, SkyFrameToDetectorFrameSkyPositionTransform, - ComponentMassesToChirpMassMassRatioTransform, + SphereSpinToCartesianSpinTransform, + MassRatioToSymmetricMassRatioTransform, + DistanceToSNRWeightedDistanceTransform, + GeocentricArrivalTimeToDetectorArrivalTimeTransform, + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform, ) from jimgw.single_event.utils import Mc_q_to_m1_m2 from flowMC.strategy.optimization import optimization_Adam @@ -27,136 +34,110 @@ ########## First we grab data ############# ########################################### +total_time_start = time.time() + # first, fetch a 4s segment centered on GW150914 gps = 1126259462.4 -duration = 4 -post_trigger_duration = 2 -start_pad = duration - post_trigger_duration -end_pad = post_trigger_duration +start = gps - 2 +end = gps + 2 fmin = 20.0 fmax = 1024.0 ifos = [H1, L1] -for ifo in ifos: - ifo.load_data(gps, start_pad, end_pad, fmin, fmax, psd_pad=16, tukey_alpha=0.2) +H1.load_data(gps, 2, 2, fmin, fmax, psd_pad=16, tukey_alpha=0.2) +L1.load_data(gps, 2, 2, fmin, fmax, psd_pad=16, tukey_alpha=0.2) +waveform = RippleIMRPhenomD(f_ref=20) + +########################################### +########## Set up priors ################## +########################################### + +prior = [] + +# Mass prior M_c_min, M_c_max = 10.0, 80.0 -eta_min, eta_max = 0.2, 0.25 -# m_1_prior = UniformPrior(Mc_q_to_m1_m2(M_c_min, q_max)[0], Mc_q_to_m1_m2(M_c_max, q_min)[0], parameter_names=["m_1"]) -# m_2_prior = UniformPrior(Mc_q_to_m1_m2(M_c_min, q_min)[1], Mc_q_to_m1_m2(M_c_max, q_max)[1], parameter_names=["m_2"]) +q_min, q_max = 0.125, 1.0 Mc_prior = UniformPrior(M_c_min, M_c_max, parameter_names=["M_c"]) -eta_prior = UniformPrior(eta_min, eta_max, parameter_names=["eta"]) -s1z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) -s2z_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) +q_prior = UniformPrior(q_min, q_max, parameter_names=["q"]) + +prior = prior + [Mc_prior, q_prior] + +# Spin prior +s1_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) +s2_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) +iota_prior = SinePrior(parameter_names=["iota"]) + +prior = prior + [ + s1_prior, + s2_prior, + iota_prior, +] + +# Extrinsic prior dL_prior = PowerLawPrior(1.0, 2000.0, 2.0, parameter_names=["d_L"]) t_c_prior = UniformPrior(-0.05, 0.05, parameter_names=["t_c"]) phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) -iota_prior = SinePrior(parameter_names=["iota"]) psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) dec_prior = CosinePrior(parameter_names=["dec"]) -prior = CombinePrior( - [ - Mc_prior, - eta_prior, - s1z_prior, - s2z_prior, - dL_prior, - t_c_prior, - phase_c_prior, - iota_prior, - psi_prior, - ra_prior, - dec_prior, - ] -) +prior = prior + [ + dL_prior, + t_c_prior, + phase_c_prior, + psi_prior, + ra_prior, + dec_prior, +] + +prior = CombinePrior(prior) + +# Defining Transforms sample_transforms = [ - # ComponentMassesToChirpMassMassRatioTransform, - BoundToUnbound( - name_mapping=(["M_c"], ["M_c_unbounded"]), - original_lower_bound=M_c_min, - original_upper_bound=M_c_max, - ), - BoundToUnbound( - name_mapping=(["eta"], ["eta_unbounded"]), - original_lower_bound=eta_min, - original_upper_bound=eta_max, - ), - BoundToUnbound( - name_mapping=(["s1_z"], ["s1_z_unbounded"]), - original_lower_bound=-1.0, - original_upper_bound=1.0, - ), - BoundToUnbound( - name_mapping=(["s2_z"], ["s2_z_unbounded"]), - original_lower_bound=-1.0, - original_upper_bound=1.0, - ), - BoundToUnbound( - name_mapping=(["d_L"], ["d_L_unbounded"]), - original_lower_bound=1.0, - original_upper_bound=2000.0, - ), - BoundToUnbound( - name_mapping=(["t_c"], ["t_c_unbounded"]), - original_lower_bound=-0.05, - original_upper_bound=0.05, - ), - BoundToUnbound( - name_mapping=(["phase_c"], ["phase_c_unbounded"]), - original_lower_bound=0.0, - original_upper_bound=2 * jnp.pi, - ), - BoundToUnbound( - name_mapping=(["iota"], ["iota_unbounded"]), - original_lower_bound=0.0, - original_upper_bound=jnp.pi, - ), - BoundToUnbound( - name_mapping=(["psi"], ["psi_unbounded"]), - original_lower_bound=0.0, - original_upper_bound=jnp.pi, - ), + DistanceToSNRWeightedDistanceTransform(gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(gps_time=gps, ifo=ifos[0]), + GeocentricArrivalTimeToDetectorArrivalTimeTransform(tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, gps_time=gps, ifo=ifos[0]), SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), - BoundToUnbound( - name_mapping=(["zenith"], ["zenith_unbounded"]), - original_lower_bound=0.0, - original_upper_bound=jnp.pi, - ), - BoundToUnbound( - name_mapping=(["azimuth"], ["azimuth_unbounded"]), - original_lower_bound=0.0, - original_upper_bound=2 * jnp.pi, - ), + BoundToUnbound(name_mapping = (["M_c"], ["M_c_unbounded"]), original_lower_bound=M_c_min, original_upper_bound=M_c_max), + BoundToUnbound(name_mapping = (["q"], ["q_unbounded"]), original_lower_bound=q_min, original_upper_bound=q_max), + BoundToUnbound(name_mapping = (["s1_z"], ["s1_z_unbounded"]) , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = (["s2_z"], ["s2_z_unbounded"]) , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = (["iota"], ["iota_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["phase_det"], ["phase_det_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["psi"], ["psi_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["zenith"], ["zenith_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["azimuth"], ["azimuth_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), ] likelihood_transforms = [ - # ComponentMassesToChirpMassSymmetricMassRatioTransform, + MassRatioToSymmetricMassRatioTransform, ] + likelihood = TransientLikelihoodFD( - ifos, - waveform=RippleIMRPhenomD(), - trigger_time=gps, - duration=4, - post_trigger_duration=2, + [H1, L1], waveform=waveform, trigger_time=gps, duration=4, post_trigger_duration=2 ) -mass_matrix = jnp.eye(11) -mass_matrix = mass_matrix.at[1, 1].set(1e-3) -mass_matrix = mass_matrix.at[5, 5].set(1e-3) -local_sampler_arg = {"step_size": mass_matrix * 3e-3} +mass_matrix = jnp.eye(prior.n_dim) +# mass_matrix = mass_matrix.at[1, 1].set(1e-3) +# mass_matrix = mass_matrix.at[9, 9].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 1e-3} Adam_optimizer = optimization_Adam(n_steps=3000, learning_rate=0.01, noise_level=1) -n_epochs = 30 -n_loop_training = 20 -learning_rate = 1e-4 +import optax +n_epochs = 20 +n_loop_training = 100 +total_epochs = n_epochs * n_loop_training +start = total_epochs // 10 +learning_rate = optax.polynomial_schedule( + 1e-3, 1e-4, 4.0, total_epochs - start, transition_begin=start +) jim = Jim( likelihood, @@ -175,13 +156,12 @@ momentum=0.9, batch_size=30000, use_global=True, + keep_quantile=0.0, train_thinning=1, output_thinning=10, local_sampler_arg=local_sampler_arg, - strategies=[Adam_optimizer, "default"], - verbose=True, + # strategies=[Adam_optimizer,"default"], ) + jim.sample(jax.random.PRNGKey(42)) -# jim.get_samples() -# jim.print_summary() From 58496605c364728e338ddc9dcc2e18f64b572714 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Wed, 16 Oct 2024 17:12:01 -0400 Subject: [PATCH 242/248] update python requirement --- pyproject.toml | 2 +- uv.lock | 276 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 273 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 681fc007..a856dd39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "jimGW" version = "0.2.0" description = "Gravitatioanl wave data analysis tool in Jax" readme = "README.md" -requires-python = ">=3.11" +requires-python = ">=3.10" authors = [ {name = "Kaze W. K. Wong", email = "kazewong.physics@gmail.com"}, ] diff --git a/uv.lock b/uv.lock index 5b641d86..5217a861 100644 --- a/uv.lock +++ b/uv.lock @@ -1,7 +1,8 @@ version = 1 -requires-python = ">=3.11" +requires-python = ">=3.10" resolution-markers = [ - "python_full_version < '3.12'", + "python_full_version < '3.11'", + "python_full_version == '3.11.*'", "python_full_version == '3.12.*'", "python_full_version >= '3.13'", ] @@ -37,6 +38,13 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/13/25/adf5ee49f3990fc5cde050cce9c6f0a74a78af4eb18bd3aec8f1eecd1a26/astropy-6.1.4.tar.gz", hash = "sha256:361558e2b093a99bebe69f1fd47fac86a192607a4c16ed39ba0a800b2ab60c34", size = 7057481 } wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/c7/bb755b5013782633554a1ce6da0f03de4fbea1afc651f70d455f2a3aa4b1/astropy-6.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7954cdfea00445ed186431888ec8ab12d9e3adfdf316038c44009f57438e6389", size = 6527362 }, + { url = "https://files.pythonhosted.org/packages/5a/58/d028afa860a28ba911fc31ea47458847370f345683b75f74069ef1810599/astropy-6.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0298f0e0d57cc61915a4438d54e47b4517ff6c7c5144ae4679077cfb0826d0b", size = 6406624 }, + { url = "https://files.pythonhosted.org/packages/17/7b/70b6a61d44013eb68ee011696873eb647748e8d4ef33462a8243898fd83f/astropy-6.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2491dc9a8f9c046b808995b2e3dc9ad8349c83042729b26c7907b5f7fbc53fd4", size = 9888968 }, + { url = "https://files.pythonhosted.org/packages/a3/da/79080f115d416e3ff294e9ef183014e1c34f227404071d33ecdb92ed91d6/astropy-6.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22cc40cb137bbd777389d96871d84438537d01e1cd1dd1fc3dca374b4ece3200", size = 9958609 }, + { url = "https://files.pythonhosted.org/packages/fb/34/5d5f836655f910277a4f425bfb5b82435a4d3235e9b85896f02e23f2a824/astropy-6.1.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ec13a67ffac3d2b79ab2b725b16943133951cdd6833ab8941db798bc4de2da49", size = 9983955 }, + { url = "https://files.pythonhosted.org/packages/26/f2/96ddc395699959c7b9771d7beee15cf6f84ae17dba0b99e2f94cde1423ca/astropy-6.1.4-cp310-cp310-win32.whl", hash = "sha256:dbd7addf8c79d50e18eeef6c85aee7dd009f2e80756bc4d9b866592eeaca3577", size = 6270731 }, + { url = "https://files.pythonhosted.org/packages/94/17/9a27766b5c378949b04f3c010bd8ced5a269070e583dc6f30a0c0415332b/astropy-6.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:86e045e7ddfbfc500d015a9a6603be446c492c68fc019e69274b26e098aae50c", size = 6391292 }, { url = "https://files.pythonhosted.org/packages/52/2b/50e0fe0d750d87573d7bd0fd8f0d37210c6f04fc924c29995945ed5d0def/astropy-6.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:601d8a9a8d44f45064d2d8cc963cecf3949d3fbc10c9b6412c241cdc0c686b2c", size = 6527451 }, { url = "https://files.pythonhosted.org/packages/15/0b/2ca63201730c3cb1335b370ee8800ae6ba16fbfd6e47958771d7b667bdb1/astropy-6.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfc23b9e5e899f66f1377dc1116c247e6c7d0f92623ca115ad084a297414de03", size = 6405709 }, { url = "https://files.pythonhosted.org/packages/9b/10/e98e7e985ac50ff75c73a925ce3403aebc6c08307ace54e56981ccd3efe8/astropy-6.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52683072d106162ca124e09f2f132de58ca9756e04c25ebd45d56cbdd6feb8f7", size = 10146911 }, @@ -151,6 +159,18 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, @@ -193,6 +213,21 @@ version = "3.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } wheels = [ + { url = "https://files.pythonhosted.org/packages/69/8b/825cc84cf13a28bfbcba7c416ec22bf85a9584971be15b21dd8300c65b7f/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", size = 196363 }, + { url = "https://files.pythonhosted.org/packages/23/81/d7eef6a99e42c77f444fdd7bc894b0ceca6c3a95c51239e74a722039521c/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", size = 125639 }, + { url = "https://files.pythonhosted.org/packages/21/67/b4564d81f48042f520c948abac7079356e94b30cb8ffb22e747532cf469d/charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", size = 120451 }, + { url = "https://files.pythonhosted.org/packages/c2/72/12a7f0943dd71fb5b4e7b55c41327ac0a1663046a868ee4d0d8e9c369b85/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", size = 140041 }, + { url = "https://files.pythonhosted.org/packages/67/56/fa28c2c3e31217c4c52158537a2cf5d98a6c1e89d31faf476c89391cd16b/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", size = 150333 }, + { url = "https://files.pythonhosted.org/packages/f9/d2/466a9be1f32d89eb1554cf84073a5ed9262047acee1ab39cbaefc19635d2/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", size = 142921 }, + { url = "https://files.pythonhosted.org/packages/f8/01/344ec40cf5d85c1da3c1f57566c59e0c9b56bcc5566c08804a95a6cc8257/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", size = 144785 }, + { url = "https://files.pythonhosted.org/packages/73/8b/2102692cb6d7e9f03b9a33a710e0164cadfce312872e3efc7cfe22ed26b4/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", size = 146631 }, + { url = "https://files.pythonhosted.org/packages/d8/96/cc2c1b5d994119ce9f088a9a0c3ebd489d360a2eb058e2c8049f27092847/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", size = 140867 }, + { url = "https://files.pythonhosted.org/packages/c9/27/cde291783715b8ec30a61c810d0120411844bc4c23b50189b81188b273db/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", size = 149273 }, + { url = "https://files.pythonhosted.org/packages/3a/a4/8633b0fc1a2d1834d5393dafecce4a1cc56727bfd82b4dc18fc92f0d3cc3/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", size = 152437 }, + { url = "https://files.pythonhosted.org/packages/64/ea/69af161062166b5975ccbb0961fd2384853190c70786f288684490913bf5/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", size = 150087 }, + { url = "https://files.pythonhosted.org/packages/3b/fd/e60a9d9fd967f4ad5a92810138192f825d77b4fa2a557990fd575a47695b/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", size = 145142 }, + { url = "https://files.pythonhosted.org/packages/6d/02/8cb0988a1e49ac9ce2eed1e07b77ff118f2923e9ebd0ede41ba85f2dcb04/charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", size = 94701 }, + { url = "https://files.pythonhosted.org/packages/d6/20/f1d4670a8a723c46be695dff449d86d6092916f9e99c53051954ee33a1bc/charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", size = 102191 }, { url = "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", size = 193339 }, { url = "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", size = 124366 }, { url = "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", size = 118874 }, @@ -301,6 +336,16 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/f5/f6/31a8f28b4a2a4fa0e01085e542f3081ab0588eff8e589d39d775172c9792/contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4", size = 13464370 } wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/e0/be8dcc796cfdd96708933e0e2da99ba4bb8f9b2caa9d560a50f3f09a65f3/contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7", size = 265366 }, + { url = "https://files.pythonhosted.org/packages/50/d6/c953b400219443535d412fcbbc42e7a5e823291236bc0bb88936e3cc9317/contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42", size = 249226 }, + { url = "https://files.pythonhosted.org/packages/6f/b4/6fffdf213ffccc28483c524b9dad46bb78332851133b36ad354b856ddc7c/contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7", size = 308460 }, + { url = "https://files.pythonhosted.org/packages/cf/6c/118fc917b4050f0afe07179a6dcbe4f3f4ec69b94f36c9e128c4af480fb8/contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab", size = 347623 }, + { url = "https://files.pythonhosted.org/packages/f9/a4/30ff110a81bfe3abf7b9673284d21ddce8cc1278f6f77393c91199da4c90/contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589", size = 317761 }, + { url = "https://files.pythonhosted.org/packages/99/e6/d11966962b1aa515f5586d3907ad019f4b812c04e4546cc19ebf62b5178e/contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41", size = 322015 }, + { url = "https://files.pythonhosted.org/packages/4d/e3/182383743751d22b7b59c3c753277b6aee3637049197624f333dac5b4c80/contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d", size = 1262672 }, + { url = "https://files.pythonhosted.org/packages/78/53/974400c815b2e605f252c8fb9297e2204347d1755a5374354ee77b1ea259/contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223", size = 1321688 }, + { url = "https://files.pythonhosted.org/packages/52/29/99f849faed5593b2926a68a31882af98afbeac39c7fdf7de491d9c85ec6a/contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f", size = 171145 }, + { url = "https://files.pythonhosted.org/packages/a9/97/3f89bba79ff6ff2b07a3cbc40aa693c360d5efa90d66e914f0ff03b95ec7/contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b", size = 216019 }, { url = "https://files.pythonhosted.org/packages/b3/1f/9375917786cb39270b0ee6634536c0e22abf225825602688990d8f5c6c19/contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad", size = 266356 }, { url = "https://files.pythonhosted.org/packages/05/46/9256dd162ea52790c127cb58cfc3b9e3413a6e3478917d1f811d420772ec/contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49", size = 250915 }, { url = "https://files.pythonhosted.org/packages/e1/5d/3056c167fa4486900dfbd7e26a2fdc2338dc58eee36d490a0ed3ddda5ded/contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66", size = 310443 }, @@ -339,6 +384,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/6a/b5242c8cb32d87f6abf4f5e3044ca397cb1a76712e3fa2424772e3ff495f/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6", size = 323469 }, { url = "https://files.pythonhosted.org/packages/6f/a6/73e929d43028a9079aca4bde107494864d54f0d72d9db508a51ff0878593/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2", size = 1260894 }, { url = "https://files.pythonhosted.org/packages/2b/1e/1e726ba66eddf21c940821df8cf1a7d15cb165f0682d62161eaa5e93dae1/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927", size = 1314829 }, + { url = "https://files.pythonhosted.org/packages/d1/09/60e486dc2b64c94ed33e58dcfb6f808192c03dfc5574c016218b9b7680dc/contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c", size = 261886 }, + { url = "https://files.pythonhosted.org/packages/19/20/b57f9f7174fcd439a7789fb47d764974ab646fa34d1790551de386457a8e/contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779", size = 311008 }, + { url = "https://files.pythonhosted.org/packages/74/fc/5040d42623a1845d4f17a418e590fd7a79ae8cb2bad2b2f83de63c3bdca4/contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4", size = 215690 }, ] [[package]] @@ -380,6 +428,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/4c/ab0b9407d5247576290b4fd8abd06b7f51bd414f04eef0f2800675512d61/cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", size = 4082606 }, { url = "https://files.pythonhosted.org/packages/05/36/e532a671998d6fcfdb9122da16434347a58a6bae9465e527e450e0bc60a5/cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", size = 2617948 }, { url = "https://files.pythonhosted.org/packages/b3/c6/c09cee6968add5ff868525c3815e5dccc0e3c6e89eec58dc9135d3c40e88/cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", size = 3070445 }, + { url = "https://files.pythonhosted.org/packages/18/23/4175dcd935e1649865e1af7bd0b827cc9d9769a586dcc84f7cbe96839086/cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034", size = 3152694 }, + { url = "https://files.pythonhosted.org/packages/ea/45/967da50269954b993d4484bf85026c7377bd551651ebdabba94905972556/cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d", size = 3713077 }, + { url = "https://files.pythonhosted.org/packages/df/e6/ccd29a1f9a6b71294e1e9f530c4d779d5dd37c8bb736c05d5fb6d98a971b/cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289", size = 3915597 }, + { url = "https://files.pythonhosted.org/packages/a2/80/fb7d668f1be5e4443b7ac191f68390be24f7c2ebd36011741f62c7645eb2/cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84", size = 2989208 }, ] [[package]] @@ -412,6 +464,10 @@ version = "1.8.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6d/00/5a8b5dc8f52617c5e41845e26290ebea1ba06377cc08155b6d245c27b386/debugpy-1.8.7.zip", hash = "sha256:18b8f731ed3e2e1df8e9cdaa23fb1fc9c24e570cd0081625308ec51c82efe42e", size = 4957835 } wheels = [ + { url = "https://files.pythonhosted.org/packages/46/50/1850a5a0cab6f65a21e452166ec60bac5f8a995184d17e18bb9dc3789c72/debugpy-1.8.7-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95fe04a573b8b22896c404365e03f4eda0ce0ba135b7667a1e57bd079793b96b", size = 2090182 }, + { url = "https://files.pythonhosted.org/packages/87/51/ef4d5c55c06689b377678bdee870e3df8eb2a3d9cf0e618b4d7255413c8a/debugpy-1.8.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:628a11f4b295ffb4141d8242a9bb52b77ad4a63a2ad19217a93be0f77f2c28c9", size = 3547569 }, + { url = "https://files.pythonhosted.org/packages/eb/df/a4ea1f95022f93522b59b71ec42d6703abe3e0bee753070118816555fee9/debugpy-1.8.7-cp310-cp310-win32.whl", hash = "sha256:85ce9c1d0eebf622f86cc68618ad64bf66c4fc3197d88f74bb695a416837dd55", size = 5153144 }, + { url = "https://files.pythonhosted.org/packages/47/f7/912408b69e83659bd62fa29ebb7984efe81aed4f5e08bfe10e31a1dc3c3a/debugpy-1.8.7-cp310-cp310-win_amd64.whl", hash = "sha256:29e1571c276d643757ea126d014abda081eb5ea4c851628b33de0c2b6245b037", size = 5185605 }, { url = "https://files.pythonhosted.org/packages/f6/0a/4a4516ef4c07891542cb25620085507cab3c6b23a42b5630c17788fff83e/debugpy-1.8.7-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:caf528ff9e7308b74a1749c183d6808ffbedbb9fb6af78b033c28974d9b8831f", size = 2204794 }, { url = "https://files.pythonhosted.org/packages/46/6f/2bb0bba20b8b74b7c341379dd99275cf6aa7722c1948fa99728716aad1b9/debugpy-1.8.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cba1d078cf2e1e0b8402e6bda528bf8fda7ccd158c3dba6c012b7897747c41a0", size = 3122160 }, { url = "https://files.pythonhosted.org/packages/c0/ce/833351375cef971f0caa63fa82adf3f6949ad85410813026a4a436083a71/debugpy-1.8.7-cp311-cp311-win32.whl", hash = "sha256:171899588bcd412151e593bd40d9907133a7622cd6ecdbdb75f89d1551df13c2", size = 5078675 }, @@ -529,6 +585,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/24/f8/a58890d615e548de5003a38636d8986e226805bbcb27aab206e1c8430f57/evosax-0.1.6-py3-none-any.whl", hash = "sha256:195145d68c67f5f1de9c580b6f248f624ac9d089de628e67665f7b3616d638e4", size = 240393 }, ] +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + [[package]] name = "executing" version = "2.1.0" @@ -554,7 +619,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jax" }, { name = "msgpack" }, - { name = "numpy" }, + { name = "numpy", marker = "python_full_version >= '3.11'" }, { name = "optax" }, { name = "orbax-checkpoint" }, { name = "pyyaml" }, @@ -591,6 +656,14 @@ version = "4.54.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/11/1d/70b58e342e129f9c0ce030029fb4b2b0670084bbbfe1121d008f6a1e361c/fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285", size = 3463867 } wheels = [ + { url = "https://files.pythonhosted.org/packages/db/f9/285c9a2d0e86b9bf2babfe19bec00502361fda56cea144d6a269ab9a32e6/fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2", size = 2766970 }, + { url = "https://files.pythonhosted.org/packages/2f/9a/9d899e7ae55b0dd30632e6ca36c0f5fa1205b1b096ec171c9be903673058/fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882", size = 2254639 }, + { url = "https://files.pythonhosted.org/packages/16/6f/b99e0c347732fb003077a2cff38c26f381969b74329aa5597e344d540fe1/fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10", size = 4574346 }, + { url = "https://files.pythonhosted.org/packages/e5/12/9a45294a7c4520cc32936edd15df1d5c24af701d2f5f51070a9a43d7664b/fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e", size = 4630045 }, + { url = "https://files.pythonhosted.org/packages/64/52/ba4f00eb6003e4089264cd9ce126cddec2b39c78f1ab01be9dc389a197ca/fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e", size = 4569527 }, + { url = "https://files.pythonhosted.org/packages/41/ff/85f93a14c8acf978f332508f980dcaff5ed5f0cf284371eb101a78f0b1f4/fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44", size = 4741677 }, + { url = "https://files.pythonhosted.org/packages/6f/f0/06ea7d9f8b7b6d4758a50271517db04039c4c6da8fa0475d417e005624d0/fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02", size = 2166797 }, + { url = "https://files.pythonhosted.org/packages/71/73/545c817e34b8c34585291951722e1a5ae579380deb009576d9d244b13ab0/fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d", size = 2210552 }, { url = "https://files.pythonhosted.org/packages/aa/2c/8b5d82fe2d9c7f260fb73121418f5e07d4e38c329ea3886a5b0e55586113/fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20", size = 2768112 }, { url = "https://files.pythonhosted.org/packages/37/2e/f94118b92f7b6a9ec93840101b64bfdd09f295b266133857e8e852a5c35c/fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2", size = 2254739 }, { url = "https://files.pythonhosted.org/packages/45/4b/8a32f56a13e78256192f77d6b65583c43538c7955f5420887bb574b91ddf/fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7", size = 4879772 }, @@ -708,6 +781,11 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cc/0c/5c2b0a88158682aeafb10c1c2b735df5bc31f165bfe192f2ee9f2a23b5f1/h5py-3.12.1.tar.gz", hash = "sha256:326d70b53d31baa61f00b8aa5f95c2fcb9621a3ee8365d770c551a13dbbcbfdf", size = 411457 } wheels = [ + { url = "https://files.pythonhosted.org/packages/df/7d/b21045fbb004ad8bb6fb3be4e6ca903841722706f7130b9bba31ef2f88e3/h5py-3.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f0f1a382cbf494679c07b4371f90c70391dedb027d517ac94fa2c05299dacda", size = 3402133 }, + { url = "https://files.pythonhosted.org/packages/29/a7/3c2a33fba1da64a0846744726fd067a92fb8abb887875a0dd8e3bac8b45d/h5py-3.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb65f619dfbdd15e662423e8d257780f9a66677eae5b4b3fc9dca70b5fd2d2a3", size = 2866436 }, + { url = "https://files.pythonhosted.org/packages/1e/d0/4bf67c3937a2437c20844165766ddd1a1817ae6b9544c3743050d8e0f403/h5py-3.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b15d8dbd912c97541312c0e07438864d27dbca857c5ad634de68110c6beb1c2", size = 5168596 }, + { url = "https://files.pythonhosted.org/packages/85/bc/e76f4b2096e0859225f5441d1b7f5e2041fffa19fc2c16756c67078417aa/h5py-3.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59685fe40d8c1fbbee088c88cd4da415a2f8bee5c270337dc5a1c4aa634e3307", size = 5341537 }, + { url = "https://files.pythonhosted.org/packages/99/bd/fb8ed45308bb97e04c02bd7aed324ba11e6a4bf9ed73967ca2a168e9cf92/h5py-3.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:577d618d6b6dea3da07d13cc903ef9634cde5596b13e832476dd861aaf651f3e", size = 2990575 }, { url = "https://files.pythonhosted.org/packages/33/61/c463dc5fc02fbe019566d067a9d18746cd3c664f29c9b8b3c3f9ed025365/h5py-3.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ccd9006d92232727d23f784795191bfd02294a4f2ba68708825cb1da39511a93", size = 3410828 }, { url = "https://files.pythonhosted.org/packages/95/9d/eb91a9076aa998bb2179d6b1788055ea09cdf9d6619cd967f1d3321ed056/h5py-3.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad8a76557880aed5234cfe7279805f4ab5ce16b17954606cca90d578d3e713ef", size = 2872586 }, { url = "https://files.pythonhosted.org/packages/b0/62/e2b1f9723ff713e3bd3c16dfeceec7017eadc21ef063d8b7080c0fcdc58a/h5py-3.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1473348139b885393125126258ae2d70753ef7e9cec8e7848434f385ae72069e", size = 5273038 }, @@ -798,6 +876,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "decorator" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "jedi" }, { name = "matplotlib-inline" }, { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, @@ -838,6 +917,11 @@ dependencies = [ { name = "scipy" }, ] wheels = [ + { url = "https://files.pythonhosted.org/packages/24/31/2e254fe2fc23201775a7d0ccd1bcde892cfa349eb805744b81b15e0dcf74/jaxlib-0.4.34-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:b7a212a3cb5c6acc201c32ae4f4b5f5a9ac09457fbb77ba8db5ce7e7d4adc214", size = 87399257 }, + { url = "https://files.pythonhosted.org/packages/1e/67/6a344c357caad33e84b871925cd043b4218fc13a427266d1a1dedcb1c095/jaxlib-0.4.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:45d719a2ce0ebf21255a277b71d756f3609b7b5be70cddc5d88fd58c35219de0", size = 67617952 }, + { url = "https://files.pythonhosted.org/packages/dd/ea/12c836126419ca80248228f2236831617eedb1e3640c34c942606f33bb08/jaxlib-0.4.34-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:3e60bc826933082e99b19b87c21818a8d26fcdb01f418d47cedff554746fd6cc", size = 69391770 }, + { url = "https://files.pythonhosted.org/packages/e4/b0/a5bd34643c070e50829beec217189eab1acdfea334df1f9ddb4e5f8bec0f/jaxlib-0.4.34-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:d840e64b85f8865404d6d225b9bb340e158df1457152a361b05680e24792b232", size = 86094116 }, + { url = "https://files.pythonhosted.org/packages/d8/c9/35a4233fe74ddd5aabe89aac1b3992b0e463982564252d21fd263d4d9992/jaxlib-0.4.34-cp310-cp310-win_amd64.whl", hash = "sha256:b0001c8f0e2b1c7bc99e4f314b524a340d25653505c1a1484d4041a9d3617f6f", size = 55206389 }, { url = "https://files.pythonhosted.org/packages/bf/14/00a3385532d72ab51bd8e9f8c3e19a2e257667955565e9fc10236771dd06/jaxlib-0.4.34-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8ee3f93836e53c86556ccd9449a4ea43516ee05184d031a71dd692e81259f7d9", size = 87420889 }, { url = "https://files.pythonhosted.org/packages/66/78/d1535ee73fe505dc6c8831c19c4846afdce7df5acefb9f8ee885aa73d700/jaxlib-0.4.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c9d3adcae43a33aad4332be9c2aedc5ef751d1e755f917a5afb30c7872eacaa8", size = 67635880 }, { url = "https://files.pythonhosted.org/packages/aa/06/3e09e794acf308e170905d732eca0d041449503c47505cc22e8ef78a989d/jaxlib-0.4.34-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:571ef03259835458111596a71a2f4a6fabf4ec34595df4cea555035362ac5bf0", size = 69421901 }, @@ -915,7 +999,6 @@ requires-dist = [ { name = "gwpy", specifier = ">=3.0.10" }, { name = "jax", specifier = ">=0.4.34" }, { name = "jaxtyping", specifier = ">=0.2.34" }, - { name = "jimgw", marker = "extra == 'docs'" }, { name = "mkdocs", marker = "extra == 'docs'" }, { name = "mkdocs-gen-files", marker = "extra == 'docs'" }, { name = "mkdocs-jupyter", marker = "extra == 'docs'" }, @@ -1015,6 +1098,7 @@ dependencies = [ { name = "nbformat" }, { name = "packaging" }, { name = "pyyaml" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/ba/81097573072b165772b71298c339d5da46dfeec53c1c354ce282109967ea/jupytext-1.16.4.tar.gz", hash = "sha256:28e33f46f2ce7a41fb9d677a4a2c95327285579b64ca104437c4b9eb1e4174e9", size = 3724368 } wheels = [ @@ -1027,6 +1111,22 @@ version = "1.4.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286 } wheels = [ + { url = "https://files.pythonhosted.org/packages/97/14/fc943dd65268a96347472b4fbe5dcc2f6f55034516f80576cd0dd3a8930f/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6", size = 122440 }, + { url = "https://files.pythonhosted.org/packages/1e/46/e68fed66236b69dd02fcdb506218c05ac0e39745d696d22709498896875d/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17", size = 65758 }, + { url = "https://files.pythonhosted.org/packages/ef/fa/65de49c85838681fc9cb05de2a68067a683717321e01ddafb5b8024286f0/kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9", size = 64311 }, + { url = "https://files.pythonhosted.org/packages/42/9c/cc8d90f6ef550f65443bad5872ffa68f3dee36de4974768628bea7c14979/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9", size = 1637109 }, + { url = "https://files.pythonhosted.org/packages/55/91/0a57ce324caf2ff5403edab71c508dd8f648094b18cfbb4c8cc0fde4a6ac/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c", size = 1617814 }, + { url = "https://files.pythonhosted.org/packages/12/5d/c36140313f2510e20207708adf36ae4919416d697ee0236b0ddfb6fd1050/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599", size = 1400881 }, + { url = "https://files.pythonhosted.org/packages/56/d0/786e524f9ed648324a466ca8df86298780ef2b29c25313d9a4f16992d3cf/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05", size = 1512972 }, + { url = "https://files.pythonhosted.org/packages/67/5a/77851f2f201e6141d63c10a0708e996a1363efaf9e1609ad0441b343763b/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407", size = 1444787 }, + { url = "https://files.pythonhosted.org/packages/06/5f/1f5eaab84355885e224a6fc8d73089e8713dc7e91c121f00b9a1c58a2195/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278", size = 2199212 }, + { url = "https://files.pythonhosted.org/packages/b5/28/9152a3bfe976a0ae21d445415defc9d1cd8614b2910b7614b30b27a47270/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5", size = 2346399 }, + { url = "https://files.pythonhosted.org/packages/26/f6/453d1904c52ac3b400f4d5e240ac5fec25263716723e44be65f4d7149d13/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad", size = 2308688 }, + { url = "https://files.pythonhosted.org/packages/5a/9a/d4968499441b9ae187e81745e3277a8b4d7c60840a52dc9d535a7909fac3/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895", size = 2445493 }, + { url = "https://files.pythonhosted.org/packages/07/c9/032267192e7828520dacb64dfdb1d74f292765f179e467c1cba97687f17d/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3", size = 2262191 }, + { url = "https://files.pythonhosted.org/packages/6c/ad/db0aedb638a58b2951da46ddaeecf204be8b4f5454df020d850c7fa8dca8/kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc", size = 46644 }, + { url = "https://files.pythonhosted.org/packages/12/ca/d0f7b7ffbb0be1e7c2258b53554efec1fd652921f10d7d85045aff93ab61/kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c", size = 55877 }, + { url = "https://files.pythonhosted.org/packages/97/6c/cfcc128672f47a3e3c0d918ecb67830600078b025bfc32d858f2e2d5c6a4/kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a", size = 48347 }, { url = "https://files.pythonhosted.org/packages/e9/44/77429fa0a58f941d6e1c58da9efe08597d2e86bf2b2cce6626834f49d07b/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54", size = 122442 }, { url = "https://files.pythonhosted.org/packages/e5/20/8c75caed8f2462d63c7fd65e16c832b8f76cda331ac9e615e914ee80bac9/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95", size = 65762 }, { url = "https://files.pythonhosted.org/packages/f4/98/fe010f15dc7230f45bc4cf367b012d651367fd203caaa992fd1f5963560e/kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935", size = 64319 }, @@ -1075,6 +1175,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364 }, { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887 }, { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530 }, + { url = "https://files.pythonhosted.org/packages/ac/59/741b79775d67ab67ced9bb38552da688c0305c16e7ee24bba7a2be253fb7/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643", size = 59491 }, + { url = "https://files.pythonhosted.org/packages/58/cc/fb239294c29a5656e99e3527f7369b174dd9cc7c3ef2dea7cb3c54a8737b/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706", size = 57648 }, + { url = "https://files.pythonhosted.org/packages/3b/ef/2f009ac1f7aab9f81efb2d837301d255279d618d27b6015780115ac64bdd/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6", size = 84257 }, + { url = "https://files.pythonhosted.org/packages/81/e1/c64f50987f85b68b1c52b464bb5bf73e71570c0f7782d626d1eb283ad620/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2", size = 80906 }, + { url = "https://files.pythonhosted.org/packages/fd/71/1687c5c0a0be2cee39a5c9c389e546f9c6e215e46b691d00d9f646892083/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4", size = 79951 }, + { url = "https://files.pythonhosted.org/packages/ea/8b/d7497df4a1cae9367adf21665dd1f896c2a7aeb8769ad77b662c5e2bcce7/kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a", size = 55715 }, ] [[package]] @@ -1086,6 +1192,7 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/81/60/8de5c89e4e5fc760649cee0c773418ecc920c3dae21ac5656fd3e5e21a9d/ligo-segments-1.4.0.tar.gz", hash = "sha256:e072a844713c5b02efdcaf5bfe4c3a8cd9ef225b08cfd3202a4e185e0f71f5dc", size = 51015 } wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/a5/665dc05c2669aa95e2dfcb2b06748315287d3696abf1374dd6e7f6c3a175/ligo_segments-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:f8a0fb3ec6bef7effb80fb2f4f4ffaac02afa77db41a0c42fce5e969201e621b", size = 50007 }, { url = "https://files.pythonhosted.org/packages/3a/db/b2de07a032a766d34480e93e6d4ecda5a4d651bb6a5e3286c2624d6c181d/ligo_segments-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:da82ae2b839bad0027e0a492b3673344e62360747d41194a265563e6c2903c7d", size = 50007 }, { url = "https://files.pythonhosted.org/packages/b5/37/c962f26408ce45271a5a3aaa918f7beae74a8e8a6f00bbe5fdf77fd778ca/ligo_segments-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:601be6d92e52bdebbb5a82b608ed1ceb781a0ea86b3e5333d61af77575b32664", size = 50555 }, ] @@ -1126,6 +1233,16 @@ version = "3.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b4/d2/38ff920762f2247c3af5cbbbbc40756f575d9692d381d7c520f45deb9b8f/markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344", size = 20249 } wheels = [ + { url = "https://files.pythonhosted.org/packages/43/a2/0482d1a157f5f10f72fc4fe8c3be9ffa3651c1f7a12b60a3ab71b2635e13/MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1", size = 14391 }, + { url = "https://files.pythonhosted.org/packages/3b/25/5ea6500d200fd2dc3ea25c765f69dea0a1a8d42ec80a38cd896ad47cb85d/MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a", size = 12414 }, + { url = "https://files.pythonhosted.org/packages/92/41/cf5397dd6bb18895d148aa402cafa71018f2ffc5f6e9d6e90d85b523c741/MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589", size = 21787 }, + { url = "https://files.pythonhosted.org/packages/2e/0d/5d91ef2b4f30afa87483a3a7c108c777d144b1c42d7113459296a8a2bfa0/MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170", size = 20954 }, + { url = "https://files.pythonhosted.org/packages/f6/de/12a4110c2c7c7b502fe0e6f911367726dbb7a37e03e207495135d064bb48/MarkupSafe-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca", size = 21086 }, + { url = "https://files.pythonhosted.org/packages/96/55/59389babc6e8ed206849a9958de9da7c23f3a75d294f46e99624fa38fb79/MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea", size = 21685 }, + { url = "https://files.pythonhosted.org/packages/3d/cb/cbad5f093e12cd79ceea3e2957ba5bd4c2706810f333d0a3422ab2aef358/MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6", size = 21348 }, + { url = "https://files.pythonhosted.org/packages/8e/70/e19c4f39d68a52406012ee118667b57efb0bbe6e950be21187cd7a1b4b80/MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25", size = 21098 }, + { url = "https://files.pythonhosted.org/packages/30/95/ca809c01624428d427e9b3a4500f9068eca941e0c520328954ce84ad966a/MarkupSafe-3.0.1-cp310-cp310-win32.whl", hash = "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97", size = 15075 }, + { url = "https://files.pythonhosted.org/packages/23/41/decb99ab07793656821a86f827a394700ce28402ebb02dc6d003210d9859/MarkupSafe-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9", size = 15535 }, { url = "https://files.pythonhosted.org/packages/ce/af/2f5d88a7fc7226bd34c6e15f6061246ad8cff979da9f19d11bdd0addd8e2/MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad", size = 14387 }, { url = "https://files.pythonhosted.org/packages/8d/43/fd588ef5d192308c5e05974bac659bf6ae29c202b7ea2c4194bcf01eacee/MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583", size = 12410 }, { url = "https://files.pythonhosted.org/packages/58/26/78f161d602fb03804118905e5faacafc0ec592bbad71aaee62537529813a/MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7", size = 24006 }, @@ -1185,6 +1302,12 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/9e/d8/3d7f706c69e024d4287c1110d74f7dabac91d9843b99eadc90de9efc8869/matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92", size = 36088381 } wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9d/84eeb82ecdd3ba71b12dd6ab5c820c5cc1e868003ecb3717d41b589ec02a/matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb", size = 7893310 }, + { url = "https://files.pythonhosted.org/packages/36/98/cbacbd30241369d099f9c13a2b6bc3b7068d85214f5b5795e583ac3d8aba/matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4", size = 7764089 }, + { url = "https://files.pythonhosted.org/packages/a8/a0/917f3c6d3a8774a3a1502d9f3dfc1456e07c1fa0c211a23b75a69e154180/matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64", size = 8192377 }, + { url = "https://files.pythonhosted.org/packages/8d/9d/d06860390f9d154fa884f1740a5456378fb153ff57443c91a4a32bab7092/matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66", size = 8303983 }, + { url = "https://files.pythonhosted.org/packages/9e/a7/c0e848ed7de0766c605af62d8097472a37f1a81d93e9afe94faa5890f24d/matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a", size = 9083318 }, + { url = "https://files.pythonhosted.org/packages/09/6c/0fa50c001340a45cde44853c116d6551aea741e59a7261c38f473b53553b/matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae", size = 7819628 }, { url = "https://files.pythonhosted.org/packages/77/c2/f9d7fe80a8fcce9bb128d1381c6fe41a8d286d7e18395e273002e8e0fa34/matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772", size = 7902925 }, { url = "https://files.pythonhosted.org/packages/28/ba/8be09886eb56ac04a218a1dc3fa728a5c4cac60b019b4f1687885166da00/matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41", size = 7773193 }, { url = "https://files.pythonhosted.org/packages/e6/9a/5991972a560db3ab621312a7ca5efec339ae2122f25901c0846865c4b72f/matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f", size = 8202378 }, @@ -1432,6 +1555,10 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ab/79/717c5e22ad25d63ce3acdfe8ff8d64bdedec18914256c59b838218708b16/ml_dtypes-0.5.0.tar.gz", hash = "sha256:3e7d3a380fe73a63c884f06136f8baa7a5249cc8e9fdec677997dd78549f8128", size = 699367 } wheels = [ + { url = "https://files.pythonhosted.org/packages/83/50/0a2048895a764b138638b5e7a62436545eb206948a5e6f77d9d5a4b02479/ml_dtypes-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c32138975797e681eb175996d64356bcfa124bdbb6a70460b9768c2b35a6fa4", size = 736793 }, + { url = "https://files.pythonhosted.org/packages/0b/b1/95e7995f031bb3890884ddb22e331f24c49b0a4a8f6c448ff5984c86012e/ml_dtypes-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab046f2ff789b1f11b2491909682c5d089934835f9a760fafc180e47dcb676b8", size = 4387416 }, + { url = "https://files.pythonhosted.org/packages/9a/5b/d47361f882ff2ae27d764f314d18706c69859da60a6c78e6c9e81714c792/ml_dtypes-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7a9152f5876fef565516aa5dd1dccd6fc298a5891b2467973905103eb5c7856", size = 4496271 }, + { url = "https://files.pythonhosted.org/packages/e6/0c/a89f5c0fe9e48ed6e7e27d53e045711ee3d5b850bece5ee22fb0fb24b281/ml_dtypes-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:968fede07d1f9b926a63df97d25ac656cac1a57ebd33701734eaf704bc55d8d8", size = 211915 }, { url = "https://files.pythonhosted.org/packages/fe/29/8968fd7ee026c0d04c553fb1ce1cd67f9da668cd567d62c0cdc995ce989e/ml_dtypes-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60275f2b51b56834e840c4809fca840565f9bf8e9a73f6d8c94f5b5935701215", size = 736792 }, { url = "https://files.pythonhosted.org/packages/19/93/14896596644dad2e041ac5ca7237e6233c484f7defa186ff88b18ee6110b/ml_dtypes-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76942f6aeb5c40766d5ea62386daa4148e6a54322aaf5b53eae9e7553240222f", size = 4392038 }, { url = "https://files.pythonhosted.org/packages/89/65/ffdbf3489b0ba2213674ea347fad3a11747be64d2d23d888f9e5abe80a18/ml_dtypes-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e7534392682c3098bc7341648c650864207169c654aed83143d7a19c67ae06f", size = 4499448 }, @@ -1452,6 +1579,17 @@ version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cb/d0/7555686ae7ff5731205df1012ede15dd9d927f6227ea151e901c7406af4f/msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e", size = 167260 } wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/f9/a892a6038c861fa849b11a2bb0502c07bc698ab6ea53359e5771397d883b/msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd", size = 150428 }, + { url = "https://files.pythonhosted.org/packages/df/7a/d174cc6a3b6bb85556e6a046d3193294a92f9a8e583cdbd46dc8a1d7e7f4/msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d", size = 84131 }, + { url = "https://files.pythonhosted.org/packages/08/52/bf4fbf72f897a23a56b822997a72c16de07d8d56d7bf273242f884055682/msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5", size = 81215 }, + { url = "https://files.pythonhosted.org/packages/02/95/dc0044b439b518236aaf012da4677c1b8183ce388411ad1b1e63c32d8979/msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5", size = 371229 }, + { url = "https://files.pythonhosted.org/packages/ff/75/09081792db60470bef19d9c2be89f024d366b1e1973c197bb59e6aabc647/msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e", size = 378034 }, + { url = "https://files.pythonhosted.org/packages/32/d3/c152e0c55fead87dd948d4b29879b0f14feeeec92ef1fd2ec21b107c3f49/msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b", size = 363070 }, + { url = "https://files.pythonhosted.org/packages/d9/2c/82e73506dd55f9e43ac8aa007c9dd088c6f0de2aa19e8f7330e6a65879fc/msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f", size = 359863 }, + { url = "https://files.pythonhosted.org/packages/cb/a0/3d093b248837094220e1edc9ec4337de3443b1cfeeb6e0896af8ccc4cc7a/msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68", size = 368166 }, + { url = "https://files.pythonhosted.org/packages/e4/13/7646f14f06838b406cf5a6ddbb7e8dc78b4996d891ab3b93c33d1ccc8678/msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b", size = 370105 }, + { url = "https://files.pythonhosted.org/packages/67/fa/dbbd2443e4578e165192dabbc6a22c0812cda2649261b1264ff515f19f15/msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044", size = 68513 }, + { url = "https://files.pythonhosted.org/packages/24/ce/c2c8fbf0ded750cb63cbcbb61bc1f2dfd69e16dca30a8af8ba80ec182dcd/msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f", size = 74687 }, { url = "https://files.pythonhosted.org/packages/b7/5e/a4c7154ba65d93be91f2f1e55f90e76c5f91ccadc7efc4341e6f04c8647f/msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7", size = 150803 }, { url = "https://files.pythonhosted.org/packages/60/c2/687684164698f1d51c41778c838d854965dd284a4b9d3a44beba9265c931/msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa", size = 84343 }, { url = "https://files.pythonhosted.org/packages/42/ae/d3adea9bb4a1342763556078b5765e666f8fdf242e00f3f6657380920972/msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701", size = 81408 }, @@ -1567,6 +1705,16 @@ version = "2.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4b/d1/8a730ea07f4a37d94f9172f4ce1d81064b7a64766b460378be278952de75/numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c", size = 18878063 } wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/a2/40a76d357f168e9f9f06d6cc2c8e22dd5fb2bfbe63fe2c433057258c145a/numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee", size = 21150947 }, + { url = "https://files.pythonhosted.org/packages/b5/d0/ba271ea9108d7278d3889a7eb38d77370a88713fb94339964e71ac184d4a/numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884", size = 13758184 }, + { url = "https://files.pythonhosted.org/packages/7c/b9/5c6507439cd756201010f7937bf90712c2469052ae094584af14557dd64f/numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648", size = 5354091 }, + { url = "https://files.pythonhosted.org/packages/60/21/7938cf724d9e84e45fb886f3fc794ab431d71facfebc261e3e9f19f3233a/numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d", size = 6887169 }, + { url = "https://files.pythonhosted.org/packages/09/8d/42a124657f5d31902fca73921b25a0d022cead2b32ce7e6975762cd2995a/numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86", size = 13888165 }, + { url = "https://files.pythonhosted.org/packages/fb/25/ba023652a39a2c127200e85aed975fc6119b421e2c348e5d0171e2046edb/numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7", size = 16326954 }, + { url = "https://files.pythonhosted.org/packages/34/58/23e6b07fad492b7c47cf09cd8bad6983658f0f925b6c535fd008e3e86274/numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03", size = 16702916 }, + { url = "https://files.pythonhosted.org/packages/91/24/37b5cf2dc7d385ac97f7b7fe50cba312abb70a2a5eac74c23af028811f73/numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466", size = 14384372 }, + { url = "https://files.pythonhosted.org/packages/ea/ec/0f6d471058a01d1a05a50d2793898de1549280fa715a8537987ee866b5d9/numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb", size = 6535361 }, + { url = "https://files.pythonhosted.org/packages/c2/3d/293cc5927f916a7bc6bf74da8f6defab63d1b13f0959d7e21878ad8a20d8/numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2", size = 12865501 }, { url = "https://files.pythonhosted.org/packages/aa/9c/9a6ec3ae89cd0648d419781284308f2956d2a61d932b5ac9682c956a171b/numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe", size = 21154845 }, { url = "https://files.pythonhosted.org/packages/02/69/9f05c4ecc75fabf297b17743996371b4c3dfc4d92e15c5c38d8bb3db8d74/numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1", size = 13789409 }, { url = "https://files.pythonhosted.org/packages/34/4e/f95c99217bf77bbfaaf660d693c10bd0dc03b6032d19316d316088c9e479/numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f", size = 5352097 }, @@ -1605,6 +1753,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/8e/fc1fdd83a55476765329ac2913321c4aed5b082a7915095628c4ca30ea72/numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35", size = 16021174 }, { url = "https://files.pythonhosted.org/packages/2a/b6/a790742aa88067adb4bd6c89a946778c1417d4deaeafce3ca928f26d4c52/numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62", size = 16400117 }, { url = "https://files.pythonhosted.org/packages/48/6f/129e3c17e3befe7fefdeaa6890f4c4df3f3cf0831aa053802c3862da67aa/numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a", size = 14066202 }, + { url = "https://files.pythonhosted.org/packages/73/c9/3e1d6bbe6d3d2e2c5a9483b24b2f29a229b323f62054278a3bba7fee11e5/numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952", size = 20981945 }, + { url = "https://files.pythonhosted.org/packages/6e/62/989c4988bde1a8e08117fccc3bab73d2886421fb98cde597168714f3c54e/numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5", size = 6750558 }, + { url = "https://files.pythonhosted.org/packages/53/b1/00ef9f30975f1312a53257f68e57b4513d14d537e03d507e2773a684b1e8/numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7", size = 16141552 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/0c04903b48dfea6be1d7b47ba70f98709fb7198fd970784a1400c391d522/numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e", size = 12789924 }, ] [[package]] @@ -1718,6 +1870,17 @@ version = "11.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a5/26/0d95c04c868f6bdb0c447e3ee2de5564411845e36a858cfd63766bc7b563/pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", size = 46737780 } wheels = [ + { url = "https://files.pythonhosted.org/packages/98/fb/a6ce6836bd7fd93fbf9144bf54789e02babc27403b50a9e1583ee877d6da/pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947", size = 3154708 }, + { url = "https://files.pythonhosted.org/packages/6a/1d/1f51e6e912d8ff316bb3935a8cda617c801783e0b998bf7a894e91d3bd4c/pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba", size = 2979223 }, + { url = "https://files.pythonhosted.org/packages/90/83/e2077b0192ca8a9ef794dbb74700c7e48384706467067976c2a95a0f40a1/pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086", size = 4183167 }, + { url = "https://files.pythonhosted.org/packages/0e/74/467af0146970a98349cdf39e9b79a6cc8a2e7558f2c01c28a7b6b85c5bda/pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9", size = 4283912 }, + { url = "https://files.pythonhosted.org/packages/85/b1/d95d4f7ca3a6c1ae120959605875a31a3c209c4e50f0029dc1a87566cf46/pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488", size = 4195815 }, + { url = "https://files.pythonhosted.org/packages/41/c3/94f33af0762ed76b5a237c5797e088aa57f2b7fa8ee7932d399087be66a8/pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f", size = 4366117 }, + { url = "https://files.pythonhosted.org/packages/ba/3c/443e7ef01f597497268899e1cca95c0de947c9bbf77a8f18b3c126681e5d/pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb", size = 4278607 }, + { url = "https://files.pythonhosted.org/packages/26/95/1495304448b0081e60c0c5d63f928ef48bb290acee7385804426fa395a21/pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97", size = 4410685 }, + { url = "https://files.pythonhosted.org/packages/45/da/861e1df971ef0de9870720cb309ca4d553b26a9483ec9be3a7bf1de4a095/pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50", size = 2249185 }, + { url = "https://files.pythonhosted.org/packages/d5/4e/78f7c5202ea2a772a5ab05069c1b82503e6353cd79c7e474d4945f4b82c3/pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c", size = 2566726 }, + { url = "https://files.pythonhosted.org/packages/77/e4/6e84eada35cbcc646fc1870f72ccfd4afacb0fae0c37ffbffe7f5dc24bf1/pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1", size = 2254585 }, { url = "https://files.pythonhosted.org/packages/f0/eb/f7e21b113dd48a9c97d364e0915b3988c6a0b6207652f5a92372871b7aa4/pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc", size = 3154705 }, { url = "https://files.pythonhosted.org/packages/25/b3/2b54a1d541accebe6bd8b1358b34ceb2c509f51cb7dcda8687362490da5b/pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a", size = 2979222 }, { url = "https://files.pythonhosted.org/packages/20/12/1a41eddad8265c5c19dda8fb6c269ce15ee25e0b9f8f26286e6202df6693/pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3", size = 4190220 }, @@ -1759,6 +1922,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d6/b9/fb620dd47fc7cc9678af8f8bd8c772034ca4977237049287e99dda360b66/pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8", size = 2253197 }, { url = "https://files.pythonhosted.org/packages/df/86/25dde85c06c89d7fc5db17940f07aae0a56ac69aa9ccb5eb0f09798862a8/pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904", size = 2572169 }, { url = "https://files.pythonhosted.org/packages/51/85/9c33f2517add612e17f3381aee7c4072779130c634921a756c97bc29fb49/pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", size = 2256828 }, + { url = "https://files.pythonhosted.org/packages/36/57/42a4dd825eab762ba9e690d696d894ba366e06791936056e26e099398cda/pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2", size = 3119239 }, + { url = "https://files.pythonhosted.org/packages/98/f7/25f9f9e368226a1d6cf3507081a1a7944eddd3ca7821023377043f5a83c8/pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2", size = 2950803 }, + { url = "https://files.pythonhosted.org/packages/59/01/98ead48a6c2e31e6185d4c16c978a67fe3ccb5da5c2ff2ba8475379bb693/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b", size = 3281098 }, + { url = "https://files.pythonhosted.org/packages/51/c0/570255b2866a0e4d500a14f950803a2ec273bac7badc43320120b9262450/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2", size = 3323665 }, + { url = "https://files.pythonhosted.org/packages/0e/75/689b4ec0483c42bfc7d1aacd32ade7a226db4f4fac57c6fdcdf90c0731e3/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830", size = 3310533 }, + { url = "https://files.pythonhosted.org/packages/3d/30/38bd6149cf53da1db4bad304c543ade775d225961c4310f30425995cb9ec/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734", size = 3414886 }, + { url = "https://files.pythonhosted.org/packages/ec/3d/c32a51d848401bd94cabb8767a39621496491ee7cd5199856b77da9b18ad/pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316", size = 2567508 }, ] [[package]] @@ -1922,6 +2092,9 @@ name = "pywin32" version = "308" source = { registry = "https://pypi.org/simple" } wheels = [ + { url = "https://files.pythonhosted.org/packages/72/a6/3e9f2c474895c1bb61b11fa9640be00067b5c5b363c501ee9c3fa53aec01/pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e", size = 5927028 }, + { url = "https://files.pythonhosted.org/packages/d9/b4/84e2463422f869b4b718f79eb7530a4c1693e96b8a4e5e968de38be4d2ba/pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e", size = 6558484 }, + { url = "https://files.pythonhosted.org/packages/9f/8f/fb84ab789713f7c6feacaa08dad3ec8105b88ade8d1c4f0f0dfcaaa017d6/pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c", size = 7971454 }, { url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156 }, { url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559 }, { url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495 }, @@ -1939,6 +2112,15 @@ version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, @@ -1989,6 +2171,18 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fd/05/bed626b9f7bb2322cdbbf7b4bd8f54b1b617b0d2ab2d3547d6e39428a48e/pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f", size = 271975 } wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/a8/9837c39aba390eb7d01924ace49d761c8dbe7bc2d6082346d00c8332e431/pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629", size = 1340058 }, + { url = "https://files.pythonhosted.org/packages/a2/1f/a006f2e8e4f7d41d464272012695da17fb95f33b54342612a6890da96ff6/pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b", size = 1008818 }, + { url = "https://files.pythonhosted.org/packages/b6/09/b51b6683fde5ca04593a57bbe81788b6b43114d8f8ee4e80afc991e14760/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764", size = 673199 }, + { url = "https://files.pythonhosted.org/packages/c9/78/486f3e2e824f3a645238332bf5a4c4b4477c3063033a27c1e4052358dee2/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c", size = 911762 }, + { url = "https://files.pythonhosted.org/packages/5e/3b/2eb1667c9b866f53e76ee8b0c301b0469745a23bd5a87b7ee3d5dd9eb6e5/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a", size = 868773 }, + { url = "https://files.pythonhosted.org/packages/16/29/ca99b4598a9dc7e468b5417eda91f372b595be1e3eec9b7cbe8e5d3584e8/pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88", size = 868834 }, + { url = "https://files.pythonhosted.org/packages/ad/e5/9efaeb1d2f4f8c50da04144f639b042bc52869d3a206d6bf672ab3522163/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f", size = 1202861 }, + { url = "https://files.pythonhosted.org/packages/c3/62/c721b5608a8ac0a69bb83cbb7d07a56f3ff00b3991a138e44198a16f94c7/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282", size = 1515304 }, + { url = "https://files.pythonhosted.org/packages/87/84/e8bd321aa99b72f48d4606fc5a0a920154125bd0a4608c67eab742dab087/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea", size = 1414712 }, + { url = "https://files.pythonhosted.org/packages/cd/cd/420e3fd1ac6977b008b72e7ad2dae6350cc84d4c5027fc390b024e61738f/pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2", size = 578113 }, + { url = "https://files.pythonhosted.org/packages/5c/57/73930d56ed45ae0cb4946f383f985c855c9b3d4063f26416998f07523c0e/pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971", size = 641631 }, + { url = "https://files.pythonhosted.org/packages/61/d2/ae6ac5c397f1ccad59031c64beaafce7a0d6182e0452cc48f1c9c87d2dd0/pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa", size = 543528 }, { url = "https://files.pythonhosted.org/packages/12/20/de7442172f77f7c96299a0ac70e7d4fb78cd51eca67aa2cf552b66c14196/pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218", size = 1340639 }, { url = "https://files.pythonhosted.org/packages/98/4d/5000468bd64c7910190ed0a6c76a1ca59a68189ec1f007c451dc181a22f4/pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4", size = 1008710 }, { url = "https://files.pythonhosted.org/packages/e1/bf/c67fd638c2f9fbbab8090a3ee779370b97c82b84cc12d0c498b285d7b2c0/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef", size = 673129 }, @@ -2034,6 +2228,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dd/9a/10ed3c7f72b4c24e719c59359fbadd1a27556a28b36cdf1cd9e4fb7845d5/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306", size = 1183489 }, { url = "https://files.pythonhosted.org/packages/72/2d/8660892543fabf1fe41861efa222455811adac9f3c0818d6c3170a1153e3/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6", size = 1492932 }, { url = "https://files.pythonhosted.org/packages/7b/d6/32fd69744afb53995619bc5effa2a405ae0d343cd3e747d0fbc43fe894ee/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0", size = 1392485 }, + { url = "https://files.pythonhosted.org/packages/53/fb/36b2b2548286e9444e52fcd198760af99fd89102b5be50f0660fcfe902df/pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072", size = 906955 }, + { url = "https://files.pythonhosted.org/packages/77/8f/6ce54f8979a01656e894946db6299e2273fcee21c8e5fa57c6295ef11f57/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1", size = 565701 }, + { url = "https://files.pythonhosted.org/packages/ee/1c/bf8cd66730a866b16db8483286078892b7f6536f8c389fb46e4beba0a970/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d", size = 794312 }, + { url = "https://files.pythonhosted.org/packages/71/43/91fa4ff25bbfdc914ab6bafa0f03241d69370ef31a761d16bb859f346582/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca", size = 752775 }, + { url = "https://files.pythonhosted.org/packages/ec/d2/3b2ab40f455a256cb6672186bea95cd97b459ce4594050132d71e76f0d6f/pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c", size = 550762 }, ] [[package]] @@ -2055,6 +2254,22 @@ version = "2024.9.11" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f9/38/148df33b4dbca3bd069b963acab5e0fa1a9dbd6820f8c322d0dd6faeff96/regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd", size = 399403 } wheels = [ + { url = "https://files.pythonhosted.org/packages/63/12/497bd6599ce8a239ade68678132296aec5ee25ebea45fc8ba91aa60fceec/regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408", size = 482488 }, + { url = "https://files.pythonhosted.org/packages/c1/24/595ddb9bec2a9b151cdaf9565b0c9f3da9f0cb1dca6c158bc5175332ddf8/regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d", size = 287443 }, + { url = "https://files.pythonhosted.org/packages/69/a8/b2fb45d9715b1469383a0da7968f8cacc2f83e9fbbcd6b8713752dd980a6/regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5", size = 284561 }, + { url = "https://files.pythonhosted.org/packages/88/87/1ce4a5357216b19b7055e7d3b0efc75a6e426133bf1e7d094321df514257/regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c", size = 783177 }, + { url = "https://files.pythonhosted.org/packages/3c/65/b9f002ab32f7b68e7d1dcabb67926f3f47325b8dbc22cc50b6a043e1d07c/regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8", size = 823193 }, + { url = "https://files.pythonhosted.org/packages/22/91/8339dd3abce101204d246e31bc26cdd7ec07c9f91598472459a3a902aa41/regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35", size = 809950 }, + { url = "https://files.pythonhosted.org/packages/cb/19/556638aa11c2ec9968a1da998f07f27ec0abb9bf3c647d7c7985ca0b8eea/regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71", size = 782661 }, + { url = "https://files.pythonhosted.org/packages/d1/e9/7a5bc4c6ef8d9cd2bdd83a667888fc35320da96a4cc4da5fa084330f53db/regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8", size = 772348 }, + { url = "https://files.pythonhosted.org/packages/f1/0b/29f2105bfac3ed08e704914c38e93b07c784a6655f8a015297ee7173e95b/regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a", size = 697460 }, + { url = "https://files.pythonhosted.org/packages/71/3a/52ff61054d15a4722605f5872ad03962b319a04c1ebaebe570b8b9b7dde1/regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d", size = 769151 }, + { url = "https://files.pythonhosted.org/packages/97/07/37e460ab5ca84be8e1e197c3b526c5c86993dcc9e13cbc805c35fc2463c1/regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137", size = 777478 }, + { url = "https://files.pythonhosted.org/packages/65/7b/953075723dd5ab00780043ac2f9de667306ff9e2a85332975e9f19279174/regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6", size = 845373 }, + { url = "https://files.pythonhosted.org/packages/40/b8/3e9484c6230b8b6e8f816ab7c9a080e631124991a4ae2c27a81631777db0/regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca", size = 845369 }, + { url = "https://files.pythonhosted.org/packages/b7/99/38434984d912edbd2e1969d116257e869578f67461bd7462b894c45ed874/regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a", size = 773935 }, + { url = "https://files.pythonhosted.org/packages/ab/67/43174d2b46fa947b7b9dfe56b6c8a8a76d44223f35b1d64645a732fd1d6f/regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0", size = 261624 }, + { url = "https://files.pythonhosted.org/packages/c4/2a/4f9c47d9395b6aff24874c761d8d620c0232f97c43ef3cf668c8b355e7a7/regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623", size = 274020 }, { url = "https://files.pythonhosted.org/packages/86/a1/d526b7b6095a0019aa360948c143aacfeb029919c898701ce7763bbe4c15/regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df", size = 482483 }, { url = "https://files.pythonhosted.org/packages/32/d9/bfdd153179867c275719e381e1e8e84a97bd186740456a0dcb3e7125c205/regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268", size = 287442 }, { url = "https://files.pythonhosted.org/packages/33/c4/60f3370735135e3a8d673ddcdb2507a8560d0e759e1398d366e43d000253/regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad", size = 284561 }, @@ -2124,6 +2339,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/aa/9e/1784d15b057b0075e5136445aaea92d23955aad2c93eaede673718a40d95/rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c", size = 222843 } wheels = [ @@ -2150,6 +2366,19 @@ version = "0.20.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/55/64/b693f262791b818880d17268f3f8181ef799b0d187f6f731b1772e05a29a/rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", size = 25814 } wheels = [ + { url = "https://files.pythonhosted.org/packages/71/2d/a7e60483b72b91909e18f29a5c5ae847bac4e2ae95b77bb77e1f41819a58/rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2", size = 318432 }, + { url = "https://files.pythonhosted.org/packages/b5/b4/f15b0c55a6d880ce74170e7e28c3ed6c5acdbbd118df50b91d1dabf86008/rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f", size = 311333 }, + { url = "https://files.pythonhosted.org/packages/36/10/3f4e490fe6eb069c07c22357d0b4804cd94cb9f8d01345ef9b1d93482b9d/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150", size = 366697 }, + { url = "https://files.pythonhosted.org/packages/f5/c8/cd6ab31b4424c7fab3b17e153b6ea7d1bb0d7cabea5c1ef683cc8adb8bc2/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e", size = 368386 }, + { url = "https://files.pythonhosted.org/packages/60/5e/642a44fda6dda90b5237af7a0ef1d088159c30a504852b94b0396eb62125/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2", size = 395374 }, + { url = "https://files.pythonhosted.org/packages/7c/b5/ff18c093c9e72630f6d6242e5ccb0728ef8265ba0a154b5972f89d23790a/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3", size = 433189 }, + { url = "https://files.pythonhosted.org/packages/4a/6d/1166a157b227f2333f8e8ae320b6b7ea2a6a38fbe7a3563ad76dffc8608d/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf", size = 354849 }, + { url = "https://files.pythonhosted.org/packages/70/a4/70ea49863ea09ae4c2971f2eef58e80b757e3c0f2f618c5815bb751f7847/rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140", size = 373233 }, + { url = "https://files.pythonhosted.org/packages/3b/d3/822a28152a1e7e2ba0dc5d06cf8736f4cd64b191bb6ec47fb51d1c3c5ccf/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f", size = 541852 }, + { url = "https://files.pythonhosted.org/packages/c6/a5/6ef91e4425dc8b3445ff77d292fc4c5e37046462434a0423c4e0a596a8bd/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce", size = 547630 }, + { url = "https://files.pythonhosted.org/packages/72/f8/d5625ee05c4e5c478954a16d9359069c66fe8ac8cd5ddf28f80d3b321837/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94", size = 525766 }, + { url = "https://files.pythonhosted.org/packages/94/3c/1ff1ed6ae323b3e16fdfcdae0f0a67f373a6c3d991229dc32b499edeffb7/rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee", size = 199174 }, + { url = "https://files.pythonhosted.org/packages/ec/ba/5762c0aee2403dfea14ed74b0f8a2415cfdbb21cf745d600d9a8ac952c5b/rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399", size = 213543 }, { url = "https://files.pythonhosted.org/packages/ab/2a/191374c52d7be0b056cc2a04d718d2244c152f915d4a8d2db2aacc526189/rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", size = 318369 }, { url = "https://files.pythonhosted.org/packages/0e/6a/2c9fdcc6d235ac0d61ec4fd9981184689c3e682abd05e3caa49bccb9c298/rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", size = 311303 }, { url = "https://files.pythonhosted.org/packages/d2/b2/725487d29633f64ef8f9cbf4729111a0b61702c8f8e94db1653930f52cce/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", size = 366424 }, @@ -2189,6 +2418,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/ed/c074d248409b4432b1ccb2056974175fa0af2d1bc1f9c21121f80a358fa3/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", size = 528380 }, { url = "https://files.pythonhosted.org/packages/d5/bd/04caf938895d2d78201e89c0c8a94dfd9990c34a19ff52fb01d0912343e3/rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", size = 200540 }, { url = "https://files.pythonhosted.org/packages/95/cc/109eb8b9863680411ae703664abacaa035820c7755acc9686d5dd02cdd2e/rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", size = 214111 }, + { url = "https://files.pythonhosted.org/packages/06/39/bf1f664c347c946ef56cecaa896e3693d91acc741afa78ebb3fdb7aba08b/rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045", size = 319444 }, + { url = "https://files.pythonhosted.org/packages/c1/71/876135d3cb90d62468540b84e8e83ff4dc92052ab309bfdea7ea0b9221ad/rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc", size = 311699 }, + { url = "https://files.pythonhosted.org/packages/f7/da/8ccaeba6a3dda7467aebaf893de9eafd56275e2c90773c83bf15fb0b8374/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02", size = 367825 }, + { url = "https://files.pythonhosted.org/packages/04/b6/02a54c47c178d180395b3c9a8bfb3b93906e08f9acf7b4a1067d27c3fae0/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92", size = 369046 }, + { url = "https://files.pythonhosted.org/packages/a7/64/df4966743aa4def8727dc13d06527c8b13eb7412c1429def2d4701bee520/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d", size = 395896 }, + { url = "https://files.pythonhosted.org/packages/6f/d9/7ff03ff3642c600f27ff94512bb158a8d815fea5ed4162c75a7e850d6003/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855", size = 432427 }, + { url = "https://files.pythonhosted.org/packages/b8/c6/e1b886f7277b3454e55e85332e165091c19114eecb5377b88d892fd36ccf/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511", size = 355403 }, + { url = "https://files.pythonhosted.org/packages/e2/62/e26bd5b944e547c7bfd0b6ca7e306bfa430f8bd298ab72a1217976a7ca8d/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51", size = 374491 }, + { url = "https://files.pythonhosted.org/packages/c3/92/93c5a530898d3a5d1ce087455071ba714b77806ed9ffee4070d0c7a53b7e/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075", size = 543622 }, + { url = "https://files.pythonhosted.org/packages/01/9e/d68fba289625b5d3c9d1925825d7da716fbf812bda2133ac409021d5db13/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60", size = 548558 }, + { url = "https://files.pythonhosted.org/packages/bf/d6/4b2fad4898154365f0f2bd72ffd190349274a4c1d6a6f94f02a83bb2b8f1/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344", size = 525753 }, + { url = "https://files.pythonhosted.org/packages/d2/ea/6f121d1802f3adae1981aea4209ea66f9d3c7f2f6d6b85ef4f13a61d17ef/rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989", size = 213529 }, ] [[package]] @@ -2209,6 +2450,14 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 } wheels = [ + { url = "https://files.pythonhosted.org/packages/64/68/3bc0cfaf64ff507d82b1e5d5b64521df4c8bf7e22bc0b897827cbee9872c/scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389", size = 39069598 }, + { url = "https://files.pythonhosted.org/packages/43/a5/8d02f9c372790326ad405d94f04d4339482ec082455b9e6e288f7100513b/scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3", size = 29879676 }, + { url = "https://files.pythonhosted.org/packages/07/42/0e0bea9666fcbf2cb6ea0205db42c81b1f34d7b729ba251010edf9c80ebd/scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0", size = 23088696 }, + { url = "https://files.pythonhosted.org/packages/15/47/298ab6fef5ebf31b426560e978b8b8548421d4ed0bf99263e1eb44532306/scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3", size = 25470699 }, + { url = "https://files.pythonhosted.org/packages/d8/df/cdb6be5274bc694c4c22862ac3438cb04f360ed9df0aecee02ce0b798380/scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d", size = 35606631 }, + { url = "https://files.pythonhosted.org/packages/47/78/b0c2c23880dd1e99e938ad49ccfb011ae353758a2dc5ed7ee59baff684c3/scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69", size = 41178528 }, + { url = "https://files.pythonhosted.org/packages/5d/aa/994b45c34b897637b853ec04334afa55a85650a0d11dacfa67232260fb0a/scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad", size = 42784535 }, + { url = "https://files.pythonhosted.org/packages/e7/1c/8daa6df17a945cb1a2a1e3bae3c49643f7b3b94017ff01a4787064f03f84/scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5", size = 44772117 }, { url = "https://files.pythonhosted.org/packages/b2/ab/070ccfabe870d9f105b04aee1e2860520460ef7ca0213172abfe871463b9/scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675", size = 39076999 }, { url = "https://files.pythonhosted.org/packages/a7/c5/02ac82f9bb8f70818099df7e86c3ad28dae64e1347b421d8e3adf26acab6/scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2", size = 29894570 }, { url = "https://files.pythonhosted.org/packages/ed/05/7f03e680cc5249c4f96c9e4e845acde08eb1aee5bc216eff8a089baa4ddb/scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617", size = 23103567 }, @@ -2300,6 +2549,11 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/64/51/d9389280f077c1cb8ecaea2790a752a57f43edeb3b26ddf2d5ef2bb0a642/tensorstore-0.1.66.tar.gz", hash = "sha256:b77ee47da8a1b3d6fd03e23a8f853a2a666037f03e21546b4eb2b4cf43e13a96", size = 6533222 } wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/50/7ded663a612389272bb4587f7569efd272722e08a99ca56941aa16e8b634/tensorstore-0.1.66-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:486be8ec698d2eb10f297353351ff69e90551297185a158d6875e097fa08de68", size = 15804475 }, + { url = "https://files.pythonhosted.org/packages/63/78/39c8f52be649022069b0b5e890506b74e6cce8e908bfcf9289adf1787cd8/tensorstore-0.1.66-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5af8bf532ae4cc75709093483043ccd1d0f480d0f2b5e965ee5724f7d229658c", size = 13923488 }, + { url = "https://files.pythonhosted.org/packages/b2/b0/2eee942c0c4b8b88e9b759e98b6b5dcf56af752e6abe3b8d5ef9c1b84240/tensorstore-0.1.66-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6de7521a0f1f0fb4c4bb80ba945cdd926c4301d1ff2d821946f1675c0f532f5c", size = 13979797 }, + { url = "https://files.pythonhosted.org/packages/98/48/88a939daeee29e9b184d4a5ad2ae27f2001dfe7a14936d8f27babdd9aa9a/tensorstore-0.1.66-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94523e656c013721924cb5a2955b6c80f3bc4b469dd3d89cb759ab38f1c96cac", size = 15306336 }, + { url = "https://files.pythonhosted.org/packages/c1/49/c293ea2ae70c43e3d8eb75dee1d0f5846016a3589f6a51160310f0f56699/tensorstore-0.1.66-cp310-cp310-win_amd64.whl", hash = "sha256:a92dfc7b05e959e5823a87b8a20dc291382d5a647dff49eb560d5516e3490346", size = 11982419 }, { url = "https://files.pythonhosted.org/packages/7a/c5/093b09cbebe80590e681cb0bd4d1917a5ee8f198a0fd2ded63a0860f4df3/tensorstore-0.1.66-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:356e2a7fa256060c9da5d88cac6d9384d99887ab0df819c61f1dbe298ab56085", size = 15809330 }, { url = "https://files.pythonhosted.org/packages/e2/3c/0508fb7778174c78bf98a423eee5448544249fad43947d8b324521d97e28/tensorstore-0.1.66-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16c4474b10c90b27d422ccffd31f78ed7c2f2976330ac80a4b53a73fd8f041d9", size = 13927590 }, { url = "https://files.pythonhosted.org/packages/20/d4/c394eaa8ee519082aed59e8d3e6f8256890eb4b0e76c81e2b741af35c442/tensorstore-0.1.66-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb3d9bc9156adaf2212953d941efd993b57a8da7053b93f512c1c313dfdddb89", size = 13979944 }, @@ -2324,6 +2578,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/4d/0db5b8a613d2a59bbc29bc5bb44a2f8070eb9ceab11c50d477502a8a0092/tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7", size = 22532 }, ] +[[package]] +name = "tomli" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/b9/de2a5c0144d7d75a57ff355c0c24054f965b2dc3036456ae03a51ea6264b/tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed", size = 16096 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/db/ce8eda256fa131af12e0a76d481711abe4681b6923c27efb9a255c9e4594/tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38", size = 13237 }, +] + [[package]] name = "toolz" version = "1.0.0" @@ -2453,6 +2716,9 @@ version = "5.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/48/a86139aaeab2db0a2482676f64798d8ac4d2dbb457523f50ab37bf02ce2c/watchdog-5.0.3.tar.gz", hash = "sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176", size = 129556 } wheels = [ + { url = "https://files.pythonhosted.org/packages/05/2b/dd2081aab6fc9e785c2eee7146d3c6de58e607f4e70049d715cd170cbf77/watchdog-5.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea", size = 96652 }, + { url = "https://files.pythonhosted.org/packages/9e/4f/f643c0a720d16ef7316aea06a79b96e229e59df4e0d83bec5e12713c1f29/watchdog-5.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb", size = 88651 }, + { url = "https://files.pythonhosted.org/packages/2b/72/acb22067d1f18161914c9b1087c703d63638131a9fde78090da290663407/watchdog-5.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e25adddab85f674acac303cf1f5835951345a56c5f7f582987d266679979c75b", size = 89289 }, { url = "https://files.pythonhosted.org/packages/70/34/946f08602f8b8e6af45bc725e4a8013975a34883ab5570bd0d827a4c9829/watchdog-5.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818", size = 96650 }, { url = "https://files.pythonhosted.org/packages/96/2b/b84e35d49e8b0bad77e5d086fc1e2c6c833bbfe74d53144cfe8b26117eff/watchdog-5.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490", size = 88653 }, { url = "https://files.pythonhosted.org/packages/d5/3f/41b5d77c10f450b79921c17b7d0b416616048867bfe63acaa072a619a0cb/watchdog-5.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e", size = 89286 }, @@ -2462,6 +2728,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/c4/49af4ab00bcfb688e9962eace2edda07a2cf89b9699ea536da48e8585cff/watchdog-5.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7", size = 96740 }, { url = "https://files.pythonhosted.org/packages/96/a4/b24de77cc9ae424c1687c9d4fb15aa560d7d7b28ba559aca72f781d0202b/watchdog-5.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906", size = 88711 }, { url = "https://files.pythonhosted.org/packages/a4/71/3f2e9fe8403386b99d788868955b3a790f7a09721501a7e1eb58f514ffaa/watchdog-5.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1", size = 89319 }, + { url = "https://files.pythonhosted.org/packages/a2/d6/1d1ca81c75d903eca3fdb7061d93845485b58a5ba182d146843b88fc51c2/watchdog-5.0.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:90a67d7857adb1d985aca232cc9905dd5bc4803ed85cfcdcfcf707e52049eda7", size = 88172 }, + { url = "https://files.pythonhosted.org/packages/47/bb/d5e0abcfd6d729029a24766682e062526db8b59e9ae0c94aff509e0fd2b9/watchdog-5.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:720ef9d3a4f9ca575a780af283c8fd3a0674b307651c1976714745090da5a9e8", size = 88644 }, { url = "https://files.pythonhosted.org/packages/60/33/7cb71c9df9a77b6927ee5f48d25e1de5562ce0fa7e0c56dcf2b0472e64a2/watchdog-5.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91", size = 79335 }, { url = "https://files.pythonhosted.org/packages/f6/91/320bc1496cf951a3cf93a7ffd18a581f0792c304be963d943e0e608c2919/watchdog-5.0.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c", size = 79334 }, { url = "https://files.pythonhosted.org/packages/8b/2c/567c5e042ed667d3544c43d48a65cf853450a2d2a9089d9523a65f195e94/watchdog-5.0.3-py3-none-manylinux2014_i686.whl", hash = "sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c", size = 79333 }, From ae9204abbf8c246b4d9f8dacf2ea38163400ca12 Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Wed, 16 Oct 2024 17:14:05 -0400 Subject: [PATCH 243/248] Update GW170817 PhenomD --- example/GW170817_IMRPhenomD.py | 184 +++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 example/GW170817_IMRPhenomD.py diff --git a/example/GW170817_IMRPhenomD.py b/example/GW170817_IMRPhenomD.py new file mode 100644 index 00000000..d3aca76d --- /dev/null +++ b/example/GW170817_IMRPhenomD.py @@ -0,0 +1,184 @@ +import time + +import jax +import jax.numpy as jnp + +from jimgw.jim import Jim +from jimgw.jim import Jim +from jimgw.prior import ( + CombinePrior, + UniformPrior, + CosinePrior, + SinePrior, + PowerLawPrior, + UniformSpherePrior, +) +from jimgw.single_event.detector import H1, L1, V1 +from jimgw.single_event.likelihood import TransientLikelihoodFD, HeterodynedTransientLikelihoodFD +from jimgw.single_event.waveform import RippleIMRPhenomD +from jimgw.transforms import BoundToUnbound +from jimgw.single_event.transforms import ( + SkyFrameToDetectorFrameSkyPositionTransform, + SphereSpinToCartesianSpinTransform, + MassRatioToSymmetricMassRatioTransform, + DistanceToSNRWeightedDistanceTransform, + GeocentricArrivalTimeToDetectorArrivalTimeTransform, + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform, +) +from jimgw.single_event.utils import Mc_q_to_m1_m2 +from flowMC.strategy.optimization import optimization_Adam + +jax.config.update("jax_enable_x64", True) + +########################################### +########## First we grab data ############# +########################################### + +total_time_start = time.time() + +# first, fetch a 4s segment centered on GW150914 + +gps = 1187008882.43 +trigger_time = gps +fmin = 20 +fmax = 2048 +minimum_frequency = fmin +maximum_frequency = fmax +duration = 128 +post_trigger_duration = 2 +epoch = duration - post_trigger_duration +f_ref = fmin + +ifos = [H1, L1, V1] + + +tukey_alpha = 2 / (duration / 2) +H1.load_data( + gps, duration, 2, fmin, fmax, psd_pad=duration + 16, tukey_alpha=tukey_alpha +) +L1.load_data( + gps, duration, 2, fmin, fmax, psd_pad=duration + 16, tukey_alpha=tukey_alpha +) +V1.load_data( + gps, duration, 2, fmin, fmax, psd_pad=duration + 16, tukey_alpha=tukey_alpha +) + + +waveform = RippleIMRPhenomD(f_ref=f_ref) + +########################################### +########## Set up priors ################## +########################################### + +prior = [] + +# Mass prior +M_c_min, M_c_max = 1.18, 1.21 +q_min, q_max = 0.125, 1.0 +Mc_prior = UniformPrior(M_c_min, M_c_max, parameter_names=["M_c"]) +q_prior = UniformPrior(q_min, q_max, parameter_names=["q"]) + +prior = prior + [Mc_prior, q_prior] + +# Spin prior +s1_prior = UniformPrior(-1.0, 1.0, parameter_names=["s1_z"]) +s2_prior = UniformPrior(-1.0, 1.0, parameter_names=["s2_z"]) +iota_prior = SinePrior(parameter_names=["iota"]) + +prior = prior + [ + s1_prior, + s2_prior, + iota_prior, +] + +# Extrinsic prior +dL_prior = PowerLawPrior(1.0, 75.0, 2.0, parameter_names=["d_L"]) +t_c_prior = UniformPrior(-0.1, 0.1, parameter_names=["t_c"]) +phase_c_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["phase_c"]) +psi_prior = UniformPrior(0.0, jnp.pi, parameter_names=["psi"]) +ra_prior = UniformPrior(0.0, 2 * jnp.pi, parameter_names=["ra"]) +dec_prior = CosinePrior(parameter_names=["dec"]) + +prior = prior + [ + dL_prior, + t_c_prior, + phase_c_prior, + psi_prior, + ra_prior, + dec_prior, +] + +prior = CombinePrior(prior) + +# Defining Transforms + +sample_transforms = [ + DistanceToSNRWeightedDistanceTransform(gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), + GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(gps_time=gps, ifo=ifos[0]), + GeocentricArrivalTimeToDetectorArrivalTimeTransform(tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, gps_time=gps, ifo=ifos[0]), + SkyFrameToDetectorFrameSkyPositionTransform(gps_time=gps, ifos=ifos), + BoundToUnbound(name_mapping = (["M_c"], ["M_c_unbounded"]), original_lower_bound=M_c_min, original_upper_bound=M_c_max), + BoundToUnbound(name_mapping = (["q"], ["q_unbounded"]), original_lower_bound=q_min, original_upper_bound=q_max), + BoundToUnbound(name_mapping = (["s1_z"], ["s1_z_unbounded"]) , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = (["s2_z"], ["s2_z_unbounded"]) , original_lower_bound=-1.0, original_upper_bound=1.0), + BoundToUnbound(name_mapping = (["iota"], ["iota_unbounded"]) , original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["phase_det"], ["phase_det_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), + BoundToUnbound(name_mapping = (["psi"], ["psi_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["zenith"], ["zenith_unbounded"]), original_lower_bound=0.0, original_upper_bound=jnp.pi), + BoundToUnbound(name_mapping = (["azimuth"], ["azimuth_unbounded"]), original_lower_bound=0.0, original_upper_bound=2 * jnp.pi), +] + +likelihood_transforms = [ + MassRatioToSymmetricMassRatioTransform, +] + + +#likelihood = TransientLikelihoodFD( +# [H1, L1, V1], waveform=waveform, trigger_time=trigger_time, duration=duration, post_trigger_duration=post_trigger_duration +#) + +likelihood = HeterodynedTransientLikelihoodFD(ifos, waveform=waveform, n_bins = 1000, trigger_time=trigger_time, duration=duration, post_trigger_duration=post_trigger_duration, prior = prior, sample_transforms = sample_transforms, likelihood_transforms = likelihood_transforms, popsize = 10, n_steps = 50) + +mass_matrix = jnp.eye(prior.n_dim) +# mass_matrix = mass_matrix.at[1, 1].set(1e-3) +# mass_matrix = mass_matrix.at[9, 9].set(1e-3) +local_sampler_arg = {"step_size": mass_matrix * 1e-3} + +Adam_optimizer = optimization_Adam(n_steps=3000, learning_rate=0.01, noise_level=1) + +import optax + +n_epochs = 20 +n_loop_training = 100 +total_epochs = n_epochs * n_loop_training +start = total_epochs // 10 +learning_rate = optax.polynomial_schedule( + 1e-3, 1e-4, 4.0, total_epochs - start, transition_begin=start +) + +jim = Jim( + likelihood, + prior, + sample_transforms=sample_transforms, + likelihood_transforms=likelihood_transforms, + n_loop_training=n_loop_training, + n_loop_production=20, + n_local_steps=10, + n_global_steps=1000, + n_chains=500, + n_epochs=n_epochs, + learning_rate=learning_rate, + n_max_examples=30000, + n_flow_sample=100000, + momentum=0.9, + batch_size=30000, + use_global=True, + keep_quantile=0.0, + train_thinning=1, + output_thinning=10, + local_sampler_arg=local_sampler_arg, + # strategies=[Adam_optimizer,"default"], +) + + +jim.sample(jax.random.PRNGKey(42)) From cfde5e3d9325291aba03165ca6d2020b02f5b64f Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Wed, 16 Oct 2024 17:22:28 -0400 Subject: [PATCH 244/248] update package workflow --- .github/workflows/python-package.yml | 34 ++++++++++++++----------- pyproject.toml | 1 + uv.lock | 37 ++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d70c2ae1..be87da1e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -3,7 +3,7 @@ name: Python package -on: [push, pull_request] +on: [push] jobs: build: @@ -14,18 +14,22 @@ jobs: matrix: python-version: ["3.10", "3.11"] + steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - python -m pip install . - - name: Test with pytest - run: | - pytest + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + + - name: Install the project + run: uv sync --all-extras --dev + + - name: Run tests + # For example, using `pytest` + run: uv run pytest + + - name: Run build + run: uv build diff --git a/pyproject.toml b/pyproject.toml index a856dd39..13432e72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "gwpy>=3.0.10", "jax>=0.4.34", "jaxtyping>=0.2.34", + "pytest>=8.3.3", "ripplegw>=0.0.9", "typed-argument-parser>=1.10.1", ] diff --git a/uv.lock b/uv.lock index 5217a861..2a79fb7c 100644 --- a/uv.lock +++ b/uv.lock @@ -845,6 +845,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/6a/4604f9ae2fa62ef47b9de2fa5ad599589d28c9fd1d335f32759813dfa91e/importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717", size = 36115 }, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + [[package]] name = "ipykernel" version = "6.29.5" @@ -975,6 +984,7 @@ dependencies = [ { name = "gwpy" }, { name = "jax" }, { name = "jaxtyping" }, + { name = "pytest" }, { name = "ripplegw" }, { name = "typed-argument-parser" }, ] @@ -1006,6 +1016,7 @@ requires-dist = [ { name = "mkdocs-material", marker = "extra == 'docs'" }, { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'" }, { name = "pymdown-extensions", marker = "extra == 'docs'" }, + { name = "pytest", specifier = ">=8.3.3" }, { name = "ripplegw", specifier = ">=0.0.9" }, { name = "typed-argument-parser", specifier = ">=1.10.1" }, ] @@ -1940,6 +1951,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, ] +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + [[package]] name = "prompt-toolkit" version = "3.0.48" @@ -2066,6 +2086,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/ec/2eb3cd785efd67806c46c13a17339708ddc346cbb684eade7a6e6f79536a/pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84", size = 106921 }, ] +[[package]] +name = "pytest" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" From 5082e1ba03d2a3a7bfc4cb3285e2097609841a84 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Wed, 16 Oct 2024 17:23:36 -0400 Subject: [PATCH 245/248] update workflow --- .github/workflows/python-publish.yml | 40 +++++++++++++++++----------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 30f2bc2f..e6191e8a 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -20,20 +20,28 @@ jobs: runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@v1.8.4 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + + - name: Install the project + run: uv sync --all-extras --dev + + - name: Run tests + # For example, using `pytest` + run: uv run pytest + + - name: Run build + run: uv build + + - name: Publish package + uses: pypa/gh-action-pypi-publish@v1.8.4 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} From 38de2ac4c694ecd6f7c8fd362587552e2f9f027d Mon Sep 17 00:00:00 2001 From: "Peter T. H. Pang" Date: Wed, 16 Oct 2024 17:25:23 -0400 Subject: [PATCH 246/248] Fixing the Pv2 test again --- test/integration/test_GW150914_Pv2.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/test/integration/test_GW150914_Pv2.py b/test/integration/test_GW150914_Pv2.py index 2b54ed3c..7f118703 100644 --- a/test/integration/test_GW150914_Pv2.py +++ b/test/integration/test_GW150914_Pv2.py @@ -24,11 +24,7 @@ DistanceToSNRWeightedDistanceTransform, GeocentricArrivalTimeToDetectorArrivalTimeTransform, GeocentricArrivalPhaseToDetectorArrivalPhaseTransform, - ComponentMassesToChirpMassMassRatioTransform, ) -from jimgw.single_event.prior import ( - ChirpMassMassRatioBoundedUniformComponentPrior -) from flowMC.strategy.optimization import optimization_Adam jax.config.update("jax_enable_x64", True) @@ -58,14 +54,10 @@ # Mass prior M_c_min, M_c_max = 10.0, 80.0 q_min, q_max = 0.125, 1.0 -m1m2_prior = ChirpMassMassRatioBoundedUniformComponentPrior( - q_min=q_min, - q_max=q_max, - M_c_min=M_c_min, - M_c_max=M_c_max, -) +Mc_prior = UniformPrior(M_c_min, M_c_max, parameter_names=['M_c']) +q_prior = UniformPrior(q_min, q_max, parameter_names=['q']) -prior = prior + [m1m2_prior] +prior = prior + [Mc_prior, q_prior] # Spin prior s1_prior = UniformSpherePrior(parameter_names=["s1"]) @@ -100,7 +92,6 @@ # Defining Transforms sample_transforms = [ - ComponentMassesToChirpMassMassRatioTransform, DistanceToSNRWeightedDistanceTransform(gps_time=gps, ifos=ifos, dL_min=dL_prior.xmin, dL_max=dL_prior.xmax), GeocentricArrivalPhaseToDetectorArrivalPhaseTransform(gps_time=gps, ifo=ifos[0]), GeocentricArrivalTimeToDetectorArrivalTimeTransform(tc_min=t_c_prior.xmin, tc_max=t_c_prior.xmax, gps_time=gps, ifo=ifos[0]), @@ -121,7 +112,6 @@ ] likelihood_transforms = [ - ComponentMassesToChirpMassMassRatioTransform, MassRatioToSymmetricMassRatioTransform, SphereSpinToCartesianSpinTransform("s1"), SphereSpinToCartesianSpinTransform("s2"), From 4055acbfcc035c887cc9d4b4fdbe881eb6912e86 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Thu, 17 Oct 2024 13:34:03 -0400 Subject: [PATCH 247/248] update build system --- pyproject.toml | 15 ++++++++++++++- uv.lock | 5 ++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 13432e72..3b6c1dff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "jimGW" +name = "jimgw" version = "0.2.0" description = "Gravitatioanl wave data analysis tool in Jax" readme = "README.md" @@ -31,3 +31,16 @@ docs = [ "mkdocstrings[python]", "pymdown-extensions", ] + +[tool.uv] +dev-dependencies = [ + "jimgw", +] + +[tool.uv.sources] +jimgw = { workspace = true } + + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/uv.lock b/uv.lock index 2a79fb7c..d3a39a98 100644 --- a/uv.lock +++ b/uv.lock @@ -975,7 +975,7 @@ wheels = [ [[package]] name = "jimgw" version = "0.2.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "astropy" }, { name = "beartype" }, @@ -1021,6 +1021,9 @@ requires-dist = [ { name = "typed-argument-parser", specifier = ">=1.10.1" }, ] +[package.metadata.requires-dev] +dev = [{ name = "jimgw", editable = "." }] + [[package]] name = "jinja2" version = "3.1.4" From 7205c56b477171321e63e92a238ae0531222c7d3 Mon Sep 17 00:00:00 2001 From: Kaze Wong Date: Thu, 17 Oct 2024 13:58:38 -0400 Subject: [PATCH 248/248] Update python-publish.yml --- .github/workflows/python-publish.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index e6191e8a..b892d49e 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -33,10 +33,6 @@ jobs: - name: Install the project run: uv sync --all-extras --dev - - name: Run tests - # For example, using `pytest` - run: uv run pytest - - name: Run build run: uv build