FV3 Bundle
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 <string>
17 #include <vector>
18 
19 #define BOOST_TEST_NO_MAIN
20 #define BOOST_TEST_ALTERNATIVE_INIT_API
21 #define BOOST_TEST_DYN_LINK
22 
23 #include <boost/test/unit_test.hpp>
24 
25 #include <boost/noncopyable.hpp>
26 #include <boost/scoped_ptr.hpp>
27 
28 #include "eckit/config/LocalConfiguration.h"
29 #include "oops/base/Variables.h"
31 #include "oops/interface/State.h"
32 #include "oops/runs/Test.h"
33 #include "oops/util/DateTime.h"
34 #include "oops/util/dot_product.h"
35 #include "oops/util/Logger.h"
36 #include "test/TestEnvironment.h"
37 
38 namespace test {
39 
40 // -----------------------------------------------------------------------------
41 
42 template <typename MODEL> class StateFixture : private boost::noncopyable {
45 
46  public:
47  static const eckit::Configuration & test() {return *getInstance().test_;}
48  static const Geometry_ & resol() {return *getInstance().resol_;}
49 
50  private:
52  static StateFixture<MODEL> theStateFixture;
53  return theStateFixture;
54  }
55 
57  test_.reset(new eckit::LocalConfiguration(TestEnvironment::config(), "StateTest"));
58 
59  const eckit::LocalConfiguration resolConfig(TestEnvironment::config(), "Geometry");
60  resol_.reset(new Geometry_(resolConfig));
61  }
62 
64 
65  boost::scoped_ptr<const eckit::LocalConfiguration> test_;
66  boost::scoped_ptr<Geometry_> resol_;
67 };
68 
69 // -----------------------------------------------------------------------------
70 
71 template <typename MODEL> void testStateConstructors() {
72  typedef StateFixture<MODEL> Test_;
73  typedef oops::State<MODEL> State_;
74 
75  const double norm = Test_::test().getDouble("norm-file");
76  const double tol = Test_::test().getDouble("tolerance");
77  const util::DateTime vt(Test_::test().getString("date"));
78 
79 // Test main constructor
80  const eckit::LocalConfiguration conf(Test_::test(), "StateFile");
81  const oops::Variables vars(conf);
82  boost::scoped_ptr<State_> xx1(new State_(Test_::resol(), vars, conf));
83 
84  BOOST_CHECK(xx1.get());
85  const double norm1 = xx1->norm();
86  BOOST_CHECK_CLOSE(norm1, norm, tol);
87  BOOST_CHECK_EQUAL(xx1->validTime(), vt);
88 
89 // Test copy constructor
90  boost::scoped_ptr<State_> xx2(new State_(*xx1));
91  BOOST_CHECK(xx2.get());
92  BOOST_CHECK_CLOSE(xx2->norm(), norm, tol);
93  BOOST_CHECK_EQUAL(xx2->validTime(), vt);
94 
95 // Destruct copy
96  xx2.reset();
97  BOOST_CHECK(!xx2.get());
98 
99 // Recompute initial norm to make sure nothing bad happened
100  const double norm2 = xx1->norm();
101  BOOST_CHECK_EQUAL(norm1, norm2);
102 }
103 
104 // -----------------------------------------------------------------------------
105 /*! \brief Interpolation test
106  *
107  * \details **testStateInterpolation()** tests the interpolation for a given
108  * model. The conceptual steps are as follows:
109  * 1. Initialize the JEDI State object based on idealized analytic formulae
110  * 2. Interpolate the State variables onto selected "observation" locations
111  * using the getValues() method of the State object. The result is
112  * placed in a JEDI GeoVaLs object
113  * 3. Compute the correct solution by applying the analytic formulae directly
114  * at the observation locations.
115  * 4. Assess the accuracy of the interpolation by comparing the interpolated
116  * values from Step 2 with the exact values from Step 3
117  *
118  * The interpolated state values are compared to the analytic solution for
119  * a series of **locations** which includes values optionally specified by the
120  * user in the "StateTest" section of the config file in addition to a
121  * randomly-generated list of **Nrandom** random locations. Nrandom is also
122  * specified by the user in the "StateTest" section of the config file, as is the
123  * (nondimensional) tolerence level (**interp_tolerance**) to be used for the tests.
124  *
125  * Relevant parameters in the **State* section of the config file include
126  *
127  * * **norm-gen** Normalization test for the generated State
128  * * **interp_tolerance** tolerance for the interpolation test
129  *
130  * \date April, 2018: M. Miesch (JCSDA) adapted a preliminary version in the
131  * feature/interp branch
132  *
133  * \warning Since this model compares the interpolated state values to an exact analytic
134  * solution, it requires that the "analytic_init" option be implemented in the model and
135  * selected in the "State.StateGenerate" section of the config file.
136  */
137 
138 template <typename MODEL> void testStateInterpolation() {
139  typedef StateFixture<MODEL> Test_;
140  typedef oops::State<MODEL> State_;
141  typedef oops::Locations<MODEL> Locations_;
142  typedef oops::GeoVaLs<MODEL> GeoVaLs_;
143 
144  // This creates a State object called xx based on information
145  // from the "Geometry" and "StateTest.StateGenerate" sections of
146  // the config file and checks its norm
147 
148  const eckit::LocalConfiguration confgen(Test_::test(), "StateGenerate");
149  const oops::Variables statevars(confgen);
150  const State_ xx(Test_::resol(), statevars, confgen);
151  const double norm = Test_::test().getDouble("norm-gen");
152  const double tol = Test_::test().getDouble("tolerance");
153  BOOST_CHECK_CLOSE(xx.norm(), norm, tol);
154 
155  // If the analytic initial conditions are not yet implemented
156  // in the model, then bypass the interpolation test for now
157  if (!confgen.has("analytic_init")) {
158  oops::Log::warning() << "Bypassing Interpolation Test";
159  return;
160  }
161 
162  // Now extract the user-defined locations from the "StateTest.Locations"
163  // section of the config file and use it to define a Locations object
164  // The user can optionally also request Nrandom random locations
165  const eckit::LocalConfiguration confloc(Test_::test(), "Locations");
166  const Locations_ locs(confloc);
167 
168  // Extract the user-defined list of variables to interpolate,
169  // from the "StateTest.InterpTest" section of the config file, and
170  // use this to define a Variables object
171  const eckit::LocalConfiguration confvar(Test_::test(), "InterpTest");
172  const oops::Variables vars(confvar);
173 
174  // Now create a GeoVaLs object from locs and vars
175  GeoVaLs_ gval(locs, vars);
176 
177  // ...and execute the interpolation
178  xx.getValues(locs, vars, gval);
179 
180  // Now create another GeoVaLs object that contains the exact
181  // analytic solutions.
182  GeoVaLs_ ref(gval);
183  ref.analytic_init(locs, confgen);
184 
185  // Compute the difference between the interpolated and exact values
186  gval -= ref;
187 
188  // Compute the normalized error
189  gval.abs();
190  gval /= ref;
191 
192  // And check to see if the errors are within specified tolerance
193  double interp_tol = Test_::test().getDouble("interp_tolerance");
194  BOOST_CHECK_SMALL(gval.norm(), interp_tol);
195 
196  // Each MODEL should define an appropriate GeoVaLs print() method that
197  // writes information about the GeoVaLs object to the oops::Log::debug()
198  // output stream in order to help with debugging in the event that the
199  // interpolation test does not pass. It is helpful if this print()
200  // method at least prints out the location and variable where the
201  // error is largest.
202 
203  oops::Log::debug() << "TestStateInterpolation() Normalized Error: "
204  << std::endl << gval << std::endl;
205 
206  // Also write the locations used for the test to the debug output stream
207  oops::Log::debug() << "TestStateInterpolation() Locations: "
208  << std::endl << locs << std::endl;
209 }
210 
211 // -----------------------------------------------------------------------------
212 
213 template <typename MODEL> class State : public oops::Test {
214  public:
215  State() {}
216  virtual ~State() {}
217  private:
218  std::string testid() const {return "test::State<" + MODEL::name() + ">";}
219 
220  void register_tests() const {
221  boost::unit_test::test_suite * ts = BOOST_TEST_SUITE("interface/State");
222 
223  ts->add(BOOST_TEST_CASE(&testStateConstructors<MODEL>));
224  ts->add(BOOST_TEST_CASE(&testStateInterpolation<MODEL>));
225 
226  boost::unit_test::framework::master_test_suite().add(ts);
227  }
228 };
229 
230 // -----------------------------------------------------------------------------
231 
232 } // namespace test
233 
234 #endif // TEST_INTERFACE_STATE_H_
integer, parameter, public warning
boost::scoped_ptr< const eckit::LocalConfiguration > test_
Definition: conf.py:1
std::string testid() const
Encapsulates the model state.
static const eckit::Configuration & config()
program test
character(len=32) name
static const eckit::Configuration & test()
logical debug
Definition: mpp.F90:1297
real, dimension(:,:,:), allocatable vt
static const Geometry_ & resol()
void testStateConstructors()
void testStateInterpolation()
Interpolation test.
oops::State< MODEL > State_
oops::Geometry< MODEL > Geometry_
static StateFixture< MODEL > & getInstance()
void register_tests() const
boost::scoped_ptr< Geometry_ > resol_