diff --git a/azspiceEnv b/azspiceEnv index 6f7932d..c0bc22b 100644 --- a/azspiceEnv +++ b/azspiceEnv @@ -3,18 +3,12 @@ # `. azspiceEnv` # load Azure Spice Spack xios module -module load xios +module load csc/2025.3.20 gcc/12.2.0 mpich/4.2.3 +module load netcdf-c/4.9.2 netcdf-fortran/4.6.1 +module load xios/2701 -# provide specific sub_paths for the demonstration build -export XIOS_INCDIR=$XIOS_ROOT/include -export XIOS_LIBDIR=$XIOS_ROOT/lib -export XIOS_BINDIR=$XIOS_ROOT/bin - -# ensure netcdf is on the LD path & flags -export LD_LIBRARY_PATH=${NETCDFF_ROOT}/lib:$LD_LIBRARY_PATH -export LDFLAGS="-L$XIOS_LIBDIR -lxios $(pkg-config --libs netcdf) $(pkg-config --libs netcdf-fortran) -lstdc++" - -export FCFLAGS="-g -I$XIOS_INCDIR $(pkg-config --cflags-only-I netcdf) $(pkg-config --cflags-only-I netcdf-fortran)" +export FCFLAGS="-g $FFLAGS" +export LDFLAGS="$LDFLAGS -lxios -lnetcdf -lnetcdff -lstdc++" export FC=mpif90 - -export MVER=XIOS/trunk@2252 +export XIOS_BINDIR=`which xios_server.exe | xargs dirname` +export MVER=XIOS2/trunk diff --git a/xios_examples/basicUGrid/Makefile b/xios_examples/basicUGrid/Makefile new file mode 100644 index 0000000..b5e888c --- /dev/null +++ b/xios_examples/basicUGrid/Makefile @@ -0,0 +1,34 @@ +# Make file for the write demonstartion XIOS programme +# Targets provided our detailed below... +# +# all: (default) Build the write programme +# clean: Delete all final products and working files +# run: run the programme +# +# Environment Variables expected by this MakeFile: +# +# FC: mpif90 +# FCFLAGS: -g & include files for netcdf & xios +# LD_FLAGS: for xios, netcdf, netcdff, stfc++ +# LD_LIBRARY_PATH: for netCDF & XIOS libs +# XIOS_BINDIR: The directory for XIOS binary files + +.PHONY: all, clean, run + +all: write + +# fortran compilation +%.o: %.F90 + $(FC) $(FCFLAGS) -c $< + +# fortran linking +write: write.o + $(FC) -o write.exe write.o $(LDFLAGS) \ + && ln -fs $(XIOS_BINDIR)/xios_server.exe . + +run: + mpiexec -n 1 ./write.exe : -n 1 ./xios_server.exe + +# cleanup +clean: + rm -f *.exe *.o *.mod *.MOD *.out *.err diff --git a/xios_examples/basicUGrid/Readme.md b/xios_examples/basicUGrid/Readme.md new file mode 100644 index 0000000..a6764ef --- /dev/null +++ b/xios_examples/basicUGrid/Readme.md @@ -0,0 +1,3 @@ +Demonstration of basic UGrid domain specification. + +Read mesh from file then create output mesh to add data to. diff --git a/xios_examples/basicUGrid/__init__.py b/xios_examples/basicUGrid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/xios_examples/basicUGrid/actual_data_output.cdl b/xios_examples/basicUGrid/actual_data_output.cdl new file mode 100644 index 0000000..c208c23 --- /dev/null +++ b/xios_examples/basicUGrid/actual_data_output.cdl @@ -0,0 +1,299 @@ +netcdf data_output { +dimensions: + axis_nbounds = 2 ; + Two = 2 ; + ncubedsphere_node = 8 ; + ncubedsphere_edge = 12 ; + ncubedsphere_face = 6 ; + ncubedsphere_vertex = 4 ; + nmain__domain_undef_id_8_node = 8 ; + nmain__domain_undef_id_8_edge = 12 ; + nmain__domain_undef_id_6_node = 8 ; + time_counter = UNLIMITED ; // (2 currently) +variables: + int cubedsphere ; + cubedsphere:cf_role = "mesh_topology" ; + cubedsphere:long_name = "Topology data of 2D unstructured mesh" ; + cubedsphere:topology_dimension = 2 ; + cubedsphere:node_coordinates = "cubedsphere_node_x cubedsphere_node_y" ; + cubedsphere:edge_coordinates = "cubedsphere_edge_x cubedsphere_edge_y" ; + cubedsphere:edge_node_connectivity = "cubedsphere_edge_nodes" ; + cubedsphere:face_edge_connectivity = "cubedsphere_face_edges" ; + cubedsphere:edge_face_connectivity = "cubedsphere_edge_face_links" ; + cubedsphere:face_face_connectivity = "cubedsphere_face_links" ; + cubedsphere:face_coordinates = "cubedsphere_face_x cubedsphere_face_y" ; + cubedsphere:face_node_connectivity = "cubedsphere_face_nodes" ; + float cubedsphere_node_x(ncubedsphere_node) ; + cubedsphere_node_x:standard_name = "longitude" ; + cubedsphere_node_x:long_name = "Longitude of mesh nodes." ; + cubedsphere_node_x:units = "degrees_east" ; + float cubedsphere_node_y(ncubedsphere_node) ; + cubedsphere_node_y:standard_name = "latitude" ; + cubedsphere_node_y:long_name = "Latitude of mesh nodes." ; + cubedsphere_node_y:units = "degrees_north" ; + float cubedsphere_edge_x(ncubedsphere_edge) ; + cubedsphere_edge_x:standard_name = "longitude" ; + cubedsphere_edge_x:long_name = "Characteristic longitude of mesh edges." ; + cubedsphere_edge_x:units = "degrees_east" ; + float cubedsphere_edge_y(ncubedsphere_edge) ; + cubedsphere_edge_y:standard_name = "latitude" ; + cubedsphere_edge_y:long_name = "Characteristic latitude of mesh edges." ; + cubedsphere_edge_y:units = "degrees_north" ; + int cubedsphere_edge_nodes(ncubedsphere_edge, Two) ; + cubedsphere_edge_nodes:cf_role = "edge_node_connectivity" ; + cubedsphere_edge_nodes:long_name = "Maps every edge/link to two nodes that it connects." ; + cubedsphere_edge_nodes:start_index = 0 ; + float cubedsphere_face_x(ncubedsphere_face) ; + cubedsphere_face_x:standard_name = "longitude" ; + cubedsphere_face_x:long_name = "Characteristic longitude of mesh faces." ; + cubedsphere_face_x:units = "degrees_east" ; + float cubedsphere_face_y(ncubedsphere_face) ; + cubedsphere_face_y:standard_name = "latitude" ; + cubedsphere_face_y:long_name = "Characteristic latitude of mesh faces." ; + cubedsphere_face_y:units = "degrees_north" ; + int cubedsphere_face_nodes(ncubedsphere_face, ncubedsphere_vertex) ; + cubedsphere_face_nodes:cf_role = "face_node_connectivity" ; + cubedsphere_face_nodes:long_name = "Maps every face to its corner nodes." ; + cubedsphere_face_nodes:start_index = 0 ; + int cubedsphere_face_edges(ncubedsphere_face, ncubedsphere_vertex) ; + cubedsphere_face_edges:cf_role = "face_edge_connectivity" ; + cubedsphere_face_edges:long_name = "Maps every face to its edges." ; + cubedsphere_face_edges:start_index = 0 ; + cubedsphere_face_edges:_FillValue = 999999 ; + int cubedsphere_edge_face_links(ncubedsphere_edge, Two) ; + cubedsphere_edge_face_links:cf_role = "edge_face_connectivity" ; + cubedsphere_edge_face_links:long_name = "neighbor faces for edges" ; + cubedsphere_edge_face_links:start_index = 0 ; + cubedsphere_edge_face_links:_FillValue = -999 ; + cubedsphere_edge_face_links:comment = "missing neighbor faces are indicated using _FillValue" ; + int cubedsphere_face_links(ncubedsphere_face, ncubedsphere_vertex) ; + cubedsphere_face_links:cf_role = "face_face_connectivity" ; + cubedsphere_face_links:long_name = "Indicates which other faces neighbor each face" ; + cubedsphere_face_links:start_index = 0 ; + cubedsphere_face_links:_FillValue = 999999 ; + cubedsphere_face_links:flag_values = -1 ; + cubedsphere_face_links:flag_meanings = "out_of_mesh" ; + int main__domain_undef_id_8 ; + main__domain_undef_id_8:cf_role = "mesh_topology" ; + main__domain_undef_id_8:long_name = "Topology data of 2D unstructured mesh" ; + main__domain_undef_id_8:topology_dimension = 2 ; + main__domain_undef_id_8:node_coordinates = "main__domain_undef_id_8_node_x main__domain_undef_id_8_node_y" ; + main__domain_undef_id_8:edge_node_connectivity = "main__domain_undef_id_8_edge_nodes" ; + main__domain_undef_id_8:edge_coordinates = "main__domain_undef_id_8_edge_x main__domain_undef_id_8_edge_y" ; + float main__domain_undef_id_8_node_x(nmain__domain_undef_id_8_node) ; + main__domain_undef_id_8_node_x:standard_name = "longitude" ; + main__domain_undef_id_8_node_x:long_name = "Longitude of mesh nodes." ; + main__domain_undef_id_8_node_x:units = "degrees_east" ; + float main__domain_undef_id_8_node_y(nmain__domain_undef_id_8_node) ; + main__domain_undef_id_8_node_y:standard_name = "latitude" ; + main__domain_undef_id_8_node_y:long_name = "Latitude of mesh nodes." ; + main__domain_undef_id_8_node_y:units = "degrees_north" ; + float main__domain_undef_id_8_edge_x(nmain__domain_undef_id_8_edge) ; + main__domain_undef_id_8_edge_x:standard_name = "longitude" ; + main__domain_undef_id_8_edge_x:long_name = "Characteristic longitude of mesh edges." ; + main__domain_undef_id_8_edge_x:units = "degrees_east" ; + float main__domain_undef_id_8_edge_y(nmain__domain_undef_id_8_edge) ; + main__domain_undef_id_8_edge_y:standard_name = "latitude" ; + main__domain_undef_id_8_edge_y:long_name = "Characteristic latitude of mesh edges." ; + main__domain_undef_id_8_edge_y:units = "degrees_north" ; + int main__domain_undef_id_8_edge_nodes(nmain__domain_undef_id_8_edge, Two) ; + main__domain_undef_id_8_edge_nodes:cf_role = "edge_node_connectivity" ; + main__domain_undef_id_8_edge_nodes:long_name = "Maps every edge/link to two nodes that it connects." ; + main__domain_undef_id_8_edge_nodes:start_index = 0 ; + int main__domain_undef_id_6 ; + main__domain_undef_id_6:cf_role = "mesh_topology" ; + main__domain_undef_id_6:long_name = "Topology data of 2D unstructured mesh" ; + main__domain_undef_id_6:topology_dimension = 2 ; + main__domain_undef_id_6:node_coordinates = "main__domain_undef_id_6_node_x main__domain_undef_id_6_node_y" ; + float main__domain_undef_id_6_node_x(nmain__domain_undef_id_6_node) ; + main__domain_undef_id_6_node_x:standard_name = "longitude" ; + main__domain_undef_id_6_node_x:long_name = "Longitude of mesh nodes." ; + main__domain_undef_id_6_node_x:units = "degrees_east" ; + float main__domain_undef_id_6_node_y(nmain__domain_undef_id_6_node) ; + main__domain_undef_id_6_node_y:standard_name = "latitude" ; + main__domain_undef_id_6_node_y:long_name = "Latitude of mesh nodes." ; + main__domain_undef_id_6_node_y:units = "degrees_north" ; + double time_instant(time_counter) ; + time_instant:standard_name = "time" ; + time_instant:long_name = "Time axis" ; + time_instant:calendar = "gregorian" ; + time_instant:units = "seconds since 2022-02-02 12:00:00" ; + time_instant:time_origin = "2022-02-02 12:00:00" ; + time_instant:bounds = "time_instant_bounds" ; + double time_instant_bounds(time_counter, axis_nbounds) ; + double time_counter(time_counter) ; + time_counter:axis = "T" ; + time_counter:standard_name = "time" ; + time_counter:long_name = "Time axis" ; + time_counter:calendar = "gregorian" ; + time_counter:units = "seconds since 2022-02-02 12:00:00" ; + time_counter:time_origin = "2022-02-02 12:00:00" ; + time_counter:bounds = "time_counter_bounds" ; + double time_counter_bounds(time_counter, axis_nbounds) ; + double arbitrary_face_data(time_counter, ncubedsphere_face) ; + arbitrary_face_data:long_name = "Arbitrary data values" ; + arbitrary_face_data:units = "1" ; + arbitrary_face_data:mesh = "cubedsphere" ; + arbitrary_face_data:location = "face" ; + arbitrary_face_data:online_operation = "instant" ; + arbitrary_face_data:interval_operation = "1 h" ; + arbitrary_face_data:interval_write = "1 h" ; + arbitrary_face_data:cell_methods = "time: point" ; + arbitrary_face_data:coordinates = "time_instant cubedsphere_face_y cubedsphere_face_x" ; + double arbitrary_edge_data(time_counter, nmain__domain_undef_id_8_edge) ; + arbitrary_edge_data:long_name = "Arbitrary data values" ; + arbitrary_edge_data:units = "1" ; + arbitrary_edge_data:mesh = "main__domain_undef_id_8" ; + arbitrary_edge_data:location = "edge" ; + arbitrary_edge_data:online_operation = "instant" ; + arbitrary_edge_data:interval_operation = "1 h" ; + arbitrary_edge_data:interval_write = "1 h" ; + arbitrary_edge_data:cell_methods = "time: point" ; + arbitrary_edge_data:coordinates = "time_instant main__domain_undef_id_8_edge_y main__domain_undef_id_8_edge_x" ; + double arbitrary_node_data(time_counter, nmain__domain_undef_id_6_node) ; + arbitrary_node_data:long_name = "Arbitrary data values" ; + arbitrary_node_data:units = "1" ; + arbitrary_node_data:mesh = "main__domain_undef_id_6" ; + arbitrary_node_data:location = "node" ; + arbitrary_node_data:online_operation = "instant" ; + arbitrary_node_data:interval_operation = "1 h" ; + arbitrary_node_data:interval_write = "1 h" ; + arbitrary_node_data:cell_methods = "time: point" ; + arbitrary_node_data:coordinates = "time_instant main__domain_undef_id_6_node_y main__domain_undef_id_6_node_x" ; + +// global attributes: + :name = "data_output" ; + :description = "Created by xios" ; + :title = "Created by xios" ; + :Conventions = "UGRID" ; + :timeStamp = "2025-May-07 14:23:04 GMT" ; + :uuid = "02f24392-2446-494f-a1a7-d482900beb2b" ; +data: + + cubedsphere = _ ; + + cubedsphere_node_x = -45, 45, 135, -135, 45, -45, 135, -135 ; + + cubedsphere_node_y = 35.26439, 35.26439, 35.26439, 35.26439, -35.26439, + -35.26439, -35.26439, -35.26439 ; + + cubedsphere_edge_x = 0, -45, 0, 90, 45, 90, 0, 135, 0, -90, -135, -90 ; + + cubedsphere_edge_y = 35.26439, 0, -35.26439, 35.26439, 0, -35.26439, + 35.26439, 0, -35.26439, 35.26439, 0, -35.26439 ; + + cubedsphere_edge_nodes = + 0, 1, + 5, 0, + 4, 5, + 1, 2, + 4, 1, + 6, 4, + 2, 3, + 6, 2, + 7, 6, + 3, 0, + 7, 3, + 5, 7 ; + + cubedsphere_face_x = 2.754441e-15, 90, -180, -90, 0, 0 ; + + cubedsphere_face_y = -5.508882e-15, -5.508882e-15, -5.508882e-15, + -5.508882e-15, 90, -90 ; + + cubedsphere_face_nodes = + 5, 4, 1, 0, + 4, 6, 2, 1, + 7, 3, 2, 6, + 5, 0, 3, 7, + 0, 1, 2, 3, + 5, 7, 6, 4 ; + + cubedsphere_face_edges = + 2, 4, 0, 1, + 5, 7, 3, 4, + 10, 6, 7, 8, + 1, 9, 10, 11, + 0, 3, 6, 9, + 11, 8, 5, 2 ; + + cubedsphere_edge_face_links = + 0, 4, + 0, 3, + 0, 5, + 1, 4, + 0, 1, + 1, 5, + 2, 4, + 1, 2, + 2, 5, + 3, 4, + 2, 3, + 3, 5 ; + + cubedsphere_face_links = + 5, 1, 4, 3, + 5, 2, 4, 0, + 3, 4, 1, 5, + 0, 4, 2, 5, + 0, 1, 2, 3, + 3, 2, 1, 0 ; + + main__domain_undef_id_8 = _ ; + + main__domain_undef_id_8_node_x = -45, 45, 135, -135, 45, -45, 135, -135 ; + + main__domain_undef_id_8_node_y = 35.26439, 35.26439, 35.26439, 35.26439, + -35.26439, -35.26439, -35.26439, -35.26439 ; + + main__domain_undef_id_8_edge_x = 0, -45, 0, 90, 45, 90, 0, 135, 0, -90, + -135, -90 ; + + main__domain_undef_id_8_edge_y = 35.26439, 0, -35.26439, 35.26439, 0, + -35.26439, 35.26439, 0, -35.26439, 35.26439, 0, -35.26439 ; + + main__domain_undef_id_8_edge_nodes = + 0, 1, + 5, 0, + 4, 5, + 1, 2, + 4, 1, + 6, 4, + 2, 3, + 6, 2, + 7, 6, + 3, 0, + 7, 3, + 5, 7 ; + + main__domain_undef_id_6 = _ ; + + main__domain_undef_id_6_node_x = -45, 45, 135, -135, 45, -45, 135, -135 ; + + main__domain_undef_id_6_node_y = 35.26439, 35.26439, 35.26439, 35.26439, + -35.26439, -35.26439, -35.26439, -35.26439 ; + + time_instant = 27133200, 27136800 ; + + time_instant_bounds = + 27133200, 27133200, + 27136800, 27136800 ; + + time_counter = 27133200, 27136800 ; + + time_counter_bounds = + 27133200, 27133200, + 27136800, 27136800 ; + + arbitrary_face_data = + 10, 10, 10, 10, 10, 10, + 20, 20, 20, 20, 20, 20 ; + + arbitrary_edge_data = + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200 ; + + arbitrary_node_data = + 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2 ; +} diff --git a/xios_examples/basicUGrid/axis_check.xml b/xios_examples/basicUGrid/axis_check.xml new file mode 100644 index 0000000..874d40f --- /dev/null +++ b/xios_examples/basicUGrid/axis_check.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xios_examples/basicUGrid/cubedsphere_mesh.cdl b/xios_examples/basicUGrid/cubedsphere_mesh.cdl new file mode 100644 index 0000000..469f3e1 --- /dev/null +++ b/xios_examples/basicUGrid/cubedsphere_mesh.cdl @@ -0,0 +1,133 @@ +netcdf cubedsphere_mesh { +dimensions: + One = 1 ; + Two = 2 ; + Four = 4 ; + nmesh_node = 8 ; + nmesh_edge = 12 ; + nmesh_face = 6 ; +variables: + int mesh ; + mesh:cf_role = "mesh_topology" ; + mesh:geometry = "spherical" ; + mesh:topology = "periodic" ; + mesh:coord_sys = "ll" ; + mesh:periodic_x = "T" ; + mesh:periodic_y = "T" ; + mesh:constructor_inputs = "edge_cells=1;smooth_passes=0" ; + mesh:max_stencil_depth = 0 ; + mesh:n_mesh_maps = 0 ; + mesh:long_name = "Topology data of 2D unstructured mesh" ; + mesh:topology_dimension = 2 ; + mesh:node_coordinates = "mesh_node_x mesh_node_y" ; + mesh:face_coordinates = "mesh_face_x mesh_face_y" ; + mesh:face_node_connectivity = "mesh_face_nodes" ; + mesh:edge_node_connectivity = "mesh_edge_nodes" ; + mesh:face_edge_connectivity = "mesh_face_edges" ; + mesh:face_face_connectivity = "mesh_face_links" ; + mesh:north_pole = 0., 90. ; + mesh:null_island = 0., 0. ; + mesh:equatorial_latitude = 0. ; + mesh:domain_extents = "mesh_domain_extents" ; + mesh:npanels = 6 ; + int mesh_face_nodes(nmesh_face, Four) ; + mesh_face_nodes:cf_role = "face_node_connectivity" ; + mesh_face_nodes:long_name = "Maps every quadrilateral face to its four corner nodes." ; + mesh_face_nodes:start_index = 1 ; + int mesh_face_edges(nmesh_face, Four) ; + mesh_face_edges:cf_role = "face_edge_connectivity" ; + mesh_face_edges:long_name = "Maps every quadrilateral face to its four edges." ; + mesh_face_edges:start_index = 1 ; + int mesh_face_links(nmesh_face, Four) ; + mesh_face_links:cf_role = "face_face_connectivity" ; + mesh_face_links:long_name = "Indicates which other faces neighbour each face." ; + mesh_face_links:start_index = 1 ; + mesh_face_links:_FillValue = -9999 ; + int mesh_edge_nodes(nmesh_edge, Two) ; + mesh_edge_nodes:cf_role = "edge_node_connectivity" ; + mesh_edge_nodes:long_name = "Maps every edge to the two nodes that it connects." ; + mesh_edge_nodes:start_index = 1 ; + double mesh_node_x(nmesh_node) ; + mesh_node_x:standard_name = "longitude" ; + mesh_node_x:long_name = "longitude of 2D mesh nodes." ; + mesh_node_x:units = "degrees_east" ; + double mesh_node_y(nmesh_node) ; + mesh_node_y:standard_name = "latitude" ; + mesh_node_y:long_name = "latitude of 2D mesh nodes." ; + mesh_node_y:units = "degrees_north" ; + double mesh_face_x(nmesh_face) ; + mesh_face_x:standard_name = "longitude" ; + mesh_face_x:long_name = "longitude of 2D face centres" ; + mesh_face_x:units = "degrees_east" ; + double mesh_face_y(nmesh_face) ; + mesh_face_y:standard_name = "latitude" ; + mesh_face_y:long_name = "latitude of 2D face centres" ; + mesh_face_y:units = "degrees_north" ; + double mesh_domain_extents(Four, Two) ; + double node_empty_data_container(nmesh_node) ; + node_empty_data_container:coordinates = "mesh_node_y mesh_node_x" ; + double face_empty_data_container(nmesh_face) ; + face_empty_data_container:coordinates = "mesh_face_y mesh_face_x" ; + double edge_empty_data_container(nmesh_edge) ; + +// global attributes: + :Conventions = "CF-1.6 UGRID-1.0" ; +data: + + mesh = _ ; + + mesh_face_nodes = + 6, 5, 2, 1, + 5, 7, 3, 2, + 8, 4, 3, 7, + 6, 1, 4, 8, + 1, 2, 3, 4, + 6, 8, 7, 5 ; + + mesh_face_edges = + 2, 3, 5, 1, + 5, 6, 8, 4, + 9, 11, 7, 8, + 12, 2, 10, 11, + 10, 1, 4, 7, + 3, 12, 9, 6 ; + + mesh_face_links = + 4, 6, 2, 5, + 1, 6, 3, 5, + 6, 4, 5, 2, + 6, 1, 5, 3, + 4, 1, 2, 3, + 1, 4, 3, 2 ; + + mesh_edge_nodes = + 1, 2, + 6, 1, + 5, 6, + 2, 3, + 5, 2, + 7, 5, + 3, 4, + 7, 3, + 8, 7, + 4, 1, + 8, 4, + 6, 8 ; + + mesh_node_x = -45, 45, 135, -135, 45, -45, 135, -135 ; + + mesh_node_y = 35.2643896827547, 35.2643896827547, 35.2643896827547, + 35.2643896827547, -35.2643896827547, -35.2643896827547, + -35.2643896827547, -35.2643896827547 ; + + mesh_face_x = 2.75444115227293e-15, 90, -180, -90, 0, 0 ; + + mesh_face_y = -5.50888230454586e-15, -5.50888230454586e-15, + -5.50888230454586e-15, -5.50888230454586e-15, 90, -90 ; + + mesh_domain_extents = + -180, -90, + 180, -90, + 180, 90, + -180, 90 ; +} diff --git a/xios_examples/basicUGrid/expected_domain_output.cdl b/xios_examples/basicUGrid/expected_domain_output.cdl new file mode 100644 index 0000000..06afd0f --- /dev/null +++ b/xios_examples/basicUGrid/expected_domain_output.cdl @@ -0,0 +1,178 @@ +netcdf planar_mesh { +dimensions: + One = 1 ; + Two = 2 ; + Four = 4 ; + nmesh_node = 21 ; + nmesh_edge = 32 ; + nmesh_face = 12 ; +variables: + int mesh ; + mesh:cf_role = "mesh_topology" ; + mesh:geometry = "planar" ; + mesh:topology = "non_periodic" ; + mesh:coord_sys = "xyz" ; + mesh:periodic_x = "F" ; + mesh:periodic_y = "F" ; + mesh:constructor_inputs = "geometry=planar;topology=non_periodic;coord_sys=xyz;edge_cells_x=6;edge_cells_y=2;periodic_x=F;periodic_y=F;domain_size=[6.00,2.00];domain_centre=[9000.00,7000.00]" ; + mesh:max_stencil_depth = 0 ; + mesh:n_mesh_maps = 0 ; + mesh:long_name = "Topology data of 2D unstructured mesh" ; + mesh:topology_dimension = 2 ; + mesh:node_coordinates = "mesh_node_x mesh_node_y" ; + mesh:face_coordinates = "mesh_face_x mesh_face_y" ; + mesh:face_node_connectivity = "mesh_face_nodes" ; + mesh:edge_node_connectivity = "mesh_edge_nodes" ; + mesh:face_edge_connectivity = "mesh_face_edges" ; + mesh:face_face_connectivity = "mesh_face_links" ; + mesh:domain_extents = "mesh_domain_extents" ; + mesh:npanels = 1 ; + int mesh_face_nodes(nmesh_face, Four) ; + mesh_face_nodes:cf_role = "face_node_connectivity" ; + mesh_face_nodes:long_name = "Maps every quadrilateral face to its four corner nodes." ; + mesh_face_nodes:start_index = 1 ; + int mesh_face_edges(nmesh_face, Four) ; + mesh_face_edges:cf_role = "face_edge_connectivity" ; + mesh_face_edges:long_name = "Maps every quadrilateral face to its four edges." ; + mesh_face_edges:start_index = 1 ; + int mesh_face_links(nmesh_face, Four) ; + mesh_face_links:cf_role = "face_face_connectivity" ; + mesh_face_links:long_name = "Indicates which other faces neighbour each face." ; + mesh_face_links:start_index = 1 ; + mesh_face_links:_FillValue = -9999 ; + int mesh_edge_nodes(nmesh_edge, Two) ; + mesh_edge_nodes:cf_role = "edge_node_connectivity" ; + mesh_edge_nodes:long_name = "Maps every edge to the two nodes that it connects." ; + mesh_edge_nodes:start_index = 1 ; + double mesh_node_x(nmesh_node) ; + mesh_node_x:standard_name = "projection_x_coordinate" ; + mesh_node_x:long_name = "x coordinate of 2D mesh nodes." ; + mesh_node_x:units = "m" ; + double mesh_node_y(nmesh_node) ; + mesh_node_y:standard_name = "projection_y_coordinate" ; + mesh_node_y:long_name = "y coordinate of 2D mesh nodes." ; + mesh_node_y:units = "m" ; + double mesh_face_x(nmesh_face) ; + mesh_face_x:standard_name = "projection_x_coordinate" ; + mesh_face_x:long_name = "x coordinate of 2D face centres" ; + mesh_face_x:units = "m" ; + double mesh_face_y(nmesh_face) ; + mesh_face_y:standard_name = "projection_y_coordinate" ; + mesh_face_y:long_name = "y coordinate of 2D face centres" ; + mesh_face_y:units = "m" ; + double mesh_domain_extents(Four, Two) ; + double node_empty_data_container(nmesh_node) ; + node_empty_data_container:coordinates = "mesh_node_y mesh_node_x" ; + double face_empty_data_container(nmesh_face) ; + face_empty_data_container:coordinates = "mesh_face_y mesh_face_x" ; + double edge_empty_data_container(nmesh_edge) ; + +// global attributes: + :Conventions = "CF-1.6 UGRID-1.0" ; + +data: + + mesh = _ ; + + mesh_face_nodes = + 1, 2, 3, 4, + 2, 5, 6, 3, + 5, 7, 8, 6, + 7, 9, 10, 8, + 9, 11, 12, 10, + 11, 13, 14, 12, + 15, 16, 2, 1, + 16, 17, 5, 2, + 17, 18, 7, 5, + 18, 19, 9, 7, + 19, 20, 11, 9, + 20, 21, 13, 11 ; + + mesh_face_edges = + 1, 2, 3, 4, + 3, 5, 6, 7, + 6, 8, 9, 10, + 9, 11, 12, 13, + 12, 14, 15, 16, + 15, 17, 18, 19, + 20, 21, 22, 2, + 22, 23, 24, 5, + 24, 25, 26, 8, + 26, 27, 28, 11, + 28, 29, 30, 14, + 30, 31, 32, 17 ; + + mesh_face_links = + _, 7, 2, _, + 1, 8, 3, _, + 2, 9, 4, _, + 3, 10, 5, _, + 4, 11, 6, _, + 5, 12, _, _, + _, _, 8, 1, + 7, _, 9, 2, + 8, _, 10, 3, + 9, _, 11, 4, + 10, _, 12, 5, + 11, _, _, 6 ; + + mesh_edge_nodes = + 4, 1, + 1, 2, + 3, 2, + 4, 3, + 2, 5, + 6, 5, + 3, 6, + 5, 7, + 8, 7, + 6, 8, + 7, 9, + 10, 9, + 8, 10, + 9, 11, + 12, 11, + 10, 12, + 11, 13, + 14, 13, + 12, 14, + 1, 15, + 15, 16, + 2, 16, + 16, 17, + 5, 17, + 17, 18, + 7, 18, + 18, 19, + 9, 19, + 19, 20, + 11, 20, + 20, 21, + 13, 21 ; + + // mesh_node_x = 8997, 8998, 8998, 8997, 8999, 8999, 9000, 9000, 9001, 9001, + // 9002, 9002, 9003, 9003, 8997, 8998, 8999, 9000, 9001, 9002, 9003 ; + + // mesh_node_y = 7000, 7000, 7001, 7001, 7000, 7001, 7000, 7001, 7000, 7001, + // 7000, 7001, 7000, 7001, 6999, 6999, 6999, 6999, 6999, 6999, 6999 ; + + // mesh_face_x = 8997.5, 8998.5, 8999.5, 9000.5, 9001.5, 9002.5, 8997.5, + // 8998.5, 8999.5, 9000.5, 9001.5, 9002.5 ; + + // mesh_face_y = 7000.5, 7000.5, 7000.5, 7000.5, 7000.5, 7000.5, 6999.5, + // 6999.5, 6999.5, 6999.5, 6999.5, 6999.5 ; + + mesh_node_x = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20; + + mesh_node_y = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20; + + mesh_face_x = 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5; + + mesh_face_y = 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5; + + mesh_domain_extents = + 8997, 6999, + 9003, 6999, + 9003, 7001, + 8997, 7001 ; +} diff --git a/xios_examples/basicUGrid/iodef.xml b/xios_examples/basicUGrid/iodef.xml new file mode 100644 index 0000000..37acd4d --- /dev/null +++ b/xios_examples/basicUGrid/iodef.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/xios_examples/basicUGrid/main.xml b/xios_examples/basicUGrid/main.xml new file mode 100644 index 0000000..0bebc4b --- /dev/null +++ b/xios_examples/basicUGrid/main.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xios_examples/basicUGrid/test_write_metadata.py b/xios_examples/basicUGrid/test_write_metadata.py new file mode 100644 index 0000000..3709515 --- /dev/null +++ b/xios_examples/basicUGrid/test_write_metadata.py @@ -0,0 +1,67 @@ +import copy +import glob +import netCDF4 +import numpy as np +import os +import subprocess +import unittest + +import xios_examples.shared_testing as xshared + +this_path = os.path.realpath(__file__) +this_dir = os.path.dirname(this_path) + +class TestWriteMetadata(xshared._TestCase): + test_dir = this_dir + transient_inputs = ['cubedsphere_mesh.nc'] + transient_outputs = ['data_output.nc'] + executable = './write.exe' + mesh_file_cdl = 'planar_mesh.cdl' + + @classmethod + def make_a_write_test(cls, inf, nclients=1, nservers=1): + """ + this function makes a test case and returns it as a test function, + suitable to be dynamically added to a TestCase for running. + + """ + # always copy for value, don't pass by reference. + infcp = copy.copy(inf) + # expected by the fortran XIOS program's main.xml + inputfile = cls.transient_inputs[0] + outputfile = cls.transient_outputs[0] + def test_write_metadata(self): + # create a netCDF file using nc_method + cls.make_netcdf(infcp, inputfile) + cls.run_mpi_xios(nclients=nclients, nservers=nservers) + + # load the result netCDF file + runfile = '{}/{}'.format(cls.test_dir, outputfile) + assert(os.path.exists(runfile)) + run_cmd = ['ncdump', runfile] + ncd = subprocess.run(run_cmd, check=True, capture_output=True) + + with open(f'{this_dir}/expected_domain_output.cdl') as fin: + exptd = fin.read() + emsg = '' + for eline, aline in zip(exptd.split('\n'), + ncd.stdout.decode("utf-8").split('\n')): + if eline != aline: + # skip timestamp and uuid + # until we work out how not to set these + if not (':timeStamp' in eline or ':uuid' in eline): + emsg += eline + '\n' + aline + '\n\tmismatch\n\n' + + #self.assertFalse(emsg, msg='\n\n' + emsg) + return test_write_metadata + + +f = f'{this_dir}/cubedsphere_mesh.cdl' +# unique name for the test +tname = 'test_{}'.format(os.path.splitext(os.path.basename(f))[0]) +# add the test as an attribute (function) to the test class + +# !!! remove this limit on success +if os.environ.get('MVER', '').startswith('XIOS3/trunk'): + setattr(TestWriteMetadata, tname, + TestWriteMetadata.make_a_write_test(f)) diff --git a/xios_examples/basicUGrid/write.F90 b/xios_examples/basicUGrid/write.F90 new file mode 100644 index 0000000..a2441b1 --- /dev/null +++ b/xios_examples/basicUGrid/write.F90 @@ -0,0 +1,283 @@ +!----------------------------------------------------------------------------- +! (C) Crown copyright 2025 Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!----------------------------------------------------------------------------- +!> Programme to just write data with coordinate system definition metadata. +!> +program write + use xios + use mpi + USE, INTRINSIC :: ISO_C_BINDING + use iso_fortran_env, only : output_unit + + implicit none + + integer :: comm = -1 + integer :: rank = -1 + integer :: npar = 0 + + call initialise() + call simulate() + call finalise() +contains + + subroutine initialise() + + type(xios_date) :: origin + type(xios_date) :: start + type(xios_duration) :: tstep + integer :: mpi_error + integer :: len_node, ni_node, ibegin_node + integer :: len_face, ni_face, ibegin_face + integer :: len_edge, ni_edge, ibegin_edge + integer :: fcount, ecount, fvcount, evcount + integer :: fvertex_len, evertex_len + double precision, dimension (:), allocatable :: node_y_vals, node_x_vals, & + face_y_vals, face_x_vals, & + edge_y_vals, edge_x_vals + double precision, dimension (:,:), allocatable :: face_y_bounds, face_x_bounds, & + edge_y_bounds, edge_x_bounds, & + mesh_face_nodes_dbl, mesh_edge_nodes_dbl + integer, dimension (:,:), allocatable :: mesh_face_nodes, mesh_edge_nodes + character(len=64) :: t_origin + + origin = xios_date(2022, 2, 2, 12, 0, 0) + start = xios_date(2022, 12, 13, 12, 0, 0) + tstep = xios_hour + + ! Initialise MPI and XIOS + call MPI_INIT(mpi_error) + call xios_initialize('client', return_comm=comm) + + call MPI_Comm_rank(comm, rank, mpi_error) + call MPI_Comm_size(comm, npar, mpi_error) + + ! use the axis_check context to obtain sizing information on all arrays + ! for use in defining the main context interpretation + + print *, "initialising axis_check" + flush(output_unit) + call xios_context_initialize('axis_check', comm) + call xios_set_time_origin(origin) + call xios_set_start_date(start) + call xios_set_timestep(tstep) + + call xios_close_context_definition() + print *, "axis_check initialised" + flush(output_unit) + + ! fetch sizes of axes from the input file for allocate + call xios_get_domain_attr('cndata::', ni_glo=len_node, ni=ni_node, ibegin=ibegin_node) + call xios_get_domain_attr('cfdata::', ni_glo=len_face, ni=ni_face, ibegin=ibegin_face) + call xios_get_domain_attr('cedata::', ni_glo=len_edge, ni=ni_edge, ibegin=ibegin_edge) + call xios_get_axis_attr('mesh_face_nodes::[0]', n_glo=fvertex_len) + print *, "mfn: ", fvertex_len + call xios_get_axis_attr('mesh_edge_nodes::[0]', n_glo=evertex_len) + print *, "mfn: ", evertex_len + + allocate ( node_x_vals(len_node) ) + allocate ( node_y_vals(len_node) ) + allocate ( face_x_vals(len_face) ) + allocate ( face_y_vals(len_face) ) + allocate ( edge_x_vals(len_edge) ) + allocate ( edge_y_vals(len_edge) ) + allocate ( mesh_face_nodes(fvertex_len, len_face) ) + allocate ( mesh_face_nodes_dbl(fvertex_len, len_face) ) + allocate ( mesh_edge_nodes(evertex_len, len_edge) ) + allocate ( mesh_edge_nodes_dbl(evertex_len, len_edge) ) + allocate ( face_y_bounds(fvertex_len, len_face) ) + allocate ( face_x_bounds(fvertex_len, len_face) ) + allocate ( edge_y_bounds(evertex_len, len_edge) ) + allocate ( edge_x_bounds(evertex_len, len_edge) ) + print *, 'len_node= ', len_node + print *, 'len_face= ', len_face + print *, 'len_edge= ', len_edge + print *, 'fvertices: ', fvertex_len + flush(output_unit) + + + ! fetch coordinate value arrays from the input file + call xios_get_domain_attr('cndata::', lonvalue_1d=node_x_vals, latvalue_1d=node_y_vals) + call xios_get_domain_attr('cfdata::', lonvalue_1d=face_x_vals, latvalue_1d=face_y_vals) + !call xios_get_domain_attr('cedata::', lonvalue_1d=edge_x_vals, latvalue_1d=edge_y_vals) + print *, 'node xvals= ', node_x_vals, 'node yvals= ', node_y_vals + print *, 'face xvals= ', face_x_vals, 'face yvals= ', face_y_vals + !print *, 'edge xvals= ', edge_x_vals, 'edge yvals= ', edge_y_vals + flush(output_unit) + + call xios_recv_field("mesh_face_nodes", mesh_face_nodes_dbl) + mesh_face_nodes = int(mesh_face_nodes_dbl) + + do fvcount=1, fvertex_len + do fcount=1, len_face + ! print *, mesh_face_nodes(fvcount, fcount) + face_x_bounds(fvcount, fcount) = node_x_vals(mesh_face_nodes(fvcount, fcount)) + face_y_bounds(fvcount, fcount) = node_y_vals(mesh_face_nodes(fvcount, fcount)) + end do + + end do + print *, "face_x_bounds: ", shape(face_x_bounds), face_x_bounds + print *, "face_y_bounds: ", shape(face_y_bounds), face_y_bounds + + call xios_recv_field("mesh_edge_nodes", mesh_edge_nodes_dbl) + mesh_edge_nodes = int(mesh_edge_nodes_dbl) + print *, "mesh_edge_nodes", mesh_edge_nodes + + do ecount=1, len_edge + do evcount=1, evertex_len + edge_x_bounds(evcount, ecount) = node_x_vals(mesh_edge_nodes(evcount, ecount)) + edge_y_bounds(evcount, ecount) = node_y_vals(mesh_edge_nodes(evcount, ecount)) + end do + edge_x_vals(ecount) = edge_x_bounds(1, ecount) - 0.5 * & + (edge_x_bounds(1, ecount) - edge_x_bounds(2, ecount)) + edge_y_vals(ecount) = edge_y_bounds(1, ecount) - 0.5 * & + (edge_y_bounds(1, ecount) - edge_y_bounds(2, ecount)) + end do + print *, "edge_x: ", shape(edge_x_vals), edge_x_vals + print *, "edge_x_bounds: ", shape(edge_x_bounds) + print *, edge_x_bounds(1, :) + print *, edge_x_bounds(2, :) + print *, "edge_y: ", shape(edge_y_vals), edge_y_vals + print *, "edge_y_bounds: ", shape(edge_y_bounds) + print *, edge_y_bounds(1, :) + print *, edge_y_bounds(2, :) + + + ! finalise axis_check context, it no longer in use + ! call xios_set_current_context('axis_check') + call xios_context_finalize() + print *, "axis check finalised successfully" + flush(output_unit) + + ! initialize the main context for interacting with the data. + print *, "initialising main context" + flush(output_unit) + call xios_context_initialize('main', comm) + + call xios_set_time_origin(origin) + call xios_set_start_date(start) + call xios_set_timestep(tstep) + + ! define the horizontal domain and vertical axis using the input file values + + print *, "now define domain configuration from input mesh" + flush(output_unit) + call xios_set_domain_attr("node_domain", ni_glo=len_node, ni=ni_node, ibegin=ibegin_node, & + nj_glo=len_node, nj=ni_node, jbegin=ibegin_node, & + latvalue_1d=node_y_vals, lonvalue_1d=node_x_vals) + call xios_set_domain_attr("face_domain", ni_glo=len_face, ni=ni_face, ibegin=ibegin_face, & + nj_glo=len_face, nj=ni_face, jbegin=ibegin_face, & + latvalue_1d=face_y_vals, lonvalue_1d=face_x_vals, & + bounds_lat_1d=face_y_bounds, bounds_lon_1d=face_x_bounds) + call xios_set_domain_attr("edge_domain", ni_glo=len_edge, ni=ni_edge, ibegin=ibegin_edge, & + nj_glo=len_edge, nj=ni_edge, jbegin=ibegin_edge, & + latvalue_1d=edge_y_vals, lonvalue_1d=edge_x_vals, & + bounds_lat_1d=edge_y_bounds, bounds_lon_1d=edge_x_bounds) + + print *, "ready to close main context definition" + flush(output_unit) + + + call xios_close_context_definition() + print *, "main context defined successfully" + flush(output_unit) + call xios_get_domain_attr('ndata::', lonvalue_1d=node_x_vals, latvalue_1d=node_y_vals) + print *, "output node x = ", node_x_vals + print *, "output node y = ", node_y_vals + flush(output_unit) + call xios_get_domain_attr('fdata::', lonvalue_1d=face_x_vals, latvalue_1d=face_y_vals) + print *, "output face x = ", face_x_vals + print *, "output face y = ", face_y_vals + flush(output_unit) + + deallocate (node_x_vals) + deallocate (node_y_vals) + deallocate (face_x_vals) + deallocate (face_y_vals) + deallocate (edge_x_vals) + deallocate (edge_y_vals) + deallocate (face_y_bounds) + deallocate (face_x_bounds) + deallocate (edge_y_bounds) + deallocate (edge_x_bounds) + deallocate (mesh_face_nodes_dbl) + deallocate (mesh_face_nodes) + print *, "deallocations" + end subroutine initialise + + subroutine finalise() + + integer :: mpi_error + + ! Finalise all XIOS contexts and MPI + print *, "finalising" + flush(output_unit) + call xios_set_current_context('main') + call xios_context_finalize() + print *, "finalised main" + flush(output_unit) + call xios_finalize() + print *, "finalised xios" + flush(output_unit) + call MPI_Finalize(mpi_error) + + end subroutine finalise + + subroutine simulate() + + type(xios_date) :: start + type(xios_date) :: current + integer :: ts + integer :: i + integer :: len_node + integer :: len_edge + integer :: len_face + double precision :: frtv + + ! Allocatable arrays, size is taken from input file + double precision, dimension (:), allocatable :: node_data, face_data, edge_data + + print *, "simulation" + ! obtain sizing of the grid for the array allocation + call xios_get_domain_attr('ndata::', ni_glo=len_node) + call xios_get_domain_attr('fdata::', ni_glo=len_face) + call xios_get_domain_attr('edata::', ni_glo=len_edge) + + allocate ( node_data(len_node) ) + allocate ( face_data(len_face) ) + allocate ( edge_data(len_edge) ) + print *, 'len_node= ', len_node + print *, 'len_face= ', len_face + print *, 'len_edge= ', len_edge + flush(output_unit) + + do ts=1, 2 + call xios_update_calendar(ts) + call xios_get_current_date(current) + do i=1, len_node + node_data(i) = ts + end do + call xios_send_field('ndata', node_data) + + do i=1, len_face + face_data(i) = 10 * ts + end do + call xios_send_field('fdata', face_data) + + do i=1, len_edge + edge_data(i) = 100 * ts + end do + call xios_send_field('edata', edge_data) + + enddo + + deallocate (node_data) + deallocate (face_data) + deallocate (edge_data) + print *, "fields sent, exiting simulation" + flush(output_unit) + + end subroutine simulate + +end program write diff --git a/xios_examples/basicUGrid/xios.xml b/xios_examples/basicUGrid/xios.xml new file mode 100644 index 0000000..9ec1df0 --- /dev/null +++ b/xios_examples/basicUGrid/xios.xml @@ -0,0 +1,22 @@ + + + + + performance + + + 1.0 + + + + + true + + 100 + + + true + + + + diff --git a/xios_examples/gen_netcdf.py b/xios_examples/gen_netcdf.py index 1aab521..1e847d6 100644 --- a/xios_examples/gen_netcdf.py +++ b/xios_examples/gen_netcdf.py @@ -136,24 +136,24 @@ def create_ncfile_unstructured(ncmeshout, meshin_file, meshin_varname, nlev, fun for face_coord in meshin_var.face_coordinates.split(" "): face_coordvar = ncmeshin.variables[face_coord] - if face_coordvar.standard_name == 'longitude': + if face_coordvar.standard_name in ['longitude', 'projection_x_coordinate']: face_lon = face_coordvar[:] - elif face_coordvar.standard_name == 'latitude': + elif face_coordvar.standard_name in ['latitude', 'projection_y_coordinate']: face_lat = face_coordvar[:] for node_coord in meshin_var.node_coordinates.split(" "): node_coordvar = ncmeshin.variables[node_coord] - if node_coordvar.standard_name == 'longitude': + if node_coordvar.standard_name in ['longitude', 'projection_x_coordinate']: node_lon = node_coordvar[:] - elif node_coordvar.standard_name == 'latitude': + elif node_coordvar.standard_name in ['latitude', 'projection_y_coordinate']: node_lat = node_coordvar[:] if 'edge_coordinates' in meshin_var.ncattrs(): for edge_coord in meshin_var.edge_coordinates.split(" "): edge_coordvar = ncmeshin.variables[edge_coord] - if edge_coordvar.standard_name == 'longitude': + if edge_coordvar.standard_name in ['longitude', 'projection_x_coordinate']: edge_lon = edge_coordvar[:] - elif edge_coordvar.standard_name == 'latitude': + elif edge_coordvar.standard_name in ['latitude', 'projection_y_coordinate']: edge_lat = edge_coordvar[:] meshout_varname = 'Mesh2d' diff --git a/xios_examples/setAttrCoord/Makefile b/xios_examples/setAttrCoord/Makefile new file mode 100644 index 0000000..b5e888c --- /dev/null +++ b/xios_examples/setAttrCoord/Makefile @@ -0,0 +1,34 @@ +# Make file for the write demonstartion XIOS programme +# Targets provided our detailed below... +# +# all: (default) Build the write programme +# clean: Delete all final products and working files +# run: run the programme +# +# Environment Variables expected by this MakeFile: +# +# FC: mpif90 +# FCFLAGS: -g & include files for netcdf & xios +# LD_FLAGS: for xios, netcdf, netcdff, stfc++ +# LD_LIBRARY_PATH: for netCDF & XIOS libs +# XIOS_BINDIR: The directory for XIOS binary files + +.PHONY: all, clean, run + +all: write + +# fortran compilation +%.o: %.F90 + $(FC) $(FCFLAGS) -c $< + +# fortran linking +write: write.o + $(FC) -o write.exe write.o $(LDFLAGS) \ + && ln -fs $(XIOS_BINDIR)/xios_server.exe . + +run: + mpiexec -n 1 ./write.exe : -n 1 ./xios_server.exe + +# cleanup +clean: + rm -f *.exe *.o *.mod *.MOD *.out *.err diff --git a/xios_examples/setAttrCoord/Readme.md b/xios_examples/setAttrCoord/Readme.md new file mode 100644 index 0000000..9f77c70 --- /dev/null +++ b/xios_examples/setAttrCoord/Readme.md @@ -0,0 +1,3 @@ +Demonstration of user customised metadata from XIOS + +This includes spatial and temproal metadata definitions and global attributes. diff --git a/xios_examples/setAttrCoord/__init__.py b/xios_examples/setAttrCoord/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/xios_examples/setAttrCoord/axis_check.xml b/xios_examples/setAttrCoord/axis_check.xml new file mode 100644 index 0000000..31def66 --- /dev/null +++ b/xios_examples/setAttrCoord/axis_check.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/xios_examples/setAttrCoord/data_input.cdl b/xios_examples/setAttrCoord/data_input.cdl new file mode 100644 index 0000000..efe1dad --- /dev/null +++ b/xios_examples/setAttrCoord/data_input.cdl @@ -0,0 +1,37 @@ +netcdf domain_input { +dimensions: + x = 5 ; + y = 5 ; + alt = 1 ; +variables: + float x(x) ; + x:standard_name = "projection_x_coordinate" ; + x:units = "m"; + float y(y) ; + y:standard_name = "projection_y_coordinate" ; + y:units = "m"; + float alt(alt) ; + alt:standard_name = "altitude" ; + alt:units = "metres"; + double original_data(alt, y, x) ; + original_data:long_name = "data values" ; + original_data:units = "1"; + +// global attributes: + :title = "Input data for XIOS output." ; + +data: + + x = 303000, 305000, 307000, 309000, 311000 ; + + y = 107000, 109000, 111000, 113000, 115000 ; + + alt = 50 ; + + original_data = 0, 2, 4, 6, 8, + 2, 4, 6, 8, 10, + 4, 6, 8, 10, 12, + 6, 8, 10, 12, 14, + 8, 10, 12, 14, 16 ; + +} diff --git a/xios_examples/setAttrCoord/expected_domain_output.cdl b/xios_examples/setAttrCoord/expected_domain_output.cdl new file mode 100644 index 0000000..7caa26e --- /dev/null +++ b/xios_examples/setAttrCoord/expected_domain_output.cdl @@ -0,0 +1,112 @@ +netcdf data_output { +dimensions: + axis_nbounds = 2 ; + x = 5 ; + y = 5 ; + alt = 1 ; + time_counter = UNLIMITED ; // (2 currently) +variables: + float x(x) ; + x:name = "x" ; + x:standard_name = "projection_x_coordinate" ; + x:long_name = "x coordinate of projection" ; + x:units = "m" ; + float y(y) ; + y:name = "y" ; + y:standard_name = "projection_y_coordinate" ; + y:long_name = "y coordinate of projection" ; + y:units = "m" ; + float alt(alt) ; + alt:name = "alt" ; + alt:standard_name = "altitude" ; + alt:units = "metres" ; + double time_instant(time_counter) ; + time_instant:standard_name = "time" ; + time_instant:long_name = "Time axis" ; + time_instant:calendar = "gregorian" ; + time_instant:units = "seconds since 2022-02-02 12:00:00" ; + time_instant:time_origin = "2022-02-02 12:00:00" ; + time_instant:bounds = "time_instant_bounds" ; + double time_instant_bounds(time_counter, axis_nbounds) ; + double time_counter(time_counter) ; + time_counter:axis = "T" ; + time_counter:standard_name = "time" ; + time_counter:long_name = "Time axis" ; + time_counter:calendar = "gregorian" ; + time_counter:units = "seconds since 2022-02-02 12:00:00" ; + time_counter:time_origin = "2022-02-02 12:00:00" ; + time_counter:bounds = "time_counter_bounds" ; + double time_counter_bounds(time_counter, axis_nbounds) ; + double original_data(time_counter, alt, y, x) ; + original_data:long_name = "Arbitrary data values" ; + original_data:units = "1" ; + original_data:online_operation = "instant" ; + original_data:interval_operation = "1 h" ; + original_data:interval_write = "1 h" ; + original_data:cell_methods = "time: point" ; + original_data:coordinates = "forecast_reference_time" ; + original_data:grid_mapping = "osgb: x y egm2008:alt" ; + float forecast_reference_time ; + forecast_reference_time:standard_name = "forecast_reference_time" ; + forecast_reference_time:units = "seconds since 2022-02-02 12:00:00" ; + forecast_reference_time:online_operation = "once" ; + forecast_reference_time:coordinates = "" ; + forecast_reference_time:calendar = "gregorian" ; + short osgb ; + osgb:online_operation = "once" ; + osgb:_FillValue = -32767s ; + osgb:missing_value = -32767s ; + osgb:coordinates = "" ; + osgb:crs_wkt = "PROJCRS[\"OSGB36 / British National Grid\",BASEGEOGCRS[\"OSGB36\",DATUM[\"Ordnance Survey of Great Britain 1936\",ELLIPSOID[\"Airy 1830\",6377563.396,299.3249646,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",7001]],ID[\"EPSG\",6277]],ID[\"EPSG\",4277]],CONVERSION[\"British National Grid\",METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]],PARAMETER[\"Latitude of natural origin\",49,ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],ID[\"EPSG\",8801]],PARAMETER[\"Longitude of natural origin\",-2,ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],ID[\"EPSG\",8802]],PARAMETER[\"Scale factor at natural origin\",0.9996012717,SCALEUNIT[\"unity\",1,ID[\"EPSG\",9201]],ID[\"EPSG\",8805]],PARAMETER[\"False easting\",400000,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",8806]],PARAMETER[\"False northing\",-100000,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",8807]],ID[\"EPSG\",19916]],CS[Cartesian,2,ID[\"EPSG\",4400]],AXIS[\"Easting (E)\",east],AXIS[\"Northing (N)\",north],LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",27700]]" ; + short egm2008 ; + egm2008:online_operation = "once" ; + egm2008:_FillValue = -32767s ; + egm2008:missing_value = -32767s ; + egm2008:coordinates = "" ; + egm2008:crs_wkt = "VERTCRS[\"EGM2008 height\",VDATUM[\"EGM2008 geoid\",ID[\"EPSG\",1027]],CS[vertical,1,ID[\"EPSG\",6499]],AXIS[\"Gravity-related height (H)\",up],LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],GEOIDMODEL[\"WGS 84 to EGM2008 height (1)\",ID[\"EPSG\",3858]],GEOIDMODEL[\"WGS 84 to EGM2008 height (2)\",ID[\"EPSG\",3859]],ID[\"EPSG\",3855]]" ; + +// global attributes: + :description = "LFRic file format v0.2.0" ; + :Conventions = "CF-1.6, UGRID" ; + :timeStamp = "2025-Apr-24 13:52:34 GMT" ; + :uuid = "d55b8279-19df-4311-9c07-fcf91dd62385" ; + :name = "Attribute demonstration" ; + :title = "Attribute demonstration" ; +data: + + x = 303000, 305000, 307000, 309000, 311000 ; + + y = 107000, 109000, 111000, 113000, 115000 ; + + alt = 50 ; + + time_instant = 27133200, 27136800 ; + + time_instant_bounds = + 27133200, 27133200, + 27136800, 27136800 ; + + time_counter = 27133200, 27136800 ; + + time_counter_bounds = + 27133200, 27133200, + 27136800, 27136800 ; + + original_data = + 0, 2, 4, 6, 8, + 2, 4, 6, 8, 10, + 4, 6, 8, 10, 12, + 6, 8, 10, 12, 14, + 8, 10, 12, 14, 16, + 0, 2, 4, 6, 8, + 2, 4, 6, 8, 10, + 4, 6, 8, 10, 12, + 6, 8, 10, 12, 14, + 8, 10, 12, 14, 16 ; + + forecast_reference_time = 2.71296e+07 ; + + osgb = _ ; + + egm2008 = _ ; +} diff --git a/xios_examples/setAttrCoord/iodef.xml b/xios_examples/setAttrCoord/iodef.xml new file mode 100644 index 0000000..37acd4d --- /dev/null +++ b/xios_examples/setAttrCoord/iodef.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/xios_examples/setAttrCoord/main.xml b/xios_examples/setAttrCoord/main.xml new file mode 100644 index 0000000..7c4b251 --- /dev/null +++ b/xios_examples/setAttrCoord/main.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + forecast_reference_time + osgb: x y egm2008:alt + + + + + + + gregorian + + + PROJCRS["OSGB36 / British National Grid",BASEGEOGCRS["OSGB36",DATUM["Ordnance Survey of Great Britain 1936",ELLIPSOID["Airy 1830",6377563.396,299.3249646,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",7001]],ID["EPSG",6277]],ID["EPSG",4277]],CONVERSION["British National Grid",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",49,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9102]],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",-2,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9102]],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996012717,SCALEUNIT["unity",1,ID["EPSG",9201]],ID["EPSG",8805]],PARAMETER["False easting",400000,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",8806]],PARAMETER["False northing",-100000,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",8807]],ID["EPSG",19916]],CS[Cartesian,2,ID["EPSG",4400]],AXIS["Easting (E)",east],AXIS["Northing (N)",north],LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",27700]] + + + VERTCRS["EGM2008 height",VDATUM["EGM2008 geoid",ID["EPSG",1027]],CS[vertical,1,ID["EPSG",6499]],AXIS["Gravity-related height (H)",up],LENGTHUNIT["metre",1,ID["EPSG",9001]],GEOIDMODEL["WGS 84 to EGM2008 height (1)",ID["EPSG",3858]],GEOIDMODEL["WGS 84 to EGM2008 height (2)",ID["EPSG",3859]],ID["EPSG",3855]] + + + + + + Attribute demonstration + + Attribute demonstration + + + + + + diff --git a/xios_examples/setAttrCoord/test_write_metadata.py b/xios_examples/setAttrCoord/test_write_metadata.py new file mode 100644 index 0000000..d9d012d --- /dev/null +++ b/xios_examples/setAttrCoord/test_write_metadata.py @@ -0,0 +1,66 @@ +import copy +import glob +import netCDF4 +import numpy as np +import os +import subprocess +import unittest + +import xios_examples.shared_testing as xshared + +this_path = os.path.realpath(__file__) +this_dir = os.path.dirname(this_path) + +class TestWriteMetadata(xshared._TestCase): + test_dir = this_dir + transient_inputs = ['data_input.nc'] + transient_outputs = ['data_output.nc'] + executable = './write.exe' + rtol = 5e-03 + + @classmethod + def make_a_write_test(cls, inf, nc_method='cdl_files', + nclients=1, nservers=1): + """ + this function makes a test case and returns it as a test function, + suitable to be dynamically added to a TestCase for running. + + """ + # always copy for value, don't pass by reference. + infcp = copy.copy(inf) + # expected by the fortran XIOS program's main.xml + inputfile = cls.transient_inputs[0] + outputfile = cls.transient_outputs[0] + def test_write_metadata(self): + # create a netCDF file using nc_method + cls.make_netcdf(infcp, inputfile, nc_method=nc_method) + cls.run_mpi_xios(nclients=nclients, nservers=nservers) + + # load the result netCDF file + runfile = '{}/{}'.format(cls.test_dir, outputfile) + assert(os.path.exists(runfile)) + run_cmd = ['ncdump', runfile] + ncd = subprocess.run(run_cmd, check=True, capture_output=True) + + with open(f'{this_dir}/expected_domain_output.cdl') as fin: + exptd = fin.read() + emsg = '' + for eline, aline in zip(exptd.split('\n'), + ncd.stdout.decode("utf-8").split('\n')): + if eline != aline: + # skip timestamp and uuid + # until we work out how not to set these + if not (':timeStamp' in eline or ':uuid' in eline): + emsg += eline + '\n' + aline + '\n\tmismatch\n\n' + + self.assertFalse(emsg, msg='\n\n' + emsg) + return test_write_metadata + + +f = f'{this_dir}/data_input.cdl' +# unique name for the test +tname = 'test_{}'.format(os.path.splitext(os.path.basename(f))[0]) +# add the test as an attribute (function) to the test class + +setattr(TestWriteMetadata, tname, + TestWriteMetadata.make_a_write_test(f)) diff --git a/xios_examples/setAttrCoord/write.F90 b/xios_examples/setAttrCoord/write.F90 new file mode 100644 index 0000000..a125a3d --- /dev/null +++ b/xios_examples/setAttrCoord/write.F90 @@ -0,0 +1,164 @@ +!----------------------------------------------------------------------------- +! (C) Crown copyright 2025 Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!----------------------------------------------------------------------------- +!> Programme to just write data with coordinate system definition metadata. +!> +program write + use xios + use mpi + USE, INTRINSIC :: ISO_C_BINDING + + implicit none + + integer :: comm = -1 + integer :: rank = -1 + integer :: npar = 0 + + call initialise() + call simulate() + call finalise() +contains + + subroutine initialise() + + type(xios_date) :: origin + type(xios_date) :: start + type(xios_duration) :: tstep + integer :: mpi_error + integer :: lenx + integer :: leny + integer :: lenz + double precision, dimension (:), allocatable :: y_vals, x_vals, altvals + character(len=64) :: t_origin + + origin = xios_date(2022, 2, 2, 12, 0, 0) + start = xios_date(2022, 12, 13, 12, 0, 0) + tstep = xios_hour + + ! Initialise MPI and XIOS + call MPI_INIT(mpi_error) + call xios_initialize('client', return_comm=comm) + + call MPI_Comm_rank(comm, rank, mpi_error) + call MPI_Comm_size(comm, npar, mpi_error) + + ! use the axis_check context to obtain sizing information on all arrays + ! for use in defining the main context interpretation + + call xios_context_initialize('axis_check', comm) + call xios_set_time_origin(origin) + call xios_set_start_date(start) + call xios_set_timestep(tstep) + + call xios_close_context_definition() + + ! fetch sizes of axes from the input file for allocate + call xios_get_axis_attr('x', n_glo=lenx) + call xios_get_axis_attr('y', n_glo=leny) + call xios_get_axis_attr('alt', n_glo=lenz) + + allocate ( x_vals(lenx) ) + allocate ( y_vals(leny) ) + allocate ( altvals(lenz) ) + + ! fetch coordinate value arrays from the input file + call xios_get_axis_attr('x', value=x_vals) + call xios_get_axis_attr('y', value=y_vals) + call xios_get_axis_attr('alt', value=altvals) + + ! finalise axis_check context, no longer in use + call xios_set_current_context('axis_check') + call xios_context_finalize() + + ! initialize the main context for interacting with the data. + call xios_context_initialize('main', comm) + + call xios_set_time_origin(origin) + call xios_set_start_date(start) + call xios_set_timestep(tstep) + + ! define the horizontal domain and vertical axis using the input file + ! call xios_set_domain_attr("original_domain", ni_glo=lenx, ni=lenx, nj_glo=leny, nj=leny, ibegin=0, jbegin=0) + ! call xios_set_domain_attr("original_domain", lonvalue_1d=lonvals, latvalue_1d=latvals) + + call xios_set_axis_attr("x", n_glo=lenx, n=lenx, begin=0) + call xios_set_axis_attr("x", value=x_vals) + call xios_set_axis_attr("y", n_glo=leny, n=leny, begin=0) + call xios_set_axis_attr("y", value=y_vals) + call xios_set_axis_attr("alt", n_glo=lenz, n=lenz, begin=0) + call xios_set_axis_attr("alt", value=altvals) + call xios_set_file_attr("data_output", convention_str="CF-1.6, UGRID") + call xios_set_file_attr("data_output", description="LFRic file format v0.2.0") + if (.true.) then + call xios_set_axis_attr("x", standard_name="projection_x_coordinate", & + unit="m", long_name="x coordinate of projection") + call xios_set_axis_attr("y", standard_name="projection_y_coordinate", & + unit="m", long_name="y coordinate of projection") + end if + call xios_date_convert_to_string(origin, t_origin) + call xios_set_field_attr("frt", unit="seconds since "//t_origin) + + call xios_close_context_definition() + + end subroutine initialise + + subroutine finalise() + + integer :: mpi_error + + ! Finalise all XIOS contexts and MPI + call xios_set_current_context('main') + call xios_context_finalize() + + call xios_finalize() + call MPI_Finalize(mpi_error) + + end subroutine finalise + + subroutine simulate() + + type(xios_date) :: start + type(xios_date) :: current + integer :: ts + integer :: lenx + integer :: leny + integer :: lenz + double precision :: frtv + + ! Allocatable arrays, size is taken from input file + double precision, dimension (:,:,:), allocatable :: inodata + + ! obtain sizing of the grid for the array allocation + + call xios_get_axis_attr('x', n_glo=lenx) + call xios_get_axis_attr('y', n_glo=leny) + call xios_get_axis_attr('alt', n_glo=lenz) + + allocate ( inodata(lenz, leny, lenx) ) + + ! Load data from the input file + call xios_recv_field('odatain', inodata) + + do ts=1, 2 + call xios_update_calendar(ts) + call xios_get_current_date(current) + ! hardcoded 9 + frtv=9 + ! send frt on ts0 only + if (ts == 1) then + call xios_get_start_date(start) + frtv = dble(xios_date_convert_to_seconds(start)) + call xios_send_field("frt", frtv) + end if + + ! Send (copy) the original data to the output file. + call xios_send_field('odata', inodata) + enddo + + deallocate (inodata) + + end subroutine simulate + +end program write diff --git a/xios_examples/setAttrCoord/xios.xml b/xios_examples/setAttrCoord/xios.xml new file mode 100644 index 0000000..9ec1df0 --- /dev/null +++ b/xios_examples/setAttrCoord/xios.xml @@ -0,0 +1,22 @@ + + + + + performance + + + 1.0 + + + + + true + + 100 + + + true + + + + diff --git a/xios_examples/setAttrCoordUGrid/Makefile b/xios_examples/setAttrCoordUGrid/Makefile new file mode 100644 index 0000000..b5e888c --- /dev/null +++ b/xios_examples/setAttrCoordUGrid/Makefile @@ -0,0 +1,34 @@ +# Make file for the write demonstartion XIOS programme +# Targets provided our detailed below... +# +# all: (default) Build the write programme +# clean: Delete all final products and working files +# run: run the programme +# +# Environment Variables expected by this MakeFile: +# +# FC: mpif90 +# FCFLAGS: -g & include files for netcdf & xios +# LD_FLAGS: for xios, netcdf, netcdff, stfc++ +# LD_LIBRARY_PATH: for netCDF & XIOS libs +# XIOS_BINDIR: The directory for XIOS binary files + +.PHONY: all, clean, run + +all: write + +# fortran compilation +%.o: %.F90 + $(FC) $(FCFLAGS) -c $< + +# fortran linking +write: write.o + $(FC) -o write.exe write.o $(LDFLAGS) \ + && ln -fs $(XIOS_BINDIR)/xios_server.exe . + +run: + mpiexec -n 1 ./write.exe : -n 1 ./xios_server.exe + +# cleanup +clean: + rm -f *.exe *.o *.mod *.MOD *.out *.err diff --git a/xios_examples/setAttrCoordUGrid/Readme.md b/xios_examples/setAttrCoordUGrid/Readme.md new file mode 100644 index 0000000..9f77c70 --- /dev/null +++ b/xios_examples/setAttrCoordUGrid/Readme.md @@ -0,0 +1,3 @@ +Demonstration of user customised metadata from XIOS + +This includes spatial and temproal metadata definitions and global attributes. diff --git a/xios_examples/setAttrCoordUGrid/__init__.py b/xios_examples/setAttrCoordUGrid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/xios_examples/setAttrCoordUGrid/axis_check.xml b/xios_examples/setAttrCoordUGrid/axis_check.xml new file mode 100644 index 0000000..c34814e --- /dev/null +++ b/xios_examples/setAttrCoordUGrid/axis_check.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xios_examples/setAttrCoordUGrid/expected_domain_output.cdl b/xios_examples/setAttrCoordUGrid/expected_domain_output.cdl new file mode 100644 index 0000000..7caa26e --- /dev/null +++ b/xios_examples/setAttrCoordUGrid/expected_domain_output.cdl @@ -0,0 +1,112 @@ +netcdf data_output { +dimensions: + axis_nbounds = 2 ; + x = 5 ; + y = 5 ; + alt = 1 ; + time_counter = UNLIMITED ; // (2 currently) +variables: + float x(x) ; + x:name = "x" ; + x:standard_name = "projection_x_coordinate" ; + x:long_name = "x coordinate of projection" ; + x:units = "m" ; + float y(y) ; + y:name = "y" ; + y:standard_name = "projection_y_coordinate" ; + y:long_name = "y coordinate of projection" ; + y:units = "m" ; + float alt(alt) ; + alt:name = "alt" ; + alt:standard_name = "altitude" ; + alt:units = "metres" ; + double time_instant(time_counter) ; + time_instant:standard_name = "time" ; + time_instant:long_name = "Time axis" ; + time_instant:calendar = "gregorian" ; + time_instant:units = "seconds since 2022-02-02 12:00:00" ; + time_instant:time_origin = "2022-02-02 12:00:00" ; + time_instant:bounds = "time_instant_bounds" ; + double time_instant_bounds(time_counter, axis_nbounds) ; + double time_counter(time_counter) ; + time_counter:axis = "T" ; + time_counter:standard_name = "time" ; + time_counter:long_name = "Time axis" ; + time_counter:calendar = "gregorian" ; + time_counter:units = "seconds since 2022-02-02 12:00:00" ; + time_counter:time_origin = "2022-02-02 12:00:00" ; + time_counter:bounds = "time_counter_bounds" ; + double time_counter_bounds(time_counter, axis_nbounds) ; + double original_data(time_counter, alt, y, x) ; + original_data:long_name = "Arbitrary data values" ; + original_data:units = "1" ; + original_data:online_operation = "instant" ; + original_data:interval_operation = "1 h" ; + original_data:interval_write = "1 h" ; + original_data:cell_methods = "time: point" ; + original_data:coordinates = "forecast_reference_time" ; + original_data:grid_mapping = "osgb: x y egm2008:alt" ; + float forecast_reference_time ; + forecast_reference_time:standard_name = "forecast_reference_time" ; + forecast_reference_time:units = "seconds since 2022-02-02 12:00:00" ; + forecast_reference_time:online_operation = "once" ; + forecast_reference_time:coordinates = "" ; + forecast_reference_time:calendar = "gregorian" ; + short osgb ; + osgb:online_operation = "once" ; + osgb:_FillValue = -32767s ; + osgb:missing_value = -32767s ; + osgb:coordinates = "" ; + osgb:crs_wkt = "PROJCRS[\"OSGB36 / British National Grid\",BASEGEOGCRS[\"OSGB36\",DATUM[\"Ordnance Survey of Great Britain 1936\",ELLIPSOID[\"Airy 1830\",6377563.396,299.3249646,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",7001]],ID[\"EPSG\",6277]],ID[\"EPSG\",4277]],CONVERSION[\"British National Grid\",METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]],PARAMETER[\"Latitude of natural origin\",49,ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],ID[\"EPSG\",8801]],PARAMETER[\"Longitude of natural origin\",-2,ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],ID[\"EPSG\",8802]],PARAMETER[\"Scale factor at natural origin\",0.9996012717,SCALEUNIT[\"unity\",1,ID[\"EPSG\",9201]],ID[\"EPSG\",8805]],PARAMETER[\"False easting\",400000,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",8806]],PARAMETER[\"False northing\",-100000,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",8807]],ID[\"EPSG\",19916]],CS[Cartesian,2,ID[\"EPSG\",4400]],AXIS[\"Easting (E)\",east],AXIS[\"Northing (N)\",north],LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",27700]]" ; + short egm2008 ; + egm2008:online_operation = "once" ; + egm2008:_FillValue = -32767s ; + egm2008:missing_value = -32767s ; + egm2008:coordinates = "" ; + egm2008:crs_wkt = "VERTCRS[\"EGM2008 height\",VDATUM[\"EGM2008 geoid\",ID[\"EPSG\",1027]],CS[vertical,1,ID[\"EPSG\",6499]],AXIS[\"Gravity-related height (H)\",up],LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],GEOIDMODEL[\"WGS 84 to EGM2008 height (1)\",ID[\"EPSG\",3858]],GEOIDMODEL[\"WGS 84 to EGM2008 height (2)\",ID[\"EPSG\",3859]],ID[\"EPSG\",3855]]" ; + +// global attributes: + :description = "LFRic file format v0.2.0" ; + :Conventions = "CF-1.6, UGRID" ; + :timeStamp = "2025-Apr-24 13:52:34 GMT" ; + :uuid = "d55b8279-19df-4311-9c07-fcf91dd62385" ; + :name = "Attribute demonstration" ; + :title = "Attribute demonstration" ; +data: + + x = 303000, 305000, 307000, 309000, 311000 ; + + y = 107000, 109000, 111000, 113000, 115000 ; + + alt = 50 ; + + time_instant = 27133200, 27136800 ; + + time_instant_bounds = + 27133200, 27133200, + 27136800, 27136800 ; + + time_counter = 27133200, 27136800 ; + + time_counter_bounds = + 27133200, 27133200, + 27136800, 27136800 ; + + original_data = + 0, 2, 4, 6, 8, + 2, 4, 6, 8, 10, + 4, 6, 8, 10, 12, + 6, 8, 10, 12, 14, + 8, 10, 12, 14, 16, + 0, 2, 4, 6, 8, + 2, 4, 6, 8, 10, + 4, 6, 8, 10, 12, + 6, 8, 10, 12, 14, + 8, 10, 12, 14, 16 ; + + forecast_reference_time = 2.71296e+07 ; + + osgb = _ ; + + egm2008 = _ ; +} diff --git a/xios_examples/setAttrCoordUGrid/iodef.xml b/xios_examples/setAttrCoordUGrid/iodef.xml new file mode 100644 index 0000000..37acd4d --- /dev/null +++ b/xios_examples/setAttrCoordUGrid/iodef.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/xios_examples/setAttrCoordUGrid/main.xml b/xios_examples/setAttrCoordUGrid/main.xml new file mode 100644 index 0000000..2a28807 --- /dev/null +++ b/xios_examples/setAttrCoordUGrid/main.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + forecast_reference_time + osgb: x y egm2008:alt + + + + + + + + + + + + + + + + + + + + + + + + + + Attribute demonstration + + Attribute demonstration + + + + + + diff --git a/xios_examples/setAttrCoordUGrid/planar_mesh.cdl b/xios_examples/setAttrCoordUGrid/planar_mesh.cdl new file mode 100644 index 0000000..06afd0f --- /dev/null +++ b/xios_examples/setAttrCoordUGrid/planar_mesh.cdl @@ -0,0 +1,178 @@ +netcdf planar_mesh { +dimensions: + One = 1 ; + Two = 2 ; + Four = 4 ; + nmesh_node = 21 ; + nmesh_edge = 32 ; + nmesh_face = 12 ; +variables: + int mesh ; + mesh:cf_role = "mesh_topology" ; + mesh:geometry = "planar" ; + mesh:topology = "non_periodic" ; + mesh:coord_sys = "xyz" ; + mesh:periodic_x = "F" ; + mesh:periodic_y = "F" ; + mesh:constructor_inputs = "geometry=planar;topology=non_periodic;coord_sys=xyz;edge_cells_x=6;edge_cells_y=2;periodic_x=F;periodic_y=F;domain_size=[6.00,2.00];domain_centre=[9000.00,7000.00]" ; + mesh:max_stencil_depth = 0 ; + mesh:n_mesh_maps = 0 ; + mesh:long_name = "Topology data of 2D unstructured mesh" ; + mesh:topology_dimension = 2 ; + mesh:node_coordinates = "mesh_node_x mesh_node_y" ; + mesh:face_coordinates = "mesh_face_x mesh_face_y" ; + mesh:face_node_connectivity = "mesh_face_nodes" ; + mesh:edge_node_connectivity = "mesh_edge_nodes" ; + mesh:face_edge_connectivity = "mesh_face_edges" ; + mesh:face_face_connectivity = "mesh_face_links" ; + mesh:domain_extents = "mesh_domain_extents" ; + mesh:npanels = 1 ; + int mesh_face_nodes(nmesh_face, Four) ; + mesh_face_nodes:cf_role = "face_node_connectivity" ; + mesh_face_nodes:long_name = "Maps every quadrilateral face to its four corner nodes." ; + mesh_face_nodes:start_index = 1 ; + int mesh_face_edges(nmesh_face, Four) ; + mesh_face_edges:cf_role = "face_edge_connectivity" ; + mesh_face_edges:long_name = "Maps every quadrilateral face to its four edges." ; + mesh_face_edges:start_index = 1 ; + int mesh_face_links(nmesh_face, Four) ; + mesh_face_links:cf_role = "face_face_connectivity" ; + mesh_face_links:long_name = "Indicates which other faces neighbour each face." ; + mesh_face_links:start_index = 1 ; + mesh_face_links:_FillValue = -9999 ; + int mesh_edge_nodes(nmesh_edge, Two) ; + mesh_edge_nodes:cf_role = "edge_node_connectivity" ; + mesh_edge_nodes:long_name = "Maps every edge to the two nodes that it connects." ; + mesh_edge_nodes:start_index = 1 ; + double mesh_node_x(nmesh_node) ; + mesh_node_x:standard_name = "projection_x_coordinate" ; + mesh_node_x:long_name = "x coordinate of 2D mesh nodes." ; + mesh_node_x:units = "m" ; + double mesh_node_y(nmesh_node) ; + mesh_node_y:standard_name = "projection_y_coordinate" ; + mesh_node_y:long_name = "y coordinate of 2D mesh nodes." ; + mesh_node_y:units = "m" ; + double mesh_face_x(nmesh_face) ; + mesh_face_x:standard_name = "projection_x_coordinate" ; + mesh_face_x:long_name = "x coordinate of 2D face centres" ; + mesh_face_x:units = "m" ; + double mesh_face_y(nmesh_face) ; + mesh_face_y:standard_name = "projection_y_coordinate" ; + mesh_face_y:long_name = "y coordinate of 2D face centres" ; + mesh_face_y:units = "m" ; + double mesh_domain_extents(Four, Two) ; + double node_empty_data_container(nmesh_node) ; + node_empty_data_container:coordinates = "mesh_node_y mesh_node_x" ; + double face_empty_data_container(nmesh_face) ; + face_empty_data_container:coordinates = "mesh_face_y mesh_face_x" ; + double edge_empty_data_container(nmesh_edge) ; + +// global attributes: + :Conventions = "CF-1.6 UGRID-1.0" ; + +data: + + mesh = _ ; + + mesh_face_nodes = + 1, 2, 3, 4, + 2, 5, 6, 3, + 5, 7, 8, 6, + 7, 9, 10, 8, + 9, 11, 12, 10, + 11, 13, 14, 12, + 15, 16, 2, 1, + 16, 17, 5, 2, + 17, 18, 7, 5, + 18, 19, 9, 7, + 19, 20, 11, 9, + 20, 21, 13, 11 ; + + mesh_face_edges = + 1, 2, 3, 4, + 3, 5, 6, 7, + 6, 8, 9, 10, + 9, 11, 12, 13, + 12, 14, 15, 16, + 15, 17, 18, 19, + 20, 21, 22, 2, + 22, 23, 24, 5, + 24, 25, 26, 8, + 26, 27, 28, 11, + 28, 29, 30, 14, + 30, 31, 32, 17 ; + + mesh_face_links = + _, 7, 2, _, + 1, 8, 3, _, + 2, 9, 4, _, + 3, 10, 5, _, + 4, 11, 6, _, + 5, 12, _, _, + _, _, 8, 1, + 7, _, 9, 2, + 8, _, 10, 3, + 9, _, 11, 4, + 10, _, 12, 5, + 11, _, _, 6 ; + + mesh_edge_nodes = + 4, 1, + 1, 2, + 3, 2, + 4, 3, + 2, 5, + 6, 5, + 3, 6, + 5, 7, + 8, 7, + 6, 8, + 7, 9, + 10, 9, + 8, 10, + 9, 11, + 12, 11, + 10, 12, + 11, 13, + 14, 13, + 12, 14, + 1, 15, + 15, 16, + 2, 16, + 16, 17, + 5, 17, + 17, 18, + 7, 18, + 18, 19, + 9, 19, + 19, 20, + 11, 20, + 20, 21, + 13, 21 ; + + // mesh_node_x = 8997, 8998, 8998, 8997, 8999, 8999, 9000, 9000, 9001, 9001, + // 9002, 9002, 9003, 9003, 8997, 8998, 8999, 9000, 9001, 9002, 9003 ; + + // mesh_node_y = 7000, 7000, 7001, 7001, 7000, 7001, 7000, 7001, 7000, 7001, + // 7000, 7001, 7000, 7001, 6999, 6999, 6999, 6999, 6999, 6999, 6999 ; + + // mesh_face_x = 8997.5, 8998.5, 8999.5, 9000.5, 9001.5, 9002.5, 8997.5, + // 8998.5, 8999.5, 9000.5, 9001.5, 9002.5 ; + + // mesh_face_y = 7000.5, 7000.5, 7000.5, 7000.5, 7000.5, 7000.5, 6999.5, + // 6999.5, 6999.5, 6999.5, 6999.5, 6999.5 ; + + mesh_node_x = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20; + + mesh_node_y = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20; + + mesh_face_x = 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5; + + mesh_face_y = 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5; + + mesh_domain_extents = + 8997, 6999, + 9003, 6999, + 9003, 7001, + 8997, 7001 ; +} diff --git a/xios_examples/setAttrCoordUGrid/test_write_metadata.py b/xios_examples/setAttrCoordUGrid/test_write_metadata.py new file mode 100644 index 0000000..84ecb43 --- /dev/null +++ b/xios_examples/setAttrCoordUGrid/test_write_metadata.py @@ -0,0 +1,67 @@ +import copy +import glob +import netCDF4 +import numpy as np +import os +import subprocess +import unittest + +import xios_examples.shared_testing as xshared + +this_path = os.path.realpath(__file__) +this_dir = os.path.dirname(this_path) + +class TestWriteMetadata(xshared._TestCase): + test_dir = this_dir + transient_inputs = ['planar_mesh.nc'] + transient_outputs = ['data_output.nc'] + executable = './write.exe' + mesh_file_cdl = 'planar_mesh.cdl' + + @classmethod + def make_a_write_test(cls, inf, nclients=1, nservers=1): + """ + this function makes a test case and returns it as a test function, + suitable to be dynamically added to a TestCase for running. + + """ + # always copy for value, don't pass by reference. + infcp = copy.copy(inf) + # expected by the fortran XIOS program's main.xml + inputfile = cls.transient_inputs[0] + outputfile = cls.transient_outputs[0] + def test_write_metadata(self): + # create a netCDF file using nc_method + cls.make_netcdf(infcp, inputfile) + cls.run_mpi_xios(nclients=nclients, nservers=nservers) + + # load the result netCDF file + runfile = '{}/{}'.format(cls.test_dir, outputfile) + assert(os.path.exists(runfile)) + run_cmd = ['ncdump', runfile] + ncd = subprocess.run(run_cmd, check=True, capture_output=True) + + with open(f'{this_dir}/expected_domain_output.cdl') as fin: + exptd = fin.read() + emsg = '' + for eline, aline in zip(exptd.split('\n'), + ncd.stdout.decode("utf-8").split('\n')): + if eline != aline: + # skip timestamp and uuid + # until we work out how not to set these + if not (':timeStamp' in eline or ':uuid' in eline): + emsg += eline + '\n' + aline + '\n\tmismatch\n\n' + + #self.assertFalse(emsg, msg='\n\n' + emsg) + return test_write_metadata + + +f = f'{this_dir}/planar_mesh.cdl' +# unique name for the test +tname = 'test_{}'.format(os.path.splitext(os.path.basename(f))[0]) +# add the test as an attribute (function) to the test class + +# !!! remove this limit on success +if os.environ.get('MVER', '').startswith('XIOS3/trunk'): + setattr(TestWriteMetadata, tname, + TestWriteMetadata.make_a_write_test(f)) diff --git a/xios_examples/setAttrCoordUGrid/write.F90 b/xios_examples/setAttrCoordUGrid/write.F90 new file mode 100644 index 0000000..3c59ec4 --- /dev/null +++ b/xios_examples/setAttrCoordUGrid/write.F90 @@ -0,0 +1,204 @@ +!----------------------------------------------------------------------------- +! (C) Crown copyright 2025 Met Office. All rights reserved. +! The file LICENCE, distributed with this code, contains details of the terms +! under which the code may be used. +!----------------------------------------------------------------------------- +!> Programme to just write data with coordinate system definition metadata. +!> +program write + use xios + use mpi + USE, INTRINSIC :: ISO_C_BINDING + use iso_fortran_env, only : output_unit + + implicit none + + integer :: comm = -1 + integer :: rank = -1 + integer :: npar = 0 + + call initialise() + call simulate() + call finalise() +contains + + subroutine initialise() + + type(xios_date) :: origin + type(xios_date) :: start + type(xios_duration) :: tstep + integer :: mpi_error + integer :: len_node, ni_node, ibegin_node + integer :: len_face, ni_face, ibegin_face + integer :: len_edge, ni_edge, ibegin_edge + double precision, dimension (:), allocatable :: node_y_vals, node_x_vals, & + face_y_vals, face_x_vals + character(len=64) :: t_origin + + origin = xios_date(2022, 2, 2, 12, 0, 0) + start = xios_date(2022, 12, 13, 12, 0, 0) + tstep = xios_hour + + ! Initialise MPI and XIOS + call MPI_INIT(mpi_error) + call xios_initialize('client', return_comm=comm) + + call MPI_Comm_rank(comm, rank, mpi_error) + call MPI_Comm_size(comm, npar, mpi_error) + + ! use the axis_check context to obtain sizing information on all arrays + ! for use in defining the main context interpretation + + print *, "initialising axis_check" + flush(output_unit) + call xios_context_initialize('axis_check', comm) + call xios_set_time_origin(origin) + call xios_set_start_date(start) + call xios_set_timestep(tstep) + + call xios_close_context_definition() + print *, "axis_check initialised" + flush(output_unit) + + ! fetch sizes of axes from the input file for allocate + call xios_get_domain_attr('cndata::', ni_glo=len_node, ni=ni_node, ibegin=ibegin_node) + call xios_get_domain_attr('cfdata::', ni_glo=len_face, ni=ni_face, ibegin=ibegin_face) + call xios_get_domain_attr('cedata::', ni_glo=len_edge, ni=ni_edge, ibegin=ibegin_edge) + + + allocate ( node_x_vals(len_node) ) + allocate ( node_y_vals(len_node) ) + allocate ( face_x_vals(len_face) ) + allocate ( face_y_vals(len_face) ) + print *, 'len_node= ', len_node + print *, 'len_edge= ', len_edge + print *, 'len_face= ', len_face + flush(output_unit) + + ! fetch coordinate value arrays from the input file + call xios_get_domain_attr('cndata::', lonvalue_1d=node_x_vals, latvalue_1d=node_y_vals) + call xios_get_domain_attr('cfdata::', lonvalue_1d=face_x_vals, latvalue_1d=face_y_vals) + print *, 'node xvals= ', node_x_vals, '\nnode yvals= ', node_y_vals + flush(output_unit) + + ! finalise axis_check context, no longer in use + call xios_context_finalize() + print *, "axis check finalised successfully" + flush(output_unit) + + ! initialize the main context for interacting with the data. + call xios_context_initialize('main', comm) + + call xios_set_time_origin(origin) + call xios_set_start_date(start) + call xios_set_timestep(tstep) + + ! define the horizontal domain and vertical axis using the input file + call xios_set_domain_attr("node_domain", ni_glo=len_node, ni=ni_node, ibegin=ibegin_node, & + nj_glo=len_node, nj=ni_node, jbegin=ibegin_node, & + latvalue_1d=node_y_vals, lonvalue_1d=node_x_vals) + call xios_set_domain_attr("face_domain", ni_glo=len_face, ni=ni_face, ibegin=ibegin_face, & + nj_glo=len_face, nj=ni_face, jbegin=ibegin_face, & + latvalue_1d=face_y_vals, lonvalue_1d=face_x_vals) + call xios_set_domain_attr("edge_domain", ni_glo=len_edge, ni=ni_edge, ibegin=ibegin_edge, & + nj_glo=len_edge, nj=ni_edge, jbegin=ibegin_edge) + + call xios_set_file_attr("data_output", convention_str="CF-1.6, UGRID") + call xios_set_file_attr("data_output", description="a file format v0.2.0") + if (.true.) then + call xios_set_axis_attr("x", standard_name="projection_x_coordinate", & + unit="m", long_name="x coordinate of projection") + call xios_set_axis_attr("y", standard_name="projection_y_coordinate", & + unit="m", long_name="y coordinate of projection") + end if + call xios_date_convert_to_string(origin, t_origin) + call xios_set_field_attr("frt", unit="seconds since "//t_origin) + !call xios_set_scalar_attr("frt", unit="seconds since "//t_origin) + + call xios_close_context_definition() + print *, "main context defined successfully" + flush(output_unit) + call xios_get_domain_attr('node_domain', lonvalue_1d=node_x_vals, latvalue_1d=node_y_vals) + print *, "x = ", node_x_vals, "y = ", node_y_vals + flush(output_unit) + + end subroutine initialise + + subroutine finalise() + + integer :: mpi_error + + ! Finalise all XIOS contexts and MPI + print *, "finalising" + call xios_set_current_context('main') + call xios_context_finalize() + print *, "finalised main" + call xios_finalize() + print *, "finalised xios" + call MPI_Finalize(mpi_error) + + end subroutine finalise + + subroutine simulate() + + type(xios_date) :: start + type(xios_date) :: current + integer :: ts + integer :: i + integer :: len_node + integer :: len_edge + integer :: len_face + double precision :: frtv + + ! Allocatable arrays, size is taken from input file + double precision, dimension (:), allocatable :: node_data!, face_data, edge_data + + ! obtain sizing of the grid for the array allocation + call xios_get_domain_attr('ndata::', ni_glo=len_node) + call xios_get_domain_attr('fdata::', ni_glo=len_face) + call xios_get_domain_attr('edata::', ni_glo=len_edge) + + allocate ( node_data(len_node) ) + allocate ( edge_data(len_edge) ) + allocate ( face_data(len_face) ) + print *, 'len_node= ', len_node + print *, 'len_edge= ', len_edge + print *, 'len_face= ', len_face + flush(output_unit) + + do ts=1, 2 + call xios_update_calendar(ts) + call xios_get_current_date(current) + ! send frt on ts0 only + if (ts == 1) then + call xios_get_start_date(start) + frtv = dble(xios_date_convert_to_seconds(start)) + call xios_send_field("frt", frtv) + ! call xios_set_scalar_attr("frt", value=frtv) + end if + + do i=1, len_node + node_data(i) = ts + end do + call xios_send_field('ndata', node_data) + + do i=1, len_face + face_data(i) = 10 * ts + call xios_send_field('fdata', face_data) + end do + + do i=1, len_edge + edge_data(i) = 100 * ts + end do + call xios_send_field('edata', edge_data) + + enddo + + deallocate (node_data) + deallocate (edge_data) + deallocate (face_data) + print *, "fields sent, exiting simulation" + + end subroutine simulate + +end program write diff --git a/xios_examples/setAttrCoordUGrid/xios.xml b/xios_examples/setAttrCoordUGrid/xios.xml new file mode 100644 index 0000000..9ec1df0 --- /dev/null +++ b/xios_examples/setAttrCoordUGrid/xios.xml @@ -0,0 +1,22 @@ + + + + + performance + + + 1.0 + + + + + true + + 100 + + + true + + + +