vortex-nwp 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. {vortex_nwp-2.2.0/src/vortex_nwp.egg-info → vortex_nwp-2.3.0}/PKG-INFO +4 -1
  2. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/pyproject.toml +8 -2
  3. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/__init__.py +3 -2
  4. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/algo/components.py +20 -2
  5. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/algo/mpitools.py +5 -6
  6. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/executables.py +28 -0
  7. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/geometries.ini +89 -24
  8. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/providers.py +44 -10
  9. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/stores.py +110 -3
  10. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/assim.py +1 -31
  11. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/forecasts.py +30 -0
  12. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/ifsroot.py +34 -20
  13. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/monitoring.py +2 -2
  14. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/odbtools.py +2 -2
  15. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/consts.py +61 -1
  16. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/diagnostics.py +2 -4
  17. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/conftools.py +7 -6
  18. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/systems.py +33 -3
  19. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0/src/vortex_nwp.egg-info}/PKG-INFO +4 -1
  20. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex_nwp.egg-info/requires.txt +6 -0
  21. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_conftools.py +1 -1
  22. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/LICENSE +0 -0
  23. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/README.md +0 -0
  24. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/setup.cfg +0 -0
  25. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/algo/__init__.py +0 -0
  26. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/algo/mpitools_templates/__init__.py +0 -0
  27. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +0 -0
  28. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +0 -0
  29. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +0 -0
  30. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/algo/serversynctools.py +0 -0
  31. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/config.py +0 -0
  32. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/__init__.py +0 -0
  33. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/abstractstores.py +0 -0
  34. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/containers.py +0 -0
  35. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/contents.py +0 -0
  36. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/flow.py +0 -0
  37. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/geometries.py +0 -0
  38. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/handlers.py +0 -0
  39. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/outflow.py +0 -0
  40. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/resources.py +0 -0
  41. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/data/sync_templates/__init__.py +0 -0
  42. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/gloves.py +0 -0
  43. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/layout/__init__.py +0 -0
  44. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/layout/contexts.py +0 -0
  45. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/layout/dataflow.py +0 -0
  46. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/layout/monitor.py +0 -0
  47. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/__init__.py +0 -0
  48. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/__init__.py +0 -0
  49. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/clim.py +0 -0
  50. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/coupling.py +0 -0
  51. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/eda.py +0 -0
  52. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/eps.py +0 -0
  53. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/fpserver.py +0 -0
  54. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/ifsnaming.py +0 -0
  55. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/mpitools.py +0 -0
  56. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/oopsroot.py +0 -0
  57. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/oopstests.py +0 -0
  58. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/request.py +0 -0
  59. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/algo/stdpost.py +0 -0
  60. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/__init__.py +0 -0
  61. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/assim.py +0 -0
  62. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/boundaries.py +0 -0
  63. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/climfiles.py +0 -0
  64. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/configfiles.py +0 -0
  65. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/ctpini.py +0 -0
  66. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/eda.py +0 -0
  67. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/eps.py +0 -0
  68. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/executables.py +0 -0
  69. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/fields.py +0 -0
  70. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/gridfiles.py +0 -0
  71. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/logs.py +0 -0
  72. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/modelstates.py +0 -0
  73. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/monitoring.py +0 -0
  74. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/namelists.py +0 -0
  75. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/obs.py +0 -0
  76. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/oopsexec.py +0 -0
  77. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/providers.py +0 -0
  78. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/query.py +0 -0
  79. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/stores.py +0 -0
  80. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/data/surfex.py +0 -0
  81. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/syntax/__init__.py +0 -0
  82. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/syntax/stdattrs.py +0 -0
  83. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/__init__.py +0 -0
  84. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/addons.py +0 -0
  85. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/agt.py +0 -0
  86. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/bdap.py +0 -0
  87. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/bdcp.py +0 -0
  88. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/bdm.py +0 -0
  89. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/bdmp.py +0 -0
  90. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/drhook.py +0 -0
  91. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/grib.py +0 -0
  92. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/gribdiff.py +0 -0
  93. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/ifstools.py +0 -0
  94. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/igastuff.py +0 -0
  95. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/mars.py +0 -0
  96. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/odb.py +0 -0
  97. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/partitioning.py +0 -0
  98. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/tools/satrad.py +0 -0
  99. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/util/__init__.py +0 -0
  100. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/util/async.py +0 -0
  101. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/util/beacon.py +0 -0
  102. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/util/diffpygram.py +0 -0
  103. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/util/ens.py +0 -0
  104. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/util/hooks.py +0 -0
  105. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/util/taskdeco.py +0 -0
  106. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/util/usepygram.py +0 -0
  107. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/nwp/util/usetnt.py +0 -0
  108. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/proxy.py +0 -0
  109. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/sessions.py +0 -0
  110. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/syntax/__init__.py +0 -0
  111. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/syntax/stdattrs.py +0 -0
  112. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/syntax/stddeco.py +0 -0
  113. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/toolbox.py +0 -0
  114. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/__init__.py +0 -0
  115. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/actions.py +0 -0
  116. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/addons.py +0 -0
  117. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/arm.py +0 -0
  118. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/compression.py +0 -0
  119. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/date.py +0 -0
  120. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/ddhpack.py +0 -0
  121. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/delayedactions.py +0 -0
  122. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/env.py +0 -0
  123. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/folder.py +0 -0
  124. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/grib.py +0 -0
  125. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/lfi.py +0 -0
  126. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/listings.py +0 -0
  127. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/names.py +0 -0
  128. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/net.py +0 -0
  129. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/odb.py +0 -0
  130. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/parallelism.py +0 -0
  131. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/prestaging.py +0 -0
  132. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/rawfiles.py +0 -0
  133. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/schedulers.py +0 -0
  134. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/services.py +0 -0
  135. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/storage.py +0 -0
  136. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/surfex.py +0 -0
  137. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/tools/targets.py +0 -0
  138. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/util/__init__.py +0 -0
  139. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/util/config.py +0 -0
  140. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/util/empty.py +0 -0
  141. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/util/helpers.py +0 -0
  142. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/util/introspection.py +0 -0
  143. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/util/iosponge.py +0 -0
  144. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/util/roles.py +0 -0
  145. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/util/storefunctions.py +0 -0
  146. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/util/structs.py +0 -0
  147. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex/util/worker.py +0 -0
  148. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex_nwp.egg-info/SOURCES.txt +0 -0
  149. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex_nwp.egg-info/dependency_links.txt +0 -0
  150. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/src/vortex_nwp.egg-info/top_level.txt +0 -0
  151. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_algo_server.py +0 -0
  152. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_arpifs_listings_integration.py +0 -0
  153. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_cfgparser.py +0 -0
  154. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_cfgtemplating.py +0 -0
  155. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_compression.py +0 -0
  156. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_config.py +0 -0
  157. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_containers.py +0 -0
  158. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_doctests.py +0 -0
  159. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_ecflow.py +0 -0
  160. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_ecmwf_interface.py +0 -0
  161. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_env.py +0 -0
  162. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_epygram.py +0 -0
  163. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_gco.py +0 -0
  164. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_geometries.py +0 -0
  165. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_ifstools.py +0 -0
  166. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_import.py +0 -0
  167. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_iosponge.py +0 -0
  168. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_job_examples.py +0 -0
  169. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_layoutappconf.py +0 -0
  170. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_layoutjobs.py +0 -0
  171. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_layoutmonitor.py +0 -0
  172. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_layoutnodes.py +0 -0
  173. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_net_netstat.py +0 -0
  174. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_net_ssh.py +0 -0
  175. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_partitioning.py +0 -0
  176. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_providers.py +0 -0
  177. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_sessions_stuff.py +0 -0
  178. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_simpleworkflow.py +0 -0
  179. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_smartftget.py +0 -0
  180. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_ssh.py +0 -0
  181. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_storage.py +0 -0
  182. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_stores.py +0 -0
  183. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_syntax.py +0 -0
  184. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_targets.py +0 -0
  185. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_templates.py +0 -0
  186. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_toolsodb.py +0 -0
  187. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_uget.py +0 -0
  188. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_user_remote_dtree.py +0 -0
  189. {vortex_nwp-2.2.0 → vortex_nwp-2.3.0}/tests/test_vortexnames.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortex-nwp
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: A Python library to write Numerical Weather Prediction pipelines components
5
5
  Author-email: The Vortex Team <vortex.support@meteo.fr>
6
6
  License: CECILL-C
@@ -14,6 +14,7 @@ Requires-Dist: footprints
14
14
  Requires-Dist: taylorism
15
15
  Requires-Dist: tomli
16
16
  Requires-Dist: arpifs_listings
17
+ Requires-Dist: importlib_metadata; python_version < "3.8"
17
18
  Provides-Extra: docs
18
19
  Requires-Dist: sphinx; extra == "docs"
19
20
  Requires-Dist: sphinx-book-theme; extra == "docs"
@@ -21,6 +22,8 @@ Requires-Dist: sphinx-copybutton; extra == "docs"
21
22
  Provides-Extra: dev
22
23
  Requires-Dist: ruff==0.9.1; extra == "dev"
23
24
  Requires-Dist: pytest; extra == "dev"
25
+ Provides-Extra: git
26
+ Requires-Dist: pygit2; extra == "git"
24
27
  Dynamic: license-file
25
28
 
26
29
  ## vortex
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vortex-nwp"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  description = "A Python library to write Numerical Weather Prediction pipelines components"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.7"
@@ -18,12 +18,18 @@ classifiers = [
18
18
  "License :: CeCILL-C Free Software License Agreement (CECILL-C)",
19
19
  ]
20
20
  dependencies = [
21
- "bronx", "footprints", "taylorism", "tomli", "arpifs_listings",
21
+ "bronx",
22
+ "footprints",
23
+ "taylorism",
24
+ "tomli",
25
+ "arpifs_listings",
26
+ "importlib_metadata; python_version < '3.8'",
22
27
  ]
23
28
 
24
29
  [project.optional-dependencies]
25
30
  docs = ["sphinx", "sphinx-book-theme", "sphinx-copybutton"]
26
31
  dev = ["ruff==0.9.1", "pytest"]
32
+ git = ["pygit2"]
27
33
 
28
34
  [tool.setuptools.packages.find]
29
35
  where = ["src"]
@@ -50,13 +50,14 @@ from .toolbox import promise as promise
50
50
  from .toolbox import diff as diff
51
51
  from .toolbox import defaults as defaults
52
52
  from .toolbox import algo as task
53
+ from .toolbox import VortexForceComplete as VortexForceComplete
53
54
 
54
55
  from . import nwp as nwp # footprints import
55
56
 
56
- __version__ = "2.2.0"
57
+ __version__ = "2.3.0"
57
58
  __prompt__ = "Vortex v-" + __version__ + ":"
58
59
 
59
- __nextversion__ = "2.2.1"
60
+ __nextversion__ = "2.3.1"
60
61
  __tocinfoline__ = "VORTEX core package"
61
62
 
62
63
  __all__ = [
@@ -1548,12 +1548,25 @@ class Expresso(ExecutableAlgoComponent):
1548
1548
  attr=dict(
1549
1549
  interpreter=dict(
1550
1550
  info="The interpreter needed to run the script.",
1551
- values=["current", "awk", "ksh", "bash", "perl", "python"],
1551
+ values=[
1552
+ "current",
1553
+ "awk",
1554
+ "ksh",
1555
+ "bash",
1556
+ "perl",
1557
+ "python",
1558
+ "singularity",
1559
+ ],
1552
1560
  ),
1553
1561
  interpreter_path=dict(
1554
1562
  info="The interpreter command.",
1555
1563
  optional=True,
1556
1564
  ),
1565
+ interpreter_args=dict(
1566
+ info="Some options to pass to the expresso",
1567
+ optional=True,
1568
+ default="",
1569
+ ),
1557
1570
  engine=dict(values=["exec", "launch"]),
1558
1571
  ),
1559
1572
  )
@@ -1581,11 +1594,16 @@ class Expresso(ExecutableAlgoComponent):
1581
1594
  )
1582
1595
 
1583
1596
  def _interpreter_args_fix(self, rh, opts):
1597
+ cmd_opts = shlex.split(self.interpreter_args)
1598
+
1584
1599
  absexec = self.absexcutable(rh.container.localpath())
1585
1600
  if self.interpreter == "awk":
1586
- return ["-f", absexec]
1601
+ return [*cmd_opts, "-f", absexec]
1602
+ if self.interpreter == "singularity":
1603
+ return ["run", *cmd_opts, absexec]
1587
1604
  else:
1588
1605
  return [
1606
+ *cmd_opts,
1589
1607
  absexec,
1590
1608
  ]
1591
1609
 
@@ -544,10 +544,6 @@ class MpiTool(footprints.FootprintBase):
544
544
  """When group are defined, associate each MPI rank with a "real" slot."""
545
545
  if self._ranks_map_cache is None:
546
546
  self._complex_ranks_map = False
547
- if not self.envelope:
548
- raise RuntimeError(
549
- "Ranks mapping should always be used within an envelope."
550
- )
551
547
  # First deal with bingroups
552
548
  ranks_map = dict()
553
549
  has_bin_groups = not all([b.group is None for b in self.binaries])
@@ -624,7 +620,7 @@ class MpiTool(footprints.FootprintBase):
624
620
  if self._complex_ranks_map or do_bin_distribution:
625
621
  if not self.envelope:
626
622
  raise RuntimeError(
627
- "Ranks mapping shoudl always be used within an envelope."
623
+ "Ranks mapping should always be used within an envelope."
628
624
  )
629
625
  if do_bin_distribution:
630
626
  if not self._supports_manual_ranks_mapping:
@@ -1404,7 +1400,10 @@ class SRun(MpiTool):
1404
1400
  ),
1405
1401
  optmap=dict(
1406
1402
  default=footprints.FPDict(
1407
- nn="nodes", nnp="ntasks-per-node", np="ntasks"
1403
+ nn="nodes",
1404
+ nnp="ntasks-per-node",
1405
+ np="ntasks",
1406
+ openmp="cpus-per-task",
1408
1407
  )
1409
1408
  ),
1410
1409
  slurmversion=dict(type=int, optional=True),
@@ -106,6 +106,34 @@ class Script(Executable):
106
106
  return self.rawopts
107
107
 
108
108
 
109
+ class SingularityImage(Executable):
110
+ """Singularity container image executable."""
111
+
112
+ _footprint = dict(
113
+ attr=dict(
114
+ kind=dict(
115
+ values=["image"],
116
+ ),
117
+ cmd=dict(
118
+ info="Command to run inside the container",
119
+ optional=True,
120
+ default="",
121
+ ),
122
+ ),
123
+ )
124
+
125
+ @property
126
+ def realkind(self):
127
+ return "image"
128
+
129
+ # Called by AlgoComponent.spawn_command_line
130
+ def command_line(self, **opts):
131
+ return " ".join(
132
+ ["--{:s} {:s}".format(opt, val) for opt, val in opts.items()]
133
+ + [self.cmd]
134
+ )
135
+
136
+
109
137
  class GnuScript(Executable):
110
138
  """Basic interpreted executable with standard command line arguments."""
111
139
 
@@ -659,6 +659,13 @@ area = indonesie1
659
659
  resolution = 2.50
660
660
  runit = km
661
661
 
662
+ [asscomindonesie2sp]
663
+ info = Arome asscom14 - indonesie2 spectral geometry
664
+ kind = projected
665
+ area = indonesie2
666
+ resolution = 2.50
667
+ runit = km
668
+
662
669
  [lace20km]
663
670
  info = Coupling grid for LACE partners
664
671
  kind = projected
@@ -673,6 +680,13 @@ area = lace
673
680
  resolution = 8.0
674
681
  runit = km
675
682
 
683
+ [lace47]
684
+ info = LACE canari domain
685
+ kind = projected
686
+ area = lace47
687
+ resolution = 4.71
688
+ runit = km
689
+
676
690
  [afgt20km]
677
691
  info = Coupling grid for afgt-Praha
678
692
  kind = projected
@@ -1201,15 +1215,15 @@ nlon = 641
1201
1215
  [paris1s100]
1202
1216
  info = PARIS 0.01
1203
1217
  kind = lonlat
1204
- area = paris1s100
1218
+ area = PARIS1S100
1205
1219
  resolution = 0.01
1206
- nlat = 221
1207
- nlon = 336
1220
+ nlat = 187
1221
+ nlon = 279
1208
1222
 
1209
1223
  [medalp1s100]
1210
1224
  info = MEDALP 0.01
1211
1225
  kind = lonlat
1212
- area = medalp1s100
1226
+ area = MEDALP1S100
1213
1227
  resolution = 0.01
1214
1228
  nlat = 481
1215
1229
  nlon = 501
@@ -1677,6 +1691,17 @@ nlat = 172800
1677
1691
  latmax = 83.999
1678
1692
  latmin = -55.999
1679
1693
 
1694
+ [srtm_01s]
1695
+ info = alternative to no_arctics7.5s used to create arome-500m PGD clim
1696
+ kind = lonlat
1697
+ area = srtm
1698
+ resolution = 1
1699
+ runit = s
1700
+ nlon = 999
1701
+ nlat = 999
1702
+ latmax = 999
1703
+ latmin = 999
1704
+
1680
1705
  [europe_sw3s]
1681
1706
  kind = lonlat
1682
1707
  area = europe_sw
@@ -2468,39 +2493,39 @@ nlon = 896
2468
2493
  resolution = 0.05
2469
2494
  runit = deg
2470
2495
 
2471
- [hycom3dmedoc]
2472
- info = Shom HYCOM3D Med-occidental at 1/60
2496
+ [hycom3dmednowe]
2497
+ info = Shom HYCOM3D Med-NorthWest at 1/60
2473
2498
  kind = lonlat
2474
- area = medoc
2475
- nlat = 331
2476
- nlon = 661
2499
+ area = mednowe
2500
+ nlat = 437
2501
+ nlon = 761
2477
2502
  resolution = 0.017
2478
2503
  runit = deg
2479
2504
 
2480
- [hycom3dmedor]
2481
- info = Shom HYCOM3D Med-oriental at 1/60
2505
+ [hycom3dmedsowe]
2506
+ info = Shom HYCOM3D Med-SouthWest at 1/60
2482
2507
  kind = lonlat
2483
- area = medor
2484
- nlat = 437
2485
- nlon = 645
2508
+ area = medsowe
2509
+ nlat = 481
2510
+ nlon = 1080
2486
2511
  resolution = 0.017
2487
2512
  runit = deg
2488
2513
 
2489
- [hycom3dmedio]
2490
- info = Shom HYCOM3D Med-ionian at 1/60
2514
+ [hycom3dmedea]
2515
+ info = Shom HYCOM3D Med-East at 1/60
2491
2516
  kind = lonlat
2492
- area = medio
2493
- nlat = 557
2494
- nlon = 781
2517
+ area = medea
2518
+ nlat = 706
2519
+ nlon = 855
2495
2520
  resolution = 0.017
2496
2521
  runit = deg
2497
2522
 
2498
- [hycom3dgibal]
2499
- info = Shom HYCOM3D Gibraltair Strait-Alboran Sea at 1/60
2523
+ [hycom3dmedinte]
2524
+ info = Shom HYCOM3D Med-Interior at 1/60
2500
2525
  kind = lonlat
2501
- area = gibal
2502
- nlat = 361
2503
- nlon = 571
2526
+ area = medinte
2527
+ nlat = 935
2528
+ nlon = 781
2504
2529
  resolution = 0.017
2505
2530
  runit = deg
2506
2531
 
@@ -2575,6 +2600,13 @@ area = corsica
2575
2600
  resolution = 2.5
2576
2601
  runit = km
2577
2602
 
2603
+ [nl2500]
2604
+ info = HARMONIE-AROME 2.5km NL domain for DAVAI
2605
+ kind = projected
2606
+ area = nl
2607
+ resolution = 2.5
2608
+ runit = km
2609
+
2578
2610
  [cors1s40]
2579
2611
  info = Corsica 0.025 deg
2580
2612
  kind = lonlat
@@ -2669,6 +2701,39 @@ area = chmh2325
2669
2701
  resolution = 2.33
2670
2702
  runit = km
2671
2703
 
2704
+ [armcu]
2705
+ info = MUSC Single point ARMCU
2706
+ kind = unstructured
2707
+ area = armcu
2708
+
2709
+ [global31c1]
2710
+ info = ARPEGE T31C1.0 geometry (from Mitraillette tests)
2711
+ kind = gauss
2712
+ area = france
2713
+ truncation = 31
2714
+ stretching = 1.0
2715
+
2716
+ [global30c24]
2717
+ info = ARPEGE T30C2.4 geometry (from Mitraillette tests)
2718
+ kind = gauss
2719
+ area = france
2720
+ truncation = 30
2721
+ stretching = 2.4
2722
+
2723
+ [portugal21km]
2724
+ info = Mitraillette Portugal grid
2725
+ kind = projected
2726
+ area = portugal
2727
+ resolution = 21.3
2728
+ runit = km
2729
+
2730
+ [occitanie4km]
2731
+ info = Mitraillette Occitanie grid
2732
+ kind = projected
2733
+ area = occitanie
2734
+ resolution = 4.4
2735
+ runit = km
2736
+
2672
2737
  #R ===================
2673
2738
  #R Combined geometries
2674
2739
  #R ===================
@@ -64,13 +64,6 @@ class Provider(footprints.FootprintBase):
64
64
  fastkeys={"namespace"},
65
65
  )
66
66
 
67
- def __init__(self, *args, **kw):
68
- logger.debug("Abstract provider init %s", self.__class__)
69
- super().__init__(*args, **kw)
70
-
71
- if not self.username:
72
- self.username = vortex.ticket().glove.user
73
-
74
67
  def _str_more(self):
75
68
  """Additional information to print representation."""
76
69
  try:
@@ -92,7 +85,9 @@ class Provider(footprints.FootprintBase):
92
85
 
93
86
  def netuser_name(self, resource): # @UnusedVariable
94
87
  """Abstract method."""
95
- return self.username
88
+ if self.username is not None:
89
+ return self.username
90
+ return vortex.ticket().glove.user
96
91
 
97
92
  def pathname(self, resource):
98
93
  """Abstract method."""
@@ -124,10 +119,11 @@ class Provider(footprints.FootprintBase):
124
119
  The different operations of the algorithm can be redefined by subclasses.
125
120
  """
126
121
  username = self.netuser_name(resource)
122
+ netloc = self.netloc(resource)
127
123
  fullnetloc = (
128
124
  "{:s}@{:s}".format(username, self.netloc(resource))
129
- if username
130
- else self.netloc(resource)
125
+ if (username and netloc)
126
+ else netloc
131
127
  )
132
128
  logger.debug(
133
129
  "scheme %s netloc %s normpath %s urlquery %s",
@@ -279,6 +275,44 @@ def set_namespace_from_cache_settings(usecache, usearchive):
279
275
  return ".".join(("vortex", domain, "fr"))
280
276
 
281
277
 
278
+ class Git(Provider):
279
+ _footprint = dict(
280
+ info="Git provider",
281
+ attr=dict(
282
+ ref=dict(
283
+ type=str,
284
+ optional=True,
285
+ default=None,
286
+ info="The reference's SHA-1 hash id",
287
+ ),
288
+ repo=dict(
289
+ type=str,
290
+ info="Path to the Git repository",
291
+ ),
292
+ path=dict(
293
+ type=str,
294
+ info="File path within the repository",
295
+ ),
296
+ ),
297
+ )
298
+
299
+ def scheme(self, resource):
300
+ return "git"
301
+
302
+ def urlquery(self, resource):
303
+ return (
304
+ f"repo={self.repo}&ref={self.ref}"
305
+ if self.ref
306
+ else f"repo={self.repo}"
307
+ )
308
+
309
+ def basename(self, resource):
310
+ return self.path
311
+
312
+ def pathname(sef, resource):
313
+ return "."
314
+
315
+
282
316
  class Vortex(Provider):
283
317
  """Main provider of the toolbox, using a fix-size path and a dedicated name factory."""
284
318
 
@@ -8,8 +8,11 @@ Store objects use the :mod:`footprints` mechanism.
8
8
  import copy
9
9
  import ftplib
10
10
  import io
11
+ from pathlib import Path
11
12
  import os
12
13
  import re
14
+ import shutil
15
+ import subprocess
13
16
 
14
17
  from bronx.fancies import loggers
15
18
  import footprints
@@ -33,6 +36,13 @@ __all__ = []
33
36
 
34
37
  logger = loggers.getLogger(__name__)
35
38
 
39
+ try:
40
+ import pygit2
41
+
42
+ NO_PYGIT2 = False
43
+ except ImportError:
44
+ NO_PYGIT2 = True
45
+
36
46
 
37
47
  def get_cache_location():
38
48
  try:
@@ -335,7 +345,6 @@ class Finder(Store):
335
345
  def _ftpinfos(self, remote, **kwargs):
336
346
  args = kwargs.copy()
337
347
  args["hostname"] = self.hostname()
338
- args["logname"] = remote["username"]
339
348
  port = self.hostname().netport
340
349
  if port is not None:
341
350
  args["port"] = port
@@ -422,6 +431,98 @@ class Finder(Store):
422
431
  return rc
423
432
 
424
433
 
434
+ class GitStore(Store):
435
+ _footprint = dict(
436
+ info="A store to access Git repositories",
437
+ attr=dict(
438
+ scheme=dict(
439
+ values=["git"],
440
+ ),
441
+ ),
442
+ )
443
+
444
+ def gitget(self, remote, local, options):
445
+ if NO_PYGIT2:
446
+ raise ModuleNotFoundError(
447
+ "pygit2 is not installed in the current environment"
448
+ )
449
+ annex_cache_path = Path(remote["query"]["repo"][0])
450
+
451
+ # If no git reference is provided, only make a copy of the
452
+ # worktree
453
+ if "ref" not in remote["query"]:
454
+ shutil.copy(
455
+ src=Path(annex_cache_path) / remote["path"].lstrip("/"),
456
+ dst=local,
457
+ )
458
+ return local
459
+
460
+ assert "ref" in remote["query"]
461
+ path = remote["path"].lstrip("/")
462
+ repo = pygit2.Repository(annex_cache_path)
463
+ oid = repo.revparse_single(remote["query"]["ref"][0])
464
+ obj = oid.peel(pygit2.Tree) / path
465
+ if obj.filemode == pygit2.enums.FileMode.LINK:
466
+ # Here we need to discriminate between links that point
467
+ # to git-annex managed files and all other symlinks.
468
+
469
+ # If the file is a git-annex link, work out the location
470
+ # of the data within the .git/annex dir and copy this
471
+ # file into the cwd with the right name
472
+ if ".git/annex/objects" in obj.data.decode("ASCII"):
473
+ gitannex_key = Path(obj.data.decode("ASCII")).name
474
+ subprocess.run(
475
+ args=["git-annex", "get", "--key", gitannex_key],
476
+ cwd=str(annex_cache_path),
477
+ )
478
+ gitannex_content_location = subprocess.run(
479
+ args=["git-annex", "contentlocation", gitannex_key],
480
+ cwd=str(annex_cache_path),
481
+ capture_output=True,
482
+ encoding="ASCII",
483
+ ).stdout
484
+ shutil.copy(
485
+ src=annex_cache_path / Path(gitannex_content_location),
486
+ dst=local,
487
+ follow_symlinks=True,
488
+ )
489
+ return local
490
+ os.symlink(
491
+ src=obj.data.decode("ASCII"),
492
+ dst=local,
493
+ )
494
+ return local
495
+
496
+ if obj.filemode == pygit2.enums.FileMode.BLOB:
497
+ with open(local, "wb") as dst:
498
+ # Could also use pygit2.BlobIO to stream content
499
+ # without having to load the entire blob data in
500
+ # memory:
501
+ #
502
+ # with pygit2.BlobIO(obj) as src:
503
+ # shutil.copyfileobj(fsrc=src, fdst=dst)
504
+ dst.write(obj.data)
505
+ return local
506
+
507
+ if obj.filemode == pygit2.enums.FileMode.TREE:
508
+ if local.endswith("/"):
509
+ localpath = "."
510
+ else:
511
+ localpath = local
512
+ os.mkdir(local)
513
+ for subobj in obj:
514
+ r = {
515
+ "query": {"ref": remote["query"]["ref"]},
516
+ "path": str(Path(path) / subobj.name),
517
+ }
518
+ self.gitget(
519
+ remote=r,
520
+ local=str(Path(localpath) / subobj.name),
521
+ options=None,
522
+ )
523
+ return local
524
+
525
+
425
526
  class _VortexStackedStorageMixin:
426
527
  """Mixin class that adds utility functions to work with stacked data."""
427
528
 
@@ -985,7 +1086,10 @@ class VortexCacheMtStore(_VortexCacheBaseStore):
985
1086
  except config.ConfigurationError:
986
1087
  cacheloc = os.path.join(os.environ["HOME"], ".vortex.d")
987
1088
 
988
- if self.username != self.system.glove.user:
1089
+ current_vortex_user = self.system.glove.user
1090
+ cacheloc = cacheloc.replace("%usr%", current_vortex_user)
1091
+
1092
+ if self.username != current_vortex_user:
989
1093
  return os.path.join(cacheloc, self.username)
990
1094
 
991
1095
  return cacheloc
@@ -1205,6 +1309,9 @@ class VortexStackStore(_AbstractVortexStackMultiStore):
1205
1309
  """Go through the various stacked stores."""
1206
1310
  return [f"{self.netloc.firstname}.stacked-cache-mt.fr"]
1207
1311
 
1312
+ def alternates_fpextras(self):
1313
+ return dict(username=self.username)
1314
+
1208
1315
 
1209
1316
  class VortexVsopStackStore(_AbstractVortexStackMultiStore):
1210
1317
  """Store intended to read and write data into VORTEX R&D stacks."""
@@ -1336,7 +1443,7 @@ class PromiseCacheStore(VortexCacheMtStore):
1336
1443
  )
1337
1444
 
1338
1445
  @property
1339
- def cache_promise(self):
1446
+ def cache_entry(self):
1340
1447
  return os.path.join(super().cache_entry, "promise")
1341
1448
 
1342
1449
  @staticmethod
@@ -5,7 +5,7 @@ AlgoComponents dedicated to computations related to Data Assimilation systems.
5
5
  from bronx.fancies import loggers
6
6
  from bronx.stdtypes.date import Date
7
7
 
8
- from vortex.algo.components import BlindRun, Parallel
8
+ from vortex.algo.components import BlindRun
9
9
  from vortex.syntax.stdattrs import a_date
10
10
  from .ifsroot import IFSParallel
11
11
  from ..tools import odb, drhook
@@ -16,36 +16,6 @@ __all__ = []
16
16
  logger = loggers.getLogger(__name__)
17
17
 
18
18
 
19
- class MergeVarBC(Parallel):
20
- """Merge two VarBC files.
21
-
22
- The VarBC file resulting from the MergeVarBC contains all the items of the
23
- first VarBC file plus any new item that would be present in the second file.
24
- """
25
-
26
- _footprint = dict(
27
- attr=dict(
28
- kind=dict(
29
- values=["mergevarbc"],
30
- ),
31
- varbcout=dict(
32
- optional=True,
33
- default="VARBC.cycle_out",
34
- ),
35
- )
36
- )
37
-
38
- def prepare(self, rh, opts):
39
- """Find any ODB candidate in input files."""
40
-
41
- sh = self.system
42
-
43
- sh.touch(self.varbcout)
44
-
45
- # Let ancesters doing real stuff
46
- super().prepare(rh, opts)
47
-
48
-
49
19
  class Anamix(IFSParallel):
50
20
  """Merge the surface and atmospheric analyses into a single file"""
51
21
 
@@ -112,6 +112,8 @@ class Forecast(IFSParallel):
112
112
  # Possibly fix post-processing clim files
113
113
  self.all_localclim_fixer(rh, thismonth)
114
114
 
115
+ self.grab(analysis, comment="analysis")
116
+
115
117
  # File linking for IAU increments
116
118
  #
117
119
  # In the case of a forecast with IAU, the IFS executable
@@ -884,3 +886,31 @@ class OfflineSurfex(Parallel, DrHookDecoMixin):
884
886
  if namsec.rh.contents.dumps_needs_update:
885
887
  namsec.rh.save()
886
888
  logger.info("Namelist dump: \n%s", namsec.rh.container.read())
889
+
890
+
891
+ class MUSCForecast(Forecast):
892
+ """Forecast for MUSC single-column model."""
893
+
894
+ _footprint = dict(
895
+ info="Run a forecast with a MUSC single-column model.",
896
+ attr=dict(
897
+ kind=dict(
898
+ values=["musc"],
899
+ ),
900
+ ),
901
+ )
902
+
903
+ def postfix(self, rh, opts):
904
+ """Post forecast information and cleaning."""
905
+ sh = self.system
906
+ # rename specific output files with hours term on 4 digits for compatibility for fmth formatting
907
+ fmt = re.compile(r"Out\.(?P<termh>\d{3})\.\d{4}\.lfa$")
908
+ for f in [
909
+ fmt.match(f)
910
+ for f in sh.listdir()
911
+ if f.startswith("Out.") and f.endswith(".lfa")
912
+ ]:
913
+ sh.rename(
914
+ f.string, "Out.{:>04}.0000.lfa".format(int(f.group("termh")))
915
+ )
916
+ super().postfix(rh, opts)