Tech Meeting 2024 Q1 - Enumerations#

The technical meeting that took place on 2024.04.03 related to the Q1 sprints covered the following topics:

  1. Running an analysis whilst modifying the .ini configuration files.

  2. Usage of enumerations within ra2ce.

  3. Discussion on long term views for ra2ce subprojects.

  4. Walk-through, on how to add new analyses to the current solution.

This jupyter notebook will cover the second point.

2. Usage of enumerations within ra2ce#

Enumerations (enum) are introduced in ra2ce mostly to declare, and therefore distinguish, all the different properties that may influence the creation of a network or the run of an analysis.

These enums are, in short, the different possibilities that can be found at the network.ini or analysis.ini section properties.

During development of new functionalities, or introduction of new configuration properties, more enumerations could be created or current ones extended. We will see how this works in this section.

2.1. Available enums#

At the current version v0.8.1 the following enumerations are present:

In ra2ce.network.network_config_data.enums:

  • AggregateWlEnum,

  • NetworkTypeEnum,

  • PartOfDayEnum,

  • RoadTypeEnum,

  • SourceEnum

[ ]:
from ra2ce.network.network_config_data.enums.network_type_enum import NetworkTypeEnum

for _network_type in NetworkTypeEnum:
    print(_network_type)

In ra2ce.analysis.analysis_config_data.enums:

  • AnalysisDirectEnum,

  • AnalysisIndirectEnum,

  • DamageCurveEnum,

  • EventTypeEnum,

  • LossTypeEnum,

  • RiskCalculationModeEnum,

  • TripPurposesEnum,

  • WeighingEnum

[ ]:
from ra2ce.analysis.analysis_config_data.enums.analysis_losses_enum import AnalysisLossesEnum

for _losses_type in AnalysisLossesEnum:
    print(_losses_type)

2.2. Ra2ceEnumBase#

In order to normalize how we handle all our enumerations, a base class Ra2ceEnumBase (ra2ce.configuration.ra2ce_enum_base) is created (not that this is not an abstract class).

All our enumerations implement said base class and therefore inherit its methods and definitions.

2.2.1. Creating a new enum#

We can check its functionality by first creating our own enumeration here.

[ ]:
from ra2ce.configuration.ra2ce_enum_base import Ra2ceEnumBase

class DocumentationEnum(Ra2ceEnumBase):
    NONE = 0
    ALL = 1
    EXAMPLE = 2
    RESEARCH = 3
    TECH_MEETING = 4
    ARCHITECTURE = 5
    DOMAIN = 6
    INVALID = 99

In our enumeration we see two sides, the name that we will use throughout the code (left side) and its associated value (right side). In short, we are just binding said names to a unique value so that python handles everything internally whilst we can write ‘readable’ code.

Some pointers for creation / modification of enums here:

  • Add new properties after the latest logical value. Sometimes you may find the last two values are BANANA = 3 and MIX = 100, choose then to add the next value as PINEAPPLE = 4 instead of PINEAPPLE = 101.

  • Always define the names with capital letters.

  • Try to use simplify the names whilst still being readable.

  • Do not modify existing values unless previously discussed with the development team.

  • When inheriting from Ra2ceEnumBase: NONE=0 is an optional entry, whilst INVALID=99 is a mandatory one.

2.2.2. Using the inherited methods / properties#

We can now start testing what the Ra2ceEnumBase does with our DocumentationEnum example

  • get_enum(str|None) will help us getting the associated ra2ce enum for a given string.

[ ]:
assert DocumentationEnum.get_enum("banana") == DocumentationEnum.INVALID
assert DocumentationEnum.get_enum(None) == DocumentationEnum.NONE
assert DocumentationEnum.get_enum("") == DocumentationEnum.NONE
assert DocumentationEnum.get_enum("reSEarch") == DocumentationEnum.RESEARCH
  • is_valid() verifies whether the selected enum property is something we consider ‘valid’. This method can be later on ‘overriden’ so that we could determine more options which are not ‘valid’ ones.

[ ]:
assert DocumentationEnum.NONE.is_valid()
assert DocumentationEnum.INVALID.is_valid() is False
[ ]:
class DocumentationEnumIsValidOverriden(Ra2ceEnumBase):
    NONE = 0
    ALL = 1
    EXAMPLE = 2
    RESEARCH = 3
    TECH_MEETING = 4
    ARCHITECTURE = 5
    DOMAIN = 6
    INVALID = 99

    def is_valid(self) -> bool:
        # We extend the current definition so that both
        # `EXAMPLE` and `TECH_MEETING` are not consider valid documents.
        if self in [
            DocumentationEnumIsValidOverriden.EXAMPLE,
            DocumentationEnumIsValidOverriden.TECH_MEETING]:
            return False
        return super().is_valid()

assert DocumentationEnumIsValidOverriden.TECH_MEETING.is_valid() is False
assert DocumentationEnumIsValidOverriden.EXAMPLE.is_valid() is False
assert DocumentationEnumIsValidOverriden.ARCHITECTURE.is_valid()
  • list_valid_options() returns all the options mapped as ‘valid’

[ ]:
print("Default valid options:")
for _valid_option in DocumentationEnum.list_valid_options():
    print(_valid_option)

print("\nValid options when overriding:")
for _valid_option in DocumentationEnumIsValidOverriden.list_valid_options():
    print(_valid_option)
  • config_value will return how the enum is to be represented in the config file (.ini).

[ ]:
for _option in DocumentationEnum:
    print(f"{_option}: {_option.config_value}")

But again, this could be easily overriden if desired.

[ ]:
class DocumentationEnumConfigValueOverriden(Ra2ceEnumBase):
    NONE = 0
    ALL = 1
    EXAMPLE = 2
    RESEARCH = 3
    TECH_MEETING = 4
    ARCHITECTURE = 5
    DOMAIN = 6
    INVALID = 99

    @property
    def config_value(self) -> str | None:
        if "_" in self.name:
            _words = [_word.capitalize() for _word in self.name.split("_")]
            return " ".join(_words)
        return super().__str__()

print(DocumentationEnumConfigValueOverriden.TECH_MEETING)