| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- # 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'.
- import numpy as np
- # Forward map: Parameters to polynomial coefficients
- def forward_map(pars):
- """
- Converts parameters (pars = [C, epsilon, k1, k2]) into polynomial coefficients (beta = [d, c, b, a]).
-
- Polynomial Definition:
- - d = C: Constant term of the polynomial.
- - c = k2^2 + epsilon^2: Coefficient of the linear term.
- - b = k1 * k2: Coefficient of the quadratic term.
- - a = k1**2 / 3: Coefficient of the cubic term.
- Parameters:
- - pars: NumPy array of parameters [C, epsilon, k1, k2].
- Returns:
- - beta: NumPy array of polynomial coefficients [d, c, b, a].
- """
- C, epsilon, k1, k2 = pars # Unpack the parameter vector
-
- # Compute coefficients
- d = C
- c = k2**2 + epsilon**2
- b = k1 * k2
- a = k1**2 / 3
-
- # Return coefficients as a NumPy array
- beta = np.array([d, c, b, a])
- return beta
- # Jacobian of the forward map: First-order derivatives
- def forward_map_jacobian(pars):
- """
- Computes the Jacobian matrix of the forward map analytically.
-
- Parameters:
- - pars: NumPy array of parameters [C, epsilon, k1, k2].
-
- Returns:
- - J: NumPy 4x4 Jacobian matrix, where J[i, j] = d(beta[i])/d(pars[j]).
- """
- C, epsilon, k1, k2 = pars # Unpack parameters
-
- # Initialize Jacobian matrix
- J = np.zeros((4, 4)) # 4x4 matrix
-
- # Partial derivatives for d = C
- J[0, 0] = 1 # d(d)/dC
- J[0, 1] = 0 # d(d)/d(epsilon)
- J[0, 2] = 0 # d(d)/d(k1)
- J[0, 3] = 0 # d(d)/d(k2)
- # Partial derivatives for c = k2^2 + epsilon^2
- J[1, 0] = 0 # d(c)/dC
- J[1, 1] = 2 * epsilon # d(c)/d(epsilon)
- J[1, 2] = 0 # d(c)/d(k1)
- J[1, 3] = 2 * k2 # d(c)/d(k2)
- # Partial derivatives for b = k1 * k2
- J[2, 0] = 0 # d(b)/dC
- J[2, 1] = 0 # d(b)/d(epsilon)
- J[2, 2] = k2 # d(b)/d(k1)
- J[2, 3] = k1 # d(b)/d(k2)
- # Partial derivatives for a = k1^2 / 3
- J[3, 0] = 0 # d(a)/dC
- J[3, 1] = 0 # d(a)/d(epsilon)
- J[3, 2] = 2 * k1 / 3 # d(a)/d(k1)
- J[3, 3] = 0 # d(a)/d(k2)
-
- return J
- # Hessian of the forward map: Second-order derivatives
- def forward_map_hessian(pars):
- """
- Computes the Hessian tensor of the forward map analytically.
-
- Parameters:
- - pars: NumPy array of parameters [C, epsilon, k1, k2].
-
- Returns:
- - H: NumPy 4x4x4 Hessian tensor, where H[i, j, k] = d^2(beta[i])/d(pars[j])d(pars[k]).
- """
- C, epsilon, k1, k2 = pars # Unpack parameters
-
- # Initialize Hessian tensor (4 x 4 x 4)
- H = np.zeros((4, 4, 4))
-
- # Hessian for d = C: All second derivatives are zero
- # Already H[0, :, :] is initialized to zero
-
- # Hessian for c = k2^2 + epsilon^2
- H[1, 1, 1] = 2 # d^2(c)/d(epsilon^2)
- H[1, 3, 3] = 2 # d^2(c)/d(k2^2)
-
- # Hessian for b = k1 * k2: All second derivatives are zero
- # Already H[2, :, :] is initialized to zero
-
- # Hessian for a = k1^2 / 3
- H[3, 2, 2] = 2 / 3 # d^2(a)/d(k1^2)
-
- return H
- # Backward map: Polynomial coefficients to parameters
- def backward_map(beta, only_one = True):
- """
- Computes the parameters (pars = [C, epsilon, k1, k2]) from the polynomial coefficients (beta = [d, c, b, a]).
-
- Polynomial Definition:
- - d = C: Constant term of the polynomial.
- - c = k2^2 + epsilon^2: Used to recover k2 and epsilon.
- - b = k1 * k2: Used to recover k1 and k2.
- - a = k1^2 / 3: Used to recover k1.
- Parameters:
- - beta: NumPy array of polynomial coefficients [d, c, b, a].
- Returns:
- - List of possible parameter sets [(C, epsilon, k1, k2)].
- """
- d, c, b, a = beta # Unpack the coefficients
-
- # Recover k1 from a (two possible values due to ± sqrt)
- if a < 0:
- raise ValueError("Coefficient 'a' must be non-negative for monotonic polynomials.")
-
- k1_options = np.unique([np.sqrt(3 * a), -np.sqrt(3 * a)]) # Two possible k1 values
-
- possible_parameters = []
-
- # For each possible k1, compute k2 and epsilon
- for k1 in k1_options:
- k2 = None
- if k1 != 0: # Ensure k1 is non-zero (avoids division by zero)
- k2 = b / k1 # Compute k2 from b and k1
- elif b == 0:
- k2 = 0
- if k2 is None: continue
-
- # Check if c >= k2^2 for valid epsilon computation
- if c >= k2**2:
- epsilon_options = np.unique([np.sqrt(c - k2**2), -np.sqrt(c - k2**2)]) # Two possible epsilon values
-
- for epsilon in epsilon_options:
- # Constant term d maps directly to C
- C = d
- sol = np.array([C, epsilon, k1, k2])
- if only_one: return sol
- possible_parameters.append(sol)
- return possible_parameters
- # -------------------------
- # 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])
- print("\nLinear func:")
- lin_fun_beta = [1,0.2,0,0]
- only_one = False
- lin_fun_pars = backward_map(lin_fun_beta, only_one=only_one)
- lin_fun_beta_recover = lin_fun_pars if only_one else np.unique([forward_map(pars) for pars in lin_fun_pars], axis=0)
-
- print(f" {only_one = }")
- print(" lin_fun_beta:", lin_fun_beta)
- print(" backwards:", lin_fun_pars)
- print(" forwards:", lin_fun_beta_recover)
|