Source code for finesse.thermal.reciprocity

"""Compute thermo-elastic deformations in optical substrates using reciprocity theorem
:cite:`PhysRevD.92.022005`. This provides a more accurate method to model thermo-elastic
deformation of surfaces when the temperature distribution is known throughout a
substrate. Reciprocity used here requires finite-element model results for volumetric
strain due to a Zernike based force applied to the surface of the substrate. Substrate
temperature distributions can then be converted into displacements and overlaps with the
volumetric strain calculated. The coefficients of these overlaps then describes the
zernike surface decomposition.

Equations all based on :cite:`PhysRevD.92.022005`:

    Modeling thermoelastic distortion of optics using elastodynamic reciprocity
    Eleanor King, Yuri Levin, David Ottaway, and Peter Veitch
    Phys. Rev. D 92, 022005 - Published 20 July 2015
    https://doi.org/10.1103/PhysRevD.92.022005

The methods here have been adapted from code and work done by Huy Tuong Cao
<huy-tuong.cao@LIGO.org>.
"""

import numpy as np
from finesse.cymath import zernike


[docs]class AxisymmetricFEAData: """An object that contains the finite element analysis data required to perform Axisymmetric thermal reciprocity calculations. This data can either be loaded from a numpy npz file that contains the r, z, V, and material data or can be provided directly to the the constructor. Attributes ---------- a : float Radius of optic in meter h : float Thickness of optic in meter r : array_like 1D radial point vector ranging from [0, a] with Nr elements z : array_like 1D thickness point vector ranging from [0, h] with Nz elements R, Z : array_like 2D meshgrid arrays of self.r, self.z V : array_like An (NV, Nz, Nr) shaped array representing the 2D basis functions for this reciprocity. material : :class:`finesse.materials.Material` An object containing various thermal and mechanical properties for the optic being modelled. Must match the values being used in the finite element model generating `self.V`. """ def __init__(self, r=None, z=None, V=None, material=None, filepath=None): if filepath: load = np.load(filepath, allow_pickle=True) self.r = load["r"] self.z = load["z"] self.V = load["V"] self.material = load["material"][()] elif r is not None and z is not None and V is not None and material is not None: self.r = r self.z = z self.V = V self.material = material else: raise RuntimeError( "Please specify r, z, V, and material or provide a .npz filename that does contain them." ) if self.V.shape[1:] != (self.z.size, self.r.size): raise RuntimeError( f"Shape of {self.V.shape} is not correct for the r and z vector" ) self.a = self.r.max() self.h = self.z.max() self.R, self.Z = np.meshgrid(self.r, self.z) @property def NV(self): return self.V.shape[0] @property def Nr(self): """Number of points in the radial vector.""" return self.r.size @property def Nz(self): return self.z.size @property def dr(self): return self.r[1] - self.r[0] @property def dz(self): return self.z[1] - self.z[0]
[docs]def zernike_coefficients_axisymmetric(data: AxisymmetricFEAData, T): """Compute the Zernike coefficient from temperature substrate profile and volumetric strain. This is for axially symmetric heating profiles and distortions. Parameters ---------- data : :class:`AxisymmetricFEAData` Finite element model data T : array_like Array of shape (data.Nz, data.Nr) of temperature over the finite element model domain. Returns ------- Z_coeffs : array_like Array of shape data.NV of Zernike coefficients of the surface """ Z_coeffs = np.zeros(data.NV) dr = data.a * (1 / (data.Nr)) dz = data.h * (1 / (data.Nz)) CZern = ( (1 / data.a**2) * data.material.E * data.material.alpha / (1 - 2 * data.material.poisson) ) dA = dr * dz # TODO - This could be sped up a bit with numpy broadcasting for i in range(data.NV): Z_coeffs[i] = 2 * np.pi * CZern * np.sum(data.V[i] * T * data.R * dA) return Z_coeffs
[docs]def zernike_surface_axisymmetric(data: AxisymmetricFEAData, Z_coeffs): """Reconstruct the surface deformation from Zernike coefficients. Parameters ---------- data : :class:`AxisymmetricFEAData` Finite element model data Z_coeffs : array_like Zernike coefficients in an array of shape `data.NV` Returns ------- W : array_like Surface displacement in metres """ n0_2D = np.arange(2, 2 * data.NV + 2, 2) # Even Zernike n-coeffs m0_2D = np.zeros(len(n0_2D)) W = np.zeros(data.r.shape) # Variable to store deformation # TODO - This could be sped up a bit with numpy broadcasting for iN, iM, cZ in zip(n0_2D, m0_2D, Z_coeffs): W -= cZ * zernike.Znm_eval(data.r, np.array([0]), iN, iM, data.a) W -= W[0] # zero center of displacement return W