MultiDomainFunctionTest.h 17.8 KB
Newer Older
1
2
3
4
5
6
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
//     NScD Oak Ridge National Laboratory, European Spallation Source
//     & Institut Laue - Langevin
// SPDX - License - Identifier: GPL - 3.0 +
7
8
9
10
#ifndef MULTIDOMAINFUNCTIONTEST_H_
#define MULTIDOMAINFUNCTIONTEST_H_

#include "MantidAPI/FunctionDomain1D.h"
LamarMoore's avatar
LamarMoore committed
11
#include "MantidAPI/FunctionFactory.h"
12
13
#include "MantidAPI/FunctionValues.h"
#include "MantidAPI/IFunction1D.h"
LamarMoore's avatar
LamarMoore committed
14
15
#include "MantidAPI/JointDomain.h"
#include "MantidAPI/MultiDomainFunction.h"
16
17
18
#include "MantidAPI/ParamFunction.h"

#include <algorithm>
LamarMoore's avatar
LamarMoore committed
19
20
#include <boost/make_shared.hpp>
#include <cxxtest/TestSuite.h>
21
22
23
24

using namespace Mantid;
using namespace Mantid::API;

25
26
class MultiDomainFunctionTest_Function : public IFunction1D,
                                         public ParamFunction {
27
public:
28
29
30
  MultiDomainFunctionTest_Function() : IFunction1D(), ParamFunction() {
    this->declareParameter("A", 0);
    this->declareParameter("B", 0);
31
  }
32
  std::string name() const override {
33
34
35
    return "MultiDomainFunctionTest_Function";
  }

36
protected:
37
38
  void function1D(double *out, const double *xValues,
                  const size_t nData) const override {
39
40
41
    const double A = getParameter(0);
    const double B = getParameter(1);

42
    for (size_t i = 0; i < nData; ++i) {
43
44
45
46
      double x = xValues[i];
      out[i] = A + B * x;
    }
  }
47
48
  void functionDeriv1D(Jacobian *out, const double *xValues,
                       const size_t nData) override {
49
    for (size_t i = 0; i < nData; ++i) {
50
      double x = xValues[i];
51
52
      out->set(i, 0, x);
      out->set(i, 1, 1.0);
53
54
55
56
    }
  }
};

57
DECLARE_FUNCTION(MultiDomainFunctionTest_Function)
58

59
60
namespace {

61
class JacobianToTestNumDeriv : public Jacobian {
62
63
  size_t n[3];
  size_t np;
64

65
66
public:
  double off_diag;
67
  JacobianToTestNumDeriv() : np(2), off_diag(0.0) {
68
    // sizes of the three domains
69
    n[0] = 9;
70
71
72
    n[1] = 10;
    n[2] = 11;
  }
73
  void set(size_t iY, size_t iP, double value) override {
74
75
76
    // domain index the data point #iY comes from
    size_t jY = 2;
    size_t size = 0;
77
    for (size_t k = 0; k < 3; ++k) {
78
      size += n[k];
79
      if (iY < size) {
80
81
82
83
84
85
        jY = k;
        break;
      }
    }
    // domain index of function that has parameter #iP
    auto jP = iP / np;
86
87
    if (jY != jP)
      off_diag += value;
88
  }
89
  double get(size_t, size_t) override { return 0.0; }
90
  void zero() override {}
91
};
LamarMoore's avatar
LamarMoore committed
92
} // namespace
93

94
class MultiDomainFunctionTest : public CxxTest::TestSuite {
95
public:
96
97
  // This pair of boilerplate methods prevent the suite being created statically
  // This means the constructor isn't called when running other tests
98
99
100
101
  static MultiDomainFunctionTest *createSuite() {
    return new MultiDomainFunctionTest();
  }
  static void destroySuite(MultiDomainFunctionTest *suite) { delete suite; }
102

103
  MultiDomainFunctionTest() {
104
105
106
107
    multi.addFunction(boost::make_shared<MultiDomainFunctionTest_Function>());
    multi.addFunction(boost::make_shared<MultiDomainFunctionTest_Function>());
    multi.addFunction(boost::make_shared<MultiDomainFunctionTest_Function>());

108
109
    multi.getFunction(0)->setParameter("A", 0);
    multi.getFunction(0)->setParameter("B", 1);
110

111
112
    multi.getFunction(1)->setParameter("A", 1);
    multi.getFunction(1)->setParameter("B", 2);
113

114
115
    multi.getFunction(2)->setParameter("A", 2);
    multi.getFunction(2)->setParameter("B", 3);
116

117
118
119
    domain.addDomain(boost::make_shared<FunctionDomain1DVector>(0, 1, 9));
    domain.addDomain(boost::make_shared<FunctionDomain1DVector>(1, 2, 10));
    domain.addDomain(boost::make_shared<FunctionDomain1DVector>(2, 3, 11));
120
121
  }

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
  void test_calc_domain0_only() {
    multi.setDomainIndex(0, 0);
    multi.setDomainIndices(1, std::vector<size_t>());
    multi.setDomainIndices(2, std::vector<size_t>());
    // multi.setDomainIndex(1,1);
    // multi.setDomainIndex(2,2);

    // std::vector<size_t> ii;
    // ii.push_back(0);
    // ii.push_back(1);
    // multi.setDomainIndices(1,ii);
    // ii.clear();
    // ii.push_back(0);
    // ii.push_back(2);
    // multi.setDomainIndices(2,ii);
137
138

    FunctionValues values(domain);
139
    multi.function(domain, values);
140
141
142

    const double A = multi.getFunction(0)->getParameter("A");
    const double B = multi.getFunction(0)->getParameter("B");
143
144
145
    const FunctionDomain1D &d =
        static_cast<const FunctionDomain1D &>(domain.getDomain(0));
    for (size_t i = 0; i < 9; ++i) {
146
147
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d[i]);
    }
148
    for (size_t i = 9; i < 19; ++i) {
149
150
      TS_ASSERT_EQUALS(values.getCalculated(i), 0);
    }
151
    for (size_t i = 19; i < 30; ++i) {
152
153
154
155
      TS_ASSERT_EQUALS(values.getCalculated(i), 0);
    }
  }

156
157
158
159
  void test_calc_domain1_only() {
    multi.setDomainIndex(0, 1);
    multi.setDomainIndices(1, std::vector<size_t>());
    multi.setDomainIndices(2, std::vector<size_t>());
160
161

    FunctionValues values(domain);
162
    multi.function(domain, values);
163
164
165

    const double A = multi.getFunction(0)->getParameter("A");
    const double B = multi.getFunction(0)->getParameter("B");
166
167
168
    const FunctionDomain1D &d =
        static_cast<const FunctionDomain1D &>(domain.getDomain(1));
    for (size_t i = 0; i < 9; ++i) {
169
170
      TS_ASSERT_EQUALS(values.getCalculated(i), 0);
    }
171
172
    for (size_t i = 9; i < 19; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d[i - 9]);
173
    }
174
    for (size_t i = 19; i < 30; ++i) {
175
176
177
178
      TS_ASSERT_EQUALS(values.getCalculated(i), 0);
    }
  }

179
180
181
182
  void test_calc_domain2_only() {
    multi.setDomainIndex(0, 2);
    multi.setDomainIndices(1, std::vector<size_t>());
    multi.setDomainIndices(2, std::vector<size_t>());
183
184

    FunctionValues values(domain);
185
    multi.function(domain, values);
186
187
188

    const double A = multi.getFunction(0)->getParameter("A");
    const double B = multi.getFunction(0)->getParameter("B");
189
190
191
    const FunctionDomain1D &d =
        static_cast<const FunctionDomain1D &>(domain.getDomain(2));
    for (size_t i = 0; i < 9; ++i) {
192
193
      TS_ASSERT_EQUALS(values.getCalculated(i), 0);
    }
194
    for (size_t i = 9; i < 19; ++i) {
195
196
      TS_ASSERT_EQUALS(values.getCalculated(i), 0);
    }
197
198
    for (size_t i = 19; i < 30; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d[i - 19]);
199
200
201
    }
  }

202
  void test_calc_all_domains() {
203
    multi.clearDomainIndices();
204
205
    multi.setDomainIndices(1, std::vector<size_t>());
    multi.setDomainIndices(2, std::vector<size_t>());
206
207

    FunctionValues values(domain);
208
    multi.function(domain, values);
209
210
211

    const double A = multi.getFunction(0)->getParameter("A");
    const double B = multi.getFunction(0)->getParameter("B");
212
213
214
    const FunctionDomain1D &d0 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(0));
    for (size_t i = 0; i < 9; ++i) {
215
216
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d0[i]);
    }
217
218
219
220
    const FunctionDomain1D &d1 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(1));
    for (size_t i = 9; i < 19; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d1[i - 9]);
221
    }
222
223
224
225
    const FunctionDomain1D &d2 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(2));
    for (size_t i = 19; i < 30; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d2[i - 19]);
226
227
228
    }
  }

229
230
231
  void test_set_wrong_index() {
    multi.setDomainIndices(1, std::vector<size_t>());
    multi.setDomainIndices(2, std::vector<size_t>());
232
233
234

    FunctionValues values(domain);

235
    multi.setDomainIndex(0, 3);
Samuel Jones's avatar
Samuel Jones committed
236
237
    TS_ASSERT_THROWS(multi.function(domain, values),
                     const std::invalid_argument &);
238

239
    multi.setDomainIndex(0, 4);
Samuel Jones's avatar
Samuel Jones committed
240
241
    TS_ASSERT_THROWS(multi.function(domain, values),
                     const std::invalid_argument &);
242
243
  }

244
245
  void test_calc() {
    multi.setDomainIndex(0, 0);
246
247
248
    std::vector<size_t> ii;
    ii.push_back(0);
    ii.push_back(1);
249
    multi.setDomainIndices(1, ii);
250
251
252
    ii.clear();
    ii.push_back(0);
    ii.push_back(2);
253
    multi.setDomainIndices(2, ii);
254
255

    FunctionValues values(domain);
256
257
258
259
260
261
262
263
264
265
266
    multi.function(domain, values);

    double A = multi.getFunction(0)->getParameter("A") +
               multi.getFunction(1)->getParameter("A") +
               multi.getFunction(2)->getParameter("A");
    double B = multi.getFunction(0)->getParameter("B") +
               multi.getFunction(1)->getParameter("B") +
               multi.getFunction(2)->getParameter("B");
    const FunctionDomain1D &d0 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(0));
    for (size_t i = 0; i < 9; ++i) {
267
268
269
270
271
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d0[i]);
    }

    A = multi.getFunction(1)->getParameter("A");
    B = multi.getFunction(1)->getParameter("B");
272
273
274
275
    const FunctionDomain1D &d1 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(1));
    for (size_t i = 9; i < 19; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d1[i - 9]);
276
277
278
279
    }

    A = multi.getFunction(2)->getParameter("A");
    B = multi.getFunction(2)->getParameter("B");
280
281
282
283
    const FunctionDomain1D &d2 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(2));
    for (size_t i = 19; i < 30; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d2[i - 19]);
284
285
286
    }
  }

287
  void test_attribute() {
288
    multi.clearDomainIndices();
289
290
291
    multi.setLocalAttributeValue(0, "domains", "i");
    multi.setLocalAttributeValue(1, "domains", "0,1");
    multi.setLocalAttributeValue(2, "domains", "0,2");
292
293

    FunctionValues values(domain);
294
295
296
297
298
299
300
301
302
303
304
    multi.function(domain, values);

    double A = multi.getFunction(0)->getParameter("A") +
               multi.getFunction(1)->getParameter("A") +
               multi.getFunction(2)->getParameter("A");
    double B = multi.getFunction(0)->getParameter("B") +
               multi.getFunction(1)->getParameter("B") +
               multi.getFunction(2)->getParameter("B");
    const FunctionDomain1D &d0 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(0));
    for (size_t i = 0; i < 9; ++i) {
305
306
307
308
309
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d0[i]);
    }

    A = multi.getFunction(1)->getParameter("A");
    B = multi.getFunction(1)->getParameter("B");
310
311
312
313
    const FunctionDomain1D &d1 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(1));
    for (size_t i = 9; i < 19; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d1[i - 9]);
314
315
316
317
    }

    A = multi.getFunction(2)->getParameter("A");
    B = multi.getFunction(2)->getParameter("B");
318
319
320
321
    const FunctionDomain1D &d2 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(2));
    for (size_t i = 19; i < 30; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d2[i - 19]);
322
323
324
    }
  }

325
  void test_attribute_domain_range() {
326
    multi.clearDomainIndices();
327
    multi.setLocalAttributeValue(0, "domains", "0-2");
328
    return;
329
330
    multi.setLocalAttributeValue(1, "domains", "i");
    multi.setLocalAttributeValue(2, "domains", "i");
331
332

    FunctionValues values(domain);
333
    multi.function(domain, values);
334
335
336

    double A = multi.getFunction(0)->getParameter("A");
    double B = multi.getFunction(0)->getParameter("B");
337
338
339
    const FunctionDomain1D &d0 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(0));
    for (size_t i = 0; i < 9; ++i) {
340
341
342
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d0[i]);
    }

343
344
345
346
347
348
349
350
    A = multi.getFunction(0)->getParameter("A") +
        multi.getFunction(1)->getParameter("A");
    B = multi.getFunction(0)->getParameter("B") +
        multi.getFunction(1)->getParameter("B");
    const FunctionDomain1D &d1 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(1));
    for (size_t i = 9; i < 19; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d1[i - 9]);
351
352
    }

353
354
355
356
357
358
359
360
    A = multi.getFunction(0)->getParameter("A") +
        multi.getFunction(2)->getParameter("A");
    B = multi.getFunction(0)->getParameter("B") +
        multi.getFunction(2)->getParameter("B");
    const FunctionDomain1D &d2 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(2));
    for (size_t i = 19; i < 30; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d2[i - 19]);
361
362
363
    }
  }

364
365
366
367
368
369
370
371
  void test_attribute_in_FunctionFactory() {
    std::string ini =
        "composite=MultiDomainFunction;"
        "name=MultiDomainFunctionTest_Function,A=0,B=1,$domains=i;"
        "name=MultiDomainFunctionTest_Function,A=1,B=2,$domains=(0,1);"
        "name=MultiDomainFunctionTest_Function,A=2,B=3,$domains=(0,2)";
    auto mfun = boost::dynamic_pointer_cast<CompositeFunction>(
        FunctionFactory::Instance().createInitialized(ini));
372
373

    FunctionValues values(domain);
374
375
376
377
378
379
380
381
382
383
    mfun->function(domain, values);

    double A = mfun->getFunction(0)->getParameter("A") +
               mfun->getFunction(1)->getParameter("A") +
               mfun->getFunction(2)->getParameter("A");
    double B = mfun->getFunction(0)->getParameter("B") +
               mfun->getFunction(1)->getParameter("B") +
               mfun->getFunction(2)->getParameter("B");
    const FunctionDomain1D &d0 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(0));
384
    double checksum = 0;
385
    for (size_t i = 0; i < 9; ++i) {
386
387
388
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d0[i]);
      checksum += values.getCalculated(i);
    }
389
    TS_ASSERT_DIFFERS(checksum, 0);
390
391
392
393

    checksum = 0;
    A = mfun->getFunction(1)->getParameter("A");
    B = mfun->getFunction(1)->getParameter("B");
394
395
396
397
    const FunctionDomain1D &d1 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(1));
    for (size_t i = 9; i < 19; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d1[i - 9]);
398
399
      checksum += values.getCalculated(i);
    }
400
    TS_ASSERT_DIFFERS(checksum, 0);
401
402
403
404

    checksum = 0;
    A = mfun->getFunction(2)->getParameter("A");
    B = mfun->getFunction(2)->getParameter("B");
405
406
407
408
    const FunctionDomain1D &d2 =
        static_cast<const FunctionDomain1D &>(domain.getDomain(2));
    for (size_t i = 19; i < 30; ++i) {
      TS_ASSERT_EQUALS(values.getCalculated(i), A + B * d2[i - 19]);
409
410
      checksum += values.getCalculated(i);
    }
411
    TS_ASSERT_DIFFERS(checksum, 0);
412
413
  }

414
  void test_derivatives_for_tied_parameters() {
415
    multi.clearDomainIndices();
416
417
418
    multi.setDomainIndex(0, 0);
    multi.setDomainIndex(1, 1);
    multi.setDomainIndex(2, 2);
419
420
    {
      JacobianToTestNumDeriv jacobian;
421
422
      multi.functionDeriv(domain, jacobian);
      TS_ASSERT_EQUALS(jacobian.off_diag, 0.0);
423
    }
424
    multi.setAttributeValue("NumDeriv", true);
425
426
    {
      JacobianToTestNumDeriv jacobian;
427
428
      multi.functionDeriv(domain, jacobian);
      TS_ASSERT_EQUALS(jacobian.off_diag, 0.0);
429
    }
430
431
432
    multi.tie("f1.A", "f0.A");
    multi.tie("f2.A", "f0.A");
    multi.setAttributeValue("NumDeriv", false);
433
434
    {
      JacobianToTestNumDeriv jacobian;
435
436
      multi.functionDeriv(domain, jacobian);
      TS_ASSERT_EQUALS(jacobian.off_diag, 0.0);
437
    }
438
    multi.setAttributeValue("NumDeriv", true);
439
440
    {
      JacobianToTestNumDeriv jacobian;
441
442
      multi.functionDeriv(domain, jacobian);
      TS_ASSERT_DIFFERS(jacobian.off_diag, 0.0);
443
444
445
    }
  }

446
447
448
449
450
451
  void test_clone_preserves_domains() {
    const auto copy = multi.clone();
    TS_ASSERT_EQUALS(copy->getNumberDomains(), multi.getNumberDomains());
  }

  void test_string_representation() {
Roman Tolchenov's avatar
Roman Tolchenov committed
452
453
454
455
456
457
    const std::string expected = "composite=MultiDomainFunction,NumDeriv=true;"
                                 "name=MultiDomainFunctionTest_Function,A=0,B="
                                 "1,$domains=i;name=MultiDomainFunctionTest_"
                                 "Function,A=0,B=2,$domains=i;name="
                                 "MultiDomainFunctionTest_Function,A=0,B=3,$"
                                 "domains=i;ties=(f1.A=f0.A,f2.A=f0.A)";
458
459
460
461
    TS_ASSERT_EQUALS(multi.asString(), expected);
    TS_ASSERT_EQUALS(multi.asString(), multi.clone()->asString());
  }

Roman Tolchenov's avatar
Roman Tolchenov committed
462
463
464
465
466
467
468
469
470
471
472
  void test_equivalent_functions() {
    std::string ini =
        "composite=MultiDomainFunction;"
        "name=MultiDomainFunctionTest_Function,A=1,B=2,$domains=i;"
        "name=MultiDomainFunctionTest_Function,A=3,B=4,$domains=i;ties=(f1.A="
        "f0.B)";
    auto mfun = boost::dynamic_pointer_cast<CompositeFunction>(
        FunctionFactory::Instance().createInitialized(ini));

    auto eqFuns = mfun->createEquivalentFunctions();
    TS_ASSERT_EQUALS(eqFuns.size(), 2);
473
474
475
476
    TS_ASSERT_EQUALS(eqFuns[0]->asString(),
                     "name=MultiDomainFunctionTest_Function,A=1,B=2");
    TS_ASSERT_EQUALS(eqFuns[1]->asString(),
                     "name=MultiDomainFunctionTest_Function,A=2,B=4");
Roman Tolchenov's avatar
Roman Tolchenov committed
477
478
  }

479
480
481
482
483
484
485
486
487
488
  void test_string_nested_composite() {
    std::string ini = "composite=MultiDomainFunction;"
                      "(composite=CompositeFunction,$domains=i;"
                      "name=MultiDomainFunctionTest_Function; "
                      "(name=MultiDomainFunctionTest_Function;name="
                      "MultiDomainFunctionTest_Function));"
                      "(composite=CompositeFunction,$domains=i;"
                      "name=MultiDomainFunctionTest_Function; "
                      "(name=MultiDomainFunctionTest_Function;name="
                      "MultiDomainFunctionTest_Function))";
Roman Tolchenov's avatar
Roman Tolchenov committed
489
490
    auto f = FunctionFactory::Instance().createInitialized(ini);
    auto g = FunctionFactory::Instance().createInitialized(f->asString());
491
    TS_ASSERT_EQUALS(g->asString(),
Roman Tolchenov's avatar
Roman Tolchenov committed
492
                     "composite=MultiDomainFunction,NumDeriv=true;(composite="
493
494
495
496
497
498
499
500
501
502
                     "CompositeFunction,NumDeriv=false,$domains=i;name="
                     "MultiDomainFunctionTest_Function,A=0,B=0;(name="
                     "MultiDomainFunctionTest_Function,A=0,B=0;name="
                     "MultiDomainFunctionTest_Function,A=0,B=0));(composite="
                     "CompositeFunction,NumDeriv=false,$domains=i;name="
                     "MultiDomainFunctionTest_Function,A=0,B=0;(name="
                     "MultiDomainFunctionTest_Function,A=0,B=0;name="
                     "MultiDomainFunctionTest_Function,A=0,B=0))");
  }

503
504
505
506
507
508
private:
  MultiDomainFunction multi;
  JointDomain domain;
};

#endif /*MULTIDOMAINFUNCTIONTEST_H_*/