===============================
SCHISM Backend Framework Tutorial
===============================

This tutorial guides you through using the SCHISM backend framework to execute SCHISM simulations with Docker containers, automatic image building, and comprehensive testing.

Getting Started
===============

Prerequisites
-------------

Before starting, ensure you have:

* Docker installed and running
* ROMPY installed with backend framework support
* SCHISM boundary conditions examples (included with ROMPY)

Quick Start
-----------

The easiest way to get started is with the boundary conditions examples:

.. code-block:: bash

    # Navigate to the SCHISM examples directory
    cd rompy/notebooks/schism

    # Run a single tidal example (dry run)
    python run_boundary_conditions_examples.py --single basic_tidal --dry-run

    # Run all tidal examples
    python run_boundary_conditions_examples.py --tidal

    # Run a complete test suite
    python test_backend_examples.py

Basic Tutorial
==============

Step 1: Understanding the Framework
-----------------------------------

The SCHISM backend framework consists of three main components:

1. **ModelRun** - ROMPY's model execution framework
2. **DockerConfig** - Backend configuration for Docker execution
3. **Boundary Conditions** - SCHISM-specific configuration system

Here's how they work together:

.. code-block:: python

    from rompy.model import ModelRun
    from rompy.backends import DockerConfig
    from rompy.core.time import TimeRange
    from datetime import datetime
    from pathlib import Path

    # 1. Create a model run
    model_run = ModelRun(
        run_id="my_schism_example",
        period=TimeRange(
            start=datetime(2023, 1, 1),
            end=datetime(2023, 1, 2),
            interval="1H"
        ),
        output_dir="schism_output",
        delete_existing=True
    )

    # 2. Configure Docker backend
    docker_config = DockerConfig(
        dockerfile=Path("Dockerfile"),
        build_context=Path("docker/schism"),
        timeout=3600,
        cpu=8,
        memory="4g"
    )

    # 3. Generate configuration files
    model_run.generate()

    # 4. Execute simulation
    success = model_run.run(backend=docker_config)

Step 2: Creating Your First SCHISM Configuration
------------------------------------------------

Create a YAML configuration file for a simple tidal simulation:

.. code-block:: yaml

    # my_tidal_example.yaml
    run_id: my_first_schism_run
    period:
      start: 2023-01-01T00:00:00
      end: 2023-01-01T12:00:00
      interval: 1H
    output_dir: my_schism_output
    delete_existing: true

    config:
      model_type: schism
      grid:
        grid_type: schism
        hgrid:
          model_type: data_blob
          source: tests/data/schism/hgrid.gr3
        drag: 2.5e-3

      data:
        data_type: schism
        boundary_conditions:
          data_type: boundary_conditions
          setup_type: tidal
          tidal_data:
            tidal_database: tests/data/schism/tides
            tidal_model: 'OCEANUM-atlas'
            constituents: [M2, S2, N2]
          boundaries:
            0:
              elev_type: 3  # HARMONIC
              vel_type: 3   # HARMONIC
              temp_type: 0  # NONE
              salt_type: 0  # NONE

      nml:
        param:
          core:
            dt: 150.0
            ibc: 1  # Barotropic
            ibtp: 0  # No tracer transport
            nspool: 24
            ihfskip: 1152
          schout:
            iof_hydro__1: 1  # elevation
            iof_hydro__26: 1  # velocity vector

Step 3: Running Your Configuration
----------------------------------

Now create a Python script to execute your configuration:

.. code-block:: python

    # run_my_example.py
    import yaml
    from rompy.model import ModelRun
    from rompy.backends import DockerConfig
    from pathlib import Path

    def main():
        # Load configuration
        with open("my_tidal_example.yaml", "r") as f:
            config = yaml.safe_load(f)

        # Create model run
        model_run = ModelRun(**config)

        # Configure Docker backend
        docker_config = DockerConfig(
            dockerfile=Path("Dockerfile"),
            build_context=Path("docker/schism"),
            timeout=3600,
            cpu=8,
            memory="4g",
            executable="bash -c 'cd /tmp/schism && mpirun --allow-run-as-root -n 8 schism_v5.13.0 4'",
            volumes=[f"{Path.cwd() / 'my_schism_output'}:/tmp/schism:rw"],
            env_vars={
                "OMPI_ALLOW_RUN_AS_ROOT": "1",
                "OMPI_ALLOW_RUN_AS_ROOT_CONFIRM": "1"
            },
            remove_container=True
        )

        try:
            # Generate configuration files
            print("Generating SCHISM configuration...")
            model_run.generate()

            # Execute simulation
            print("Running SCHISM simulation...")
            success = model_run.run(backend=docker_config)

            if success:
                print("✅ Simulation completed successfully!")

                # Check for output files
                output_dir = Path("my_schism_output")
                outputs = list(output_dir.glob("**/outputs/*.nc"))
                print(f"Generated {len(outputs)} output files")

            else:
                print("❌ Simulation failed")

        except Exception as e:
            print(f"❌ Error: {e}")

    if __name__ == "__main__":
        main()

Step 4: Execute Your Example
----------------------------

Run your example:

.. code-block:: bash

    python run_my_example.py

Expected output:

.. code-block:: text

    Generating SCHISM configuration...
    Running SCHISM simulation...
    ✅ Simulation completed successfully!
    Generated 1 output files

Intermediate Tutorial
=====================

Step 5: Adding Wave Coupling
-----------------------------

Extend your configuration to include wave coupling:

.. code-block:: yaml

    # Add to your existing configuration
    config:
      data:
        wave:
          buffer: 0.0
          coords:
            t: time
            x: lon
            y: lat
            z: depth
          id: wavedata
          source:
            catalog_uri: tests/data/catalog.yaml
            dataset_id: ausspec
            model_type: intake

      nml:
        param:
          opt:
            ihot: 0
            nstep_wwm: 4
          schout:
            iof_wwm__1: 1   # significant wave height
            iof_wwm__9: 1   # peak wave period
            iof_wwm__18: 1  # peak wave direction
        wwminput:
          proc:
            deltc: 600

Step 6: Hybrid Boundary Conditions
-----------------------------------

Configure hybrid boundaries that combine tidal and ocean data:

.. code-block:: yaml

    config:
      data:
        boundary_conditions:
          data_type: boundary_conditions
          setup_type: hybrid
          tidal_data:
            tidal_database: tests/data/schism/tides
            tidal_model: 'OCEANUM-atlas'
            constituents: [M2, S2, N2]
          boundaries:
            0:
              elev_type: 5  # HARMONICEXTERNAL
              vel_type: 5   # HARMONICEXTERNAL
              temp_type: 4  # EXTERNAL
              salt_type: 4  # EXTERNAL
              elev_source:
                data_type: boundary
                source:
                  model_type: file
                  uri: tests/data/schism/hycom.nc
                variables: [surf_el]
                coords:
                  t: time
                  x: xlon
                  y: ylat
              vel_source:
                data_type: boundary
                source:
                  model_type: file
                  uri: tests/data/schism/hycom.nc
                variables: [water_u, water_v]
                coords:
                  t: time
                  x: xlon
                  y: ylat
                  z: depth

Step 7: Adding Hotstart Generation
-----------------------------------

Generate initial conditions from your ocean data:

.. code-block:: yaml

    config:
      data:
        boundary_conditions:
          hotstart_config:
            enabled: true
            temp_var: temperature
            salt_var: salinity
            output_filename: hotstart.nc

Advanced Tutorial
=================

Step 8: Custom Docker Configuration
------------------------------------

Create advanced Docker configurations for specific needs:

.. code-block:: python

    # High-performance configuration
    docker_config = DockerConfig(
        dockerfile=Path("Dockerfile.optimized"),
        build_context=Path("docker/schism"),
        build_args={
            "SCHISM_VERSION": "v5.13.0",
            "ENABLE_OPTIMIZATION": "ON"
        },
        cpu=16,
        memory="16g",
        timeout=7200,
        volumes=[
            f"{output_dir}:/tmp/schism:rw",
            "/tmp:/tmp_host:rw"
        ],
        env_vars={
            "OMPI_ALLOW_RUN_AS_ROOT": "1",
            "OMPI_ALLOW_RUN_AS_ROOT_CONFIRM": "1",
            "OMP_NUM_THREADS": "16"
        }
    )

Step 9: Multiple Boundary Types
--------------------------------

Configure complex scenarios with multiple boundary types:

.. code-block:: yaml

    config:
      data:
        boundary_conditions:
          data_type: boundary_conditions
          setup_type: mixed
          tidal_data:
            tidal_database: tests/data/schism/tides
            tidal_model: 'OCEANUM-atlas'
            constituents: [M2, S2, N2]
          boundaries:
            0:  # Ocean boundary (tidal + external)
              elev_type: 5
              vel_type: 5
              temp_type: 4
              salt_type: 4
              # ... data sources
            1:  # River boundary (constant flow)
              elev_type: 0
              vel_type: 2
              temp_type: 2
              salt_type: 2
              const_flow: -100.0
              const_temp: 15.0
              const_salt: 0.1
            2:  # Nested boundary (relaxation)
              elev_type: 5
              vel_type: 7
              temp_type: 4
              salt_type: 4
              inflow_relax: 0.8
              outflow_relax: 0.2

Step 10: Automated Testing
---------------------------

Create automated testing for your configurations:

.. code-block:: python

    # test_my_configurations.py
    import pytest
    import yaml
    from pathlib import Path
    from rompy.model import ModelRun
    from rompy.backends import DockerConfig

    class TestSchismConfigurations:

        def test_basic_tidal_config(self):
            """Test basic tidal configuration."""
            config_file = Path("my_tidal_example.yaml")
            assert config_file.exists()

            with open(config_file, "r") as f:
                config = yaml.safe_load(f)

            model_run = ModelRun(**config)
            assert model_run.run_id == "my_first_schism_run"

        def test_docker_config_creation(self):
            """Test Docker configuration creation."""
            docker_config = DockerConfig(
                dockerfile=Path("Dockerfile"),
                build_context=Path("docker/schism"),
                cpu=8,
                memory="4g"
            )

            assert docker_config.dockerfile == Path("Dockerfile")
            assert docker_config.cpu == 8
            assert docker_config.memory == "4g"

        def test_dry_run_execution(self):
            """Test configuration generation without execution."""
            with open("my_tidal_example.yaml", "r") as f:
                config = yaml.safe_load(f)

            model_run = ModelRun(**config)

            # Test configuration generation
            try:
                model_run.generate()
                assert True, "Configuration generation succeeded"
            except Exception as e:
                pytest.fail(f"Configuration generation failed: {e}")

    if __name__ == "__main__":
        pytest.main([__file__, "-v"])

Production Usage
================

Step 11: Batch Processing
--------------------------

Run multiple configurations in batch:

.. code-block:: python

    # batch_runner.py
    from concurrent.futures import ThreadPoolExecutor
    from pathlib import Path
    import yaml
    from rompy.model import ModelRun
    from rompy.backends import DockerConfig

    def run_configuration(config_file):
        """Run a single configuration file."""
        try:
            with open(config_file, "r") as f:
                config = yaml.safe_load(f)

            model_run = ModelRun(**config)

            docker_config = DockerConfig(
                dockerfile=Path("Dockerfile"),
                build_context=Path("docker/schism"),
                timeout=3600,
                cpu=8,
                memory="4g"
            )

            model_run.generate()
            success = model_run.run(backend=docker_config)

            return config_file.name, success

        except Exception as e:
            return config_file.name, False

    def main():
        # Find all configuration files
        config_files = list(Path(".").glob("*_example.yaml"))

        # Run configurations in parallel
        with ThreadPoolExecutor(max_workers=3) as executor:
            results = list(executor.map(run_configuration, config_files))

        # Report results
        for config_name, success in results:
            status = "✅" if success else "❌"
            print(f"{status} {config_name}")

    if __name__ == "__main__":
        main()

Step 12: Monitoring and Logging
--------------------------------

Add comprehensive monitoring to your runs:

.. code-block:: python

    # monitored_runner.py
    import logging
    import time
    from pathlib import Path
    from rompy.model import ModelRun
    from rompy.backends import DockerConfig

    # Configure logging
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler('schism_runs.log'),
            logging.StreamHandler()
        ]
    )

    logger = logging.getLogger(__name__)

    def run_with_monitoring(config_file):
        """Run configuration with detailed monitoring."""
        start_time = time.time()

        try:
            logger.info(f"Starting run: {config_file}")

            with open(config_file, "r") as f:
                config = yaml.safe_load(f)

            model_run = ModelRun(**config)

            logger.info(f"Run ID: {model_run.run_id}")
            logger.info(f"Period: {model_run.period.start} to {model_run.period.end}")

            docker_config = DockerConfig(
                dockerfile=Path("Dockerfile"),
                build_context=Path("docker/schism"),
                timeout=3600,
                cpu=8,
                memory="4g"
            )

            # Generate configuration
            logger.info("Generating configuration files...")
            model_run.generate()

            # Execute simulation
            logger.info("Starting SCHISM simulation...")
            success = model_run.run(backend=docker_config)

            execution_time = time.time() - start_time

            if success:
                logger.info(f"✅ Run completed successfully in {execution_time:.1f}s")

                # Check output files
                output_dir = Path(config["output_dir"])
                outputs = list(output_dir.glob("**/outputs/*.nc"))
                logger.info(f"Generated {len(outputs)} output files")

                return True
            else:
                logger.error(f"❌ Run failed after {execution_time:.1f}s")
                return False

        except Exception as e:
            execution_time = time.time() - start_time
            logger.error(f"❌ Run crashed after {execution_time:.1f}s: {e}")
            return False

Troubleshooting
===============

Common Issues and Solutions
---------------------------

**Docker Build Failures:**

.. code-block:: bash

    # Check Docker daemon
    docker version

    # Verify Dockerfile exists
    ls -la docker/schism/Dockerfile

    # Test manual build
    cd docker/schism
    docker build -t test-schism .

**Configuration Errors:**

.. code-block:: python

    # Validate YAML syntax
    import yaml
    with open("my_config.yaml", "r") as f:
        config = yaml.safe_load(f)
    print("✅ YAML is valid")

    # Test ModelRun creation
    from rompy.model import ModelRun
    model_run = ModelRun(**config)
    print("✅ ModelRun created successfully")

**Resource Issues:**

.. code-block:: python

    # Check system resources
    import psutil
    print(f"Available CPUs: {psutil.cpu_count()}")
    print(f"Available Memory: {psutil.virtual_memory().available / 1024**3:.1f} GB")

    # Adjust Docker configuration accordingly
    docker_config = DockerConfig(
        cpu=min(8, psutil.cpu_count()),
        memory=f"{min(4, psutil.virtual_memory().available // 1024**3)}g"
    )

**File Path Issues:**

.. code-block:: python

    # Use absolute paths for volume mounts
    from pathlib import Path
    output_dir = Path("my_output").absolute()

    docker_config = DockerConfig(
        volumes=[f"{output_dir}:/tmp/schism:rw"]
    )

Best Practices Summary
======================

1. **Start Simple** - Begin with basic tidal configurations
2. **Test Configurations** - Use dry runs to validate before execution
3. **Monitor Resources** - Set appropriate CPU and memory limits
4. **Use Absolute Paths** - Avoid path resolution issues
5. **Enable Logging** - Track execution progress and errors
6. **Clean Up** - Remove containers after execution
7. **Version Control** - Track configuration changes
8. **Document Runs** - Record parameters and results

Next Steps
==========

After completing this tutorial, you should be able to:

- Create and run basic SCHISM configurations
- Use the Docker backend framework effectively
- Configure complex boundary conditions
- Implement automated testing
- Monitor and troubleshoot runs

For more advanced features, see:

- :doc:`boundary_conditions` - Comprehensive boundary conditions guide
- :doc:`hotstart` - Initial conditions configuration
- :doc:`backend_framework` - Complete backend framework reference
- :doc:`../backends` - ROMPY backend system documentation

Happy modeling with SCHISM and ROMPY! 🌊
