iopdownsamplingfilter function in digital.py has wrong coefficients for 65kHz to 2kHz IOP filter
While getting lost in the details of trying to solve a mystery for the seismic team, I went to my go-to function for plotting the iop down sampling filters. In the latest version of pydarm within this project, this filter is defined by the coefficients within the https://git.ligo.org/Calibration/pydarm/-/blob/master/pydarm/digital.py library, under iopdownsamplingfilters
This is, naturally, the third or fourth generation of this function as it's been coped around since well before O1 when I first wrote the function circa 2012, when we still thought O1 was going to be called "S7."
- https://svn.ligo.caltech.edu/svn/aligocalibration/trunk/Common/pyDARM/src/iopdownsamplingfilters.py
- https://svn.ligo.caltech.edu/svn/aligocalibration/trunk/Runs/O2/DARMmodel/src/iopdownsamplingfilters.m
- It once lived in https://svn.ligo.caltech.edu/svn/aligocalibration/trunk/Runs/S7/Common/MatlabTools/ as indicated in LHO aLOG 17847, but I think when Evan and I started to convert the DARM loop model from matlab to python, we had a spell of "wow! This file was hard to find, and we should make sure there's only one copy of it" and removed it from the svn -- and then I told him to aLOG that we'd done so, and thus LHO aLOG 27173
But, when I wrote it, I was deceived by documentation in T1500075 that only the 4 kHz IOP downsampling filter had been modified for the purposes of the SEI team, reflecting what happened 2009 -- when Matt Evans got us excited about digital filter noise, (see G0900928).
This document is wrong. The 2 kHz IOP filter also has a softer roll-off in phase, like the 4 kHz design.
You can find this to be true in the code that defines the hard-coded coefficients, which now lives in gitlab, https://git.ligo.org/cds/advligorts/-/blob/master/src/include/controller.h where you can see the comments around Line 410 retain the history of the filter,
/* Coeffs for the 32x downsampling filter (2K system) */
#if 0
/* Original Rana coeffs from 40m lab elog */
static double __attribute__ ((unused)) feCoeff32x[9] =
{0.0001104130574447,
0.9701834961388200, -0.0010837026165800, -0.0200761119821899, 0.0085463156103800,
0.9871502388637901, -0.0039246182095299, 3.9871502388637898, 3.9960753817904697};
#endif
/* Coeffs for the 32x downsampling filter (2K system) per Brian Lantz May 5,
* 2009 */
static double __attribute__( ( unused ) ) feCoeff32x[ 9 ] = {
0.010581064947739, 0.90444302586137004, -0.0063413204375699639,
-0.056459743474659874, 0.032075154877300172, 0.92390910024681006,
-0.0097523655540199261, 0.077383808424050127, 0.14238741130302013
};
Which was true all the way back
- in RCG 3.0.2 -- see 2016 code starting on lines 207 of https://redoubt.ligo-wa.caltech.edu/svn/advLigoRTS/tags/advLigoRTS-3.0.2/src/fe/controller.c
- in RCG 2.0.2 -- see 2011 code starting on lines 390 of https://redoubt.ligo-wa.caltech.edu/svn/advLigoRTS/tags/advLigoRTS-2.0.2/src/fe/controller.c
- in RCG 1.9 -- see 2010 code starting on lines 348 of https://redoubt.ligo-wa.caltech.edu/svn/advLigoRTS/tags/advLigoRTS-1.9/src/fe/controller.c
So.... why should pyDARM care? it doesn't. We've always calibrated the DARM loop, which has always run at 16 kHz, and those filter coefficients have been right. And the code even makes the right update to the 16 kHz filter at RCG 3.0.2, when Peter redesigned the filter to have less phase loss, a la T1600066.
The only reason to update this is such that when Jeff Kissel points other groups of people who learn and forget about these filters all the time (usually because they don't have to care), then it'll be the right transfer function.
I can take this as an action item.