Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions components/omega/configs/Default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,19 @@ Omega:
Contents:
- State
- Base
# Forcing is the external forcing in standalone mode.
# It contains the surface stress, surface fluxes
# and the surface restoring target files (if appl.).
Forcing:
UsePointerFile: false
Filename: forcing.nc

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not saying we should do this, but could we set this to init.nc to delay the need for a polaris PR?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be helpful for the tests that do not have any forcing. It would be weird to me to create an (empty?) forcing file for cases that wouldn't otherwise need one.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to alter the logic so the forcing stream isn't read if it's not needed. Pointing to an unrelated file by default seems like a messy solution to me.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or to comment out the forcing stream by default.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could change the FreqUnits to never for now. And manually change to OnStartup for the few test we actually need it?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that's the right solution.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds ideal. @alicebarthel Could you use this approach in a companion Polaris PR and check that it works as expected?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I like this approach. I'll look into it and check its behavior.

Mode: read
Precision: double
Freq: 1
FreqUnits: OnStartup
UseStartEnd: false
Contents:
- Forcing
# Restarts are used to initialize for all job submissions after the very
# first startup job. We use UseStartEnd with a start time just after the
# simulation start time so that omega does not attempt to use a restart
Expand Down
34 changes: 34 additions & 0 deletions components/omega/src/ocn/Forcing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "Forcing.h"
#include "Field.h"
#include "IOStream.h"
#include "Logging.h"
#include "Pacer.h"

Expand Down Expand Up @@ -58,6 +59,7 @@ Forcing *Forcing::create(const std::string &Name, const HorzMesh *Mesh,
}

// Initialize the default forcing instance and read configuration.
// Reads the forcing fields from streams at startup (for now).
void Forcing::init() {
if (DefaultForcing != nullptr) {
return;
Expand All @@ -78,6 +80,10 @@ void Forcing::init() {

Config *OmegaConfig = Config::getOmegaConfig();
DefaultForcing->readConfigOptions(OmegaConfig);
// for now, forcing fields are read at start-up only.
// to be extended to include switch from standalone to coupled.
// to be moved to a Forcing->prepareForStep(SimTime) method later.
DefaultForcing->readStreamIntoArrays();
}

// Return the default forcing instance.
Expand Down Expand Up @@ -159,4 +165,32 @@ I4 Forcing::exchangeHalo() const {
return Err;
}

// Read forcing fields from input stream.
// To be extended with time indexing later.
void Forcing::readStreamIntoArrays() {
Error Err;

Real FillValueReal = -999._Real;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be doing anything here to make it consistent with #428 or is the change best made after #428 is merged?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I am aware of the overlap/conflict with #428.
My thought was to get that one in first and then update asap for #428. Or vice versa if #428 gets merged first.
I am not sure how to make this section consistent with #428 and test it without the whole #428 change.

deepCopy(SfcStressForcing.ZonalStressCell, FillValueReal);
deepCopy(SfcStressForcing.MeridStressCell, FillValueReal);

std::string StreamName = "Forcing";

// Attempt to read stream; if unavailable, log and fall back to zero forcing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it fall back to zero forcing because all forcing tendencies will be computed and applied with zeros?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, if the tendency flag is on but the forcing stream is missing, it defaults the forcing to zero and calculates the tendencies. If the tendency flag is off, then it skips the tendency calculation.
We can change that behavior later if we want a visible fail, the idea here was to write it in the log and proceed without failing.

@andrewdnolan andrewdnolan Jun 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cbegeman I suggested this; in order to produce as few breaking changes as possible. With us staring down the coupling deadline, I figure this might be worth it (though in generally it's not the best practice).

I think if we are OK with this for now, we should open up a linked issue about "failing fast" here. We can circle back to this after delivering a first pass at the coupled model.

I don't want to speak for @alicebarthel, but I don't think we are that tied to this approach. We are just trying to mindful of the work left and timeline. If people feel very strongly against defaulting to zeros, then I'd think we are okay with dropping this and just failing if the read is unsuccessful.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a check for whether any forcing tendency is on before we attempt to load the forcing stream? I.e., if we leave off the default-to-zero feature and the forcing stream is not provided, will the simulation always fail?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't a check on the tendency flag before the Forcing::init() or read from file.
Because the tendency flags are per forcing type, I didn't think of that since I expect most longer cases to have some forcing, but for the spin-down ones, we may be able to skip this altogether?
A check on whether at least one forcing flag exists is worth considering...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel strongly that we need to run through all forcing flags before trying to read from the forcing stream. My concern with default-to-fill-value right now is that we may be introducing a requirement to provide a forcing file for all cases.

It could be good in the long run to have: if SfcStressForcingTendencyEnable is True, throw a critical error if SfcStressZonal and SfcStressMeridional are not included in the forcing stream (and do the same for other forcing fields and their corresponding flags).

I'm fine with default-to-zero for now as long as we open an issue that this needs to be fixed later.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! I'll open an issue.
The key point of default to zero was indeed to avoid introducing a forcing file requirement for now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. Glad we're on the same page

Err = IOStream::read(StreamName);
if (Err.isFail()) {
LOG_INFO("Forcing: Error while reading {} stream, using zero forcing",
StreamName);
deepCopy(SfcStressForcing.ZonalStressCell, 0._Real);
deepCopy(SfcStressForcing.MeridStressCell, 0._Real);
}

I4 HaloErr = exchangeHalo();
if (HaloErr != 0) {
ABORT_ERROR("Forcing: Error exchanging halo for startup forcing fields");
}

computeAll();
}

} // namespace OMEGA
3 changes: 3 additions & 0 deletions components/omega/src/ocn/Forcing.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ class Forcing {
/// Unregister surface stress fields from IO streams
void unregisterFields() const;

/// Read forcing fields from input stream at startup
void readStreamIntoArrays();

/// Compute all forcing variables
void computeAll() const;

Expand Down
4 changes: 2 additions & 2 deletions components/omega/src/ocn/OceanRun.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ int ocnRun(TimeInstant &CurrTime ///< [inout] current sim time
// track step count
++IStep;

// call forcing routines, anything needed pre-timestep
DefForcing->computeAll();
// placeholder: call needed pre-timestep compute here
// (e.g. forcing routine)

// do forward time step
// first call to doStep can sometimes take very long
Expand Down
4 changes: 4 additions & 0 deletions components/omega/test/infra/IOStreamTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ void initIOStreamTest(Clock *&ModelClock // Model clock
// Initialize Tracers
Tracers::init();

// IOStream::validateAll() depends on Forcing::init() so Forcing fields
// are registered before stream validation.
Forcing::init();

// Initialize Aux State
AuxiliaryState::init();

Expand Down
18 changes: 12 additions & 6 deletions components/omega/test/ocn/ForcingTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ struct TestSetupSphere {
};

#ifdef FORCING_TEST_PLANE
constexpr Geometry Geom = Geometry::Planar;
constexpr char DefaultMeshFile[] = "OmegaPlanarMesh.nc";
using TestSetup = TestSetupPlane;
constexpr Geometry Geom = Geometry::Planar;
constexpr char DefaultMeshFile[] = "OmegaPlanarMesh.nc";
constexpr char DefaultForcingFile[] = "forcingPlanar.nc";
using TestSetup = TestSetupPlane;
#else
constexpr Geometry Geom = Geometry::Spherical;
constexpr char DefaultMeshFile[] = "OmegaSphereMesh.nc";
using TestSetup = TestSetupSphere;
constexpr Geometry Geom = Geometry::Spherical;
constexpr char DefaultMeshFile[] = "OmegaSphereMesh.nc";
constexpr char DefaultForcingFile[] = "forcingSphere.nc";
using TestSetup = TestSetupSphere;
#endif

int testSfcStressForcingVars(Real RTol) {
Expand Down Expand Up @@ -144,6 +146,10 @@ int initForcingTest(const std::string &MeshFile) {
Field::init(ModelClock);
IOStream::init(ModelClock);

// Select the case-specific forcing file before Forcing::init() performs
// startup stream reads.
IOStream::changeFilename("Forcing", DefaultForcingFile);

Err = Halo::init();
if (Err != 0) {
ABORT_ERROR("ForcingTest: error initializing default halo");
Expand Down
6 changes: 6 additions & 0 deletions components/omega/test/ocn/StateTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "Dimension.h"
#include "Error.h"
#include "Field.h"
#include "Forcing.h"
#include "Halo.h"
#include "HorzMesh.h"
#include "IO.h"
Expand Down Expand Up @@ -89,6 +90,10 @@ void initStateTest() {
// Initialize tracers
Tracers::init();

// IOStream::validateAll() depends on Forcing::init() so Forcing fields
// are registered before stream validation.
Forcing::init();

// Initialize Aux State variables
AuxiliaryState::init();

Expand Down Expand Up @@ -406,6 +411,7 @@ int main(int argc, char *argv[]) {
// Finalize Omega objects
OceanState::clear();
Tracers::clear();
Forcing::clear();
AuxiliaryState::clear();
PressureGrad::clear();
Tendencies::clear();
Expand Down
Loading