OOPS
test/interface/State.h
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2009-2016 ECMWF.
3  *
4  * This software is licensed under the terms of the Apache Licence Version 2.0
5  * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
6  * In applying this licence, ECMWF does not waive the privileges and immunities
7  * granted to it by virtue of its status as an intergovernmental organisation nor
8  * does it submit to any jurisdiction.
9  */
10 
11 #ifndef TEST_INTERFACE_STATE_H_
12 #define TEST_INTERFACE_STATE_H_
13 
14 #include <cmath>
15 #include <iostream>
16 #include <memory>
17 #include <string>
18 #include <vector>
19 
20 #define ECKIT_TESTING_SELF_REGISTER_CASES 0
21 
22 #include <boost/noncopyable.hpp>
23 
24 #include "eckit/config/LocalConfiguration.h"
25 #include "eckit/testing/Test.h"
26 #include "oops/base/Variables.h"
28 #include "oops/interface/State.h"
29 #include "oops/mpi/mpi.h"
30 #include "oops/runs/Test.h"
31 #include "oops/util/DateTime.h"
32 #include "oops/util/dot_product.h"
33 #include "oops/util/Logger.h"
34 #include "test/TestEnvironment.h"
35 
36 namespace test {
37 
38 // -----------------------------------------------------------------------------
39 
40 template <typename MODEL> class StateFixture : private boost::noncopyable {
43 
44  public:
45  static const eckit::Configuration & test() {return *getInstance().test_;}
46  static const Geometry_ & resol() {return *getInstance().resol_;}
47 
48  private:
50  static StateFixture<MODEL> theStateFixture;
51  return theStateFixture;
52  }
53 
55  test_.reset(new eckit::LocalConfiguration(TestEnvironment::config(), "state test"));
56 
57  const eckit::LocalConfiguration resolConfig(TestEnvironment::config(), "geometry");
58  resol_.reset(new Geometry_(resolConfig, oops::mpi::world()));
59  }
60 
61  ~StateFixture<MODEL>() {}
62 
63  std::unique_ptr<const eckit::LocalConfiguration> test_;
64  std::unique_ptr<Geometry_> resol_;
65 };
66 
67 // -----------------------------------------------------------------------------
68 
69 template <typename MODEL> void testStateConstructors() {
70  typedef StateFixture<MODEL> Test_;
71  typedef oops::State<MODEL> State_;
72 
73  const double norm = Test_::test().getDouble("norm file");
74  const double tol = Test_::test().getDouble("tolerance");
75  const util::DateTime vt(Test_::test().getString("date"));
76 
77 // Test main constructor
78  const eckit::LocalConfiguration conf(Test_::test(), "statefile");
79  std::unique_ptr<State_> xx1(new State_(Test_::resol(), conf));
80 
81  EXPECT(xx1.get());
82  const double norm1 = xx1->norm();
83  EXPECT(oops::is_close(norm1, norm, tol));
84  EXPECT(xx1->validTime() == vt);
85 
86 // Test copy constructor
87  std::unique_ptr<State_> xx2(new State_(*xx1));
88  EXPECT(xx2.get());
89  EXPECT(oops::is_close(xx2->norm(), norm, tol));
90  EXPECT(xx2->validTime() == vt);
91 
92 // Destruct copy
93  xx2.reset();
94  EXPECT(!xx2.get());
95 
96 // Recompute initial norm to make sure nothing bad happened
97  const double norm2 = xx1->norm();
98  EXPECT(norm1 == norm2);
99 
100 // Test State(const Geometry_ &, const Variables &, const util::DateTime &) constructor
101  oops::Variables vars(xx1->variables());
102  State_ xx3(Test_::resol(), vars, vt);
103  EXPECT(xx3.norm() == 0);
104  EXPECT(xx3.validTime() == vt);
105  EXPECT(xx3.variables() == vars);
106 
107 // Test State(const Geometry_ &, const State &) constructor
108  State_ xx4(Test_::resol(), *xx1);
109  EXPECT(oops::is_close(xx4.norm(), norm, tol));
110  EXPECT(xx4.validTime() == vt);
111  EXPECT(xx4.variables() == xx1->variables());
112 
113 // Test explicit State(const State_ &); constructor
114 // needed for the 1dvar filter
115  State_ xx5(xx1->state());
116  EXPECT(xx5.variables() == xx1->variables());
117 }
118 
119 // -----------------------------------------------------------------------------
120 
121 /*! \brief Interpolation test
122  *
123  * \details **testStateAnalyticInitialCondition()** tests the creation of an
124  * analytic state for a given model. The conceptual steps are as follows:
125  *
126  * 1. Initialize the JEDI State object based on idealized analytic formulae
127  * 2. Compare norm to precomputed norm
128  *
129  * Relevant parameters in the **State* section of the config file include
130  *
131  * * **norm-gen** Normalization test for the generated State
132  *
133  * \date April, 2018: M. Miesch (JCSDA) adapted a preliminary version in the
134  * feature/interp branch
135  *
136  * \warning Since this model compares the interpolated state values to an exact analytic
137  * solution, it requires that the "analytic_init" option be implemented in the model and
138  * selected in the "state.state generate" section of the config file.
139  */
140 
141 template <typename MODEL> void testStateAnalyticInitialCondition() {
142  typedef StateFixture<MODEL> Test_;
143  typedef oops::State<MODEL> State_;
144 
145  // This creates a State object called xx based on information
146  // from the "geometry" and "state test.state generate" sections of
147  // the config file and checks its norm
148 
149  if (!Test_::test().has("state generate")) {
150  oops::Log::warning() << "Bypassing Analytical Initial Condition Test";
151  return;
152  }
153 
154  const eckit::LocalConfiguration confgen(Test_::test(), "state generate");
155  const State_ xx(Test_::resol(), confgen);
156  const double norm = Test_::test().getDouble("norm generated state");
157  const double tol = Test_::test().getDouble("tolerance");
158 
159  oops::Log::debug() << "xx.norm(): " << std::fixed << std::setprecision(8) << xx.norm()
160  << std::endl;
161  oops::Log::debug() << "norm: " << std::fixed << std::setprecision(8) << norm << std::endl;
162 
163  EXPECT(oops::is_close(xx.norm(), norm, tol));
164 }
165 
166 // -----------------------------------------------------------------------------
167 
168 /*! \brief Tests of zero and accumul
169  *
170  * \details testStateZeroAndAccumul tests the folllowing:
171  *
172  * 1. Call State.zero() and check the resulting norm is indeed zero.
173  * 2. Call State.accumul(), passing a multiplication factor and a second State
174  * as arguments, and verify that the resulting norm is as expected.
175  * Several multiplication factors are tested.
176  */
177 
178 template <typename MODEL> void testStateZeroAndAccumul() {
179  typedef StateFixture<MODEL> Test_;
180  typedef oops::State<MODEL> State_;
181 
182  const eckit::LocalConfiguration conf(Test_::test(), "statefile");
183  State_ xx(Test_::resol(), conf);
184  const double tol = Test_::test().getDouble("tolerance");
185 
186  // Set state xx to zero
187  xx.zero();
188  EXPECT(xx.norm() == 0.0);
189 
190  // Set state xx to various multiples of yy
191  const State_ yy(Test_::resol(), conf);
192  const std::vector<double> mults {3.0, 0.0, -3.0};
193  for (const auto & mult : mults) {
194  xx.zero();
195  xx.accumul(mult, yy);
196  EXPECT(oops::is_close(xx.norm(), std::abs(mult) * yy.norm(), tol));
197  }
198 
199  // Ensure that a non-zero state, when acted on with accumul, is not equal to the result
200  State_ zz(Test_::resol(), conf);
201  zz.accumul(3.0, yy);
202  EXPECT_NOT(oops::is_close(zz.norm(), yy.norm(), tol, oops::TestVerbosity::SILENT));
203 }
204 
205 /*! \brief validTime and updateTime tests
206  *
207  * \details **testStateDateTime()** tests the validTime and updateTime routines.
208  *
209  * This is performed by updating the initial state time in two ways:
210  * - two lots of one hour,
211  * - one lot of two hours.
212  *
213  * validTime is then used in the comparison of the two times obtained.
214  */
215 
216 template <typename MODEL> void testStateDateTime() {
217  typedef StateFixture<MODEL> Test_;
218  typedef oops::State<MODEL> State_;
219 
220  // Configuration to read initial state
221  const eckit::LocalConfiguration conf(Test_::test(), "statefile");
222  State_ xx(Test_::resol(), conf);
223 
224  // Update the time by two lots of one hour
225  const util::Duration onehour(3600);
226  xx.updateTime(onehour);
227  xx.updateTime(onehour);
228 
229  // Create another state
230  State_ yy(Test_::resol(), conf);
231 
232  // Update the time of the second state by two hours
233  const util::Duration twohours(7200);
234  yy.updateTime(twohours);
235 
236  EXPECT(xx.validTime() == yy.validTime());
237 
238  // Increment the first state's time again and check the times are now not equal
239  xx.updateTime(onehour);
240  EXPECT_NOT(xx.validTime() == yy.validTime());
241 }
242 
243 // -----------------------------------------------------------------------------
244 
245 /*! \brief Read and write tests
246  *
247  * \details **testStateReadWrite()** tests reading and writing model state files.
248  *
249  * The tests are as follows:
250  * 1. Read an input file and check it sets a state correctly.
251  * 2. Write an output file and read it in again, checking the states are the same.
252  */
253 
254 template <typename MODEL> void testStateReadWrite() {
255  typedef StateFixture<MODEL> Test_;
256  typedef oops::State<MODEL> State_;
257 
258  // Configuration to read initial state
259  const eckit::LocalConfiguration conf(Test_::test(), "statefile");
260  State_ xx(Test_::resol(), conf);
261  const double tol = Test_::test().getDouble("tolerance");
262 
263  // Determine initial state norm
264  const double norm = xx.norm();
265 
266  // Set state to zero
267  xx.zero();
268 
269  // Read input file
270  xx.read(conf);
271 
272  // Check norm has its initial value
273  EXPECT(xx.norm() == norm);
274 
275  if (Test_::test().has("statefileout")) {
276  // Modify state
277  const double mult = 2.0;
278  xx.accumul(mult, xx);
279 
280  // Determine modified state norm
281  const double normout = xx.norm();
282 
283  // Configuration to read and write output state
284  const eckit::LocalConfiguration confout(Test_::test(), "statefileout");
285 
286  // Write modified state to output file
287  xx.write(confout);
288 
289  // Read modifed state from output file
290  State_ yy(Test_::resol(), confout);
291 
292  // Check modified state norm has its expected value
293  EXPECT(oops::is_close(yy.norm(), normout, tol));
294 
295  // Check modified state norm is not equal to the initial norm
296  EXPECT_NOT(oops::is_close(norm, normout, tol, oops::TestVerbosity::SILENT));
297  }
298 }
299 
300 // -----------------------------------------------------------------------------
301 
302 template <typename MODEL>
303 class State : public oops::Test {
304  public:
305  State() {}
306  virtual ~State() {}
307  private:
308  std::string testid() const override {return "test::State<" + MODEL::name() + ">";}
309 
310  void register_tests() const override {
311  std::vector<eckit::testing::Test>& ts = eckit::testing::specification();
312 
313  ts.emplace_back(CASE("interface/State/testStateConstructors")
314  { testStateConstructors<MODEL>(); });
315  ts.emplace_back(CASE("interface/State/testStateAnalyticInitialCondition")
316  { testStateAnalyticInitialCondition<MODEL>(); });
317  ts.emplace_back(CASE("interface/State/testStateZeroAndAccumul")
318  { testStateZeroAndAccumul<MODEL>(); });
319  ts.emplace_back(CASE("interface/State/testStateDateTime")
320  { testStateDateTime<MODEL>(); });
321  ts.emplace_back(CASE("interface/State/testStateReadWrite")
322  { testStateReadWrite<MODEL>(); });
323  }
324 
325  void clear() const override {}
326 };
327 
328 // -----------------------------------------------------------------------------
329 
330 } // namespace test
331 
332 #endif // TEST_INTERFACE_STATE_H_
test::StateFixture::Geometry_
oops::Geometry< MODEL > Geometry_
Definition: test/interface/State.h:41
test::StateFixture::resol_
std::unique_ptr< Geometry_ > resol_
Definition: test/interface/State.h:64
test::StateFixture::State_
oops::State< MODEL > State_
Definition: test/interface/State.h:42
test::State::~State
virtual ~State()
Definition: test/interface/State.h:306
test::State::State
State()
Definition: test/interface/State.h:305
mpi.h
test::State::clear
void clear() const override
Definition: test/interface/State.h:325
test::StateFixture::getInstance
static StateFixture< MODEL > & getInstance()
Definition: test/interface/State.h:49
test::CASE
CASE("test_linearmodelparameterswrapper_valid_name")
Definition: LinearModelFactory.cc:22
test::State
Definition: test/interface/State.h:303
test
Definition: LinearModelFactory.cc:20
test::testStateZeroAndAccumul
void testStateZeroAndAccumul()
Tests of zero and accumul.
Definition: test/interface/State.h:178
test::StateFixture
Definition: test/interface/State.h:40
test::testStateDateTime
void testStateDateTime()
validTime and updateTime tests
Definition: test/interface/State.h:216
test::testStateAnalyticInitialCondition
void testStateAnalyticInitialCondition()
Interpolation test.
Definition: test/interface/State.h:141
Test.h
test::TestEnvironment::config
static const eckit::Configuration & config()
Definition: TestEnvironment.h:40
test::testStateConstructors
void testStateConstructors()
Definition: test/interface/State.h:69
oops::Geometry
Geometry class used in oops; subclass of interface class above.
Definition: oops/interface/Geometry.h:189
test::testStateReadWrite
void testStateReadWrite()
Read and write tests.
Definition: test/interface/State.h:254
TestEnvironment.h
oops_variables_mod::has
logical function has(this, var)
Definition: variables_mod.F90:140
oops::State
Encapsulates the model state.
Definition: CostJbState.h:28
oops::Test
Definition: Test.h:39
State.h
test::StateFixture::test
static const eckit::Configuration & test()
Definition: test/interface/State.h:45
oops::mpi::world
const eckit::mpi::Comm & world()
Default communicator with all MPI tasks (ie MPI_COMM_WORLD)
Definition: oops/mpi/mpi.cc:22
oops::Variables
Definition: oops/base/Variables.h:23
test::StateFixture::resol
static const Geometry_ & resol()
Definition: test/interface/State.h:46
test::State::register_tests
void register_tests() const override
Definition: test/interface/State.h:310
Variables.h
test::StateFixture::test_
std::unique_ptr< const eckit::LocalConfiguration > test_
Definition: test/interface/State.h:63
test::State::testid
std::string testid() const override
Definition: test/interface/State.h:308
Geometry.h