Suite Multiple Inheritance

Rose Advanced Tutorial: Suite Multiple Inheritance

Suite Multiple Inheritance

Introduction

This part of the Rose user guide walks you through using more than one cylc family for configuring a task's runtime (multiple inheritance).

This helps when tasks naturally belong to more than one category - e.g. an application type category and a job submission type category.

Example

Our example suite will control a drag race.

Drag race starting in Saskatoon, Saskatchewan

Create a new suite (or just a new directory somewhere - e.g. in your homespace) containing a blank rose-suite.conf and a suite.rc file that looks like this.

Setup (2)

Create a subdirectory in your suite directory called lib/ and create a file inside this subdirectory called output.html with this content. This is a web page that summarises the suite output.

We also need to copy the library jquery.min.js into the same directory.

Setup (3)

We'll also need an executable script that looks like this - create a bin/ subdirectory in your suite directory, and paste the script into a file called race. The bin/ directory is added to the path by cylc, which means you can just specify the command in script (e.g. script = race).

We need to make the file executable as well - you'll need to run:

chmod +x bin/race

where your current working directory is the suite directory.

Explanation

A realistic scenario for multiple inheritance might be when you have one set of families handling job submission (e.g. parallel vs serial) and one set driving the particular setup of the code run by the task (e.g. for different systems).

In our suite.rc file, there is a set of families for the engine (ENGINE_PETROL, ENGINE_ELECTRIC), and one for the actual chassis and body (BODY_CAR, BODY_TRUCK).

Explanation (2)

We need to use configuration from both sets to build a racer.

We can see from the graph specification in dependencies that we have a family called COMPETITOR_VEHICLE whose tasks will be run.

BODY_CAR and BODY_TRUCK themselves inherit from COMPETITOR_VEHICLE, so any tasks belonging to these families will also belong to COMPETITOR_VEHICLE and will be run.

Single Inheritance

Let's start off with creating a configuration for a task called red_car.

We'll inherit from a single family BODY_CAR - put this under the [runtime] section:

    [[red_car]]
        inherit = BODY_CAR

Since BODY_CAR inherits from COMPETITOR_VEHICLE, and every task/family inherits from root, the order configuration is applied and overwritten in is:

settings in root
extended/overwritten by settings in COMPETITOR_VEHICLE
extended/overwritten by settings in BODY_CAR
extended/overwritten by settings in red_car

Diagram

In a diagram form, the inheritance looks like:

      root
        |
  COMPETITOR_VEHICLE
        |
     BODY_CAR
        |
     red_car

Multiple Inheritance

This means that any settings in, for example, root, can be overwritten by settings from the others. This is the standard way that single inheritance of families works.

We now need an engine - this means that we need to include some configuration from an entirely separate family.

Add another family to inherit from by writing:

    [[red_car]]
        inherit = BODY_CAR, ENGINE_ELECTRIC

Multiple Inheritance Precedence

In a diagram form, the inheritance now looks like:

       root
      /   \__________
     /               \
COMPETITOR_VEHICLE  ENGINE_ELECTRIC
     /          _____/
  BODY_CAR     /
      \       /
        red_car

The order of inheritance becomes:

settings in root
extended/overwritten by settings in ENGINE_ELECTRIC
extended/overwritten by settings in COMPETITOR_VEHICLE
extended/overwritten by BODY_CAR
extended/overwritten by red_car

Multiple Inheritance Precedence (2)

We can see that settings in ENGINE_ELECTRIC could be overwritten by the other families and the task itself - in other words, the inheritance list in inherit = BODY_CAR, ENGINE_ELECTRIC is basically last-to-first (ENGINE_ELECTRIC then the BODY_CAR-related families (COMPETITOR_VEHICLE and BODY_CAR)).

Task Configuration

We also need some racing colours, which should be task-specific - change the runtime to:

    [[red_car]]
        inherit = BODY_CAR, ENGINE_ELECTRIC
        [[[environment]]]
            COMP_COLOUR = red

Task Configuration (Cont.)

When cylc evaluates this runtime now, it will be equivalent to:

    [[red_car]]
        # From COMPETITOR_VEHICLE:
        script = race

        [[[environment]]]
            # From ENGINE ELECTRIC:
            COMP_GEAR_RATIOS = 2.0
            COMP_POWER_FRAC_VS_1000_RPM = 0.63 0.65 0.66 0.69 0.71 0.73 0.76 0.78 0.81 0.84 0.88 0.92 0.96 1.0 0.8 0.5
            COMP_MAX_POWER_KW = 126

            # From BODY_CAR:
            COMP_MASS_KG = 1000
            COMP_WHEEL_DIAMETER_M = 0.5

            # From red_car itself:
            COMP_COLOUR = red

Task Configuration Overrides

Note that because configuration in the task is applied last, you can override any family configuration at the task level - whether single inheritance or multiple.

Another Contender

A race isn't a race without more than one competitor - add another COMPETITOR_VEHICLE task in the runtime:

    [[blue_truck]]
        inherit = BODY_TRUCK, ENGINE_PETROL
        [[[environment]]]
            COMP_COLOUR = blue

This task inherits from different families, but gives a similar kind of configuration to red_car.

Running the Suite

Go ahead and run the suite with rose suite-run. The two vehicles will race over a quarter mile, and you should see the results in the cylc gui. The environment variables we set up will be printed in the standard output of both tasks. You can view the output with Rose Bush by following the link to the job.out file of the output task.

This is a successful implementation of multiple inheritance! The next thing to do is to make it slightly more tricky.

More Complex Inheritance

Let's suppose the race organisers decide on a maximum engine power for competitors to adhere to. At the moment, COMP_MAX_POWER_KW is different in the two types of engine we've specified.

As this will be common to all competitors, it makes sense to move the setting to the COMPETITOR_VEHICLE class.

Implementing Diamond Inheritance

Remove all the lines in the suite.rc file for COMP_MAX_POWER_KW, and change the COMPETITOR_VEHICLE runtime to:

    [[COMPETITOR_VEHICLE]]
        pre-script = printenv | sort | grep ^COMP
        script = race
        [[[environment]]]
            COMP_MAX_POWER_KW = 130
            COMP_OUTPUT_DIR = $CYLC_SUITE_SHARE_PATH

We'll also change the ENGINE families so that they include this setting through inheritance - add inherit lines to both ENGINE families:

Implementing Diamond Inheritance (2)

    [[ENGINE_PETROL]]
        inherit = COMPETITOR_VEHICLE

and

    [[ENGINE_ELECTRIC]]
        inherit = COMPETITOR_VEHICLE

Implementing Diamond Inheritance (3)

Our red_car task has inherit = BODY_CAR, ENGINE_ELECTRIC.

The inheritance order for the red_car task used to be:

settings in root
extended/overwritten by settings in ENGINE_ELECTRIC
extended/overwritten by settings in COMPETITOR_VEHICLE
extended/overwritten by BODY_CAR
extended/overwritten by red_car

Implementing Diamond Inheritance (4)

However, since the ENGINE families now inherit from COMPETITOR_VEHICLE, they should be evaluated after it - they should be able to override that family. The algorithm will recognise this and change the order accordingly.

This means that the order will now be:

settings in root
extended/overwritten by settings in COMPETITOR_VEHICLE
extended/overwritten by settings in ENGINE_ELECTRIC
extended/overwritten by BODY_CAR
extended/overwritten by red_car

which is subtly different.

Implementing Diamond Inheritance (5)

This is a case of diamond inheritance, where the diagram goes:

        root
          |
  COMPETITOR_VEHICLE
     /         \
  BODY_CAR   ENGINE_ELECTRIC
      \       /
        red_car

Overriding Multiple Inheritance

Although this is more complex, it's still easy to override settings.

Let's suppose there's some cheating going on, and the standard ENGINE_PETROL family actually has a bit more power. Change the ENGINE_PETROL family to override the COMP_MAX_POWER_KW setting:

    [[ENGINE_PETROL]]
        inherit = COMPETITOR_VEHICLE
        [[[environment]]]
            COMP_GEAR_RATIOS = 3.18 2.26 1.68 1.29 1.06
            COMP_POWER_FRAC_VS_1000_RPM = 0.18 0.3 0.6 0.88 1.0 0.6
            COMP_MAX_POWER_KW = 200  # Cheating!

Overriding Diamond Inheritance (2)

Meanwhile, our red_car task has invested in some oversize wheels - change the red_car runtime to read:

    [[red_car]]
        inherit = BODY_CAR, ENGINE_ELECTRIC
        [[[environment]]]
            COMP_COLOUR = red
            COMP_WHEEL_DIAMETER_M = 1.0  # Override the standard BODY_CAR setting

Re-Running the suite

Run the suite again, and have a look at the output files - the overrides or overwriting of settings should have happened. COMP_WHEEL_DIAMETER_M should now be 1.0 for the red_car task, and the COMP_MAX_POWER_KW should be 200 in the blue_truck task.

Further work

If you like, have a go at overriding various settings in the tasks and families, and seeing at which points this becomes effective.

Notes

You can inherit from any number of families, not just two - for example, you could invent a scenario where you might write:

    [[red_car]]
        inherit = BODY_CAR, ENGINE_ELECTRIC, SPOILER_OUTSIZE

The inheritance order will still be evaluated last-to-first, so both BODY_CAR and ENGINE_ELECTRIC (and any families they inherit from) can override SPOILER_OUTSIZE. The red_car task itself can override any inheritance.

Further Reading

For more information, see: