Custom Test Setups
The built-in test_setup presets (e2848_default, bifi_e2848_etotal,
bifi_power_tc, e2848_spec_corrected_poa) cover the most common
capacity-test configurations. When a project calls for a different regression
equation or a non-standard column calculation, pvcaptest lets you supply your
own reg_cols_meas and reg_cols_sim dicts without modifying the package.
This page explains the structure of those dicts, shows how the built-in
captest.calcparams functions plug into them, and describes the three
ways to wire a custom dict into a CapTest.
The regression column dict grammar
Each key in reg_cols_meas or reg_cols_sim maps a regression term (such
as "power" or "poa") to one of three node forms.
A simple measured column is a two-element tuple of the column-group id and an aggregation function name:
"poa": ("irr_poa", "mean")
A simple modeled column is a plain string matching a column name in the PVsyst output:
"poa": "GlobInc"
A calculated column is a two-element tuple of a callable and a dict mapping the callable’s keyword arguments to column names, aggregation tuples, or nested calculated-column tuples:
"poa": (
e_total,
{
"poa": ("irr_poa", "mean"),
"rpoa": ("irr_rpoa", "mean"),
},
)
Nesting is allowed to any depth. During
setup(), pvcaptest recursively walks the tree bottom-up:
each (func, kwargs_dict) tuple creates a new column named
func.__name__ in CapData.data, and that name is passed upward as input
to any parent tuple.
Using calcparams functions
The functions in captest.calcparams are the public building blocks
for calculated columns. Import the ones you need:
from captest.calcparams import (
e_total,
bom_temp,
cell_temp,
power_temp_correct,
rpoa_pvsyst,
scale,
)
The example below builds measured and modeled column dicts that compute temperature-corrected power from raw power, back-of-module temperature (estimated from POA, ambient temperature, and wind speed), and cell temperature:
from captest.calcparams import bom_temp, cell_temp, power_temp_correct
my_meas_cols = {
"power": (
power_temp_correct,
{
"power": ("real_pwr_mtr", "sum"),
"cell_temp": (
cell_temp,
{
"poa": ("irr_poa", "mean"),
"bom": (
bom_temp,
{
"poa": ("irr_poa", "mean"),
"temp_amb": ("temp_amb", "mean"),
"wind_speed": ("wind_speed", "mean"),
},
),
},
),
},
),
"poa": ("irr_poa", "mean"),
"t_amb": ("temp_amb", "mean"),
"w_vel": ("wind_speed", "mean"),
}
my_sim_cols = {
"power": (
power_temp_correct,
{"power": "E_Grid", "cell_temp": "TArray"},
),
"poa": "GlobInc",
"t_amb": "T_Amb",
"w_vel": "WindVel",
}
Scalar auto-injection
Scalar parameters such as power_temp_coeff, base_temp,
bifaciality, and spectral_module_type do not need to appear in the
dict. When a captest.calcparams function has a keyword argument
whose name matches an attribute on the CapData instance, pvcaptest injects
that value automatically. CapTest propagates
these scalars onto both CapData instances during
setup(), so setting them on the
CapTest instance is sufficient:
ct = CapTest.from_params(
test_setup="custom",
reg_cols_meas=my_meas_cols,
reg_cols_sim=my_sim_cols,
reg_fml="power ~ poa + I(poa * poa) + I(poa * t_amb) + I(poa * w_vel) - 1",
meas=meas,
sim=sim,
ac_nameplate=6_000_000,
power_temp_coeff=-0.36, # injected automatically into power_temp_correct
base_temp=25, # injected automatically into power_temp_correct
)
To override the auto-injected value for a specific node, include the scalar explicitly in that node’s kwarg dict.
Wiring a custom dict into CapTest
There are three equivalent ways to supply custom column dicts.
Route 1 — fully custom setup. Pass test_setup='custom' with all three
required overrides. scatter_plots and rep_conditions default to
scatter_default and an empty dict if omitted:
from captest import CapTest
ct = CapTest.from_params(
test_setup="custom",
reg_cols_meas=my_meas_cols,
reg_cols_sim=my_sim_cols,
reg_fml="power ~ poa + I(poa * poa) + I(poa * t_amb) + I(poa * w_vel) - 1",
meas=meas,
sim=sim,
ac_nameplate=6_000_000,
test_tolerance="- 4",
)
Route 2 — override a named preset. If a built-in preset’s formula is
correct but the column mappings need to change, pass reg_cols_meas and / or
reg_cols_sim alongside a named test_setup. The preset’s formula,
scatter plot, and reporting conditions are inherited:
ct = CapTest.from_params(
test_setup="e2848_default",
reg_cols_meas=my_meas_cols,
reg_cols_sim=my_sim_cols,
meas=meas,
sim=sim,
ac_nameplate=6_000_000,
)
Route 3 — assign directly. Attributes can be set on the instance before
calling setup():
ct = CapTest(test_setup="custom", ac_nameplate=6_000_000)
ct.meas = meas
ct.sim = sim
ct.reg_cols_meas = my_meas_cols
ct.reg_cols_sim = my_sim_cols
ct.reg_fml = "power ~ poa + I(poa * poa) + I(poa * t_amb) + I(poa * w_vel) - 1"
ct.setup()
Using custom functions
The dict also accepts plain Python functions that are not part of
captest.calcparams, as long as they follow the same signature
convention:
First positional argument must be
data, the source DataFrame.Remaining arguments are column names passed as strings.
The function returns a
pandas.Seriesindexed likedata.The function must be defined with
def(not alambda) so it has a__name__.
def my_adjusted_poa(data, poa=None, adjustment=1.0, verbose=True):
"""Scale a POA column by a site-specific adjustment factor."""
if verbose:
print(f"Calculating my_adjusted_poa as {poa} * {adjustment}")
return data[poa] * adjustment
my_meas_cols = {
"poa": (
my_adjusted_poa,
{"poa": ("irr_poa", "mean"), "adjustment": 1.05},
),
...
}
Note
Each function name must be unique within a single reg_cols_meas or
reg_cols_sim dict because the column added to CapData.data is
always named func.__name__. If two nodes call functions with the same
name, the second call overwrites the column produced by the first.