| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- """
- Function supporting monotonic cubic polynomials:
- first parametrization
- We define the polynomial:
- poly(x) = sum_i beta_i * x^i
- where the coefficients beta_i are parameterized by the vector 'pars':
- beta_i is a function pars
- """
- import numpy as np
- def forward_map(pars, small=1e-8):
- """
- Map parameter vector pars to coefficients beta.
- beta = [p0, (p2^2 + p1^2) / a, p2, p3^2],
- where a = 3*p3^2 + small.
- """
- pars = np.asarray(pars, dtype=float)
- p0, p1, p2, p3 = pars
- a = 3.0 * p3**2 + small
- beta = np.array([
- p0,
- (p2**2 + p1**2) / a,
- p2,
- p3**2
- ], dtype=float)
- return beta
- def backward_map(beta, small=1e-8):
- """
- Map coefficients beta back to parameter vector pars.
- Nonnegative roots are used for p1 and p3.
- """
- beta = np.asarray(beta, dtype=float)
- b0, b1, b2, b3 = beta
- p0 = b0
- p3 = np.sqrt(b3)
- a = 3.0 * b3 + small
- p2 = b2
- p1_sq = b1 * a - p2**2
- if p1_sq < 0:
- raise ValueError(f"Negative square root encountered for p1^2 = {p1_sq}")
- p1 = np.sqrt(p1_sq)
- return np.array([p0, p1, p2, p3], dtype=float)
- def forward_map_jacobian(pars, small=1e-8):
- """
- Compute Jacobian of forward_map at given pars.
- Returns J: d(beta)/d(pars), shape (4, 4).
- """
- p0, p1, p2, p3 = np.asarray(pars, dtype=float)
- a = 3.0 * p3**2 + small
- J = np.zeros((4, 4), dtype=float)
- # beta0 row
- J[0, 0] = 1.0
- # beta1 row
- J[1, 1] = 2.0 * p1 / a
- J[1, 2] = 2.0 * p2 / a
- J[1, 3] = -6.0 * p3 * (p2**2 + p1**2) / a**2
- # beta2 row
- J[2, 2] = 1.0
- # beta3 row
- J[3, 3] = 2.0 * p3
- return J
- def forward_map_hessian(pars, small=1e-8):
- """
- Compute Hessians of forward_map at given pars.
- Returns H: shape (4, 4, 4),
- where H[i] is the 4x4 Hessian of beta[i] w.r.t. pars.
- """
- p0, p1, p2, p3 = np.asarray(pars, dtype=float)
- a = 3.0 * p3**2 + small
- N = p1**2 + p2**2
- H = np.zeros((4, 4, 4), dtype=float)
- # beta0: all zeros
- # beta1 Hessian
- H1 = np.zeros((4, 4), dtype=float)
- H1[1, 1] = 2.0 / a
- H1[2, 2] = 2.0 / a
- H1[1, 3] = -12.0 * p1 * p3 / a**2
- H1[3, 1] = H1[1, 3]
- H1[2, 3] = -12.0 * p2 * p3 / a**2
- H1[3, 2] = H1[2, 3]
- H1[3, 3] = 6.0 * N * (12.0 * p3**2 - a) / a**3
- H[1] = H1
- # beta2: all zeros
- # beta3 Hessian
- H3 = np.zeros((4, 4), dtype=float)
- H3[3, 3] = 2.0
- H[3] = H3
- return H
- # -------------------------
- # Round-trip test
- # -------------------------
- if __name__ == "__main__":
- pars_original = np.array([1.0, 2.0, 3.0, 4.0])
- beta = forward_map(pars_original)
- pars_recovered = backward_map(beta)
- print("Original pars: ", pars_original)
- print("Beta: ", beta)
- print("Recovered pars:", pars_recovered)
- print("Difference: ", pars_recovered - pars_original)
- print("\nJacobian at pars:")
- print(forward_map_jacobian(pars_original))
- print("\nHessian for beta1:")
- print(forward_map_hessian(pars_original)[1])
|