1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #pragma once
6
7 #include <armnn/Tensor.hpp>
8 #include <armnn/utility/Assert.hpp>
9 #include <armnnUtils/FloatingPointComparison.hpp>
10
11 #include <QuantizeHelper.hpp>
12
13 #include <boost/multi_array.hpp>
14 #include <boost/random/uniform_real_distribution.hpp>
15 #include <boost/random/mersenne_twister.hpp>
16 #include <boost/test/unit_test.hpp>
17
18 #include <array>
19 #include <cmath>
20 #include <vector>
21
22 constexpr float g_FloatCloseToZeroTolerance = 1.0e-6f;
23
24 template<typename T, bool isQuantized = true>
25 struct SelectiveComparer
26 {
CompareSelectiveComparer27 static bool Compare(T a, T b)
28 {
29 return (std::max(a, b) - std::min(a, b)) <= 1;
30 }
31
32 };
33
34 template<typename T>
35 struct SelectiveComparer<T, false>
36 {
CompareSelectiveComparer37 static bool Compare(T a, T b)
38 {
39 // If a or b is zero, percent_tolerance does an exact match, so compare to a small, constant tolerance instead.
40 if (a == 0.0f || b == 0.0f)
41 {
42 return std::abs(a - b) <= g_FloatCloseToZeroTolerance;
43 }
44
45 if (std::isinf(a) && a == b)
46 {
47 return true;
48 }
49
50 if (std::isnan(a) && std::isnan(b))
51 {
52 return true;
53 }
54
55 // For unquantized floats we use a tolerance of 1%.
56 return armnnUtils::within_percentage_tolerance(a, b);
57 }
58 };
59
60 template<typename T>
SelectiveCompare(T a,T b)61 bool SelectiveCompare(T a, T b)
62 {
63 return SelectiveComparer<T, armnn::IsQuantizedType<T>()>::Compare(a, b);
64 };
65
66 template<typename T>
SelectiveCompareBoolean(T a,T b)67 bool SelectiveCompareBoolean(T a, T b)
68 {
69 return (((a == 0) && (b == 0)) || ((a != 0) && (b != 0)));
70 };
71
72 template <typename T, std::size_t n>
CompareTensors(const boost::multi_array<T,n> & a,const boost::multi_array<T,n> & b,bool compareBoolean=false,bool isDynamic=false)73 boost::test_tools::predicate_result CompareTensors(const boost::multi_array<T, n>& a,
74 const boost::multi_array<T, n>& b,
75 bool compareBoolean = false,
76 bool isDynamic = false)
77 {
78 if (!isDynamic)
79 {
80 // Checks they are same shape.
81 for (unsigned int i = 0;
82 i < n;
83 i++)
84 {
85 if (a.shape()[i] != b.shape()[i])
86 {
87 boost::test_tools::predicate_result res(false);
88 res.message() << "Different shapes ["
89 << a.shape()[i]
90 << "!="
91 << b.shape()[i]
92 << "]";
93 return res;
94 }
95 }
96 }
97
98 // Now compares element-wise.
99
100 // Fun iteration over n dimensions.
101 std::array<unsigned int, n> indices;
102 for (unsigned int i = 0; i < n; i++)
103 {
104 indices[i] = 0;
105 }
106
107 std::stringstream errorString;
108 int numFailedElements = 0;
109 constexpr int maxReportedDifferences = 3;
110
111 while (true)
112 {
113 bool comparison;
114 // As true for uint8_t is non-zero (1-255) we must have a dedicated compare for Booleans.
115 if(compareBoolean)
116 {
117 comparison = SelectiveCompareBoolean(a(indices), b(indices));
118 }
119 else
120 {
121 comparison = SelectiveCompare(a(indices), b(indices));
122 }
123
124 if (!comparison)
125 {
126 ++numFailedElements;
127
128 if (numFailedElements <= maxReportedDifferences)
129 {
130 if (numFailedElements >= 2)
131 {
132 errorString << ", ";
133 }
134 errorString << "[";
135 for (unsigned int i = 0; i < n; ++i)
136 {
137 errorString << indices[i];
138 if (i != n - 1)
139 {
140 errorString << ",";
141 }
142 }
143 errorString << "]";
144
145 errorString << " (" << +a(indices) << " != " << +b(indices) << ")";
146 }
147 }
148
149 ++indices[n - 1];
150 for (unsigned int i=n-1; i>0; i--)
151 {
152 if (indices[i] == a.shape()[i])
153 {
154 indices[i] = 0;
155 ++indices[i - 1];
156 }
157 }
158
159 if (indices[0] == a.shape()[0])
160 {
161 break;
162 }
163 }
164
165 boost::test_tools::predicate_result comparisonResult(true);
166 if (numFailedElements > 0)
167 {
168 comparisonResult = false;
169 comparisonResult.message() << numFailedElements << " different values at: ";
170 if (numFailedElements > maxReportedDifferences)
171 {
172 errorString << ", ... (and " << (numFailedElements - maxReportedDifferences) << " other differences)";
173 }
174 comparisonResult.message() << errorString.str();
175 }
176
177 return comparisonResult;
178 }
179
180
181 // Creates a boost::multi_array with the shape defined by the given TensorInfo.
182 template <typename T, std::size_t n>
MakeTensor(const armnn::TensorInfo & tensorInfo)183 boost::multi_array<T, n> MakeTensor(const armnn::TensorInfo& tensorInfo)
184 {
185 std::array<unsigned int, n> shape;
186
187 for (unsigned int i = 0; i < n; i++)
188 {
189 shape[i] = tensorInfo.GetShape()[i];
190 }
191
192 return boost::multi_array<T, n>(shape);
193 }
194
195 // Creates a boost::multi_array with the shape defined by the given TensorInfo and contents defined by the given vector.
196 template <typename T, std::size_t n>
MakeTensor(const armnn::TensorInfo & tensorInfo,const std::vector<T> & flat,bool isDynamic=false)197 boost::multi_array<T, n> MakeTensor(
198 const armnn::TensorInfo& tensorInfo, const std::vector<T>& flat, bool isDynamic = false)
199 {
200 if (!isDynamic)
201 {
202 ARMNN_ASSERT_MSG(flat.size() == tensorInfo.GetNumElements(), "Wrong number of components supplied to tensor");
203 }
204
205 std::array<unsigned int, n> shape;
206
207 // NOTE: tensorInfo.GetNumDimensions() might be different from n
208 const unsigned int returnDimensions = static_cast<unsigned int>(n);
209 const unsigned int actualDimensions = tensorInfo.GetNumDimensions();
210
211 const unsigned int paddedDimensions =
212 returnDimensions > actualDimensions ? returnDimensions - actualDimensions : 0u;
213
214 for (unsigned int i = 0u; i < returnDimensions; i++)
215 {
216 if (i < paddedDimensions)
217 {
218 shape[i] = 1u;
219 }
220 else
221 {
222 shape[i] = tensorInfo.GetShape()[i - paddedDimensions];
223 }
224 }
225
226 boost::const_multi_array_ref<T, n> arrayRef(&flat[0], shape);
227 return boost::multi_array<T, n>(arrayRef);
228 }
229
230 template <typename T, std::size_t n>
MakeRandomTensor(const armnn::TensorInfo & tensorInfo,unsigned int seed,float min=-10.0f,float max=10.0f)231 boost::multi_array<T, n> MakeRandomTensor(const armnn::TensorInfo& tensorInfo,
232 unsigned int seed,
233 float min = -10.0f,
234 float max = 10.0f)
235 {
236 boost::random::mt19937 gen(seed);
237 boost::random::uniform_real_distribution<float> dist(min, max);
238
239 std::vector<float> init(tensorInfo.GetNumElements());
240 for (unsigned int i = 0; i < init.size(); i++)
241 {
242 init[i] = dist(gen);
243 }
244
245 const float qScale = tensorInfo.GetQuantizationScale();
246 const int32_t qOffset = tensorInfo.GetQuantizationOffset();
247
248 return MakeTensor<T, n>(tensorInfo, armnnUtils::QuantizedVector<T>(init, qScale, qOffset));
249 }
250