From 357a8445f16e10b40158d8e005179812ecc6cc86 Mon Sep 17 00:00:00 2001 From: Greg Allan Date: Thu, 27 Mar 2025 15:54:21 -0700 Subject: [PATCH 1/6] Use testbed interface for physical testbeds --- falco/est.py | 20 ++++++++++++-------- falco/imaging.py | 16 ++++++++++++---- falco/wfsc.py | 6 ++++-- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/falco/est.py b/falco/est.py index 6e38f8e..af619fb 100644 --- a/falco/est.py +++ b/falco/est.py @@ -9,7 +9,7 @@ from . import check -def wrapper(mp, ev, jacStruct): +def wrapper(mp, ev, jacStruct, tb = None): """ Select and run the chosen estimator. @@ -23,6 +23,8 @@ def wrapper(mp, ev, jacStruct): Structure containing estimation variables. jacStruct : ModelParameters Structure containing control Jacobians for each specified DM. + tb : pyHCIT_hwControl.interface.TestbedInterface or None + (Optional) Control interface for a physical testbed. Returns ------- @@ -33,15 +35,15 @@ def wrapper(mp, ev, jacStruct): falco.est.perfect(mp, ev) with falco.util.TicToc('Getting updated summed image'): - ev.Im = falco.imaging.get_summed_image(mp) + ev.Im = falco.imaging.get_summed_image(mp, tb) elif mp.estimator in ['pairwise', 'pairwise-square', 'pairwise-rect', 'pwp-bp-square', 'pwp-bp', 'pwp-kf']: if mp.est.flagUseJac: # Send in the Jacobian if true - falco.est.pairwise_probing(mp, ev, jacStruct=jacStruct) + falco.est.pairwise_probing(mp, ev, jacStruct=jacStruct, tb) else: # Otherwise don't pass the Jacobian - falco.est.pairwise_probing(mp, ev) + falco.est.pairwise_probing(mp, ev, tb) return None @@ -219,7 +221,7 @@ def _est_perfect_Efield_with_Zernikes_in_parallel(mp, ilist, inds_list): return E2D -def pairwise_probing(mp, ev, jacStruct=np.array([])): +def pairwise_probing(mp, ev, jacStruct=np.array([]), tb = None): """ Estimate the dark hole E-field with pair-wise probing. @@ -230,6 +232,8 @@ def pairwise_probing(mp, ev, jacStruct=np.array([])): ev : falco.config.Object() jacStruct : array_like, optional Array containing the control Jacobian. Default is an empty array. + tb : pyHCIT_hwControl.interface.TestbedInterface or None + (Optional) Control interface for a physical testbed. Returns ------- @@ -394,7 +398,7 @@ def pairwise_probing(mp, ev, jacStruct=np.array([])): # Take initial, unprobed image (for unprobed DM settings). whichImage = 0 - I0 = falco.imaging.get_sbp_image(mp, iSubband) + I0 = falco.imaging.get_sbp_image(mp, iSubband, tb) I0vec = I0[mp.Fend.corr.maskBool] # Vectorize the dark hole # Image already includes all stars, so don't sum over star loop @@ -460,7 +464,7 @@ def pairwise_probing(mp, ev, jacStruct=np.array([])): mp.dm2.V = DM2Vnom + dDM2Vprobe # Take probed image - Im = falco.imaging.get_sbp_image(mp, iSubband) + Im = falco.imaging.get_sbp_image(mp, iSubband, tb) # ImNonneg = Im # ImNonneg[Im < 0] = 0 @@ -875,4 +879,4 @@ def gen_pairwise_probe(mp, InormDes, phaseShift, starIndex, rotation): probeCmd = falco.dm.fit_surf_to_act(dm, probeHeight) probeCmd = mp.est.probe.gainFudge[starIndex] * probeCmd # Scale the probe amplitude empirically if needed - return probeCmd \ No newline at end of file + return probeCmd diff --git a/falco/imaging.py b/falco/imaging.py index 316435c..c8b673c 100644 --- a/falco/imaging.py +++ b/falco/imaging.py @@ -4,6 +4,7 @@ from concurrent.futures import ThreadPoolExecutor as PoolExecutor # from concurrent.futures import ProcessPoolExecutor as PoolExecutor import matplotlib.pyplot as plt +from pyHCIT_hwControl.interface import TestbedInterface import falco from falco import check @@ -246,7 +247,7 @@ def _model_full_norm_wrapper(mp, ilist, inds_list): return np.max(np.abs(Etemp)**2) -def get_summed_image(mp): +def get_summed_image(mp, tb = None): """ Get the broadband image over the entire bandpass. @@ -257,6 +258,8 @@ def get_summed_image(mp): ---------- mp: falco.config.ModelParameters Structure of model parameters + tb: pyHCIT_hwControl.interface.TestbedInterface + (Optional) control interface for a physical testbed Returns ------- @@ -270,7 +273,7 @@ def get_summed_image(mp): if not (mp.flagParallel and mp.flagSim): summedImage = 0 for si in range(mp.Nsbp): - summedImage += mp.sbp_weights[si] * get_sbp_image(mp, si) + summedImage += mp.sbp_weights[si] * get_sbp_image(mp, si, tb) else: # Compute simulated images in parallel @@ -360,7 +363,7 @@ def _get_single_sim_full_image(mp, ilist, vals_list): return np.abs(Estar)**2 # Apply spectral weighting outside this function -def get_sbp_image(mp, si): +def get_sbp_image(mp, si, tb = None): """ Get an image in the specified sub-bandpass. @@ -373,6 +376,8 @@ def get_sbp_image(mp, si): Structure of model parameters si: int Index of sub-bandpass for which to take the image + tb: pyHCIT_hwControl.interface.TestbedInterface or None + (Optional) Control interface for a physical testbed Returns ------- @@ -387,7 +392,10 @@ def get_sbp_image(mp, si): if mp.flagSim: Isbp = get_sim_sbp_image(mp, si) else: - Isbp = get_testbed_sbp_image(mp, si) + if type(tb) is not TestbedInterface: + raise TypeError('Input "tb" must be of type TestbedInterface') + tb.dm.apply(mp.dm1.V) + Isbp = tb.get_sbp_image(si) return Isbp diff --git a/falco/wfsc.py b/falco/wfsc.py index cae6fc2..d4ee151 100644 --- a/falco/wfsc.py +++ b/falco/wfsc.py @@ -9,7 +9,7 @@ import falco -def loop(mp, out): +def loop(mp, out, tb = None): """ Loop over the estimator and controller for WFSC. @@ -19,6 +19,8 @@ def loop(mp, out): Structure of model parameters out : falco.config.Object Output variables + tb : pyHCIT_hwControl.interface.TestbedInterface or None + (Optional) Control interface for a physical testbed. Returns ------- @@ -90,7 +92,7 @@ def loop(mp, out): if Itr > 0: EestPrev = ev.Eest # save previous estimate for Delta E plot - falco.est.wrapper(mp, ev, jacStruct) + falco.est.wrapper(mp, ev, jacStruct, tb) store_intensities(mp, out, ev, Itr) From e8ba6b0c66cf3425d27f8dc2130714a5947bcbe2 Mon Sep 17 00:00:00 2001 From: joelco Date: Tue, 8 Jul 2025 15:12:13 -0700 Subject: [PATCH 2/6] Fix pairwise probing tb argument --- falco/est.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/falco/est.py b/falco/est.py index af619fb..4437a57 100644 --- a/falco/est.py +++ b/falco/est.py @@ -41,9 +41,9 @@ def wrapper(mp, ev, jacStruct, tb = None): 'pwp-bp-square', 'pwp-bp', 'pwp-kf']: if mp.est.flagUseJac: # Send in the Jacobian if true - falco.est.pairwise_probing(mp, ev, jacStruct=jacStruct, tb) + falco.est.pairwise_probing(mp, ev, jacStruct=jacStruct, tb=tb) else: # Otherwise don't pass the Jacobian - falco.est.pairwise_probing(mp, ev, tb) + falco.est.pairwise_probing(mp, ev, tb=tb) return None From a759cc0b4039ecf9c89e51139edb48c8287fd0be Mon Sep 17 00:00:00 2001 From: joelco Date: Wed, 9 Jul 2025 10:27:29 -0700 Subject: [PATCH 3/6] Expose astropy to mp config --- falco/config/ModelParameters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/falco/config/ModelParameters.py b/falco/config/ModelParameters.py index 3441450..c72d48a 100755 --- a/falco/config/ModelParameters.py +++ b/falco/config/ModelParameters.py @@ -9,6 +9,7 @@ from falco.config.yaml_loader import object_constructor, load_from_str from falco.util import _spec_arg from falco.config import Probe, ProbeSchedule, Object +import astropy class ModelParameters(Object): @@ -251,7 +252,7 @@ def from_yaml(text, context=None): data = load_from_str( text, - {'np': numpy, 'falco': falco, 'math': math}, + {'np': numpy, 'falco': falco, 'math': math, 'astropy': astropy}, context, { "!Probe": object_constructor(Probe), From 46b63d08cfc3c2f846fbb719f3266f6e1bd9d1fd Mon Sep 17 00:00:00 2001 From: joelco Date: Wed, 9 Jul 2025 18:13:12 -0700 Subject: [PATCH 4/6] Remove type check on tb object --- falco/imaging.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/falco/imaging.py b/falco/imaging.py index c8b673c..38316cc 100644 --- a/falco/imaging.py +++ b/falco/imaging.py @@ -392,8 +392,6 @@ def get_sbp_image(mp, si, tb = None): if mp.flagSim: Isbp = get_sim_sbp_image(mp, si) else: - if type(tb) is not TestbedInterface: - raise TypeError('Input "tb" must be of type TestbedInterface') tb.dm.apply(mp.dm1.V) Isbp = tb.get_sbp_image(si) From 07648f379441494cf69eec7a15075ad6263fa107 Mon Sep 17 00:00:00 2001 From: hcit Date: Tue, 15 Jul 2025 09:58:12 -0700 Subject: [PATCH 5/6] Fix tb object passing --- falco/ctrl.py | 14 +++++++------- falco/wfsc.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/falco/ctrl.py b/falco/ctrl.py index 5c83f1a..6735630 100644 --- a/falco/ctrl.py +++ b/falco/ctrl.py @@ -14,7 +14,7 @@ import falco -def wrapper(mp, cvar, jacStruct): +def wrapper(mp, cvar, jacStruct, tb=None): """ Outermost wrapper function for all the controller functions. @@ -98,7 +98,7 @@ def wrapper(mp, cvar, jacStruct): print('Control beginning ...') # Established, conventional controllers if mp.controller.lower() == 'plannedefc': - dDM = _planned_efc(mp, cvar) + dDM = _planned_efc(mp, cvar, tb=tb) elif mp.controller.lower() == 'gridsearchefc': dDM = _grid_search_efc(mp, cvar) @@ -556,7 +556,7 @@ def _planned_ad_efc(mp, cvar): return dDM -def _planned_efc(mp, cvar): +def _planned_efc(mp, cvar, tb=None): """ Perform a scheduled/planned set of EFC iterations. @@ -627,7 +627,7 @@ def _planned_efc(mp, cvar): for ni in range(Nvals): - [InormVec[ni], dDM_temp] = _efc(ni, vals_list, mp, cvar) + [InormVec[ni], dDM_temp] = _efc(ni, vals_list, mp, cvar, tb=tb) ImCube[ni, :, :] = dDM_temp.Itotal # delta voltage commands @@ -703,7 +703,7 @@ def _planned_efc(mp, cvar): vals_list = [(x, y) for y in np.array([cvar.latestBestDMfac]) for x in np.array([log10regSchedOut])] - [cvar.cMin, dDM] = _efc(ni, vals_list, mp, cvar) + [cvar.cMin, dDM] = _efc(ni, vals_list, mp, cvar, tb=tb) cvar.Im = np.squeeze(dDM.Itotal) if mp.ctrl.flagUseModel: print(('Model expects scheduled log10(reg) = %.1f\t to give ' + @@ -800,7 +800,7 @@ def efc_schedule_generator(scheduleMatrix): return Nitr, relinItrVec, gridSearchItrVec, log10regSched, dm_ind_sched -def _efc(ni, vals_list, mp, cvar): +def _efc(ni, vals_list, mp, cvar, tb=None): """ Compute the main EFC equation. Called by a wrapper controller function. @@ -856,7 +856,7 @@ def _efc(ni, vals_list, mp, cvar): Itotal = falco.imaging.get_expected_summed_image(mp, cvar, dDM) InormAvg = np.mean(Itotal[mp.Fend.corr.maskBool]) else: # Get actual image from full model or testbed - Itotal = falco.imaging.get_summed_image(mp) + Itotal = falco.imaging.get_summed_image(mp, tb=tb) InormAvg = np.mean(Itotal[mp.Fend.corr.maskBool]) dDM.Itotal = Itotal diff --git a/falco/wfsc.py b/falco/wfsc.py index d4ee151..f623da5 100644 --- a/falco/wfsc.py +++ b/falco/wfsc.py @@ -124,7 +124,7 @@ def loop(mp, out, tb = None): # Send a copy of jacStruct so that spatial weights don't show up # outside the controller or get applied multiple times. - falco.ctrl.wrapper(mp, cvar, copy(jacStruct)) + falco.ctrl.wrapper(mp, cvar, copy(jacStruct), tb=tb) # Store key data in out object out.log10regHist[Itr] = cvar.log10regUsed From d1a68334e63622e640fc902c45aac89bc0b1741a Mon Sep 17 00:00:00 2001 From: joelco Date: Wed, 10 Dec 2025 14:29:05 -0800 Subject: [PATCH 6/6] Move testbed interface into falco --- falco/config/TestbedInterface.py | 9 +++++++++ falco/est.py | 4 ++-- falco/imaging.py | 5 ++--- falco/wfsc.py | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 falco/config/TestbedInterface.py diff --git a/falco/config/TestbedInterface.py b/falco/config/TestbedInterface.py new file mode 100644 index 0000000..47ce0b2 --- /dev/null +++ b/falco/config/TestbedInterface.py @@ -0,0 +1,9 @@ +from abc import ABC, abstractmethod + +class TestbedInterface(ABC): + '''A general purpose interface for all testbeds to implement.''' + + @abstractmethod + def get_sbp_image(self, subband_index): + pass + diff --git a/falco/est.py b/falco/est.py index 4437a57..81f7b89 100644 --- a/falco/est.py +++ b/falco/est.py @@ -23,7 +23,7 @@ def wrapper(mp, ev, jacStruct, tb = None): Structure containing estimation variables. jacStruct : ModelParameters Structure containing control Jacobians for each specified DM. - tb : pyHCIT_hwControl.interface.TestbedInterface or None + tb : falco.config.TestbedInterface or None (Optional) Control interface for a physical testbed. Returns @@ -232,7 +232,7 @@ def pairwise_probing(mp, ev, jacStruct=np.array([]), tb = None): ev : falco.config.Object() jacStruct : array_like, optional Array containing the control Jacobian. Default is an empty array. - tb : pyHCIT_hwControl.interface.TestbedInterface or None + tb : falco.config.TestbedInterface or None (Optional) Control interface for a physical testbed. Returns diff --git a/falco/imaging.py b/falco/imaging.py index 38316cc..d8b49f8 100644 --- a/falco/imaging.py +++ b/falco/imaging.py @@ -4,7 +4,6 @@ from concurrent.futures import ThreadPoolExecutor as PoolExecutor # from concurrent.futures import ProcessPoolExecutor as PoolExecutor import matplotlib.pyplot as plt -from pyHCIT_hwControl.interface import TestbedInterface import falco from falco import check @@ -258,7 +257,7 @@ def get_summed_image(mp, tb = None): ---------- mp: falco.config.ModelParameters Structure of model parameters - tb: pyHCIT_hwControl.interface.TestbedInterface + tb: falco.config.TestbedInterface (Optional) control interface for a physical testbed Returns @@ -376,7 +375,7 @@ def get_sbp_image(mp, si, tb = None): Structure of model parameters si: int Index of sub-bandpass for which to take the image - tb: pyHCIT_hwControl.interface.TestbedInterface or None + tb: falco.config.TestbedInterface or None (Optional) Control interface for a physical testbed Returns diff --git a/falco/wfsc.py b/falco/wfsc.py index f623da5..625f0c5 100644 --- a/falco/wfsc.py +++ b/falco/wfsc.py @@ -19,7 +19,7 @@ def loop(mp, out, tb = None): Structure of model parameters out : falco.config.Object Output variables - tb : pyHCIT_hwControl.interface.TestbedInterface or None + tb : falco.config.TestbedInterface or None (Optional) Control interface for a physical testbed. Returns