Package: armory-matrix
OverviewArmory-matrix provides a utility to automatically expand parameter
specifications in order to evaluate all combinations of parameters.For example, given two parametersxandywherexcan be an integer
between 1 and 3 andycan be 1.5 or 6.5, we have the following matrix of
parameter combinations:xy111.5216.5321.5426.5531.5636.5As the range of values for each parameter increases, and as more variable
parameters are added, the matrix becomes significantly larger.Installationpipinstallarmory-matrixUsageUse thematrixdecorator to specify a set or range of values for parameters of
a function. When the decorated function is invoked, it will automatically be
called once for each combination of input parameters.fromarmory.matriximportmatrix@matrix(x=range(1,3),y=[1.5,6.5],)defprint_xy(x,y):print(f"{x=},{y=}")print_xy()Will produce the following output:x=1, y=1.5
x=1, y=6.5
x=2, y=1.5
x=2, y=6.5This is roughly equivalent to the nested loops below:defprint_xy(x,y):print(f"{x=},{y=}")forxinrange(1,3):foryin[1.5,6.5]:print_xy(x=x,y=y)Parameter inspectionThe resulting parameter matrix may be inspected via thenum_rowsandmatrixproperties of the decorated function.assertprint_xy.num_rows==4forrowinprint_xy.matrix:print(row)Will produce the following output:{'x': 1, 'y': 1.5}
{'x': 1, 'y': 6.5}
{'x': 2, 'y': 1.5}
{'x': 2, 'y': 6.5}Parameter range specificationThe parameter ranges specified with the@matrixdecorator can be any type as
long as it is iterable. When a non-iterable value is used, it is effectively a
fixed value for all rows of the matrix. The following are all valid matrix
parameter values:defgen():fordin[None,int,str]:yieldd@matrix(a=gen,# generator functionb=(bforbin(True,False)),# generatorc=42,# fixed valued=range(5),# iterable object (via __iter__)e=["odd","even"],# sequence (set, tuple, list))deffoo(a,b,c,d):passSee the note below inDynamic parametersregarding use of
generator functions.Return valuesThe return value from calling the decorated function will be a sequence of all
the return values for each invocation of the function with parameters from rows
of the matrix. If the function raises an exception, the caught exception will be
the return value for that call.@matrix(a=range(1,3),b=range(3))defdivision(a,b):returna/bprint(division())Will produce the following output:[
    ZeroDivisionError('division by zero'), # 1 / 0
    1.0,                                   # 1 / 1
    0.5,                                   # 1 / 2
    ZeroDivisionError('division by zero'), # 2 / 0
    2.0,                                   # 2 / 1
    1.0,                                   # 2 / 2
]Partial parameter specificationIt is also possible to only specify a subset of a function's arguments as matrix
parameters. Any arguments not defined with the@matrixdecorator must be
specified when the function is invoked.@matrix(x=range(1,4))defquadratic(a,x,b):return(a*x)+bassertquadratic(a=2,b=10)==[12,14,16]Post-definition overridesParameters may be overridden after definition:print_xy.override(x=[7])()x=7, y=1.5
x=7, y=6.5Dynamic parametersDynamic or dependent parameters may be generated using a callable rather than a
value range.defy(x):ifx==1:return[1.5,6.5]return[1.7,2.3,3.4]@matrix(x=range(1,3),y=y,)defprint_xy(x,y):print(f"{x=},{y=}")print_xy()x=1, y=1.5
x=1, y=6.5
x=2, y=1.7
x=2, y=2.3
x=2, y=3.4This is roughly equivalent to the following:defy(x):ifx==1:return[1.5,6.5]return[1.7,2.3,3.4]defprint_xy(x,y):print(f"{x=},{y=}")forxinrange(1,3):for_yiny(x=x):print_xy(x=x,y=_y)The callable will be invoked with all prior generated parameters, in the order
they are defined in the@matrixdecorator. It is possible to specify multiple
dynamic parameters, where the latter parameters depend on the former.Generator FunctionsA custom generator function as a parameter range is effectively equivalent to a
dynamic parameter range. This is intentional, since generators cannot be rewound
and it is often necessary to iterate multiple times through amatrixparameter
space. However, this means that you may need to account for the prior parameter
arguments that will be passed to the generator function.defgen(**kwargs):fordin[None,int,str]:yieldd@matrix(a=range(5),b=gen,)deffoo(a,b):passIf we omitted the**kwargsfromgen, we would encounter an "unexpected
keyword argument" error.FilteringRows may be omitted from execution by providing a filter callable:assertprint_xy.num_rows==4filtered_print_xy=print_xy.filter(lambdax,y:x==1andy==1.6)assertfiltered_print_xy.num_rows==3filtered_print_xy()Any row for which the filter callable returnsTruewill be omitted from the
matrix.x=1, y=1.5
x=2, y=1.5
x=2, y=6.5PartitioningFor distributed runs (e.g., as a job submitted to a SLURM cluster), one may
specify a partition of the matrix to be run:print_xy[0::2]()# invoke partition 1 of 2print("---")print_xy[1::2]()# invoke partition 2 of 2x=1, y=1.5
x=1, y=6.5
---
x=2, y=1.5
x=2, y=6.5Typical Python slice operations apply, including use of a stop index. Note
however, that negative start or stop values are not supported.print_xy[10]()# Skip the first 10 rowsprint_xy[5:10]()# Execute rows 5 through 9ParallelizationFunction invocations may be parallelized within the Python process by using a
thread pool and specifying the max number of workers in the pool:print_xy.parallel(2)()