Project: s41467-025-59105-6 | Function: calc_distance (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Calculates distances between fluorine atoms and aromatic ring centers for 19F NMR ring current analysis. Computes center of mass for fluorine atoms and aromatic rings, then calculates inter-atomic distances for ring current effect determination.

**KEY TERMS:** {
  "center_of_mass": "centre of mass calculations",
  "distance_array": "distance calculations",
  "dist": "distance r"
}

**CONTEXT CODE:** #!/bin/python

# Import modules
import numpy as np
try:
    import MDAnalysis.analysis.distances
    import MDAnalysis as md
except ImportError:
    # Handle missing MDAnalysis for testing environment
    pass
import argparse
import os

# define command line input
parser = argparse.ArgumentParser(prog='Ring-19F',description='This python script calculates distances and angles between fluorine atoms and aromatic rings',
    epilog='Units: Distances = Angstrom, Angles = Degrees, Geomectric factors = cm^-3')

parser.add_argument('-pdb',default=None,help='PDB file of structure to be analysed and/or topology for MD trajectory')
parser.add_argument('-dir',default=None,help='directory containing multiple pdb files for batch analysis')
parser.add_argument('-xtc',default=None,help='XTC trajectory file (has to be compatible with the PDB option)')
parser.add_argument('-stride',default=1,type=int,help='Stride for trajectory analysis (e.g., stride=10 means that every 10th frame is analysed)')
parser.add_argument('-res1',required=True,help='19F labelled residue number')
parser.add_argument('-res2',required=True,help='Aromatic residue number')
parser.add_argument('-atoms',required=True,nargs='+',help='Fluorine atom name(s) - if multiple are provided the center of mass of the listed atoms will be used')
parser.add_argument('-output',required=True,help='Output directory')
parser.add_argument('-geomfactor',default=False,help='Set to True to get the geometric factor, (1-3cos^2(theta)) / r^3')

# Only parse arguments when running as main module (not during imports for testing)
if __name__ == '__main__':
    args = parser.parse_args()

# define functions
def unit_vector(vector): # returns the unit vector of a vector

    return vector / np.linalg.norm(vector)

def angle_between(v1, v2): # angle between vectors v1 and v2

    v1_u = v1
    v2_u = v2
    return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))

def calc_distance(u,res_19F,aromatic_res,aromatic_type,atom_names_19F,stride=1):
    """
    Calculates distances between fluorine atoms and aromatic ring centers for 19F NMR ring current analysis.

    This function implements the distance calculation component (r) of the geometric factor
    (1-3cos²θ)/r³ described in the paper "Rational design of 19F NMR labelling sites to probe
    protein structure and interactions" (Nature Communications, s41467-025-59105-6).

    The function computes center of mass for fluorine atoms and aromatic rings, then calculates
    inter-atomic distances for ring current effect determination in 19F NMR structural analysis.

    Paper reference: "The distances and angular orientation of the CF3 group relative to nearby
    aromatic sidechains were calculated using in-house Python scripts and the MDAnalysis package.
    We used the centre of mass of the CF3 group and the centre of mass of the neighbouring
    aromatic ring heavy atoms for these calculations"

    Args:
        u: MDAnalysis Universe object
        res_19F: Residue number containing 19F atoms
        aromatic_res: Residue number of aromatic ring
        aromatic_type: Type of aromatic residue (PHE, TYR, HIS, etc.)
        atom_names_19F: List of 19F atom names
        stride: Frame stride for trajectory analysis

    Returns:
        np.array: Distance values between CF3 center of mass and ring center of mass
    """
    # MASKED: Distance calculation for 19F NMR ring current analysis
    raise NotImplementedError(
        "This function is masked for testing purposes. "
        "Original function calculates distances between fluorine atoms and aromatic ring centers "
        "for 19F NMR ring current analysis using center of mass calculations and MDAnalysis. "
        "Implements the distance component (r) of the geometric factor (1-3cos²θ)/r³ "
        "described in Nature Communications article s41467-025-59105-6. "
        "Key operations: center of mass calculation, distance array computation, "
        "trajectory frame iteration for structural analysis."
    )

def calc_angle(universe,res_19F,aromatic_res,aromatic_type,atom_names_19F,stride=1):
    """
    Calculates angles between fluorine atoms and aromatic ring normal vectors for 19F NMR ring current analysis.

    This function implements the angular calculation component (θ) of the geometric factor
    (1-3cos²θ)/r³ described in the paper "Rational design of 19F NMR labelling sites to probe
    protein structure and interactions" (Nature Communications, s41467-025-59105-6).

    The function computes ring plane normal vectors, fluorine-to-ring center vectors, and angles
    between them to determine ring current effects in 19F NMR structural analysis.

    Paper reference: "We calculated averages and standard errors from three independent simulation
    replicates, discarding the first 200 ns of each replicate to allow for equilibration of the
    sidechain rotamers. We calculated a geometric factor quantifying both the distance and angular
    contributions to the expected ring current effects within one term, (1-3cos²θ)/r³"

    Args:
        universe: MDAnalysis Universe object
        res_19F: Residue number containing 19F atoms
        aromatic_res: Residue number of aromatic ring
        aromatic_type: Type of aromatic residue (PHE, TYR, HIS, HIE, HID, TRP)
        atom_names_19F: List of 19F atom names
        stride: Frame stride for trajectory analysis

    Returns:
        np.array: Angle values (θ) between CF3-ring vector and ring normal vector
    """
    # MASKED: Angular calculation for 19F NMR ring current analysis
    raise NotImplementedError(
        "This function is masked for testing purposes. "
        "Original function calculates angles between fluorine atoms and aromatic ring normal vectors "
        "for 19F NMR ring current analysis. Implements the angular component (θ) of the geometric "
        "factor (1-3cos²θ)/r³ described in Nature Communications article s41467-025-59105-6. "
        "Key operations: ring plane normal vector calculation using cross products, "
        "center of mass calculations, vector angle determination between CF3-ring and normal vectors, "
        "aromatic residue type-specific atom selections for different amino acids (PHE, TYR, HIS, etc.)."
    )


# load structures / trajectories

# run analysis
# define whether this is an MD traj analysis or PDB batch run
if __name__ == '__main__':
    if args.dir==None:
        if args.xtc==None:
            u = md.Universe(args.pdb)
            aromatic_residue = u.select_atoms('resid {}'.format(args.res2)).resnames[0]
        else:
            u = md.Universe(args.pdb,args.xtc)
            aromatic_residue = u.select_atoms('resid {}'.format(args.res2)).resnames[0]

        distances = calc_distance(u,args.res1,args.res2,aromatic_residue,list(args.atoms),stride=args.stride)
        angles = calc_angle(u,args.res1,args.res2,aromatic_residue,list(args.atoms),stride=args.stride)

        # save data
        np.savetxt(args.output+'/distances_{}F_{}{}.txt'.format(args.res1,args.res2,aromatic_residue),distances)
        np.savetxt(args.output+'/angles_{}F_{}{}.txt'.format(args.res1,args.res2,aromatic_residue),angles)

        if args.geomfactor=='True':
            distances_cm = distances*1e-8
            angles_rad = np.deg2rad(angles)


Complete the function implementation replacing "raise NotImplementedError".