mono_cubic1.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. """
  2. Function supporting monotonic cubic polynomials:
  3. first parametrization
  4. We define the polynomial:
  5. poly(x) = sum_i beta_i * x^i
  6. where the coefficients beta_i are parameterized by the vector 'pars':
  7. beta_i is a function pars
  8. """
  9. import numpy as np
  10. def forward_map(pars, small=1e-8):
  11. """
  12. Map parameter vector pars to coefficients beta.
  13. beta = [p0, (p2^2 + p1^2) / a, p2, p3^2],
  14. where a = 3*p3^2 + small.
  15. """
  16. pars = np.asarray(pars, dtype=float)
  17. p0, p1, p2, p3 = pars
  18. a = 3.0 * p3**2 + small
  19. beta = np.array([
  20. p0,
  21. (p2**2 + p1**2) / a,
  22. p2,
  23. p3**2
  24. ], dtype=float)
  25. return beta
  26. def backward_map(beta, small=1e-8):
  27. """
  28. Map coefficients beta back to parameter vector pars.
  29. Nonnegative roots are used for p1 and p3.
  30. """
  31. beta = np.asarray(beta, dtype=float)
  32. b0, b1, b2, b3 = beta
  33. p0 = b0
  34. p3 = np.sqrt(b3)
  35. a = 3.0 * b3 + small
  36. p2 = b2
  37. p1_sq = b1 * a - p2**2
  38. if p1_sq < 0:
  39. raise ValueError(f"Negative square root encountered for p1^2 = {p1_sq}")
  40. p1 = np.sqrt(p1_sq)
  41. return np.array([p0, p1, p2, p3], dtype=float)
  42. def forward_map_jacobian(pars, small=1e-8):
  43. """
  44. Compute Jacobian of forward_map at given pars.
  45. Returns J: d(beta)/d(pars), shape (4, 4).
  46. """
  47. p0, p1, p2, p3 = np.asarray(pars, dtype=float)
  48. a = 3.0 * p3**2 + small
  49. J = np.zeros((4, 4), dtype=float)
  50. # beta0 row
  51. J[0, 0] = 1.0
  52. # beta1 row
  53. J[1, 1] = 2.0 * p1 / a
  54. J[1, 2] = 2.0 * p2 / a
  55. J[1, 3] = -6.0 * p3 * (p2**2 + p1**2) / a**2
  56. # beta2 row
  57. J[2, 2] = 1.0
  58. # beta3 row
  59. J[3, 3] = 2.0 * p3
  60. return J
  61. def forward_map_hessian(pars, small=1e-8):
  62. """
  63. Compute Hessians of forward_map at given pars.
  64. Returns H: shape (4, 4, 4),
  65. where H[i] is the 4x4 Hessian of beta[i] w.r.t. pars.
  66. """
  67. p0, p1, p2, p3 = np.asarray(pars, dtype=float)
  68. a = 3.0 * p3**2 + small
  69. N = p1**2 + p2**2
  70. H = np.zeros((4, 4, 4), dtype=float)
  71. # beta0: all zeros
  72. # beta1 Hessian
  73. H1 = np.zeros((4, 4), dtype=float)
  74. H1[1, 1] = 2.0 / a
  75. H1[2, 2] = 2.0 / a
  76. H1[1, 3] = -12.0 * p1 * p3 / a**2
  77. H1[3, 1] = H1[1, 3]
  78. H1[2, 3] = -12.0 * p2 * p3 / a**2
  79. H1[3, 2] = H1[2, 3]
  80. H1[3, 3] = 6.0 * N * (12.0 * p3**2 - a) / a**3
  81. H[1] = H1
  82. # beta2: all zeros
  83. # beta3 Hessian
  84. H3 = np.zeros((4, 4), dtype=float)
  85. H3[3, 3] = 2.0
  86. H[3] = H3
  87. return H
  88. # -------------------------
  89. # Round-trip test
  90. # -------------------------
  91. if __name__ == "__main__":
  92. pars_original = np.array([1.0, 2.0, 3.0, 4.0])
  93. beta = forward_map(pars_original)
  94. pars_recovered = backward_map(beta)
  95. print("Original pars: ", pars_original)
  96. print("Beta: ", beta)
  97. print("Recovered pars:", pars_recovered)
  98. print("Difference: ", pars_recovered - pars_original)
  99. print("\nJacobian at pars:")
  100. print(forward_map_jacobian(pars_original))
  101. print("\nHessian for beta1:")
  102. print(forward_map_hessian(pars_original)[1])