Contributor how to guides#

Adding class methods to the ValueSequence and MultiPathSequence classes#

Adding class methods to hpcflow.app.ValueSequence#

ValueSequence exposes class methods that can be used to generate sequences of values (i.e. multiple elements) within a task. Within a YAML workflow template, the requirement to generate a sequence via a class method is written using a double-colon syntax as follows:

tasks:
  - objective: t1
    sequences:
      - path: inputs.p1
        values::from_range:
          start: 0
          stop: 10
          step: 1

In the case above, we are telling hpcFlow to use the method hpcflow.app.ValueSequence.from_range() to generate multiple elements for the task. The inner block containing start, stop, and step is then passed as a dict of keyword arguments to that class method.

Follow these steps to add a new sequence-generating class method to hpcflow.app.ValueSequence:

  1. Consider how to name your new method. For consistency with existing methods, please consider a name that is prefixed by from.... For this example, let’s consider a method named from_my_new_method.

  2. Add a new method that generates a list of values using your new approach, named _values_from_my_new_method` (i.e. your new method name, prefixed by _values_). If your new technique is parametrised by two arguments, arg_1 (an integer), and arg_2 (a list of strings), the signature of this method should look like this (the **kwargs is optional—consider if it is necessary to include):

    @classmethod
    def _values_from_my_new_method(
        cls,
        arg_1: int,
        arg_2: list[str],
        **kwargs,
    ) -> Self:
        pass # implementation here
    
  3. Add a new method that can be used within the API:

    @classmethod
    def from_my_new_method(
        cls,
        path: str,
        arg_1: int,
        arg_2: list[str],
        nesting_order: float = 0,
        label: str | int | None = None,
        **kwargs,
    ) -> Self:
        """
        Build a sequence from ...
        """
        args = {"arg_1": arg_1, "arg_2": arg_2, **kwargs}
        values = cls._values_from_my_new_method(**args)
        obj = cls(values=values, path=path, nesting_order=nesting_order, label=label)
        obj._values_method = "from_my_new_method"
        obj._values_method_args = args
        return obj
    

    Note that in addition to arg_1 and arg_2, this method signature must include some positional and keyword arguments: path, nesting_order, and label, which should be passed directly to the constructor. We use the method added previously (_values_from_my_new_method, in this case) to generate the values, and then pass those values on to the constructor. Note also that after object construction, we must assign two attributes:

    • _values_method which should be the name of this method

    • _values_method_args, which should be a mapping of argument names and values used to parametrise the value-generating method (_values_from_my_new_method, in this case).

  4. Write some tests. Please include somewhere within hpcflow/tests/unit at least one test to convince yourself that your new method generates the correct sequence of values:

    from hpcflow.app import app as hf
    
    def test_sequence_from_my_new_method():
        seq = hf.ValueSequence.from_my_new_method(path="inputs.p1", arg_1=9, arg_2=['a', 'b'])
    
        # check the expected number of values generated:
        assert len(seq.values) == 2
    
        # check the expected values, if possible:
        assert seq.values == ["val_a", "val_b"]
    
        # check the correct attributes are set:
        assert seq._values_method == "from_my_new_method"
        assert seq._values_method_args == {"arg_1": 9, "arg_2": ['a', 'b']}
    

Adding class methods to hpcflow.app.MultiPathSequence#

MultiPathSequence exposes class methods that can be used to generate multiple sequences of values (i.e. multiple elements) within a task, corresponding to multiple paths (i.e. inputs or resources). Within a YAML workflow template, the requirement to generate a multi-path sequence via a class method is written using a double-colon syntax as follows:

tasks:
  - objective: t1
    multi_path_sequences:
      - paths: [inputs.p1, inputs.p2]
        values::from_latin_hypercube:
          num_samples: 5

In the case above, we are telling hpcFlow to use the method hpcflow.app.MultiPathSequence.from_latin_hypercube() to generate five elements for the task, by combining five values for the input p1 with five values for the input p2, where all ten values are generated at the same time via a Latin hypercube sampling. The inner block containing num_samples, is passed as a dict of keyword arguments to that class method.

The same process as above can be used for adding new class methods to MultiPathSequence, with two exceptions. Firstly, a paths postitional argument (note: plural) must be specified in the values-generating method as defined in step 2. above. Thus the method should look like this:

@classmethod
def _values_from_my_new_method(
    cls,
    paths: Seqeuence[str], # <- note additional `paths` argument
    arg_1: int,
    arg_2: list[str],
    **kwargs,
) -> Self:
    pass # implementation here

The reason for including the paths argument is so we can know, for example, for how many paths the MultiPathSequence should generate values for.

Secondly, the path (note: singular) argument in the public-facing method (from_my_new_method in this case) should be replaced by paths (note: plural), which should have the type annotation: Sequence[str].