Skip to content

Handle variable number of arrays to interpolate #3

Description

@matthewrankin

Nothing stops you correcting for a preamp gain, an attenuator, or a second
cable today except that the API has no place to put them. cable_losses is
the only correction the signature admits, and it is not special: it is a
frequency/amplitude array that gets deduplicated, interpolated onto the
analyzer frequencies, and added. Any number of others would go through the
same three steps.

Proposed signature

A keyword-only mapping of name to array, alongside cable_losses rather than
in place of it:

def apply_antenna_factor(
    analyzer_readings: npt.NDArray,
    antenna_factors: npt.NDArray,
    cable_losses: npt.NDArray | None = None,
    keep_max: bool = True,
    allow_extrapolation: bool = False,
    *,
    corrections: Mapping[str, npt.NDArray] | None = None,
) -> npt.NDArray:

Each value is deduplicated with _remove_duplicate_frequencies under the same
keep_max, interpolated with _interpolate_at under the same
allow_extrapolation, and added to the result. remove_antenna_factor takes
the same argument and subtracts them, so that applying and removing with the
same corrections still round trips.

The key is what the correction is, and it reaches the user in
_interpolate_at's error message, which today reads

3 of 40 frequencies fall outside the frequencies covered by the antenna
factors, which span ...

and would read outside the frequencies covered by the preamp gain for
corrections={"preamp gain": ...}. So the names want to be lowercase noun
phrases. That is the whole reason for a mapping rather than a list: a
positional array that fails to cover the sweep can only be reported by index.

cable_losses stays, and is exactly equivalent to
corrections={"cable losses": ...}. Passing both is fine and they are merged;
passing cable_losses and a correction literally named cable losses should
raise rather than pick one.

Signs follow the existing convention. apply_antenna_factor adds every
correction, as it does with the antenna factors and cable losses, per Eqn 7.62
in Paul. Anything that has to come off instead — a preamp gain, say — is
negated by the caller, which is already what the docstring says to do to the
antenna factors to get the H-field form.

The part that needs a decision

apply_antenna_factor_show_af_cl returns
(incident_field, antenna_factors, cable_losses), and the interpolated
corrections belong in that return. A fourth element breaks every
a, b, c = ... at the call site, so it cannot simply grow.

The way out is a successor that returns a NamedTupleincident_field,
antenna_factors, cable_losses, and corrections as a dict keyed by the
same names — with apply_antenna_factor_show_af_cl kept as a thin wrapper
that returns the first three. That also retires a name that has never read
well.

Why not the signature this issue originally proposed

The issue was filed in 2013 asking for:

def apply_antenna_factor(base_array, keep_max=True, *args, **kwargs):

Three things argue against it now:

  • It breaks every positional caller, and keep_max moving into second
    position means the breakage is silent for anyone passing antenna factors
    positionally: they become the keep_max flag.
  • allow_extrapolation was added since, and it is a keyword that has to reach
    _interpolate_at for each correction. In **kwargs it sits in the same bag
    as the correction arrays themselves, so the function has to guess which keys
    are settings and which are data.
  • There are three public entry points now rather than one, and
    _interpolate_at wants a name per correction for its error messages.
    Variadic positional args have none to give it.

The capability being asked for is the same one. Only the spelling has been
brought forward.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions