rom-py / rompy

Relocatable Ocean Modelling in PYthon (rompy) combines templated cookie-cutter model configuration with various xarray extensions to assist in the setup and evaluation of coastal ocean model
https://rom-py.github.io/rompy/
BSD 3-Clause "New" or "Revised" License
2 stars 9 forks source link

SWAN Boundary #66

Closed rafa-guedes closed 3 months ago

rafa-guedes commented 5 months ago

Implement data objects to generate different types of swan boundary.

Example notebooks showing usage for all the new classes are defined here.

rafa-guedes commented 5 months ago

Thanks @benjaminleighton ,

there is still some work to do indeed for this to entirely support rotated domains. In the SIDE type boundary for example, SWAN applies the boundary to the side whose normal has the nearest direction to the specified side. However these objects use coordinates from the specific side in the grid array to interpolate the boundary data.

The IJ specification will not be difficult to implement, the object will look similar to SegmentXY but we will need to translate coordinates into grid nodes.

rafa-guedes commented 5 months ago

Closes https://github.com/rom-py/rompy/issues/37

rafa-guedes commented 3 months ago

@tomdurrant I have synced main here and fixed the issue with filter_time, this should be ready to be reviewed.

benjaminleighton commented 3 months ago

@rafa-guedes I'm getting some issues model.generate() is working alright but round tripping model.model_dump(mode='json') back to model = ModelRun(**model_as_json) is throwing a bunch of errors

ValidationError: 7 validation errors for ModelRun config.swanconfig.boundary.boundary_interface.kind.function-after[assert_has_wavespectra_accessor(), Boundnest1].data_type Input should be 'boundary' [type=literal_error, input_value='data_boundary', input_type=str] For further information visit https://errors.pydantic.dev/2.5/v/literal_error config.swanconfig.boundary.boundary_interface.kind.function-after[assert_has_wavespectra_accessor(), BoundspecSide].model_type Input should be 'boundspecside' or 'BOUNDSPECSIDE' [type=literal_error, input_value='boundnest1', input_type=str] For further information visit https://errors.pydantic.dev/2.5/v/literal_error config.swanconfig.boundary.boundary_interface.kind.function-after[assert_has_wavespectra_accessor(), BoundspecSide].data_type Input should be 'boundary' [type=literal_error, input_value='data_boundary', input_type=str] For further information visit https://errors.pydantic.dev/2.5/v/literal_error config.swanconfig.boundary.boundary_interface.kind.function-after[assert_has_wavespectra_accessor(), BoundspecSide].location Field required [type=missing, input_value={'model_type': 'boundnest...n', 'rectangle': 'open'}, input_type=dict] For further information visit https://errors.pydantic.dev/2.5/v/missing config.swanconfig.boundary.boundary_interface.kind.function-after[assert_has_wavespectra_accessor(), BoundspecSegmentXY].model_type Input should be 'boundspecside' or 'BOUNDSPECSIDE' [type=literal_error, input_value='boundnest1', input_type=str] For further information visit https://errors.pydantic.dev/2.5/v/literal_error config.swanconfig.boundary.boundary_interface.kind.function-after[assert_has_wavespectra_accessor(), BoundspecSegmentXY].data_type Input should be 'boundary' [type=literal_error, input_value='data_boundary', input_type=str] For further information visit https://errors.pydantic.dev/2.5/v/literal_error config.swanconfig.boundary.boundary_interface.kind.function-after[assert_has_wavespectra_accessor(), BoundspecSegmentXY].location Field required [type=missing, input_value={'model_type': 'boundnest...n', 'rectangle': 'open'}, input_type=dict] For further information visit https://errors.pydantic.dev/2.5/v/missing

rafa-guedes commented 3 months ago

@benjaminleighton I'll have a look at this. Has this worked on the existing version of the code in main as well or is it specific to this branch?

benjaminleighton commented 3 months ago

@rafa-guedes I haven't tried against main because I think I'm heavily using components of this branch in the setup. Most of the errors look like they might be related to new boundary spec stuff. This could be something todo with my install as I"m getting warnings too like

////lib/python3.10/site-packages/pydantic/main.py:308: UserWarning: Pydantic serializer warnings: Expected Union[BaseConfig, SwanConfig, definition-ref, definition-ref, SchismCSIROConfig] but got SwanConfigComponents - serialized value may not be as expected Expected Union[definition-ref, definition-ref, definition-ref, definition-ref, definition-ref, definition-ref, definition-ref, definition-ref, definition-ref, definition-ref] but got BoundaryInterface - serialized value may not be as expected Expected Union[Boundnest1, BoundspecSide, BoundspecSegmentXY] but got Boundnest1 - serialized value may not be as expected return self.__pydantic_serializer__.to_python(

the last one of those saying Boundnest1 doesn't match Boundnest1 doesn't make much sense.

benjaminleighton commented 3 months ago

@rafa-guedes I'm trying and failing to replicate my error in a clean github workspaces enviornment so I think this maybe an issue with my local installation.

benjaminleighton commented 3 months ago

Sorry @rafa-guedes, Actually I think I've replicated it in a slightly modified example_procedural_boundspec.ipynb see attached example_procedural_boundspec.zip. Changing the boundary forcing to use Boundnest1 like:

from rompy.swan.boundary import BoundspecSide, BoundspecSegmentXY, Boundnest1
from rompy.swan.interface import BoundaryInterface
from rompy.swan.subcomponents.boundary import SIDE, SIDES
from rompy.core.data import SourceFile

bndsource = SourceFile(
    uri=DATADIR / "aus-20230101.nc",
    kwargs=dict(engine="netcdf4"),
)
location = SIDES(
    sides=[
        SIDE(side="south", direction="clockwise"),
        SIDE(side="west", direction="clockwise"),
    ],
)

bnd = Boundnest1(
    id="westaus",
    source=bndsource,
)

boundary_segment = BoundaryInterface(kind=bnd)
boundary_segment

then later after

from rompy.model import ModelRun
from rompy.core.time import TimeRange

start, end = era5.time.to_index()[[0, -1]]

run = ModelRun(
    run_id="run1",
    period=TimeRange(start=start, end=end, interval="1h"),
    output_dir=str(workdir),
    config=config,
)

rundir = run()

trying

SwanConfigComponents(**config.model_dump())
rafa-guedes commented 3 months ago

@benjaminleighton thanks for the reproducible example. This should hopefully be fixed with https://github.com/rom-py/rompy/commit/eb43e42907070c068e99403b21a56ea921d2aee1, there was some change in a core base class that was the root cause all those validation errors.

Out of curiosity, why are you instantiating the Config class using the model_dump of an existing Config instance? This approach may fail with some other objects. For example, if we try to instantiate ModelRun with the dump of an existing ModelRun instance, it fails because the TimeRange object requires only two of start, end or duration to be provided, but the instance will have had all of those defined.

benjaminleighton commented 3 months ago

@rafa-guedes I was thinking that this would be a good way to serialize the whole model configuration, allow for editing it and load it back in.