iliekturtles / uom

Units of measurement -- type-safe zero-cost dimensional analysis
Apache License 2.0
1.01k stars 92 forks source link

New units `VolumetricFlux` and `FlowResistivity` #428

Open indietyp opened 1 year ago

indietyp commented 1 year ago

I have a PR ready for two new units:

As one can see, both have three factors (two of them being SI metrics) I ended up generating all of the different possible units through a python script and as expected, there are quite a lot... (for VolumetricFlux to be specific 2.6k) I don't think that's feasible. What should the criteria be for the inclusion of a unit?

The script:

from itertools import combinations, product

prefix = {
    "yotta": "Y",
    "zetta": "Z",
    "exa": "E",
    "peta": "P",
    "tera": "T",
    "giga": "G",
    "mega": "M",
    "kilo": "k",
    "hecto": "h",
    "deca": "da",
    "deci": "d",
    "centi": "c",
    "milli": "m",
    "micro": "µ",
    "nano": "n",
    "pico": "p",
    "femto": "f",
    "atto": "a",
    "zepto": "z",
    "yocto": "y",
    "second": "s",
    "minute": "min",
    "hour": "h"
}

first = ["yotta", "zetta", "exa", "peta", "tera", "giga", "mega", "kilo", "hecto", "deca",
         None, "deci", "centi", "milli", "micro", "nano", "pico", "femto", "atto",
         "zepto", "yocto"]

second = ["second", "minute", "hour"]

third = ["yotta", "zetta", "exa", "peta", "tera", "giga", "mega", "kilo", "hecto", "deca",
         None, "deci", "centi", "milli", "micro", "nano", "pico", "femto", "atto",
         "zepto", "yocto"]

combinations = list(product(first, second, third))

# we need to do two runs, first for cubic meters, then for liters.

for first, second, third in combinations:
    first_str = first if first is not None else ""
    third_str = third if third is not None else ""

    unit_name = f'@cubic_{first_str}meter_per_{second}_per_square_{third_str}meter'

    first_factor = '*'.join((f'prefix!({first})', ) * 3) if first is not None else "1.0E0"
    match second:
        case "second":
            second_factor = "1.0E0"
        case "minute":
            second_factor = "60.0E0"
        case "hour":
            second_factor = "3600.0E0"
        case _:
            raise ValueError(f"Unknown unit {second}")

    third_factor = '*'.join((f'prefix!({third})', ) * 2) if third is not None else "1.0E0"

    conversion_factor = f'({first_factor})/({second_factor})/({third_factor})'

    first_prefix = prefix[first] if first is not None else ""
    second_prefix = prefix[second]
    third_prefix = prefix[third] if third is not None else ""

    unit = f'{first_prefix}m³·{second_prefix}·{third_prefix}m⁻²'
    singular = f'cubic {first_str}meter per {second} per square {third_str}meter'
    # collapse multiple spaces into one
    singular = ' '.join(singular.split())

    plural = f'cubic {first_str}meters per {second} per square {third_str}meter'
    # collapse multiple spaces into one
    plural = ' '.join(plural.split())

    print(f'{unit_name}: {conversion_factor}; "{unit}", "{singular}", "{plural}";')

for first, second, third in combinations:
    first_str = first if first is not None else ""
    third_str = third if third is not None else ""

    unit_name = f'@{first_str}liter_per_{second}_per_square_{third_str}meter'

    first_factor = '*'.join(('prefix!(milli)',) +(f'prefix!({first})', ) * 3) if first is not None else "1.0E0"
    match second:
        case "second":
            second_factor = "1.0E0"
        case "minute":
            second_factor = "60.0E0"
        case "hour":
            second_factor = "3600.0E0"
        case _:
            raise ValueError(f"Unknown unit {second}")

    third_factor = '*'.join((f'prefix!({third})', ) * 2) if third is not None else "1.0E0"

    conversion_factor = f'({first_factor})/({second_factor})/({third_factor})'

    first_prefix = prefix[first] if first is not None else ""
    second_prefix = prefix[second]
    third_prefix = prefix[third] if third is not None else ""

    unit = f'{first_prefix}L·{second_prefix}·{third_prefix}m⁻²'
    singular = f'{first_str}liter per {second} per square {third_str}meter'
    # collapse multiple spaces into one
    singular = ' '.join(singular.split())

    plural = f'{first_str}liters per {second} per square {third_str}meter'
    # collapse multiple spaces into one
    plural = ' '.join(plural.split())

    print(f'{unit_name}: {conversion_factor}; "{unit}", "{singular}", "{plural}";')
iliekturtles commented 1 year ago

For quantities like this with a lot of unit combinations I'll usually do yotta through yocto for the dimension that is most commonly used and then hand pick other commonly used units. For these quantities perhaps start with yotta-yocto+hour/minute/second+no prefix and no prefix+hour/minute/second+yotta-yocto. I believe that will give 120 combinations each which isn't too bad.