Source code for pycif.plugins.obsoperators.standard.transforms.utils.precursors_successors

import itertools


[docs] def add_precursors_successors( self, all_transforms, transforms_ids, mapper, pipe_links, pipe_subend, transf, simu, mode="adjoint", fetch_precursors=True): """Register a transform's sub-simulation and link it to its immediate neighbours. Called once per ``(transform, sub-simulation)`` pair, twice (with ``fetch_precursors`` True then False) from :func:`..fwd_pipe.fwd_adj_pipe`. Appends ``(simu, transf, "adjoint" if fetch_precursors else "forward")`` to ``transforms_ids`` and, unless already present in ``pipe_links``, initializes its (empty) list of links — skipping dead-end transforms that have no successors/precursors and are not a pipe end/start (unless ``self.force_full_operator`` is set). Then, for each input trid (if ``fetch_precursors``) or output trid (otherwise) of this sub-simulation, walks its precursors/successors and, for each of their matching sub-simulations, appends the corresponding node id to ``pipe_links[transf_id]``. If the trid is flagged with ``break_{fwd,adj}_onlyinit_pipe`` on either side, the link is instead redirected to a same-transform self-loop and the neighbour is recorded in ``pipe_subend`` (used later to seed the virtual end-of-pipe node), so that only-init propagation stops there instead of walking further into the neighbour. If the transform has no precursors/successors at all in the relevant direction, appends a self-loop link so the transform still appears in its own pipe. Args: self: The parent object (obs operator), exposing ``force_full_operator``. all_transforms: Namespace holding all registered transform instances, used to check ``end_pipe``/``start_pipe`` flags. transforms_ids: List of every registered ``(ddi, transform, direction)`` node id, mutated in place. mapper: Dictionary mapping each transform id to its inputs/outputs/ precursors/successors/subsimus metadata. pipe_links: Mapping of each node id to the list of its immediate neighbour node ids, mutated in place. pipe_subend: List of neighbour node ids reached through a ``break_*_onlyinit_pipe`` boundary, mutated in place; used to seed the virtual end-of-pipe node. transf: Id of the transform being processed. simu: Sub-simulation date/index being processed. mode: Either ``"forward"`` or ``"adjoint"``, the direction of the pipe currently being built (distinct from ``fetch_precursors``, which selects which side of the transform to walk). fetch_precursors: If True, walk the transform's inputs/precursors (building the adjoint-direction node); if False, walk its outputs/successors (building the forward-direction node). Returns: None. ``transforms_ids``, ``pipe_links`` and ``pipe_subend`` are mutated in place. """ out_mode = "adjoint" if fetch_precursors else "forward" transf_id = (simu, transf, out_mode) if transf_id not in transforms_ids: transforms_ids.append(transf_id) # If the transform is not already in the pipe, # add it only if it has some successors (resp. precursors), # or if it is related to the observation vector (resp. control vector) # in adjoint (resp. forward) mode # Other transformations are dead-ends and don't need to be computed if transf_id not in pipe_links: all_successors = \ [success for trid in mapper[transf]["successors"] for success in mapper[transf]["successors"][trid]] if not all_successors: if not getattr(all_transforms, transf).end_pipe \ and not self.force_full_operator: return all_precursors = \ [precurs for trid in mapper[transf]["precursors"] for precurs in mapper[transf]["precursors"][trid]] if not all_precursors: if not getattr(all_transforms, transf).start_pipe \ and not self.force_full_operator: return pipe_links[transf_id] = [] # Loop on all precursors and check whether they produce outputs # necessary for the present sub-simulation transf_subsimus = mapper[transf]["subsimus"][simu] if fetch_precursors: neighbours_id = "precursors" transform_inout = "inputs" neighbour_inout = "outputs" else: neighbours_id = "successors" transform_inout = "outputs" neighbour_inout = "inputs" transf_neighbours = mapper[transf][neighbours_id] for trid in transf_neighbours: if trid not in transf_subsimus[transform_inout]: continue # Loop on the input sub-simulation for that trid trid_subsimus = transf_subsimus[transform_inout][trid] if mode == "adjoint": trid_subsimus = list(trid_subsimus.keys())[::-1] trid_neighbours = transf_neighbours[trid] for neigh_simu, neighbour in itertools.product( trid_subsimus, trid_neighbours): neighbour_subsimus = mapper[neighbour]["subsimus"] is_in_neighbours = [ tmp_simu for tmp_simu in neighbour_subsimus if neigh_simu in neighbour_subsimus[ tmp_simu][neighbour_inout].get(trid, []) ] if mode == "adjoint": is_in_neighbours = is_in_neighbours[::-1] for tmp_simu in is_in_neighbours: neigh_id = (tmp_simu, neighbour, "adjoint" if fetch_precursors else "forward") if neigh_id in transforms_ids: transforms_ids.remove(neigh_id) transforms_ids.append(neigh_id) # Do not add precursor in onlyinit mode if # no propagation of information break_id = f"break_{'fwd' if mode == 'forward' else 'adj'}" \ f"_onlyinit_pipe" break_transf = mapper[transf][transform_inout][trid].get( break_id, False) break_neighbour = mapper[neighbour][neighbour_inout][trid].get( break_id, False) if ( ((fetch_precursors and mode == "forward") or (not fetch_precursors and mode == "adjoint")) and (break_transf or break_neighbour) ): if (transf_id[0], transf_id[1], mode) \ not in pipe_links[transf_id]: pipe_links[transf_id].append( (transf_id[0], transf_id[1], mode)) pipe_subend.append(neigh_id) # setattr( # getattr(all_transforms, neigh_id[1]), # "end_pipe" if mode == "forward" else "start_pipe", # True # ) continue # # Do not add precursor in onlyinit mode if # # no propagation of information # if fetch_precursors and mode == "forward": # if mapper[transf][transform_inout][trid].get( # "break_fwd_onlyinit_pipe", False): # pipe_links[transf_id].append( # (transf_id[0], transf_id[1], "forward")) # getattr(all_transforms, neigh_id[1]).end_pipe = True # continue # # if not fetch_precursors and mode == "adjoint": # if mapper[transf][transform_inout][trid].get( # "break_adj_onlyinit_pipe", False): # pipe_links[transf_id].append( # (transf_id[0], transf_id[1], "adjoint")) # getattr(all_transforms, neigh_id[1]).start_pipe = True pipe_links[transf_id].append(neigh_id) # When no precursor, set precursor to same transform in forward if len(list(itertools.chain(*transf_neighbours.values()))) == 0: if fetch_precursors and mode == "forward": pipe_links[transf_id].append( (transf_id[0], transf_id[1], "forward") ) return if not fetch_precursors and mode == "adjoint": pipe_links[transf_id].append( (transf_id[0], transf_id[1], "adjoint") ) return