-
Notifications
You must be signed in to change notification settings - Fork 6
Port sphere transport time-varying prescribed velocity fields #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
1ad35f4
ef763e1
3a7d020
bb61598
14659fa
7159790
a6f40a4
8ef6214
852572a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,16 +10,20 @@ | |
| #include "ForwardBackwardStepper.h" | ||
| #include "RungeKutta2Stepper.h" | ||
| #include "RungeKutta4Stepper.h" | ||
| #include "Logging.h" | ||
|
|
||
| namespace OMEGA { | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| // create the static class members | ||
| // Default model time stepper | ||
| TimeStepper *TimeStepper::DefaultTimeStepper = nullptr; | ||
| // All defined time steppers | ||
| std::map<std::string, std::unique_ptr<TimeStepper>> | ||
| TimeStepper::AllTimeSteppers; | ||
| PrescribeStateType TimeStepper::DefaultPrescribeThicknessMode = | ||
| PrescribeStateType::None; | ||
| PrescribeStateType TimeStepper::DefaultPrescribeVelocityMode = | ||
| PrescribeStateType::None; | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| // utility functions | ||
|
|
@@ -44,6 +48,36 @@ TimeStepperType getTimeStepperFromStr(const std::string &InString) { | |
| return TimeStepperChoice; | ||
| } | ||
|
|
||
| PrescribeStateType getPrescribeThicknessTypeFromStr(const std::string &InString) { | ||
|
|
||
| if (InString == "None") { | ||
| return PrescribeStateType::None; | ||
| } | ||
| if (InString == "Init") { | ||
| return PrescribeStateType::Init; | ||
| } | ||
|
|
||
| ABORT_ERROR("PrescribeStateType should be 'None' or 'Init' for thickness but got {}", | ||
| InString); | ||
| return PrescribeStateType::Invalid; | ||
| } | ||
| PrescribeStateType getPrescribeVelocityTypeFromStr(const std::string &InString) { | ||
|
|
||
| if (InString == "None") { | ||
| return PrescribeStateType::None; | ||
| }else if (InString == "Init") { | ||
| return PrescribeStateType::Init; | ||
| }else if (InString == "NonDivergent") { | ||
| return PrescribeStateType::NonDivergent; | ||
| }else if (InString == "Divergent") { | ||
| return PrescribeStateType::Divergent; | ||
| } | ||
|
|
||
| ABORT_ERROR("PrescribeStateType should be 'None', 'Init', 'NonDivergent' or 'Divergent' for velocity but got {}", | ||
| InString); | ||
| return PrescribeStateType::Invalid; | ||
| } | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| // Constructors and creation methods. | ||
|
|
||
|
|
@@ -119,6 +153,11 @@ TimeStepper *TimeStepper::create( | |
| ABORT_ERROR("Unknown time stepping method"); | ||
| } | ||
|
|
||
| NewTimeStepper->PrescribeThicknessMode = | ||
| DefaultPrescribeThicknessMode; | ||
| NewTimeStepper->PrescribeVelocityMode = | ||
| DefaultPrescribeVelocityMode; | ||
|
|
||
| // Attach data pointers | ||
| NewTimeStepper->attachData(InTend, InAuxState, InMesh, InVCoord, InMeshHalo); | ||
|
|
||
|
|
@@ -170,6 +209,11 @@ TimeStepper *TimeStepper::create( | |
| ABORT_ERROR("Unknown time stepping method"); | ||
| } | ||
|
|
||
| NewTimeStepper->PrescribeThicknessMode = | ||
| DefaultPrescribeThicknessMode; | ||
| NewTimeStepper->PrescribeVelocityMode = | ||
| DefaultPrescribeVelocityMode; | ||
|
|
||
| // Store instance | ||
| AllTimeSteppers.emplace(InName, NewTimeStepper); | ||
|
|
||
|
|
@@ -298,6 +342,22 @@ void TimeStepper::init1() { | |
| StopTime = StopTime2; | ||
| } | ||
|
|
||
| Config StateConfig("State"); | ||
| Error StateErr = OmegaConfig->get(StateConfig); | ||
| if (StateErr.isSuccess()) { | ||
| std::string ThicknessMode; | ||
| if (StateConfig.get("PrescribeThicknessType", ThicknessMode).isSuccess()) { | ||
| TimeStepper::DefaultPrescribeThicknessMode = | ||
| getPrescribeThicknessTypeFromStr(ThicknessMode); | ||
| } | ||
|
|
||
| std::string VelocityMode; | ||
| if (StateConfig.get("PrescribeVelocityType", VelocityMode).isSuccess()) { | ||
| TimeStepper::DefaultPrescribeVelocityMode = | ||
| getPrescribeVelocityTypeFromStr(VelocityMode); | ||
| } | ||
| } | ||
|
|
||
| // Now that all the inputs are defined, create the default time stepper | ||
| // Use the partial creation function for only the time info. Data | ||
| // pointers will be attached in phase 2 initialization | ||
|
|
@@ -460,6 +520,169 @@ void TimeStepper::updateStateByTend(OceanState *State1, int TimeLevel1, | |
| updateVelocityByTend(State1, TimeLevel1, State2, TimeLevel2, Coeff); | ||
| } | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| // Reset state variables to their initial values | ||
| void TimeStepper::prescribeThickness(OceanState *State1, int TimeLevel1, | ||
| OceanState *State2, int TimeLevel2) const { | ||
|
|
||
| if (PrescribeThicknessMode == PrescribeStateType::None) { | ||
| return; | ||
| } | ||
|
|
||
| if (PrescribeThicknessMode == PrescribeStateType::Init) { | ||
| Array2DReal LayerThick1 = State1->getLayerThickness(TimeLevel1); | ||
| Array2DReal LayerThick2 = State2->getLayerThickness(TimeLevel2); | ||
|
|
||
| OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); | ||
| OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); | ||
|
|
||
| parallelForOuter( | ||
| "prescribeThickness", {Mesh->NCellsAll}, | ||
| KOKKOS_LAMBDA(int ICell, const TeamMember &Team) { | ||
| const int KMin = MinLayerCell(ICell); | ||
| const int KMax = MaxLayerCell(ICell); | ||
| const int KRange = vertRange(KMin, KMax); | ||
|
|
||
| parallelForInner( | ||
| Team, KRange, INNER_LAMBDA(int KChunk) { | ||
| const int K = KMin + KChunk; | ||
| LayerThick1(ICell, K) = LayerThick2(ICell, K); | ||
| }); | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| void TimeStepper::prescribeVelocity(OceanState *State1, int TimeLevel1, | ||
| OceanState *State2, int TimeLevel2, | ||
| const TimeInstant &SimTime) const { | ||
|
|
||
| if (PrescribeVelocityMode == PrescribeStateType::None) { | ||
| return; | ||
| } | ||
|
|
||
| if (PrescribeVelocityMode == PrescribeStateType::Init) { | ||
| Array2DReal NormalVel1 = State1->getNormalVelocity(TimeLevel1); | ||
| Array2DReal NormalVel2 = State2->getNormalVelocity(TimeLevel2); | ||
|
|
||
| OMEGA_SCOPE(MinLayerEdgeBot, VCoord->MinLayerEdgeBot); | ||
| OMEGA_SCOPE(MaxLayerEdgeTop, VCoord->MaxLayerEdgeTop); | ||
|
|
||
| parallelForOuter( | ||
| "prescribeVelocity", {Mesh->NEdgesAll}, | ||
| KOKKOS_LAMBDA(int IEdge, const TeamMember &Team) { | ||
| const int KMin = MinLayerEdgeBot(IEdge); | ||
| const int KMax = MaxLayerEdgeTop(IEdge); | ||
| const int KRange = vertRange(KMin, KMax); | ||
|
|
||
| parallelForInner( | ||
| Team, KRange, INNER_LAMBDA(int KChunk) { | ||
| const int K = KMin + KChunk; | ||
| NormalVel2(IEdge, K) = NormalVel1(IEdge, K); | ||
| }); | ||
| }); | ||
| return; | ||
| } else if (PrescribeVelocityMode == PrescribeStateType::NonDivergent) { | ||
| Array2DReal NormalVel2 = State2->getNormalVelocity(TimeLevel2); | ||
|
|
||
| OMEGA_SCOPE(LatEdge, Mesh->LatEdgeH); | ||
| OMEGA_SCOPE(LonEdge, Mesh->LonEdgeH); | ||
| OMEGA_SCOPE(AngleEdge, Mesh->AngleEdgeH); | ||
| OMEGA_SCOPE(MinLayerEdgeBot, VCoord->MinLayerEdgeBotH); | ||
| OMEGA_SCOPE(MaxLayerEdgeTop, VCoord->MaxLayerEdgeTopH); | ||
|
|
||
| const Clock *ModelClock = StepClock.get(); | ||
| R8 ElapsedTimeSec; | ||
| TimeInterval ElapsedTimeInterval = SimTime - ModelClock->getCurrentTime(); | ||
| ElapsedTimeInterval.get(ElapsedTimeSec, TimeUnits::Seconds); | ||
|
|
||
| const R8 Tau = 12. * Day2Sec; // 12 days in seconds | ||
| const R8 TSim = ElapsedTimeSec; | ||
|
|
||
| parallelForOuter( | ||
| "prescribeVelocityNonDivergent", {Mesh->NEdgesAll}, | ||
| KOKKOS_LAMBDA(int IEdge, const TeamMember &Team) { | ||
| const int KMin = MinLayerEdgeBot(IEdge); | ||
| const int KMax = MaxLayerEdgeTop(IEdge); | ||
| const int KRange = vertRange(KMin, KMax); | ||
|
|
||
| const R8 lon_p = | ||
| LonEdge(IEdge) - 2.0 * Pi * TSim / Tau; | ||
| const R8 u = (1 / Tau) * | ||
| (10.0 * Kokkos::pow(sin(lon_p), 2) * | ||
| sin(2.0 * LatEdge(IEdge)) * | ||
| cos(Pi * TSim / Tau) + | ||
| 2.0 * Pi * cos(LatEdge(IEdge))); | ||
| const R8 v = (10.0 / Tau) * sin(2.0 * lon_p) * | ||
| cos(LatEdge(IEdge)) * cos(Pi * TSim / Tau); | ||
| const R8 normalVel = REarth * ( | ||
| u * cos(AngleEdge(IEdge)) + v * sin(AngleEdge(IEdge))); | ||
|
|
||
| parallelForInner( | ||
| Team, KRange, INNER_LAMBDA(int KChunk) { | ||
| const int K = KMin + KChunk; | ||
| NormalVel2(IEdge, K) = normalVel; | ||
| }); | ||
| }); | ||
| return; | ||
| } else if (PrescribeVelocityMode == PrescribeStateType::Divergent) { | ||
| Array2DReal NormalVel2 = State2->getNormalVelocity(TimeLevel2); | ||
|
|
||
| OMEGA_SCOPE(LatEdge, Mesh->LatEdgeH); | ||
| OMEGA_SCOPE(LonEdge, Mesh->LonEdgeH); | ||
| OMEGA_SCOPE(AngleEdge, Mesh->AngleEdgeH); | ||
| OMEGA_SCOPE(MinLayerEdgeBot, VCoord->MinLayerEdgeBotH); | ||
| OMEGA_SCOPE(MaxLayerEdgeTop, VCoord->MaxLayerEdgeTopH); | ||
|
|
||
| const Clock *ModelClock = StepClock.get(); | ||
| R8 ElapsedTimeSec; | ||
| TimeInterval ElapsedTimeInterval = SimTime - ModelClock->getCurrentTime(); | ||
| ElapsedTimeInterval.get(ElapsedTimeSec, TimeUnits::Seconds); | ||
|
|
||
| const R8 Tau = 12. * Day2Sec; // 14 days in seconds | ||
| const R8 TSim = ElapsedTimeSec; | ||
|
|
||
| parallelForOuter( | ||
| "prescribeVelocityDivergent", {Mesh->NEdgesAll}, | ||
| KOKKOS_LAMBDA(int IEdge, const TeamMember &Team) { | ||
| const int KMin = MinLayerEdgeBot(IEdge); | ||
| const int KMax = MaxLayerEdgeTop(IEdge); | ||
| const int KRange = vertRange(KMin, KMax); | ||
|
|
||
| const R8 lon_p = | ||
| LonEdge(IEdge) - 2.0 * Pi * TSim / Tau; | ||
| const R8 u = (1.0 / Tau) * | ||
| (-5.0 * Kokkos::pow(sin(lon_p / 2), 2) * | ||
| sin(2.0 * LatEdge(IEdge)) * | ||
| Kokkos::pow(cos(LatEdge(IEdge)), 2) * | ||
| cos(Pi * TSim / Tau) + | ||
| 2.0 * Pi * cos(LatEdge(IEdge))); | ||
| const R8 v = ((2.5 / Tau) * | ||
| sin(lon_p) * | ||
| Kokkos::pow(cos(LatEdge(IEdge)), 3) * | ||
| cos(Pi * TSim / Tau)); | ||
| const R8 normalVel = REarth * ( | ||
| u * cos(AngleEdge(IEdge)) + v * sin(AngleEdge(IEdge))); | ||
|
Comment on lines
+655
to
+666
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't been able to track down the reason for the discrepancy between MPAS-O's velocity field (top) and Omega's velocity field (bottom) for the divergent case. I need to pivot to other Omega work so @overfelt any time you have to look into this would be much appreciated! It should be possible to reproduce these results using E3SM-Project/polaris#500. Let me know if you have any trouble |
||
|
|
||
| parallelForInner( | ||
| Team, KRange, INNER_LAMBDA(int KChunk) { | ||
| const int K = KMin + KChunk; | ||
| NormalVel2(IEdge, K) = normalVel; | ||
| }); | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| void TimeStepper::prescribeState(OceanState *State1, int TimeLevel1, | ||
| OceanState *State2, int TimeLevel2, | ||
| const TimeInstant &SimTime) const { | ||
| prescribeThickness(State1, TimeLevel1, State2, TimeLevel2); | ||
| prescribeVelocity(State1, TimeLevel1, State2, TimeLevel2, SimTime); | ||
| } | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| // Updates tracers | ||
| // NextTracers = (CurTracers * LayerThickness2(TimeLevel2)) + | ||
|
|
||




There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cbegeman
I see in MPAS-Ocean:
u = (1/twelve_days)*(10*(sin(lon_p)**2)*sin(2*lat)*cost + two_pi*coslat)I think the factor of 10 was moved slightly. Might make a difference in time step stability if the change resulted in a larger velocity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@overfelt Thanks for pointing that out! I'll fix and retest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@overfelt That fix seems to have resolved at least the stability issues with the divergent velocity case (top) but not the non-divergent case (bottom). I tried reducing the time step to 1s per km horizontal resolution. Only the rotation_2d has convergence with space and time refinement. Can I delegate the debugging to you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cbegeman I'll take a look. Maybe I'll get lucky and find a difference with mpas-ocean. Maybe......
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @overfelt!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After this fix and reducing the timestep, the velocity fields for MPAS-O and Omega match.
There is a significant increase in the error levels for Omega (convergence plots pending)
These are the results for the tracer fields with MPAS-O:



And these are the results with Omega:


