Files
AMSS-NCKU/AMSS_NCKU_source/expansion_Jacobian.C
2026-01-13 15:01:15 +08:00

387 lines
13 KiB
C

#include "macrodef.h"
#ifdef With_AHF
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include "util_Table.h"
#include "cctk.h"
#include "config.h"
#include "stdc.h"
#include "util.h"
#include "array.h"
#include "cpm_map.h"
#include "linear_map.h"
#include "coords.h"
#include "tgrid.h"
#include "fd_grid.h"
#include "patch.h"
#include "patch_edge.h"
#include "patch_interp.h"
#include "ghost_zone.h"
#include "patch_system.h"
#include "Jacobian.h"
#include "gfns.h"
#include "gr.h"
namespace AHFinderDirect
{
using jtutil::error_exit;
namespace
{
void expansion_Jacobian_partial_SD(patch_system &ps, Jacobian &Jac,
bool print_msg_flag);
void add_ghost_zone_Jacobian(const patch_system &ps,
Jacobian &Jac,
fp mol,
const patch &xp, const ghost_zone &xmgz,
int x_II,
int xm_irho, int xm_isigma);
enum expansion_status
expansion_Jacobian_dr_FD(patch_system *ps_ptr, Jacobian *Jac_ptr, fp add_to_expansion,
bool initial_flag,
bool print_msg_flag);
}
//******************************************************************************
//
// If ps_ptr != NULL and Jac_ptr != NULL, this function computes the
// Jacobian matrix J[Theta(h)] of the expansion Theta(h). We assume
// that Theta(h) has already been computed.
//
// If ps_ptr == NULL and Jac_ptr == NULL, this function does a dummy
// computation, in which only any expansion() (and hence geometry
// interpolator) calls are done, these with the number of interpolation
// points set to 0 and all the output array pointers set to NULL.
//
// It's illegal for one but not both of ps_ptr and Jac_ptr to be NULL.
//
// Arguments:
// ps_ptr --> The patch system, or == NULL to do (only) a dummy computation.
// Jac_ptr --> The Jacobian, or == NULL to do (only) a dummy computation.
// add_to_expansion = A real number to add to the expansion.
//
// Results:
// This function returns a status code indicating whether the computation
// succeeded or failed, and if the latter, what caused the failure.
//
enum expansion_status
expansion_Jacobian(patch_system *ps_ptr, Jacobian *Jac_ptr,
fp add_to_expansion,
bool initial_flag,
bool print_msg_flag /* = false */)
{
const bool active_flag = (ps_ptr != NULL) && (Jac_ptr != NULL);
enum expansion_status status;
if (active_flag)
then expansion_Jacobian_partial_SD(*ps_ptr, *Jac_ptr,
print_msg_flag);
// this function looks at ps_ptr and Jac_ptr (non-NULL vs NULL)
// to choose a normal vs dummy computation
{
status = expansion_Jacobian_dr_FD(ps_ptr, Jac_ptr, add_to_expansion,
initial_flag,
print_msg_flag);
if (status != expansion_success)
then return status; // *** ERROR RETURN ***
}
return expansion_success; // *** NORMAL RETURN ***
}
//
// This function computes the partial derivative terms in the Jacobian
// matrix of the expansion Theta(h), by symbolic differentiation from
// the Jacobian coefficient (angular) gridfns. The Jacobian is traversed
// by rows, using equation (25) of my 1996 apparent horizon finding paper.
//
// Inputs (angular gridfns, on ghosted grid):
// h # shape of trial surface
// Theta # Theta(h) assumed to already be computed
// partial_Theta_wrt_partial_d_h # Jacobian coefficients
// partial_Theta_wrt_partial_dd_h # (also assumed to already be computed)
//
// Outputs:
// The Jacobian matrix is stored in the Jacobian object Jac.
//
namespace
{
void expansion_Jacobian_partial_SD(patch_system &ps, Jacobian &Jac,
bool print_msg_flag)
{
Jac.zero_matrix();
ps.compute_synchronize_Jacobian();
for (int xpn = 0; xpn < ps.N_patches(); ++xpn)
{
patch &xp = ps.ith_patch(xpn);
for (int x_irho = xp.min_irho(); x_irho <= xp.max_irho(); ++x_irho)
{
for (int x_isigma = xp.min_isigma(); x_isigma <= xp.max_isigma(); ++x_isigma)
{
//
// compute the main Jacobian terms for this grid point, i.e.
// partial Theta(this point x, Jacobian row II)
// ---------------------------------------------
// partial h(other points y, Jacobian column JJ)
//
// Jacobian row index
const int II = ps.gpn_of_patch_irho_isigma(xp, x_irho, x_isigma);
// Jacobian coefficients for this point
const fp Jacobian_coeff_rho = xp.gridfn(gfns::gfn__partial_Theta_wrt_partial_d_h_1,
x_irho, x_isigma);
const fp Jacobian_coeff_sigma = xp.gridfn(gfns::gfn__partial_Theta_wrt_partial_d_h_2,
x_irho, x_isigma);
const fp Jacobian_coeff_rho_rho = xp.gridfn(gfns::gfn__partial_Theta_wrt_partial_dd_h_11,
x_irho, x_isigma);
const fp Jacobian_coeff_rho_sigma = xp.gridfn(gfns::gfn__partial_Theta_wrt_partial_dd_h_12,
x_irho, x_isigma);
const fp Jacobian_coeff_sigma_sigma = xp.gridfn(gfns::gfn__partial_Theta_wrt_partial_dd_h_22,
x_irho, x_isigma);
// partial_rho, partial_rho_rho
{
for (int m_irho = xp.molecule_min_m();
m_irho <= xp.molecule_max_m();
++m_irho)
{
const int xm_irho = x_irho + m_irho;
const fp Jac_rho = Jacobian_coeff_rho * xp.partial_rho_coeff(m_irho);
const fp Jac_rho_rho = Jacobian_coeff_rho_rho * xp.partial_rho_rho_coeff(m_irho);
const fp Jac_sum = Jac_rho + Jac_rho_rho;
if (xp.is_in_nominal_grid(xm_irho, x_isigma))
then
{
const int xm_JJ = Jac.II_of_patch_irho_isigma(xp, xm_irho, x_isigma);
Jac.sum_into_element(II, xm_JJ, Jac_sum);
}
else
add_ghost_zone_Jacobian(ps, Jac,
Jac_sum,
xp, xp.minmax_rho_ghost_zone(m_irho < 0),
II, xm_irho, x_isigma);
}
}
// partial_sigma, partial_sigma_sigma
{
for (int m_isigma = xp.molecule_min_m();
m_isigma <= xp.molecule_max_m();
++m_isigma)
{
const int xm_isigma = x_isigma + m_isigma;
const fp Jac_sigma = Jacobian_coeff_sigma * xp.partial_sigma_coeff(m_isigma);
const fp Jac_sigma_sigma = Jacobian_coeff_sigma_sigma * xp.partial_sigma_sigma_coeff(m_isigma);
const fp Jac_sum = Jac_sigma + Jac_sigma_sigma;
if (xp.is_in_nominal_grid(x_irho, xm_isigma))
then
{
const int xm_JJ = Jac.II_of_patch_irho_isigma(xp, x_irho, xm_isigma);
Jac.sum_into_element(II, xm_JJ, Jac_sum);
}
else
add_ghost_zone_Jacobian(ps, Jac,
Jac_sum,
xp, xp.minmax_sigma_ghost_zone(m_isigma < 0),
II, x_irho, xm_isigma);
}
}
// partial_rho_sigma
{
for (int m_irho = xp.molecule_min_m();
m_irho <= xp.molecule_max_m();
++m_irho)
{
for (int m_isigma = xp.molecule_min_m();
m_isigma <= xp.molecule_max_m();
++m_isigma)
{
const int xm_irho = x_irho + m_irho;
const int xm_isigma = x_isigma + m_isigma;
const fp Jac_rho_sigma = Jacobian_coeff_rho_sigma * xp.partial_rho_sigma_coeff(m_irho, m_isigma);
if (xp.is_in_nominal_grid(xm_irho, xm_isigma))
then
{
const int xm_JJ = Jac.II_of_patch_irho_isigma(xp, xm_irho, xm_isigma);
Jac.sum_into_element(II, xm_JJ, Jac_rho_sigma);
}
else
{
const ghost_zone &xmgz = xp.corner_ghost_zone_containing_point(m_irho < 0, m_isigma < 0,
xm_irho, xm_isigma);
add_ghost_zone_Jacobian(ps, Jac,
Jac_rho_sigma,
xp, xmgz,
II, xm_irho, xm_isigma);
}
}
}
}
}
}
}
}
}
//******************************************************************************
//
// This function adds the ghost-zone Jacobian dependency contributions
// for a single ghost-zone point, to a Jacobian matrix.
//
// Arguments:
// ps = The patch system.
// Jac = (out) The Jacobian matrix.
// mol = The molecule coefficient.
// xp = The patch containing the center point of the molecule.
// xmgz = If the x+m point is in a ghost zone, this must be that ghost zone.
// If the x+m point is not in a ghost zone, this argument is ignored.
// x_II = The Jacobian row of the x point.
// xm_(irho,isigma) = The coordinates (in xp) of the x+m point of the molecule.
namespace
{
void add_ghost_zone_Jacobian(const patch_system &ps,
Jacobian &Jac,
fp mol,
const patch &xp, const ghost_zone &xmgz,
int x_II,
int xm_irho, int xm_isigma)
{
const patch_edge &xme = xmgz.my_edge();
const int xm_iperp = xme.iperp_of_irho_isigma(xm_irho, xm_isigma);
const int xm_ipar = xme.ipar_of_irho_isigma(xm_irho, xm_isigma);
// FIXME: this won't change from one call to another
// ==> it would be more efficient to reuse the same buffer
// across multiple calls on this function
int global_min_ym, global_max_ym;
ps.synchronize_Jacobian_global_minmax_ym(global_min_ym, global_max_ym);
jtutil::array1d<fp> Jacobian_buffer(global_min_ym, global_max_ym);
// on what other points y does this molecule point xm depend
// via the patch_system::synchronize() operation?
int y_iperp;
int y_posn, min_ym, max_ym;
const patch_edge &ye = ps.synchronize_Jacobian(xmgz,
xm_iperp, xm_ipar,
y_iperp,
y_posn, min_ym, max_ym,
Jacobian_buffer);
patch &yp = ye.my_patch();
// add the Jacobian contributions from the ym points
for (int ym = min_ym; ym <= max_ym; ++ym)
{
const int y_ipar = y_posn + ym;
const int y_irho = ye.irho_of_iperp_ipar(y_iperp, y_ipar);
const int y_isigma = ye.isigma_of_iperp_ipar(y_iperp, y_ipar);
const int y_JJ = Jac.II_of_patch_irho_isigma(yp, y_irho, y_isigma);
Jac.sum_into_element(x_II, y_JJ, mol * Jacobian_buffer(ym));
}
}
}
//******************************************************************************
//
// If ps_ptr != NULL and Jac_ptr != NULL, this function sums the d/dr
// terms into the Jacobian matrix of the expansion Theta(h), computing
// those terms by finite differencing.
//
// If ps_ptr == NULL and Jac_ptr == NULL, this function does a dummy
// computation, in which only any expansion() (and hence geometry
// interpolator) calls are done, these with the number of interpolation
// points set to 0 and all the output array pointers set to NULL.
//
// It's illegal for one but not both of ps_ptr and Jac_ptr to be NULL.
//
// The basic algorithm is that
// Jac += diag[ (Theta(h+epsilon) - Theta(h)) / epsilon ]
//
// Inputs (angular gridfns, on ghosted grid):
// h # shape of trial surface
// Theta # Theta(h) assumed to already be computed
//
// Outputs:
// Jac += d/dr terms
//
// Results:
// This function returns a status code indicating whether the computation
// succeeded or failed, and if the latter, what caused the failure.
//
namespace
{
enum expansion_status
expansion_Jacobian_dr_FD(patch_system *ps_ptr, Jacobian *Jac_ptr, fp add_to_expansion,
bool initial_flag,
bool print_msg_flag)
{
const bool active_flag = (ps_ptr != NULL) && (Jac_ptr != NULL);
const double epsilon = 1e-6;
// compute Theta(h+epsilon)
if (active_flag)
then
{
ps_ptr->gridfn_copy(gfns::gfn__Theta, gfns::gfn__save_Theta);
ps_ptr->add_to_ghosted_gridfn(epsilon, gfns::gfn__h);
}
const enum expansion_status status = expansion(ps_ptr, add_to_expansion,
initial_flag);
if (status != expansion_success)
then return status; // *** ERROR RETURN ***
if (active_flag)
then
{
for (int pn = 0; pn < ps_ptr->N_patches(); ++pn)
{
patch &p = ps_ptr->ith_patch(pn);
for (int irho = p.min_irho(); irho <= p.max_irho(); ++irho)
{
for (int isigma = p.min_isigma();
isigma <= p.max_isigma();
++isigma)
{
const int II = ps_ptr->gpn_of_patch_irho_isigma(p, irho, isigma);
const fp old_Theta = p.gridfn(gfns::gfn__save_Theta,
irho, isigma);
const fp new_Theta = p.gridfn(gfns::gfn__Theta,
irho, isigma);
const fp d_dr_term = (new_Theta - old_Theta) / epsilon;
Jac_ptr->sum_into_element(II, II, d_dr_term);
}
}
}
// restore h and Theta
ps_ptr->add_to_ghosted_gridfn(-epsilon, gfns::gfn__h);
ps_ptr->gridfn_copy(gfns::gfn__save_Theta, gfns::gfn__Theta);
}
return expansion_success; // *** NORMAL RETURN ***
}
}
//******************************************************************************
} // namespace AHFinderDirect
#endif