diff --git a/examples/1_creating_an_OSNAP_section.ipynb b/examples/1_creating_an_OSNAP_section.ipynb index f79c9a9..7f58a59 100644 --- a/examples/1_creating_an_OSNAP_section.ipynb +++ b/examples/1_creating_an_OSNAP_section.ipynb @@ -23,10 +23,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:38.470215Z", - "iopub.status.busy": "2026-08-02T23:27:38.470102Z", - "iopub.status.idle": "2026-08-02T23:27:39.843144Z", - "shell.execute_reply": "2026-08-02T23:27:39.842435Z" + "iopub.execute_input": "2026-08-04T02:01:42.488689Z", + "iopub.status.busy": "2026-08-04T02:01:42.488449Z", + "iopub.status.idle": "2026-08-04T02:01:45.442158Z", + "shell.execute_reply": "2026-08-04T02:01:45.441763Z" } }, "outputs": [ @@ -34,7 +34,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Sectionate version: 0.4.0rc2.dev1\n" + "Sectionate version: 0.4.0rc2.dev3\n" ] } ], @@ -74,10 +74,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:39.873184Z", - "iopub.status.busy": "2026-08-02T23:27:39.872797Z", - "iopub.status.idle": "2026-08-02T23:27:39.965910Z", - "shell.execute_reply": "2026-08-02T23:27:39.965381Z" + "iopub.execute_input": "2026-08-04T02:01:45.456437Z", + "iopub.status.busy": "2026-08-04T02:01:45.456296Z", + "iopub.status.idle": "2026-08-04T02:01:45.489296Z", + "shell.execute_reply": "2026-08-04T02:01:45.488946Z" } }, "outputs": [ @@ -122,10 +122,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:39.967330Z", - "iopub.status.busy": "2026-08-02T23:27:39.967244Z", - "iopub.status.idle": "2026-08-02T23:27:39.969068Z", - "shell.execute_reply": "2026-08-02T23:27:39.968678Z" + "iopub.execute_input": "2026-08-04T02:01:45.490519Z", + "iopub.status.busy": "2026-08-04T02:01:45.490459Z", + "iopub.status.idle": "2026-08-04T02:01:45.492106Z", + "shell.execute_reply": "2026-08-04T02:01:45.491756Z" } }, "outputs": [], @@ -147,10 +147,10 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:39.970692Z", - "iopub.status.busy": "2026-08-02T23:27:39.970589Z", - "iopub.status.idle": "2026-08-02T23:27:40.937846Z", - "shell.execute_reply": "2026-08-02T23:27:40.937285Z" + "iopub.execute_input": "2026-08-04T02:01:45.493150Z", + "iopub.status.busy": "2026-08-04T02:01:45.493100Z", + "iopub.status.idle": "2026-08-04T02:01:45.875493Z", + "shell.execute_reply": "2026-08-04T02:01:45.874991Z" } }, "outputs": [], @@ -167,10 +167,10 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:40.941415Z", - "iopub.status.busy": "2026-08-02T23:27:40.941308Z", - "iopub.status.idle": "2026-08-02T23:27:41.934693Z", - "shell.execute_reply": "2026-08-02T23:27:41.933929Z" + "iopub.execute_input": "2026-08-04T02:01:45.876703Z", + "iopub.status.busy": "2026-08-04T02:01:45.876630Z", + "iopub.status.idle": "2026-08-04T02:01:46.650395Z", + "shell.execute_reply": "2026-08-04T02:01:46.649969Z" } }, "outputs": [ @@ -218,10 +218,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:41.937082Z", - "iopub.status.busy": "2026-08-02T23:27:41.936913Z", - "iopub.status.idle": "2026-08-02T23:27:41.965446Z", - "shell.execute_reply": "2026-08-02T23:27:41.965007Z" + "iopub.execute_input": "2026-08-04T02:01:46.651743Z", + "iopub.status.busy": "2026-08-04T02:01:46.651652Z", + "iopub.status.idle": "2026-08-04T02:01:46.673811Z", + "shell.execute_reply": "2026-08-04T02:01:46.673484Z" } }, "outputs": [], @@ -249,10 +249,10 @@ "execution_count": 7, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:41.966990Z", - "iopub.status.busy": "2026-08-02T23:27:41.966897Z", - "iopub.status.idle": "2026-08-02T23:27:42.221809Z", - "shell.execute_reply": "2026-08-02T23:27:42.221184Z" + "iopub.execute_input": "2026-08-04T02:01:46.674910Z", + "iopub.status.busy": "2026-08-04T02:01:46.674851Z", + "iopub.status.idle": "2026-08-04T02:01:46.808809Z", + "shell.execute_reply": "2026-08-04T02:01:46.808475Z" } }, "outputs": [ @@ -307,10 +307,10 @@ "execution_count": 8, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.223413Z", - "iopub.status.busy": "2026-08-02T23:27:42.223309Z", - "iopub.status.idle": "2026-08-02T23:27:42.426340Z", - "shell.execute_reply": "2026-08-02T23:27:42.425741Z" + "iopub.execute_input": "2026-08-04T02:01:46.810059Z", + "iopub.status.busy": "2026-08-04T02:01:46.809995Z", + "iopub.status.idle": "2026-08-04T02:01:46.912776Z", + "shell.execute_reply": "2026-08-04T02:01:46.912484Z" } }, "outputs": [ @@ -318,7 +318,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/opt/anaconda3/envs/sectionate-pr8/lib/python3.14/site-packages/xgcm/grid.py:463: UserWarning: The north-fold (tripolar) boundary condition is experimental. Its API and numerical behavior may change in future releases, and it has not yet been validated across the full range of grid configurations. Please review results carefully and report any issues at https://github.com/xgcm/xgcm/issues.\n", + "/Users/hfdrake/miniforge3/envs/docs_env_sectionate_fix-layer-interface-matching/lib/python3.14/site-packages/xgcm/grid.py:463: UserWarning: The north-fold (tripolar) boundary condition is experimental. Its API and numerical behavior may change in future releases, and it has not yet been validated across the full range of grid configurations. Please review results carefully and report any issues at https://github.com/xgcm/xgcm/issues.\n", " warnings.warn(\n" ] } @@ -343,10 +343,10 @@ "execution_count": 9, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.428919Z", - "iopub.status.busy": "2026-08-02T23:27:42.428805Z", - "iopub.status.idle": "2026-08-02T23:27:42.430711Z", - "shell.execute_reply": "2026-08-02T23:27:42.430325Z" + "iopub.execute_input": "2026-08-04T02:01:46.913869Z", + "iopub.status.busy": "2026-08-04T02:01:46.913814Z", + "iopub.status.idle": "2026-08-04T02:01:46.915373Z", + "shell.execute_reply": "2026-08-04T02:01:46.915068Z" } }, "outputs": [], @@ -370,10 +370,10 @@ "execution_count": 10, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.432016Z", - "iopub.status.busy": "2026-08-02T23:27:42.431933Z", - "iopub.status.idle": "2026-08-02T23:27:42.455060Z", - "shell.execute_reply": "2026-08-02T23:27:42.454501Z" + "iopub.execute_input": "2026-08-04T02:01:46.916215Z", + "iopub.status.busy": "2026-08-04T02:01:46.916166Z", + "iopub.status.idle": "2026-08-04T02:01:46.932327Z", + "shell.execute_reply": "2026-08-04T02:01:46.931936Z" } }, "outputs": [], @@ -390,10 +390,10 @@ "execution_count": 11, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.456567Z", - "iopub.status.busy": "2026-08-02T23:27:42.456469Z", - "iopub.status.idle": "2026-08-02T23:27:42.591668Z", - "shell.execute_reply": "2026-08-02T23:27:42.590945Z" + "iopub.execute_input": "2026-08-04T02:01:46.933351Z", + "iopub.status.busy": "2026-08-04T02:01:46.933294Z", + "iopub.status.idle": "2026-08-04T02:01:47.029043Z", + "shell.execute_reply": "2026-08-04T02:01:47.028616Z" } }, "outputs": [ @@ -433,10 +433,10 @@ "execution_count": 12, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.602954Z", - "iopub.status.busy": "2026-08-02T23:27:42.602673Z", - "iopub.status.idle": "2026-08-02T23:27:42.665350Z", - "shell.execute_reply": "2026-08-02T23:27:42.664867Z" + "iopub.execute_input": "2026-08-04T02:01:47.030083Z", + "iopub.status.busy": "2026-08-04T02:01:47.030000Z", + "iopub.status.idle": "2026-08-04T02:01:47.045217Z", + "shell.execute_reply": "2026-08-04T02:01:47.044885Z" } }, "outputs": [], @@ -453,10 +453,10 @@ "execution_count": 13, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.666984Z", - "iopub.status.busy": "2026-08-02T23:27:42.666876Z", - "iopub.status.idle": "2026-08-02T23:27:42.767392Z", - "shell.execute_reply": "2026-08-02T23:27:42.766990Z" + "iopub.execute_input": "2026-08-04T02:01:47.046152Z", + "iopub.status.busy": "2026-08-04T02:01:47.046094Z", + "iopub.status.idle": "2026-08-04T02:01:47.111056Z", + "shell.execute_reply": "2026-08-04T02:01:47.110790Z" } }, "outputs": [ @@ -529,10 +529,10 @@ "execution_count": 14, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.769036Z", - "iopub.status.busy": "2026-08-02T23:27:42.768933Z", - "iopub.status.idle": "2026-08-02T23:27:42.771564Z", - "shell.execute_reply": "2026-08-02T23:27:42.771179Z" + "iopub.execute_input": "2026-08-04T02:01:47.112255Z", + "iopub.status.busy": "2026-08-04T02:01:47.112186Z", + "iopub.status.idle": "2026-08-04T02:01:47.114192Z", + "shell.execute_reply": "2026-08-04T02:01:47.113925Z" } }, "outputs": [ @@ -593,10 +593,10 @@ "execution_count": 15, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.773006Z", - "iopub.status.busy": "2026-08-02T23:27:42.772911Z", - "iopub.status.idle": "2026-08-02T23:27:42.776164Z", - "shell.execute_reply": "2026-08-02T23:27:42.775686Z" + "iopub.execute_input": "2026-08-04T02:01:47.115147Z", + "iopub.status.busy": "2026-08-04T02:01:47.115093Z", + "iopub.status.idle": "2026-08-04T02:01:47.117491Z", + "shell.execute_reply": "2026-08-04T02:01:47.117191Z" } }, "outputs": [ @@ -644,10 +644,10 @@ "execution_count": 16, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.777978Z", - "iopub.status.busy": "2026-08-02T23:27:42.777891Z", - "iopub.status.idle": "2026-08-02T23:27:42.780786Z", - "shell.execute_reply": "2026-08-02T23:27:42.780371Z" + "iopub.execute_input": "2026-08-04T02:01:47.118350Z", + "iopub.status.busy": "2026-08-04T02:01:47.118300Z", + "iopub.status.idle": "2026-08-04T02:01:47.120829Z", + "shell.execute_reply": "2026-08-04T02:01:47.120449Z" } }, "outputs": [ @@ -702,10 +702,10 @@ "execution_count": 17, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.781950Z", - "iopub.status.busy": "2026-08-02T23:27:42.781877Z", - "iopub.status.idle": "2026-08-02T23:27:42.783865Z", - "shell.execute_reply": "2026-08-02T23:27:42.783545Z" + "iopub.execute_input": "2026-08-04T02:01:47.121682Z", + "iopub.status.busy": "2026-08-04T02:01:47.121634Z", + "iopub.status.idle": "2026-08-04T02:01:47.123385Z", + "shell.execute_reply": "2026-08-04T02:01:47.123069Z" } }, "outputs": [ @@ -745,10 +745,10 @@ "execution_count": 18, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.785151Z", - "iopub.status.busy": "2026-08-02T23:27:42.785078Z", - "iopub.status.idle": "2026-08-02T23:27:42.840517Z", - "shell.execute_reply": "2026-08-02T23:27:42.840050Z" + "iopub.execute_input": "2026-08-04T02:01:47.124215Z", + "iopub.status.busy": "2026-08-04T02:01:47.124167Z", + "iopub.status.idle": "2026-08-04T02:01:47.162616Z", + "shell.execute_reply": "2026-08-04T02:01:47.162264Z" } }, "outputs": [], @@ -765,10 +765,10 @@ "execution_count": 19, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:42.842286Z", - "iopub.status.busy": "2026-08-02T23:27:42.842175Z", - "iopub.status.idle": "2026-08-02T23:27:43.001832Z", - "shell.execute_reply": "2026-08-02T23:27:43.001394Z" + "iopub.execute_input": "2026-08-04T02:01:47.163631Z", + "iopub.status.busy": "2026-08-04T02:01:47.163580Z", + "iopub.status.idle": "2026-08-04T02:01:47.255905Z", + "shell.execute_reply": "2026-08-04T02:01:47.255590Z" } }, "outputs": [ @@ -817,10 +817,10 @@ "execution_count": 20, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:43.003275Z", - "iopub.status.busy": "2026-08-02T23:27:43.003175Z", - "iopub.status.idle": "2026-08-02T23:27:43.085192Z", - "shell.execute_reply": "2026-08-02T23:27:43.084731Z" + "iopub.execute_input": "2026-08-04T02:01:47.256958Z", + "iopub.status.busy": "2026-08-04T02:01:47.256900Z", + "iopub.status.idle": "2026-08-04T02:01:47.296309Z", + "shell.execute_reply": "2026-08-04T02:01:47.295955Z" } }, "outputs": [ @@ -868,10 +868,10 @@ "execution_count": 21, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:43.087089Z", - "iopub.status.busy": "2026-08-02T23:27:43.086940Z", - "iopub.status.idle": "2026-08-02T23:27:43.090552Z", - "shell.execute_reply": "2026-08-02T23:27:43.090034Z" + "iopub.execute_input": "2026-08-04T02:01:47.297356Z", + "iopub.status.busy": "2026-08-04T02:01:47.297301Z", + "iopub.status.idle": "2026-08-04T02:01:47.299807Z", + "shell.execute_reply": "2026-08-04T02:01:47.299484Z" } }, "outputs": [], @@ -895,10 +895,10 @@ "execution_count": 22, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:43.091872Z", - "iopub.status.busy": "2026-08-02T23:27:43.091788Z", - "iopub.status.idle": "2026-08-02T23:27:43.278070Z", - "shell.execute_reply": "2026-08-02T23:27:43.277310Z" + "iopub.execute_input": "2026-08-04T02:01:47.300821Z", + "iopub.status.busy": "2026-08-04T02:01:47.300769Z", + "iopub.status.idle": "2026-08-04T02:01:47.393286Z", + "shell.execute_reply": "2026-08-04T02:01:47.392961Z" } }, "outputs": [ diff --git a/examples/2_OSNAP_transports_CM4p25.ipynb b/examples/2_OSNAP_transports_CM4p25.ipynb index 1cf2214..a1e02c6 100644 --- a/examples/2_OSNAP_transports_CM4p25.ipynb +++ b/examples/2_OSNAP_transports_CM4p25.ipynb @@ -21,10 +21,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:47.595970Z", - "iopub.status.busy": "2026-08-02T23:27:47.595854Z", - "iopub.status.idle": "2026-08-02T23:27:48.624613Z", - "shell.execute_reply": "2026-08-02T23:27:48.624116Z" + "iopub.execute_input": "2026-08-04T02:01:48.388420Z", + "iopub.status.busy": "2026-08-04T02:01:48.388248Z", + "iopub.status.idle": "2026-08-04T02:01:49.080762Z", + "shell.execute_reply": "2026-08-04T02:01:49.080434Z" } }, "outputs": [ @@ -32,7 +32,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Sectionate version: 0.4.0rc2.dev1\n" + "Sectionate version: 0.4.0rc2.dev3\n" ] } ], @@ -60,10 +60,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:48.644718Z", - "iopub.status.busy": "2026-08-02T23:27:48.644333Z", - "iopub.status.idle": "2026-08-02T23:27:48.836750Z", - "shell.execute_reply": "2026-08-02T23:27:48.836323Z" + "iopub.execute_input": "2026-08-04T02:01:49.082054Z", + "iopub.status.busy": "2026-08-04T02:01:49.081933Z", + "iopub.status.idle": "2026-08-04T02:01:49.208403Z", + "shell.execute_reply": "2026-08-04T02:01:49.207974Z" } }, "outputs": [ @@ -71,7 +71,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/opt/anaconda3/envs/sectionate-pr8/lib/python3.14/site-packages/xgcm/grid.py:463: UserWarning: The north-fold (tripolar) boundary condition is experimental. Its API and numerical behavior may change in future releases, and it has not yet been validated across the full range of grid configurations. Please review results carefully and report any issues at https://github.com/xgcm/xgcm/issues.\n", + "/Users/hfdrake/miniforge3/envs/docs_env_sectionate_fix-layer-interface-matching/lib/python3.14/site-packages/xgcm/grid.py:463: UserWarning: The north-fold (tripolar) boundary condition is experimental. Its API and numerical behavior may change in future releases, and it has not yet been validated across the full range of grid configurations. Please review results carefully and report any issues at https://github.com/xgcm/xgcm/issues.\n", " warnings.warn(\n" ] } @@ -94,10 +94,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:48.838076Z", - "iopub.status.busy": "2026-08-02T23:27:48.837995Z", - "iopub.status.idle": "2026-08-02T23:27:48.839913Z", - "shell.execute_reply": "2026-08-02T23:27:48.839517Z" + "iopub.execute_input": "2026-08-04T02:01:49.209584Z", + "iopub.status.busy": "2026-08-04T02:01:49.209516Z", + "iopub.status.idle": "2026-08-04T02:01:49.211241Z", + "shell.execute_reply": "2026-08-04T02:01:49.210950Z" } }, "outputs": [], @@ -124,10 +124,10 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:48.841021Z", - "iopub.status.busy": "2026-08-02T23:27:48.840942Z", - "iopub.status.idle": "2026-08-02T23:27:48.862137Z", - "shell.execute_reply": "2026-08-02T23:27:48.861670Z" + "iopub.execute_input": "2026-08-04T02:01:49.212231Z", + "iopub.status.busy": "2026-08-04T02:01:49.212180Z", + "iopub.status.idle": "2026-08-04T02:01:49.226750Z", + "shell.execute_reply": "2026-08-04T02:01:49.226317Z" } }, "outputs": [], @@ -144,10 +144,10 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:48.863631Z", - "iopub.status.busy": "2026-08-02T23:27:48.863543Z", - "iopub.status.idle": "2026-08-02T23:27:49.026393Z", - "shell.execute_reply": "2026-08-02T23:27:49.025967Z" + "iopub.execute_input": "2026-08-04T02:01:49.227864Z", + "iopub.status.busy": "2026-08-04T02:01:49.227804Z", + "iopub.status.idle": "2026-08-04T02:01:49.343097Z", + "shell.execute_reply": "2026-08-04T02:01:49.342741Z" }, "tags": [] }, @@ -195,10 +195,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.027854Z", - "iopub.status.busy": "2026-08-02T23:27:49.027748Z", - "iopub.status.idle": "2026-08-02T23:27:49.058675Z", - "shell.execute_reply": "2026-08-02T23:27:49.058272Z" + "iopub.execute_input": "2026-08-04T02:01:49.344132Z", + "iopub.status.busy": "2026-08-04T02:01:49.344069Z", + "iopub.status.idle": "2026-08-04T02:01:49.364171Z", + "shell.execute_reply": "2026-08-04T02:01:49.363778Z" } }, "outputs": [ @@ -206,13 +206,13 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/hfdrake/code/wt-sectionate-hpad/sectionate/transports.py:531: UserWarning: This section is open (its first and last waypoints are far apart), so it does not enclose a polygon and the inward/outward sense of `positive_in` is undefined. Sectionate applies the left-of-transect convention instead: with `positive_in=True` positive transport points to the LEFT of the section as it is traversed from the first to the last waypoint (set `positive_in=False` to flip to right-of-transect). Verify the sign matches your expectations, e.g. by plotting the section's normal vectors.\n", + "/Users/hfdrake/code/sectionate/sectionate/transports.py:565: UserWarning: This section is open (its first and last waypoints are far apart), so it does not enclose a polygon and the inward/outward sense of `positive_in` is undefined. Sectionate applies the left-of-transect convention instead: with `positive_in=True` positive transport points to the LEFT of the section as it is traversed from the first to the last waypoint (set `positive_in=False` to flip to right-of-transect). Verify the sign matches your expectations, e.g. by plotting the section's normal vectors.\n", " warnings.warn(\n" ] } ], "source": [ - "Trp_west = sectionate.convergent_transport(grid, i_c, j_c, layer=\"sigma2_l\", interface=\"sigma2_i\")" + "Trp_west = sectionate.convergent_transport(grid, i_c, j_c)" ] }, { @@ -220,10 +220,10 @@ "execution_count": 7, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.059964Z", - "iopub.status.busy": "2026-08-02T23:27:49.059874Z", - "iopub.status.idle": "2026-08-02T23:27:49.164671Z", - "shell.execute_reply": "2026-08-02T23:27:49.164220Z" + "iopub.execute_input": "2026-08-04T02:01:49.365281Z", + "iopub.status.busy": "2026-08-04T02:01:49.365213Z", + "iopub.status.idle": "2026-08-04T02:01:49.446445Z", + "shell.execute_reply": "2026-08-04T02:01:49.446021Z" } }, "outputs": [ @@ -255,10 +255,10 @@ "execution_count": 8, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.166069Z", - "iopub.status.busy": "2026-08-02T23:27:49.165971Z", - "iopub.status.idle": "2026-08-02T23:27:49.287222Z", - "shell.execute_reply": "2026-08-02T23:27:49.286776Z" + "iopub.execute_input": "2026-08-04T02:01:49.447595Z", + "iopub.status.busy": "2026-08-04T02:01:49.447529Z", + "iopub.status.idle": "2026-08-04T02:01:49.522301Z", + "shell.execute_reply": "2026-08-04T02:01:49.521934Z" } }, "outputs": [ @@ -317,10 +317,10 @@ "execution_count": 9, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.288979Z", - "iopub.status.busy": "2026-08-02T23:27:49.288871Z", - "iopub.status.idle": "2026-08-02T23:27:49.290888Z", - "shell.execute_reply": "2026-08-02T23:27:49.290465Z" + "iopub.execute_input": "2026-08-04T02:01:49.523408Z", + "iopub.status.busy": "2026-08-04T02:01:49.523349Z", + "iopub.status.idle": "2026-08-04T02:01:49.524914Z", + "shell.execute_reply": "2026-08-04T02:01:49.524639Z" } }, "outputs": [], @@ -340,10 +340,10 @@ "execution_count": 10, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.291929Z", - "iopub.status.busy": "2026-08-02T23:27:49.291856Z", - "iopub.status.idle": "2026-08-02T23:27:49.312830Z", - "shell.execute_reply": "2026-08-02T23:27:49.312318Z" + "iopub.execute_input": "2026-08-04T02:01:49.525806Z", + "iopub.status.busy": "2026-08-04T02:01:49.525746Z", + "iopub.status.idle": "2026-08-04T02:01:49.540458Z", + "shell.execute_reply": "2026-08-04T02:01:49.540125Z" } }, "outputs": [], @@ -360,10 +360,10 @@ "execution_count": 11, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.314592Z", - "iopub.status.busy": "2026-08-02T23:27:49.314494Z", - "iopub.status.idle": "2026-08-02T23:27:49.442662Z", - "shell.execute_reply": "2026-08-02T23:27:49.442212Z" + "iopub.execute_input": "2026-08-04T02:01:49.541502Z", + "iopub.status.busy": "2026-08-04T02:01:49.541448Z", + "iopub.status.idle": "2026-08-04T02:01:49.631238Z", + "shell.execute_reply": "2026-08-04T02:01:49.630940Z" } }, "outputs": [ @@ -410,10 +410,10 @@ "execution_count": 12, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.444267Z", - "iopub.status.busy": "2026-08-02T23:27:49.444152Z", - "iopub.status.idle": "2026-08-02T23:27:49.476385Z", - "shell.execute_reply": "2026-08-02T23:27:49.476025Z" + "iopub.execute_input": "2026-08-04T02:01:49.632524Z", + "iopub.status.busy": "2026-08-04T02:01:49.632463Z", + "iopub.status.idle": "2026-08-04T02:01:49.670014Z", + "shell.execute_reply": "2026-08-04T02:01:49.669657Z" } }, "outputs": [ @@ -421,13 +421,13 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/hfdrake/code/wt-sectionate-hpad/sectionate/transports.py:531: UserWarning: This section is open (its first and last waypoints are far apart), so it does not enclose a polygon and the inward/outward sense of `positive_in` is undefined. Sectionate applies the left-of-transect convention instead: with `positive_in=True` positive transport points to the LEFT of the section as it is traversed from the first to the last waypoint (set `positive_in=False` to flip to right-of-transect). Verify the sign matches your expectations, e.g. by plotting the section's normal vectors.\n", + "/Users/hfdrake/code/sectionate/sectionate/transports.py:565: UserWarning: This section is open (its first and last waypoints are far apart), so it does not enclose a polygon and the inward/outward sense of `positive_in` is undefined. Sectionate applies the left-of-transect convention instead: with `positive_in=True` positive transport points to the LEFT of the section as it is traversed from the first to the last waypoint (set `positive_in=False` to flip to right-of-transect). Verify the sign matches your expectations, e.g. by plotting the section's normal vectors.\n", " warnings.warn(\n" ] } ], "source": [ - "Trp_east = sectionate.convergent_transport(grid, i_c, j_c, layer=\"sigma2_l\", interface=\"sigma2_i\")" + "Trp_east = sectionate.convergent_transport(grid, i_c, j_c)" ] }, { @@ -435,10 +435,10 @@ "execution_count": 13, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.477957Z", - "iopub.status.busy": "2026-08-02T23:27:49.477865Z", - "iopub.status.idle": "2026-08-02T23:27:49.584960Z", - "shell.execute_reply": "2026-08-02T23:27:49.584504Z" + "iopub.execute_input": "2026-08-04T02:01:49.671028Z", + "iopub.status.busy": "2026-08-04T02:01:49.670972Z", + "iopub.status.idle": "2026-08-04T02:01:49.749043Z", + "shell.execute_reply": "2026-08-04T02:01:49.748684Z" } }, "outputs": [ @@ -470,10 +470,10 @@ "execution_count": 14, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.586528Z", - "iopub.status.busy": "2026-08-02T23:27:49.586425Z", - "iopub.status.idle": "2026-08-02T23:27:49.707838Z", - "shell.execute_reply": "2026-08-02T23:27:49.707407Z" + "iopub.execute_input": "2026-08-04T02:01:49.750155Z", + "iopub.status.busy": "2026-08-04T02:01:49.750092Z", + "iopub.status.idle": "2026-08-04T02:01:49.836438Z", + "shell.execute_reply": "2026-08-04T02:01:49.836017Z" } }, "outputs": [ @@ -530,10 +530,10 @@ "execution_count": 15, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.709464Z", - "iopub.status.busy": "2026-08-02T23:27:49.709355Z", - "iopub.status.idle": "2026-08-02T23:27:49.711193Z", - "shell.execute_reply": "2026-08-02T23:27:49.710827Z" + "iopub.execute_input": "2026-08-04T02:01:49.837429Z", + "iopub.status.busy": "2026-08-04T02:01:49.837368Z", + "iopub.status.idle": "2026-08-04T02:01:49.838781Z", + "shell.execute_reply": "2026-08-04T02:01:49.838537Z" } }, "outputs": [], @@ -553,10 +553,10 @@ "execution_count": 16, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.712384Z", - "iopub.status.busy": "2026-08-02T23:27:49.712308Z", - "iopub.status.idle": "2026-08-02T23:27:49.752189Z", - "shell.execute_reply": "2026-08-02T23:27:49.751738Z" + "iopub.execute_input": "2026-08-04T02:01:49.839688Z", + "iopub.status.busy": "2026-08-04T02:01:49.839633Z", + "iopub.status.idle": "2026-08-04T02:01:49.867487Z", + "shell.execute_reply": "2026-08-04T02:01:49.867055Z" } }, "outputs": [], @@ -573,10 +573,10 @@ "execution_count": 17, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.753651Z", - "iopub.status.busy": "2026-08-02T23:27:49.753562Z", - "iopub.status.idle": "2026-08-02T23:27:49.793008Z", - "shell.execute_reply": "2026-08-02T23:27:49.792530Z" + "iopub.execute_input": "2026-08-04T02:01:49.868502Z", + "iopub.status.busy": "2026-08-04T02:01:49.868449Z", + "iopub.status.idle": "2026-08-04T02:01:49.898245Z", + "shell.execute_reply": "2026-08-04T02:01:49.897927Z" } }, "outputs": [ @@ -584,13 +584,13 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/hfdrake/code/wt-sectionate-hpad/sectionate/transports.py:531: UserWarning: This section is open (its first and last waypoints are far apart), so it does not enclose a polygon and the inward/outward sense of `positive_in` is undefined. Sectionate applies the left-of-transect convention instead: with `positive_in=True` positive transport points to the LEFT of the section as it is traversed from the first to the last waypoint (set `positive_in=False` to flip to right-of-transect). Verify the sign matches your expectations, e.g. by plotting the section's normal vectors.\n", + "/Users/hfdrake/code/sectionate/sectionate/transports.py:565: UserWarning: This section is open (its first and last waypoints are far apart), so it does not enclose a polygon and the inward/outward sense of `positive_in` is undefined. Sectionate applies the left-of-transect convention instead: with `positive_in=True` positive transport points to the LEFT of the section as it is traversed from the first to the last waypoint (set `positive_in=False` to flip to right-of-transect). Verify the sign matches your expectations, e.g. by plotting the section's normal vectors.\n", " warnings.warn(\n" ] } ], "source": [ - "Trp = sectionate.convergent_transport(grid, i_c, j_c, layer=\"sigma2_l\", interface=\"sigma2_i\")" + "Trp = sectionate.convergent_transport(grid, i_c, j_c)" ] }, { @@ -598,10 +598,10 @@ "execution_count": 18, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.794221Z", - "iopub.status.busy": "2026-08-02T23:27:49.794137Z", - "iopub.status.idle": "2026-08-02T23:27:49.938325Z", - "shell.execute_reply": "2026-08-02T23:27:49.937836Z" + "iopub.execute_input": "2026-08-04T02:01:49.899254Z", + "iopub.status.busy": "2026-08-04T02:01:49.899198Z", + "iopub.status.idle": "2026-08-04T02:01:50.002350Z", + "shell.execute_reply": "2026-08-04T02:01:50.001979Z" } }, "outputs": [ @@ -658,10 +658,10 @@ "execution_count": 19, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.939726Z", - "iopub.status.busy": "2026-08-02T23:27:49.939634Z", - "iopub.status.idle": "2026-08-02T23:27:49.941430Z", - "shell.execute_reply": "2026-08-02T23:27:49.941028Z" + "iopub.execute_input": "2026-08-04T02:01:50.003392Z", + "iopub.status.busy": "2026-08-04T02:01:50.003333Z", + "iopub.status.idle": "2026-08-04T02:01:50.004876Z", + "shell.execute_reply": "2026-08-04T02:01:50.004533Z" } }, "outputs": [], @@ -681,10 +681,10 @@ "execution_count": 20, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.942641Z", - "iopub.status.busy": "2026-08-02T23:27:49.942569Z", - "iopub.status.idle": "2026-08-02T23:27:49.946032Z", - "shell.execute_reply": "2026-08-02T23:27:49.945664Z" + "iopub.execute_input": "2026-08-04T02:01:50.005800Z", + "iopub.status.busy": "2026-08-04T02:01:50.005751Z", + "iopub.status.idle": "2026-08-04T02:01:50.008642Z", + "shell.execute_reply": "2026-08-04T02:01:50.008176Z" } }, "outputs": [], @@ -709,10 +709,10 @@ "execution_count": 21, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:49.947340Z", - "iopub.status.busy": "2026-08-02T23:27:49.947265Z", - "iopub.status.idle": "2026-08-02T23:27:50.052826Z", - "shell.execute_reply": "2026-08-02T23:27:50.052369Z" + "iopub.execute_input": "2026-08-04T02:01:50.009563Z", + "iopub.status.busy": "2026-08-04T02:01:50.009509Z", + "iopub.status.idle": "2026-08-04T02:01:50.084867Z", + "shell.execute_reply": "2026-08-04T02:01:50.084458Z" } }, "outputs": [ diff --git a/examples/3_Labrador_convergence_CM4p25.ipynb b/examples/3_Labrador_convergence_CM4p25.ipynb index 60331a5..2c28820 100644 --- a/examples/3_Labrador_convergence_CM4p25.ipynb +++ b/examples/3_Labrador_convergence_CM4p25.ipynb @@ -21,10 +21,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:52.781827Z", - "iopub.status.busy": "2026-08-02T23:27:52.781739Z", - "iopub.status.idle": "2026-08-02T23:27:53.820263Z", - "shell.execute_reply": "2026-08-02T23:27:53.819772Z" + "iopub.execute_input": "2026-08-04T02:01:50.972072Z", + "iopub.status.busy": "2026-08-04T02:01:50.971866Z", + "iopub.status.idle": "2026-08-04T02:01:51.710112Z", + "shell.execute_reply": "2026-08-04T02:01:51.709792Z" } }, "outputs": [ @@ -32,7 +32,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Sectionate version: 0.4.0rc2.dev1\n" + "Sectionate version: 0.4.0rc2.dev3\n" ] } ], @@ -60,10 +60,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:53.840361Z", - "iopub.status.busy": "2026-08-02T23:27:53.840158Z", - "iopub.status.idle": "2026-08-02T23:27:54.023106Z", - "shell.execute_reply": "2026-08-02T23:27:54.022686Z" + "iopub.execute_input": "2026-08-04T02:01:51.711277Z", + "iopub.status.busy": "2026-08-04T02:01:51.711166Z", + "iopub.status.idle": "2026-08-04T02:01:51.832845Z", + "shell.execute_reply": "2026-08-04T02:01:51.832479Z" } }, "outputs": [ @@ -71,7 +71,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/opt/anaconda3/envs/sectionate-pr8/lib/python3.14/site-packages/xgcm/grid.py:463: UserWarning: The north-fold (tripolar) boundary condition is experimental. Its API and numerical behavior may change in future releases, and it has not yet been validated across the full range of grid configurations. Please review results carefully and report any issues at https://github.com/xgcm/xgcm/issues.\n", + "/Users/hfdrake/miniforge3/envs/docs_env_sectionate_fix-layer-interface-matching/lib/python3.14/site-packages/xgcm/grid.py:463: UserWarning: The north-fold (tripolar) boundary condition is experimental. Its API and numerical behavior may change in future releases, and it has not yet been validated across the full range of grid configurations. Please review results carefully and report any issues at https://github.com/xgcm/xgcm/issues.\n", " warnings.warn(\n" ] } @@ -94,10 +94,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:54.024630Z", - "iopub.status.busy": "2026-08-02T23:27:54.024534Z", - "iopub.status.idle": "2026-08-02T23:27:54.026704Z", - "shell.execute_reply": "2026-08-02T23:27:54.026288Z" + "iopub.execute_input": "2026-08-04T02:01:51.833990Z", + "iopub.status.busy": "2026-08-04T02:01:51.833928Z", + "iopub.status.idle": "2026-08-04T02:01:51.835664Z", + "shell.execute_reply": "2026-08-04T02:01:51.835344Z" } }, "outputs": [], @@ -120,10 +120,10 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:54.028109Z", - "iopub.status.busy": "2026-08-02T23:27:54.028009Z", - "iopub.status.idle": "2026-08-02T23:27:54.065508Z", - "shell.execute_reply": "2026-08-02T23:27:54.065095Z" + "iopub.execute_input": "2026-08-04T02:01:51.836649Z", + "iopub.status.busy": "2026-08-04T02:01:51.836596Z", + "iopub.status.idle": "2026-08-04T02:01:51.864344Z", + "shell.execute_reply": "2026-08-04T02:01:51.864033Z" } }, "outputs": [], @@ -140,10 +140,10 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:54.067212Z", - "iopub.status.busy": "2026-08-02T23:27:54.067123Z", - "iopub.status.idle": "2026-08-02T23:27:54.221317Z", - "shell.execute_reply": "2026-08-02T23:27:54.220882Z" + "iopub.execute_input": "2026-08-04T02:01:51.865600Z", + "iopub.status.busy": "2026-08-04T02:01:51.865535Z", + "iopub.status.idle": "2026-08-04T02:01:51.980369Z", + "shell.execute_reply": "2026-08-04T02:01:51.979958Z" } }, "outputs": [ @@ -192,10 +192,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:54.222765Z", - "iopub.status.busy": "2026-08-02T23:27:54.222678Z", - "iopub.status.idle": "2026-08-02T23:27:54.258957Z", - "shell.execute_reply": "2026-08-02T23:27:54.258476Z" + "iopub.execute_input": "2026-08-04T02:01:51.981486Z", + "iopub.status.busy": "2026-08-04T02:01:51.981410Z", + "iopub.status.idle": "2026-08-04T02:01:52.020015Z", + "shell.execute_reply": "2026-08-04T02:01:52.019586Z" } }, "outputs": [], @@ -208,15 +208,15 @@ "execution_count": 7, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:54.260619Z", - "iopub.status.busy": "2026-08-02T23:27:54.260525Z", - "iopub.status.idle": "2026-08-02T23:27:54.313611Z", - "shell.execute_reply": "2026-08-02T23:27:54.313086Z" + "iopub.execute_input": "2026-08-04T02:01:52.021157Z", + "iopub.status.busy": "2026-08-04T02:01:52.021084Z", + "iopub.status.idle": "2026-08-04T02:01:52.062912Z", + "shell.execute_reply": "2026-08-04T02:01:52.062595Z" } }, "outputs": [], "source": [ - "Trp = sectionate.convergent_transport(grid, i_c, j_c, layer=\"sigma2_l\", interface=\"sigma2_i\")\n", + "Trp = sectionate.convergent_transport(grid, i_c, j_c)\n", "Trp = Trp.assign_coords({\"distance\": xr.DataArray(Trp.dl.cumsum(\"sect\").values, dims=(\"sect\",), attrs={\"units\":\"m\"})})" ] }, @@ -225,10 +225,10 @@ "execution_count": 8, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:54.315120Z", - "iopub.status.busy": "2026-08-02T23:27:54.315021Z", - "iopub.status.idle": "2026-08-02T23:27:54.418478Z", - "shell.execute_reply": "2026-08-02T23:27:54.418021Z" + "iopub.execute_input": "2026-08-04T02:01:52.064165Z", + "iopub.status.busy": "2026-08-04T02:01:52.064104Z", + "iopub.status.idle": "2026-08-04T02:01:52.141159Z", + "shell.execute_reply": "2026-08-04T02:01:52.140798Z" } }, "outputs": [ @@ -264,10 +264,10 @@ "execution_count": 9, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:54.419953Z", - "iopub.status.busy": "2026-08-02T23:27:54.419846Z", - "iopub.status.idle": "2026-08-02T23:27:54.622013Z", - "shell.execute_reply": "2026-08-02T23:27:54.621592Z" + "iopub.execute_input": "2026-08-04T02:01:52.142231Z", + "iopub.status.busy": "2026-08-04T02:01:52.142172Z", + "iopub.status.idle": "2026-08-04T02:01:52.278672Z", + "shell.execute_reply": "2026-08-04T02:01:52.278340Z" } }, "outputs": [ diff --git a/examples/4_sections_on_global_tripolar_grid.ipynb b/examples/4_sections_on_global_tripolar_grid.ipynb index 1038aa9..a5b13f9 100644 --- a/examples/4_sections_on_global_tripolar_grid.ipynb +++ b/examples/4_sections_on_global_tripolar_grid.ipynb @@ -23,10 +23,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:57.331975Z", - "iopub.status.busy": "2026-08-02T23:27:57.331890Z", - "iopub.status.idle": "2026-08-02T23:27:58.445080Z", - "shell.execute_reply": "2026-08-02T23:27:58.444616Z" + "iopub.execute_input": "2026-08-04T02:01:53.171667Z", + "iopub.status.busy": "2026-08-04T02:01:53.171402Z", + "iopub.status.idle": "2026-08-04T02:01:55.635990Z", + "shell.execute_reply": "2026-08-04T02:01:55.635261Z" } }, "outputs": [ @@ -34,7 +34,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Sectionate version: 0.4.0rc2.dev1\n" + "Sectionate version: 0.4.0rc2.dev3\n" ] } ], @@ -66,10 +66,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:58.465958Z", - "iopub.status.busy": "2026-08-02T23:27:58.465752Z", - "iopub.status.idle": "2026-08-02T23:27:58.471237Z", - "shell.execute_reply": "2026-08-02T23:27:58.470877Z" + "iopub.execute_input": "2026-08-04T02:01:55.638398Z", + "iopub.status.busy": "2026-08-04T02:01:55.638098Z", + "iopub.status.idle": "2026-08-04T02:01:55.646099Z", + "shell.execute_reply": "2026-08-04T02:01:55.645540Z" } }, "outputs": [], @@ -143,10 +143,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:58.472590Z", - "iopub.status.busy": "2026-08-02T23:27:58.472508Z", - "iopub.status.idle": "2026-08-02T23:27:58.665945Z", - "shell.execute_reply": "2026-08-02T23:27:58.665566Z" + "iopub.execute_input": "2026-08-04T02:01:55.647677Z", + "iopub.status.busy": "2026-08-04T02:01:55.647564Z", + "iopub.status.idle": "2026-08-04T02:01:55.794379Z", + "shell.execute_reply": "2026-08-04T02:01:55.793983Z" } }, "outputs": [ @@ -154,7 +154,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/opt/anaconda3/envs/sectionate-pr8/lib/python3.14/site-packages/xgcm/grid.py:463: UserWarning: The north-fold (tripolar) boundary condition is experimental. Its API and numerical behavior may change in future releases, and it has not yet been validated across the full range of grid configurations. Please review results carefully and report any issues at https://github.com/xgcm/xgcm/issues.\n", + "/Users/hfdrake/miniforge3/envs/docs_env_sectionate_fix-layer-interface-matching/lib/python3.14/site-packages/xgcm/grid.py:463: UserWarning: The north-fold (tripolar) boundary condition is experimental. Its API and numerical behavior may change in future releases, and it has not yet been validated across the full range of grid configurations. Please review results carefully and report any issues at https://github.com/xgcm/xgcm/issues.\n", " warnings.warn(\n" ] } @@ -177,10 +177,10 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:58.667251Z", - "iopub.status.busy": "2026-08-02T23:27:58.667175Z", - "iopub.status.idle": "2026-08-02T23:27:58.669902Z", - "shell.execute_reply": "2026-08-02T23:27:58.669604Z" + "iopub.execute_input": "2026-08-04T02:01:55.795569Z", + "iopub.status.busy": "2026-08-04T02:01:55.795503Z", + "iopub.status.idle": "2026-08-04T02:01:55.798005Z", + "shell.execute_reply": "2026-08-04T02:01:55.797632Z" } }, "outputs": [], @@ -203,10 +203,10 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:27:58.671303Z", - "iopub.status.busy": "2026-08-02T23:27:58.671236Z", - "iopub.status.idle": "2026-08-02T23:28:00.082760Z", - "shell.execute_reply": "2026-08-02T23:28:00.082299Z" + "iopub.execute_input": "2026-08-04T02:01:55.799062Z", + "iopub.status.busy": "2026-08-04T02:01:55.799000Z", + "iopub.status.idle": "2026-08-04T02:01:56.913038Z", + "shell.execute_reply": "2026-08-04T02:01:56.912614Z" } }, "outputs": [ @@ -251,10 +251,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:00.085250Z", - "iopub.status.busy": "2026-08-02T23:28:00.085162Z", - "iopub.status.idle": "2026-08-02T23:28:01.419745Z", - "shell.execute_reply": "2026-08-02T23:28:01.419168Z" + "iopub.execute_input": "2026-08-04T02:01:56.915401Z", + "iopub.status.busy": "2026-08-04T02:01:56.915332Z", + "iopub.status.idle": "2026-08-04T02:01:57.818895Z", + "shell.execute_reply": "2026-08-04T02:01:57.818522Z" } }, "outputs": [ @@ -305,10 +305,10 @@ "execution_count": 7, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:01.422782Z", - "iopub.status.busy": "2026-08-02T23:28:01.422691Z", - "iopub.status.idle": "2026-08-02T23:28:01.424548Z", - "shell.execute_reply": "2026-08-02T23:28:01.424109Z" + "iopub.execute_input": "2026-08-04T02:01:57.821173Z", + "iopub.status.busy": "2026-08-04T02:01:57.821093Z", + "iopub.status.idle": "2026-08-04T02:01:57.822690Z", + "shell.execute_reply": "2026-08-04T02:01:57.822323Z" } }, "outputs": [], @@ -328,10 +328,10 @@ "execution_count": 8, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:01.425812Z", - "iopub.status.busy": "2026-08-02T23:28:01.425735Z", - "iopub.status.idle": "2026-08-02T23:28:02.126746Z", - "shell.execute_reply": "2026-08-02T23:28:02.126242Z" + "iopub.execute_input": "2026-08-04T02:01:57.823644Z", + "iopub.status.busy": "2026-08-04T02:01:57.823593Z", + "iopub.status.idle": "2026-08-04T02:01:58.205365Z", + "shell.execute_reply": "2026-08-04T02:01:58.204922Z" } }, "outputs": [ @@ -370,10 +370,10 @@ "execution_count": 9, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:02.129908Z", - "iopub.status.busy": "2026-08-02T23:28:02.129808Z", - "iopub.status.idle": "2026-08-02T23:28:02.704940Z", - "shell.execute_reply": "2026-08-02T23:28:02.704452Z" + "iopub.execute_input": "2026-08-04T02:01:58.207583Z", + "iopub.status.busy": "2026-08-04T02:01:58.207497Z", + "iopub.status.idle": "2026-08-04T02:01:58.606342Z", + "shell.execute_reply": "2026-08-04T02:01:58.605892Z" } }, "outputs": [ @@ -412,10 +412,10 @@ "execution_count": 10, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:02.707490Z", - "iopub.status.busy": "2026-08-02T23:28:02.707390Z", - "iopub.status.idle": "2026-08-02T23:28:03.292131Z", - "shell.execute_reply": "2026-08-02T23:28:03.291735Z" + "iopub.execute_input": "2026-08-04T02:01:58.608545Z", + "iopub.status.busy": "2026-08-04T02:01:58.608465Z", + "iopub.status.idle": "2026-08-04T02:01:59.012843Z", + "shell.execute_reply": "2026-08-04T02:01:59.012464Z" } }, "outputs": [ @@ -456,10 +456,10 @@ "execution_count": 11, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:03.295532Z", - "iopub.status.busy": "2026-08-02T23:28:03.295430Z", - "iopub.status.idle": "2026-08-02T23:28:04.082086Z", - "shell.execute_reply": "2026-08-02T23:28:04.081719Z" + "iopub.execute_input": "2026-08-04T02:01:59.015039Z", + "iopub.status.busy": "2026-08-04T02:01:59.014959Z", + "iopub.status.idle": "2026-08-04T02:01:59.577602Z", + "shell.execute_reply": "2026-08-04T02:01:59.577160Z" } }, "outputs": [ @@ -499,10 +499,10 @@ "execution_count": 12, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:04.085130Z", - "iopub.status.busy": "2026-08-02T23:28:04.085028Z", - "iopub.status.idle": "2026-08-02T23:28:04.643379Z", - "shell.execute_reply": "2026-08-02T23:28:04.642888Z" + "iopub.execute_input": "2026-08-04T02:01:59.579759Z", + "iopub.status.busy": "2026-08-04T02:01:59.579676Z", + "iopub.status.idle": "2026-08-04T02:01:59.972989Z", + "shell.execute_reply": "2026-08-04T02:01:59.972545Z" } }, "outputs": [ @@ -543,10 +543,10 @@ "execution_count": 13, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:04.646142Z", - "iopub.status.busy": "2026-08-02T23:28:04.646042Z", - "iopub.status.idle": "2026-08-02T23:28:05.658128Z", - "shell.execute_reply": "2026-08-02T23:28:05.657556Z" + "iopub.execute_input": "2026-08-04T02:01:59.975278Z", + "iopub.status.busy": "2026-08-04T02:01:59.975201Z", + "iopub.status.idle": "2026-08-04T02:02:00.688345Z", + "shell.execute_reply": "2026-08-04T02:02:00.687998Z" } }, "outputs": [ @@ -588,10 +588,10 @@ "execution_count": 14, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:05.661784Z", - "iopub.status.busy": "2026-08-02T23:28:05.661685Z", - "iopub.status.idle": "2026-08-02T23:28:06.180805Z", - "shell.execute_reply": "2026-08-02T23:28:06.180347Z" + "iopub.execute_input": "2026-08-04T02:02:00.690842Z", + "iopub.status.busy": "2026-08-04T02:02:00.690767Z", + "iopub.status.idle": "2026-08-04T02:02:01.046170Z", + "shell.execute_reply": "2026-08-04T02:02:01.045845Z" } }, "outputs": [ @@ -623,10 +623,10 @@ "execution_count": 15, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:06.184002Z", - "iopub.status.busy": "2026-08-02T23:28:06.183898Z", - "iopub.status.idle": "2026-08-02T23:28:06.708612Z", - "shell.execute_reply": "2026-08-02T23:28:06.708259Z" + "iopub.execute_input": "2026-08-04T02:02:01.048283Z", + "iopub.status.busy": "2026-08-04T02:02:01.048205Z", + "iopub.status.idle": "2026-08-04T02:02:01.418272Z", + "shell.execute_reply": "2026-08-04T02:02:01.417864Z" } }, "outputs": [ @@ -658,10 +658,10 @@ "execution_count": 16, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:06.711246Z", - "iopub.status.busy": "2026-08-02T23:28:06.711144Z", - "iopub.status.idle": "2026-08-02T23:28:07.278241Z", - "shell.execute_reply": "2026-08-02T23:28:07.277788Z" + "iopub.execute_input": "2026-08-04T02:02:01.420269Z", + "iopub.status.busy": "2026-08-04T02:02:01.420194Z", + "iopub.status.idle": "2026-08-04T02:02:01.811776Z", + "shell.execute_reply": "2026-08-04T02:02:01.811376Z" } }, "outputs": [ @@ -701,10 +701,10 @@ "execution_count": 17, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:07.281512Z", - "iopub.status.busy": "2026-08-02T23:28:07.281414Z", - "iopub.status.idle": "2026-08-02T23:28:07.795080Z", - "shell.execute_reply": "2026-08-02T23:28:07.794556Z" + "iopub.execute_input": "2026-08-04T02:02:01.813687Z", + "iopub.status.busy": "2026-08-04T02:02:01.813606Z", + "iopub.status.idle": "2026-08-04T02:02:02.168679Z", + "shell.execute_reply": "2026-08-04T02:02:02.168282Z" } }, "outputs": [ diff --git a/examples/5_MOC_transports_ECCOv4r4.ipynb b/examples/5_MOC_transports_ECCOv4r4.ipynb index e748ae5..37f91c3 100644 --- a/examples/5_MOC_transports_ECCOv4r4.ipynb +++ b/examples/5_MOC_transports_ECCOv4r4.ipynb @@ -33,10 +33,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:10.493627Z", - "iopub.status.busy": "2026-08-02T23:28:10.493529Z", - "iopub.status.idle": "2026-08-02T23:28:11.672000Z", - "shell.execute_reply": "2026-08-02T23:28:11.671503Z" + "iopub.execute_input": "2026-08-04T02:09:56.567055Z", + "iopub.status.busy": "2026-08-04T02:09:56.566859Z", + "iopub.status.idle": "2026-08-04T02:09:57.455411Z", + "shell.execute_reply": "2026-08-04T02:09:57.455061Z" } }, "outputs": [ @@ -44,7 +44,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Sectionate version: 0.4.0rc2.dev1\n" + "Sectionate version: 0.4.0rc2.dev3\n" ] } ], @@ -77,10 +77,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:11.702020Z", - "iopub.status.busy": "2026-08-02T23:28:11.701814Z", - "iopub.status.idle": "2026-08-02T23:28:12.792659Z", - "shell.execute_reply": "2026-08-02T23:28:12.792237Z" + "iopub.execute_input": "2026-08-04T02:09:57.469675Z", + "iopub.status.busy": "2026-08-04T02:09:57.469513Z", + "iopub.status.idle": "2026-08-04T02:09:58.262430Z", + "shell.execute_reply": "2026-08-04T02:09:58.262018Z" } }, "outputs": [ @@ -129,10 +129,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:12.794098Z", - "iopub.status.busy": "2026-08-02T23:28:12.794001Z", - "iopub.status.idle": "2026-08-02T23:28:12.796233Z", - "shell.execute_reply": "2026-08-02T23:28:12.795855Z" + "iopub.execute_input": "2026-08-04T02:09:58.263547Z", + "iopub.status.busy": "2026-08-04T02:09:58.263491Z", + "iopub.status.idle": "2026-08-04T02:09:58.265337Z", + "shell.execute_reply": "2026-08-04T02:09:58.265038Z" } }, "outputs": [], @@ -150,7 +150,6 @@ " conv = sectionate.transports.convergent_transport(\n", " grid, i_c, j_c, f_c=f_c,\n", " utr=\"utr\", vtr=\"vtr\",\n", - " layer=None, interface=None, # vertical coordinate is the depth dimension 'k'\n", " )\n", " # convergence is \"inward\" to the enclosed polar cap; negate -> northward, then\n", " # integrate along the section at each depth level and average over time.\n", @@ -173,10 +172,10 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:12.797484Z", - "iopub.status.busy": "2026-08-02T23:28:12.797393Z", - "iopub.status.idle": "2026-08-02T23:28:18.803362Z", - "shell.execute_reply": "2026-08-02T23:28:18.802953Z" + "iopub.execute_input": "2026-08-04T02:09:58.266314Z", + "iopub.status.busy": "2026-08-04T02:09:58.266260Z", + "iopub.status.idle": "2026-08-04T02:10:02.515464Z", + "shell.execute_reply": "2026-08-04T02:10:02.514970Z" } }, "outputs": [ @@ -246,10 +245,10 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:18.809112Z", - "iopub.status.busy": "2026-08-02T23:28:18.808944Z", - "iopub.status.idle": "2026-08-02T23:28:41.126030Z", - "shell.execute_reply": "2026-08-02T23:28:41.125527Z" + "iopub.execute_input": "2026-08-04T02:10:02.518605Z", + "iopub.status.busy": "2026-08-04T02:10:02.518471Z", + "iopub.status.idle": "2026-08-04T02:10:17.559173Z", + "shell.execute_reply": "2026-08-04T02:10:17.558828Z" } }, "outputs": [ @@ -294,10 +293,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2026-08-02T23:28:41.128810Z", - "iopub.status.busy": "2026-08-02T23:28:41.128714Z", - "iopub.status.idle": "2026-08-02T23:28:41.353272Z", - "shell.execute_reply": "2026-08-02T23:28:41.352808Z" + "iopub.execute_input": "2026-08-04T02:10:17.560375Z", + "iopub.status.busy": "2026-08-04T02:10:17.560315Z", + "iopub.status.idle": "2026-08-04T02:10:17.727353Z", + "shell.execute_reply": "2026-08-04T02:10:17.726950Z" } }, "outputs": [ diff --git a/examples/load_example_model_grid.py b/examples/load_example_model_grid.py index ed10c53..2bbcdcd 100644 --- a/examples/load_example_model_grid.py +++ b/examples/load_example_model_grid.py @@ -23,6 +23,11 @@ def load_MOM6_example_grid(): coords={ 'X': {'center': 'xh', 'outer': 'xq'}, 'Y': {'center': 'yh', 'outer': 'yq'}, + # Sections are only ever traced horizontally, so the vertical axis plays no part + # in that -- but declaring it is what lets `convergent_transport` label its output + # with the layer interfaces ("sigma2_i") that go with the layers the transports + # are resolved over ("sigma2_l"). + 'Z': {'center': 'sigma2_l', 'outer': 'sigma2_i'}, } # This is a global tripolar grid: zonally periodic, with a bipolar north fold along # its top edge. Declaring the fold (xgcm >= the bipolar-fold release) lets sectionate diff --git a/sectionate/tests/test_convergent_transport.py b/sectionate/tests/test_convergent_transport.py index 34d2db1..36849b0 100644 --- a/sectionate/tests/test_convergent_transport.py +++ b/sectionate/tests/test_convergent_transport.py @@ -51,7 +51,6 @@ def test_convergent_transport(): j, utr="u", vtr="v", - layer=None, geometry="cartesian" )['conv_mass_transport'].sum().values @@ -133,7 +132,7 @@ def total(i_c, j_c, positive_in=True): with warnings.catch_warnings(): warnings.simplefilter("ignore") # open-section UserWarning is expected here return convergent_transport( - grid, i_c, j_c, utr="u", vtr="v", layer=None, + grid, i_c, j_c, utr="u", vtr="v", geometry="cartesian", positive_in=positive_in, )["conv_mass_transport"].sum().values @@ -147,7 +146,7 @@ def total(i_c, j_c, positive_in=True): # The open-section orientation warning must be raised. with pytest.warns(UserWarning, match="left-of-transect"): convergent_transport( - grid, i, j, utr="u", vtr="v", layer=None, geometry="cartesian", + grid, i, j, utr="u", vtr="v", geometry="cartesian", ) @@ -170,7 +169,6 @@ def test_convergent_transport_convention(): j, utr="u", vtr="v", - layer=None )['conv_mass_transport'].sum().values conv_rev = convergent_transport( @@ -179,7 +177,166 @@ def test_convergent_transport_convention(): j[::-1], utr="u", vtr="v", - layer=None )['conv_mass_transport'].sum().values assert np.equal(-3., conv) and np.equal(-3, conv_rev) + + + +# --------------------------------------------------------------------------- +# Vertical coordinates on the output: the layer coordinate comes along with the +# transports, the interface coordinate comes from the grid. +# --------------------------------------------------------------------------- + +def initialize_minimal_vertical_grid( + layer="z_l", interface="z_i", register_z=True, axis="Z" +): + """The minimal 1x1-cell 'outer' grid of `initialize_minimal_outer_grid`, plus a + single-layer vertical coordinate named (`layer`, `interface`) that the transports + "u"/"v" are resolved over. `register_z` selects whether the grid declares that + vertical axis or (as most grids handed to sectionate do, since sections are only + ever traced horizontally) only its horizontal ones; `axis` names it.""" + xh, yh = np.array([0.5]), np.array([0.5]) + xq, yq = np.array([0., 1.]), np.array([0., 1.]) + + lon, lat = np.meshgrid(xh, yh) + lon_c, lat_c = np.meshgrid(xq, yq) + ds = xr.Dataset({}, coords={ + "xh": xr.DataArray(xh, dims=("xh",)), + "yh": xr.DataArray(yh, dims=("yh",)), + "xq": xr.DataArray(xq, dims=("xq",)), + "yq": xr.DataArray(yq, dims=("yq",)), + layer: xr.DataArray(np.array([0.5]), dims=(layer,)), + interface: xr.DataArray(np.array([0., 1.]), dims=(interface,)), + "geolon": xr.DataArray(lon, dims=("yh", "xh")), + "geolat": xr.DataArray(lat, dims=("yh", "xh")), + "geolon_c": xr.DataArray(lon_c, dims=("yq", "xq",)), + "geolat_c": xr.DataArray(lat_c, dims=("yq", "xq",)), + }) + ds["u"] = xr.DataArray(np.array([[[1., -np.sqrt(2.)]]]), dims=(layer, "yh", "xq")) + ds["v"] = xr.DataArray(np.array([[[0.], [np.pi]]]), dims=(layer, "yq", "xh")) + coords = { + 'X': {'outer': 'xq', 'center': 'xh'}, + 'Y': {'outer': 'yq', 'center': 'yh'}, + } + if register_z: + coords[axis] = {'center': layer, 'outer': interface} + return xgcm.Grid(ds, coords=coords, padding={'X': "extend", 'Y': "extend"}, + autoparse_metadata=False) + + +# The closed path of vorticity points around the single cell of the minimal grid, +# i.e. what `grid_section` returns for the square [0,1]x[0,1] (see +# `test_convergent_transport`); hardcoded so these tests exercise only how +# `convergent_transport` labels its output vertically. +MINIMAL_LOOP_I = np.array([0, 1, 1, 0, 0]) +MINIMAL_LOOP_J = np.array([0, 0, 1, 1, 0]) + +# The transport around that cell, independent of how it is resolved vertically. +MINIMAL_LOOP_TRANSPORT = 1. + 0. + np.sqrt(2.) - np.pi + + +def transport_around_minimal_cell(grid): + from sectionate.transports import convergent_transport + return convergent_transport( + grid, + MINIMAL_LOOP_I, + MINIMAL_LOOP_J, + utr="u", + vtr="v", + geometry="cartesian", + ) + + +def test_layer_coordinate_comes_from_the_transports(): + """The layer (cell-center) coordinate is a dimension of `utr`/`vtr` themselves, so it + reaches the output without being named. A grid declaring only its horizontal axes -- + the usual case -- has no interface coordinate to add.""" + grid = initialize_minimal_vertical_grid("sigma2_l", "sigma2_i", register_z=False) + dsout = transport_around_minimal_cell(grid) + + assert "sigma2_l" in dsout.coords + np.testing.assert_array_equal(dsout["sigma2_l"].values, np.array([0.5])) + assert "sigma2_i" not in dsout.coords + assert np.isclose( + dsout['conv_mass_transport'].sum().values, MINIMAL_LOOP_TRANSPORT, rtol=1.e-14 + ) + + +def test_interface_coordinate_comes_from_the_grid(): + """The interface coordinate is one point longer than the layer coordinate, so it is a + dimension of nothing on the section and cannot ride along. A grid that registers its + vertical axis names it, and it is attached from there.""" + grid = initialize_minimal_vertical_grid("sigma2_l", "sigma2_i", register_z=True) + dsout = transport_around_minimal_cell(grid) + + assert "sigma2_l" in dsout.coords and "sigma2_i" in dsout.coords + np.testing.assert_array_equal(dsout["sigma2_i"].values, np.array([0., 1.])) + # Only the vertical axis contributes: the horizontal ones are indexed away along the + # section, so no "xq"/"yq" dimension is dragged back onto the output. + assert set(dsout.sizes) == {"sect", "sigma2_l", "sigma2_i"} + assert np.isclose( + dsout['conv_mass_transport'].sum().values, MINIMAL_LOOP_TRANSPORT, rtol=1.e-14 + ) + + +@pytest.mark.parametrize("layer,interface", [ + ("z_l", "z_i"), # the canonical MOM6 depth coordinate + ("lam_l", "lam_i"), # a stem containing an "l" ... + ("level_l", "level_i"), # ... and one containing two + ("MyCenters", "MyEdges"), # no shared stem and no suffix convention at all +]) +def test_vertical_coordinate_names_need_no_convention(layer, interface): + """The pair is matched through the grid axis that registers it, so it works whatever + the names spell. Matching them by rewriting every "l" of the layer name into an "i" + rejected any stem that itself contained an "l".""" + grid = initialize_minimal_vertical_grid(layer, interface, register_z=True) + dsout = transport_around_minimal_cell(grid) + assert layer in dsout.coords and interface in dsout.coords + + +def test_vertical_axis_need_not_be_named_Z(): + """The axis is found through the dimension the transports carry, not by looking up an + axis called "Z", so a differently named vertical axis works the same way.""" + grid = initialize_minimal_vertical_grid( + "sigma2_l", "sigma2_i", register_z=True, axis="sigma2" + ) + dsout = transport_around_minimal_cell(grid) + assert "sigma2_l" in dsout.coords and "sigma2_i" in dsout.coords + + +def test_interface_dimension_without_coordinate_values_attaches_nothing(): + """An axis position may name a bare dimension carrying no coordinate values; there is + then nothing to attach, and the layer coordinate still arrives with the transports.""" + grid = initialize_minimal_vertical_grid("z_l", "z_i", register_z=True) + grid._ds = grid._ds.drop_vars("z_i") # keep the dim, drop its values + dsout = transport_around_minimal_cell(grid) + assert "z_l" in dsout.coords + assert "z_i" not in dsout.coords + + +def test_two_dimensional_transports_get_no_vertical_coordinates(): + """Transports with no vertical dimension pick up no vertical coordinate -- and, unlike + the old `layer="z_l"` default, trigger no lookup of a name the grid never heard of.""" + from sectionate.transports import convergent_transport + grid = initialize_minimal_outer_grid() + grid._ds['u'] = xr.DataArray(np.array([[1., -np.sqrt(2.)]]), dims=("yh", "xq",)) + grid._ds['v'] = xr.DataArray(np.array([[0], [np.pi]]), dims=("yq", "xh",)) + dsout = transport_around_minimal_cell(grid) + assert set(dsout.sizes) == {"sect"} + assert np.isclose( + dsout['conv_mass_transport'].sum().values, MINIMAL_LOOP_TRANSPORT, rtol=1.e-14 + ) + + +@pytest.mark.parametrize("kwarg", ["layer", "interface"]) +def test_layer_and_interface_keywords_are_gone(kwarg): + """Both were removed: the layer coordinate comes from the data and the interface + coordinate from the grid, so there is nothing left for a caller to name.""" + from sectionate.transports import convergent_transport + grid = initialize_minimal_vertical_grid("z_l", "z_i", register_z=True) + with pytest.raises(TypeError, match=kwarg): + convergent_transport( + grid, MINIMAL_LOOP_I, MINIMAL_LOOP_J, utr="u", vtr="v", + geometry="cartesian", **{kwarg: "z_l"}, + ) diff --git a/sectionate/tests/test_cube_left_grid.py b/sectionate/tests/test_cube_left_grid.py index e5282ba..f1025a7 100644 --- a/sectionate/tests/test_cube_left_grid.py +++ b/sectionate/tests/test_cube_left_grid.py @@ -363,8 +363,7 @@ def test_cube_section_transport_is_streamfunction_difference(lonlat): grid, psi = cube_left_grid() lons, lats = lonlat i_c, j_c, f_c, lons_c, lats_c = grid_section(grid, list(lons), list(lats)) - conv = convergent_transport(grid, i_c, j_c, f_c, utr="u", vtr="v", - layer=None)["conv_mass_transport"].sum().values + conv = convergent_transport(grid, i_c, j_c, f_c, utr="u", vtr="v")["conv_mass_transport"].sum().values # endpoint psi values, from the walked path's own native endpoints ot = outer_topology(grid) cxyz = _outer_corners_xyz() @@ -406,8 +405,7 @@ def test_cube_section_over_pole_has_stable_geometric_sign(lonlat, faces_expected assert 0 < int(at_pole[0]) < len(lats_c) - 1 assert set(np.asarray(f_c).tolist()) == faces_expected - conv = convergent_transport(grid, i_c, j_c, f_c, utr="u", vtr="v", - layer=None)["conv_mass_transport"] + conv = convergent_transport(grid, i_c, j_c, f_c, utr="u", vtr="v")["conv_mass_transport"] # The most pole-adjacent velocity face genuinely carries flux, so its sign is # not free to be wrong-yet-harmless. polest = int(np.argmax(np.asarray(conv["lat"].values))) @@ -447,8 +445,7 @@ def test_cube_closed_section_around_vertex_carries_zero_net_transport(): i_c, j_c, f_c, _, _ = grid_section(grid, lons, lats) assert len(set(np.asarray(f_c).tolist())) >= 3 # truly encircles the vertex - conv = convergent_transport(grid, i_c, j_c, f_c, utr="u", vtr="v", - layer=None)["conv_mass_transport"].sum().values + conv = convergent_transport(grid, i_c, j_c, f_c, utr="u", vtr="v")["conv_mass_transport"].sum().values assert abs(float(conv)) < 1e-12 @@ -609,8 +606,7 @@ def test_outer_reverse_gluing_matches_by_coincidence(): # a section crossing several faces (hence reverse seams): transport == dpsi i_c, j_c, f_c, lons, lats = grid_section(grid, [10., -150.], [40., -30.]) assert len(set(np.asarray(f_c).tolist())) >= 3 - conv = convergent_transport(grid, i_c, j_c, f_c, utr="u", vtr="v", - layer=None)["conv_mass_transport"].sum().values + conv = convergent_transport(grid, i_c, j_c, f_c, utr="u", vtr="v")["conv_mass_transport"].sum().values p0 = psi[int(f_c[0]), int(j_c[0]), int(i_c[0])] p1 = psi[int(f_c[-1]), int(j_c[-1]), int(i_c[-1])] assert np.isclose(abs(float(conv)), abs(p1 - p0), rtol=1e-9) diff --git a/sectionate/tests/test_left_grid.py b/sectionate/tests/test_left_grid.py index 0a68b55..632e2c3 100644 --- a/sectionate/tests/test_left_grid.py +++ b/sectionate/tests/test_left_grid.py @@ -164,10 +164,10 @@ def test_left_transport_matches_outer(): assert np.array_equal(io, il) and np.array_equal(jo, jl) conv_o = convergent_transport( - outer, io, jo, utr="u", vtr="v", layer=None, geometry="cartesian" + outer, io, jo, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values conv_l = convergent_transport( - left, il, jl, utr="u", vtr="v", layer=None, geometry="cartesian" + left, il, jl, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values assert np.isclose(conv_o, conv_l, rtol=1e-12) @@ -192,18 +192,18 @@ def box_mask(grid): # center cells enclosed by the box i, j, *_ = grid_section(single, lonseg, latseg) geom = convergent_transport( - single, i, j, utr="u", vtr="v", layer=None, geometry="cartesian", + single, i, j, utr="u", vtr="v", geometry="cartesian", )["conv_mass_transport"].sum().values assert np.abs(geom) > 1e-6 # non-trivial: the sign actually matters conv_single = convergent_transport( - single, i, j, utr="u", vtr="v", layer=None, geometry="cartesian", + single, i, j, utr="u", vtr="v", geometry="cartesian", positive_in=box_mask(single), )["conv_mass_transport"].sum().values i2, j2, f2, *_ = grid_section(split, lonseg, latseg) assert set(np.unique(f2).tolist()) == {0, 1} # genuinely crosses the seam conv_split = convergent_transport( - split, i2, j2, f2, utr="u", vtr="v", layer=None, geometry="cartesian", + split, i2, j2, f2, utr="u", vtr="v", geometry="cartesian", positive_in=box_mask(split), )["conv_mass_transport"].sum().values @@ -219,13 +219,13 @@ def test_left_multitile_seam_transport_matches_uncut_grid(): i, j, lons, lats = grid_section(single, lonseg, latseg) conv_single = convergent_transport( - single, i, j, utr="u", vtr="v", layer=None, geometry="cartesian" + single, i, j, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values i2, j2, f2, l2, la2 = grid_section(split, lonseg, latseg) assert set(np.unique(f2).tolist()) == {0, 1} # really crosses the seam conv_split = convergent_transport( - split, i2, j2, f2, utr="u", vtr="v", layer=None, geometry="cartesian" + split, i2, j2, f2, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values assert np.isclose(conv_single, conv_split, rtol=1e-12) diff --git a/sectionate/tests/test_section_multitile.py b/sectionate/tests/test_section_multitile.py index 39295bb..e42f766 100644 --- a/sectionate/tests/test_section_multitile.py +++ b/sectionate/tests/test_section_multitile.py @@ -278,7 +278,7 @@ def test_within_face_transport_matches_single_tile(): assert np.all(f == 0) # stays on face 0 conv_mt = convergent_transport( - grid, i, j, f, utr="u", vtr="v", layer=None, geometry="cartesian" + grid, i, j, f, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values # Same section + transports on the standalone face-0 grid must agree exactly. @@ -286,7 +286,7 @@ def test_within_face_transport_matches_single_tile(): i0, j0, lons0, lats0 = grid_section(slab, lonseg, latseg) assert np.array_equal(i, i0) and np.array_equal(j, j0) conv_st = convergent_transport( - slab, i0, j0, utr="u", vtr="v", layer=None, geometry="cartesian" + slab, i0, j0, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values assert np.isclose(conv_mt, conv_st, rtol=1e-14) @@ -384,13 +384,13 @@ def test_seam_crossing_transport_matches_uncut_grid(): i, j, lons, lats = grid_section(single, lonseg, latseg) conv_single = convergent_transport( - single, i, j, utr="u", vtr="v", layer=None, geometry="cartesian" + single, i, j, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values i2, j2, f2, lons2, lats2 = grid_section(split, lonseg, latseg) assert set(np.unique(f2).tolist()) == {0, 1} # the section really crosses the seam conv_split = convergent_transport( - split, i2, j2, f2, utr="u", vtr="v", layer=None, geometry="cartesian" + split, i2, j2, f2, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values assert np.isclose(conv_single, conv_split, rtol=1e-12) @@ -447,12 +447,12 @@ def test_nonsymmetric_seam_transport_matches_uncut_grid(): latseg = np.array([-15., -15., 15., 15., -15.]) i, j, lons, lats = grid_section(single, lonseg, latseg) conv_single = convergent_transport( - single, i, j, utr="u", vtr="v", layer=None, geometry="cartesian" + single, i, j, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values i2, j2, f2, l2, la2 = grid_section(split, lonseg, latseg) assert set(np.unique(f2).tolist()) == {0, 1} conv_split = convergent_transport( - split, i2, j2, f2, utr="u", vtr="v", layer=None, geometry="cartesian" + split, i2, j2, f2, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values assert np.isclose(conv_single, conv_split, rtol=1e-12) @@ -505,7 +505,7 @@ def test_rotated_seam_transport_streamfunction(): i, j, f, lons, lats = grid_section(grid, [10., 70.], [20., 20.]) assert set(np.unique(f).tolist()) == {0, 1} # the section really crosses the rotated seam conv = convergent_transport( - grid, i, j, f, utr="u", vtr="v", layer=None, geometry="cartesian" + grid, i, j, f, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values dpsi = _psi(lons[-1], lats[-1]) - _psi(lons[0], lats[0]) assert np.isclose(abs(conv), abs(dpsi), rtol=1e-9) @@ -541,7 +541,7 @@ def test_save_load_roundtrip_preserves_face_indices(tmp_path): np.testing.assert_array_equal(np.asarray(gs2.j_c), np.asarray(gs.j_c)) # transport from the reloaded section matches the original - kw = dict(utr="u", vtr="v", layer=None, geometry="cartesian") + kw = dict(utr="u", vtr="v", geometry="cartesian") t1 = convergent_transport(grid, gs.i_c, gs.j_c, gs.f_c, **kw)["conv_mass_transport"].sum().values t2 = convergent_transport(grid, gs2.i_c, gs2.j_c, gs2.f_c, **kw)["conv_mass_transport"].sum().values assert np.isclose(t1, t2) diff --git a/sectionate/tests/test_section_reversibility.py b/sectionate/tests/test_section_reversibility.py index b3f0bea..49e841d 100644 --- a/sectionate/tests/test_section_reversibility.py +++ b/sectionate/tests/test_section_reversibility.py @@ -65,10 +65,10 @@ def test_oblique_section_reversible_and_equal_transport(): with warnings.catch_warnings(): warnings.simplefilter("ignore") # open-section UserWarning c_fwd = convergent_transport( - grid, i, j, utr="u", vtr="v", layer=None, geometry="cartesian" + grid, i, j, utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values c_rev = convergent_transport( - grid, i[::-1], j[::-1], utr="u", vtr="v", layer=None, geometry="cartesian" + grid, i[::-1], j[::-1], utr="u", vtr="v", geometry="cartesian" )["conv_mass_transport"].sum().values assert np.isclose(abs(c_fwd), abs(c_rev), rtol=1e-12) diff --git a/sectionate/transports.py b/sectionate/transports.py index d47fc0a..5e24a91 100644 --- a/sectionate/transports.py +++ b/sectionate/transports.py @@ -382,6 +382,44 @@ def uvcoords_from_qindices(grid, i_c, j_c, f_c=None): uvindices_from_qindices(grid, i_c, j_c, f_c=f_c), ) +# xgcm positions at which a vertical *interface* (cell-edge) coordinate can sit, +# as opposed to the "center" position where the paired layer coordinate sits. +_INTERFACE_POSITIONS = ("outer", "inner", "left", "right") + + +def _interface_coords(grid, da): + """Interface (cell-edge) coordinates matching the vertical dimensions `da` carries. + + A transport's layer (cell-center) coordinate needs no help: it is one of the + transport's own dimensions, so it rides along from `utr`/`vtr` into the section. + Its interface coordinate cannot -- it is one point longer, and so is a dimension of + nothing on the section. The grid knows it, though: an axis that registers one of + `da`'s dimensions at its "center" position names the matching interface coordinate + at one of its interface positions. + + Keyed off the data's own dimensions rather than off an axis called "Z", so a + vertical axis under any name is found, and a grid that declares only its horizontal + axes (as most grids handed to sectionate do, since sections are only ever traced + horizontally) simply contributes nothing. + + Only names present in `grid._ds` are returned: xgcm requires an axis' positions to + name *dimensions*, but a dimension need not carry coordinate values. + """ + coords = {} + for axis in grid.axes.values(): + positions = axis.coords + if positions.get("center") not in da.dims: + continue + name = next( + (positions[pos] for pos in _INTERFACE_POSITIONS + if positions.get(pos) in grid._ds), + None + ) + if name is not None: + coords[name] = grid._ds[name] + return coords + + def convergent_transport( grid, i_c, @@ -389,8 +427,6 @@ def convergent_transport( f_c=None, utr="umo", vtr="vmo", - layer="z_l", - interface="z_i", outname="conv_mass_transport", sect_coord="sect", geometry="spherical", @@ -419,11 +455,6 @@ def convergent_transport( Name of "X"-direction tracer transport vtr: str Name of "Y"-direction tracer transport - layer : str or None - Name of the vertical layer (cell-center) coordinate, or None for grids without one. - interface : str or None - Name of the vertical interface coordinate, or None. If both are given, they must be - consistent (`layer` is `interface` with "l" in place of "i"). outname : str Name of output xr.DataArray variable. Default: "conv_mass_transport". sect_coord: str @@ -452,12 +483,15 @@ def convergent_transport( Contains the calculated normal transport and the coordinates of the contributing velocity points (`lon`, `lat`), as well as some useful metadata, such as whether each point corresponds to a "U" or "V" velocity and whether the sign of the transport had to be flipped to make it point inwards. + + Vertical coordinates are not named anywhere in the call: the layer (cell-center) + coordinate is inherited from `utr`/`vtr`, being one of their dimensions, and the + matching interface (cell-edge) coordinate is read off whichever axis of `grid` + registers that layer dimension at its "center" position. Declare the vertical + axis on the `xgcm.Grid` (e.g. `coords={..., "Z": {"center": "z_l", "outer": + "z_i"}}`) if the interface coordinate is wanted downstream. """ - - if (layer is not None) and (interface is not None): - if layer.replace("l", "i") != interface: - raise ValueError("Inconsistent layer and interface grid variables!") - + # On a multi-tile grid the contributing velocity face varies along the section # (`uvindices["face"]`); it is selected pointwise in every `.isel` below. facedim = get_facedim(grid) if f_c is not None else None @@ -605,10 +639,9 @@ def convergent_transport( "positive_in":positive_in, }} - if layer is not None: - dsout[layer] = grid._ds[layer] - if interface is not None: - dsout[interface] = grid._ds[interface] + # The layer (cell-center) coordinate arrived with `utr`/`vtr`; its interface + # coordinate is a point longer and has to come from the grid (`_interface_coords`). + dsout = dsout.assign_coords(_interface_coords(grid, dsout[outname])) return dsout