Constituent Lookup: Darwin vs. Doodson Notation#

This example provides tools to look up and compare constituent representations across the two prediction engines.

It demonstrates:

  • How to list all constituents supported by each engine

  • Which major constituents are common to both engines

  • Which constituents are unique to each engine

  • How to check if a specific constituent is supported

from __future__ import annotations

import pyfes

Get Constituent Lists#

Each engine maintains its own list of supported constituents.

darwin_constituents = pyfes.darwin.WaveTable()
perth_constituents = pyfes.perth.WaveTable()

print('=' * 70)
print('PyFES Constituent Support by Engine')
print('=' * 70)
print(f'\nDarwin Engine:  {len(darwin_constituents):3d} constituents')
print(f'PERTH5 Engine:  {len(perth_constituents):3d} constituents')
======================================================================
PyFES Constituent Support by Engine
======================================================================

Darwin Engine:   99 constituents
PERTH5 Engine:   80 constituents

Major Tidal Constituents Comparison#

Let’s compare support for the most important tidal constituents.

print('\n' + '=' * 70)
print('Major Tidal Constituents: Engine Comparison')
print('=' * 70)

major_constituents_info = [
    ('M2', 'Principal lunar semidiurnal'),
    ('S2', 'Principal solar semidiurnal'),
    ('N2', 'Larger lunar elliptic semidiurnal'),
    ('K2', 'Lunisolar semidiurnal'),
    ('K1', 'Lunisolar diurnal'),
    ('O1', 'Lunar diurnal'),
    ('P1', 'Solar diurnal'),
    ('Q1', 'Larger lunar elliptic diurnal'),
    ('2N2', 'Lunar elliptic semidiurnal second-order'),
    ('Mu2', 'Variational'),
    ('Nu2', 'Larger lunar evectional'),
    ('Lambda2', 'Smaller lunar evectional'),
    ('L2', 'Smaller lunar elliptic'),
    ('T2', 'Larger solar elliptic'),
    ('Mf', 'Lunisolar fortnightly'),
    ('Mm', 'Lunar monthly'),
    ('Ssa', 'Solar semiannual'),
    ('Sa', 'Solar annual'),
]

print(f'\n{"Constituent":<15} {"Darwin":<10} {"PERTH5":<10} {"Description"}')
print('-' * 70)

for const, desc in major_constituents_info:
    in_darwin = '✓' if const in darwin_constituents else '✗'
    in_perth = '✓' if const in perth_constituents else '✗'
    print(f'{const:<15} {in_darwin:<10} {in_perth:<10} {desc}')
======================================================================
Major Tidal Constituents: Engine Comparison
======================================================================

Constituent     Darwin     PERTH5     Description
----------------------------------------------------------------------
M2              ✓          ✓          Principal lunar semidiurnal
S2              ✓          ✓          Principal solar semidiurnal
N2              ✓          ✓          Larger lunar elliptic semidiurnal
K2              ✓          ✓          Lunisolar semidiurnal
K1              ✓          ✓          Lunisolar diurnal
O1              ✓          ✓          Lunar diurnal
P1              ✓          ✓          Solar diurnal
Q1              ✓          ✓          Larger lunar elliptic diurnal
2N2             ✓          ✓          Lunar elliptic semidiurnal second-order
Mu2             ✓          ✓          Variational
Nu2             ✓          ✓          Larger lunar evectional
Lambda2         ✓          ✓          Smaller lunar evectional
L2              ✓          ✓          Smaller lunar elliptic
T2              ✓          ✓          Larger solar elliptic
Mf              ✓          ✓          Lunisolar fortnightly
Mm              ✓          ✓          Lunar monthly
Ssa             ✓          ✓          Solar semiannual
Sa              ✓          ✓          Solar annual

Common, Unique, and Engine-Specific Constituents#

darwin_set = {item.name() for item in darwin_constituents}
perth_set = {item.name() for item in perth_constituents}

common = darwin_set & perth_set
darwin_only = darwin_set - perth_set
perth_only = perth_set - darwin_set

print('\n' + '=' * 70)
print('Constituent Set Analysis')
print('=' * 70)
print(f'\nCommon to both engines:  {len(common):3d} constituents')
print(f'Darwin-only constituents: {len(darwin_only):3d} constituents')
print(f'PERTH5-only constituents: {len(perth_only):3d} constituents')
======================================================================
Constituent Set Analysis
======================================================================

Common to both engines:   69 constituents
Darwin-only constituents:  30 constituents
PERTH5-only constituents:  11 constituents

Show Darwin-Only Constituents#

if darwin_only:
    print('\n' + '=' * 70)
    print('Darwin-Only Constituents')
    print('=' * 70)
    print(
        f'\nThese {len(darwin_only)} constituents are ONLY in '
        'the Darwin engine:'
    )
    print('-' * 70)

    # Sort alphabetically for easier reading
    sorted_darwin_only = sorted(darwin_only)

    # Print in columns
    cols = 6
    for i in range(0, len(sorted_darwin_only), cols):
        row = sorted_darwin_only[i : i + cols]
        print('  ' + '  '.join(f'{c:<10}' for c in row))
======================================================================
Darwin-Only Constituents
======================================================================

These 30 constituents are ONLY in the Darwin engine:
----------------------------------------------------------------------
  2MK2        2MNS4       2MP5        2MSN4       2NM6        2NS2
  2SMu2       3MS4        3MS8        A5          M0          M11
  M12         ML4         MNK6        MNS2        MNu4        MNuS2
  MP1         MSK2        Mf1         Mf2         Mm1         Mm2
  NK4         NKM2        OQ2         SK3         SKM2        SO3

Show PERTH5-Only Constituents#

if perth_only:
    print('\n' + '=' * 70)
    print('PERTH5-Only Constituents')
    print('=' * 70)
    print(
        f'\nThese {len(perth_only)} constituents are ONLY in the PERTH5 engine:'
    )
    print('-' * 70)

    # Sort alphabetically
    sorted_perth_only = sorted(perth_only)

    # Print in columns
    cols = 6
    for i in range(0, len(sorted_perth_only), cols):
        row = sorted_perth_only[i : i + cols]
        print('  ' + '  '.join(f'{c:<10}' for c in row))
======================================================================
PERTH5-Only Constituents
======================================================================

These 11 constituents are ONLY in the PERTH5 engine:
----------------------------------------------------------------------
  Alpa2       Beta1       Beta2       Delta2      Gamma2      MSm
  MStm        Mqm         Node        Tau1        Ups1

Constituent Lookup Function#

A utility function to check constituent availability.

def check_constituent(name: str) -> None:
    """Check if a constituent is supported by each engine."""
    in_darwin = name in darwin_constituents
    in_perth = name in perth_constituents

    print(f'\nConstituent: {name}')
    print(
        f'  Darwin Engine:  {"✓ Supported" if in_darwin else "✗ Not supported"}'
    )
    print(
        f'  PERTH5 Engine:  {"✓ Supported" if in_perth else "✗ Not supported"}'
    )

    if in_darwin and in_perth:
        print('  → Available in BOTH engines')
    elif in_darwin:
        print('  → Only in Darwin engine (use FES atlases)')
    elif in_perth:
        print('  → Only in PERTH5 engine (use GOT atlases)')
    else:
        print('  → NOT available in either engine')

Example Constituent Lookups#

print('\n' + '=' * 70)
print('Example Constituent Lookups')
print('=' * 70)

# Check some common constituents
check_constituent('M2')
check_constituent('K1')
check_constituent('Mf')

# Check some that might differ
if darwin_only:
    example_darwin = next(iter(darwin_only))
    check_constituent(example_darwin)

if perth_only:
    example_perth = next(iter(perth_only))
    check_constituent(example_perth)
======================================================================
Example Constituent Lookups
======================================================================

Constituent: M2
  Darwin Engine:  ✓ Supported
  PERTH5 Engine:  ✓ Supported
  → Available in BOTH engines

Constituent: K1
  Darwin Engine:  ✓ Supported
  PERTH5 Engine:  ✓ Supported
  → Available in BOTH engines

Constituent: Mf
  Darwin Engine:  ✓ Supported
  PERTH5 Engine:  ✓ Supported
  → Available in BOTH engines

Constituent: 2NS2
  Darwin Engine:  ✓ Supported
  PERTH5 Engine:  ✗ Not supported
  → Only in Darwin engine (use FES atlases)

Constituent: MStm
  Darwin Engine:  ✗ Not supported
  PERTH5 Engine:  ✓ Supported
  → Only in PERTH5 engine (use GOT atlases)

Constituent Groups by Frequency#

Group constituents by their tidal frequency type.

print('\n' + '=' * 70)
print('Constituent Classification by Type')
print('=' * 70)

# Common constituent name patterns for classification
long_period_patterns = ['Sa', 'Ssa', 'Mm', 'Mf', 'Mt', 'Msqm', 'Mq']
diurnal_patterns = ['1', 'J1', 'K1', 'M1', 'O1', 'OO1', 'P1', 'Q1', 'S1']
semidiurnal_patterns = ['2', 'K2', 'L2', 'M2', 'N2', 'Nu2', 'S2', 'T2', 'Mu2']
terdiurnal_patterns = ['M3', 'MK3', 'S3']
compound_patterns = ['M4', 'M6', 'M8', 'MS4', 'MN4', 'N4', 'S4']


def _match_patterns(name_upper: str, patterns: list[str]) -> bool:
    """Check if any pattern matches the name."""
    for pattern in patterns:
        if pattern.upper() in name_upper:
            return True
    return False


def classify_constituent(name: str) -> str:
    """Classify a constituent by its tidal frequency."""
    name_upper = name.upper()

    classifications = [
        (long_period_patterns, 'Long-period'),
        (diurnal_patterns, 'Diurnal'),
        (semidiurnal_patterns, 'Semidiurnal'),
        (terdiurnal_patterns, 'Terdiurnal'),
        (compound_patterns, 'Compound'),
    ]
    for patterns, tide_type in classifications:
        if _match_patterns(name_upper, patterns):
            return tide_type
    return 'Other'


# Classify all constituents
darwin_classified: dict[str, list[pyfes.darwin.Wave]] = {}
perth_classified: dict[str, list[pyfes.perth.Wave]] = {}

for fes_tidal_constituent in darwin_constituents:
    tide_type = classify_constituent(fes_tidal_constituent.name())
    if tide_type not in darwin_classified:
        darwin_classified[tide_type] = []
    darwin_classified[tide_type].append(fes_tidal_constituent)

for got_tidal_constituent in perth_constituents:
    tide_type = classify_constituent(got_tidal_constituent.name())
    if tide_type not in perth_classified:
        perth_classified[tide_type] = []
    perth_classified[tide_type].append(got_tidal_constituent)

# Print classification
print(f'\n{"Type":<15} {"Darwin":<10} {"PERTH5":<10}')
print('-' * 35)

all_types = sorted(
    set(list(darwin_classified.keys()) + list(perth_classified.keys()))
)
for tide_type in all_types:
    darwin_count = len(darwin_classified.get(tide_type, []))
    perth_count = len(perth_classified.get(tide_type, []))
    print(f'{tide_type:<15} {darwin_count:<10} {perth_count:<10}')
======================================================================
Constituent Classification by Type
======================================================================

Type            Darwin     PERTH5
-----------------------------------
Compound        9          8
Diurnal         21         21
Long-period     11         9
Other           18         12
Semidiurnal     38         28
Terdiurnal      2          2

Summary#

print('\n' + '=' * 70)
print('Summary')
print('=' * 70)
print(f"""
Constituent Support Summary:
  • Darwin engine supports {len(darwin_constituents)} constituents
  • PERTH5 engine supports {len(perth_constituents)} constituents
{len(common)} constituents are common to both engines
{len(darwin_only)} constituents are unique to Darwin
{len(perth_only)} constituents are unique to PERTH5

Most major tidal constituents (M2, S2, K1, O1, etc.) are available in both
engines. The differences primarily lie in minor constituents and specific
modeling choices for each engine.

When working with tidal constituents:
  • Check constituent availability using pyfes.darwin.constituents() or
    pyfes.perth.constituents()
  • Match your constituent list to your tidal atlas format
  • Use Darwin engine with FES atlases
  • Use PERTH5 engine with GOT atlases
""")

print('=' * 70)
print('End of Constituent Lookup Example')
print('=' * 70)
======================================================================
Summary
======================================================================

Constituent Support Summary:
  • Darwin engine supports 99 constituents
  • PERTH5 engine supports 80 constituents
  • 69 constituents are common to both engines
  • 30 constituents are unique to Darwin
  • 11 constituents are unique to PERTH5

Most major tidal constituents (M2, S2, K1, O1, etc.) are available in both
engines. The differences primarily lie in minor constituents and specific
modeling choices for each engine.

When working with tidal constituents:
  • Check constituent availability using pyfes.darwin.constituents() or
    pyfes.perth.constituents()
  • Match your constituent list to your tidal atlas format
  • Use Darwin engine with FES atlases
  • Use PERTH5 engine with GOT atlases

======================================================================
End of Constituent Lookup Example
======================================================================

Total running time of the script: (0 minutes 0.006 seconds)

Gallery generated by Sphinx-Gallery