1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "LSHProjection.h"
18 
19 #include "NeuralNetworksWrapper.h"
20 
21 #include <gmock/gmock.h>
22 #include <gmock/gmock-matchers.h>
23 #include <gtest/gtest.h>
24 
25 using ::testing::FloatNear;
26 using ::testing::Matcher;
27 
28 namespace android {
29 namespace nn {
30 namespace wrapper {
31 
32 using ::testing::ElementsAre;
33 
34 #define FOR_ALL_INPUT_AND_WEIGHT_TENSORS(ACTION) \
35     ACTION(Hash, float)                          \
36     ACTION(Input, int)                           \
37     ACTION(Weight, float)
38 
39 // For all output and intermediate states
40 #define FOR_ALL_OUTPUT_TENSORS(ACTION) ACTION(Output, int)
41 
42 class LSHProjectionOpModel {
43    public:
LSHProjectionOpModel(LSHProjectionType type,std::initializer_list<uint32_t> hash_shape,std::initializer_list<uint32_t> input_shape,std::initializer_list<uint32_t> weight_shape)44     LSHProjectionOpModel(LSHProjectionType type, std::initializer_list<uint32_t> hash_shape,
45                          std::initializer_list<uint32_t> input_shape,
46                          std::initializer_list<uint32_t> weight_shape)
47         : type_(type) {
48         std::vector<uint32_t> inputs;
49 
50         OperandType HashTy(Type::TENSOR_FLOAT32, hash_shape);
51         inputs.push_back(model_.addOperand(&HashTy));
52         OperandType InputTy(Type::TENSOR_INT32, input_shape);
53         inputs.push_back(model_.addOperand(&InputTy));
54         OperandType WeightTy(Type::TENSOR_FLOAT32, weight_shape);
55         inputs.push_back(model_.addOperand(&WeightTy));
56 
57         OperandType TypeParamTy(Type::INT32, {});
58         inputs.push_back(model_.addOperand(&TypeParamTy));
59 
60         std::vector<uint32_t> outputs;
61 
62         auto multiAll = [](const std::vector<uint32_t>& dims) -> uint32_t {
63             uint32_t sz = 1;
64             for (uint32_t d : dims) {
65                 sz *= d;
66             }
67             return sz;
68         };
69 
70         uint32_t outShapeDimension = 0;
71         if (type == LSHProjectionType_SPARSE || type == LSHProjectionType_SPARSE_DEPRECATED) {
72             auto it = hash_shape.begin();
73             Output_.insert(Output_.end(), *it, 0.f);
74             outShapeDimension = *it;
75         } else {
76             Output_.insert(Output_.end(), multiAll(hash_shape), 0.f);
77             outShapeDimension = multiAll(hash_shape);
78         }
79 
80         OperandType OutputTy(Type::TENSOR_INT32, {outShapeDimension});
81         outputs.push_back(model_.addOperand(&OutputTy));
82 
83         model_.addOperation(ANEURALNETWORKS_LSH_PROJECTION, inputs, outputs);
84         model_.identifyInputsAndOutputs(inputs, outputs);
85 
86         model_.finish();
87     }
88 
89 #define DefineSetter(X, T) \
90     void Set##X(const std::vector<T>& f) { X##_.insert(X##_.end(), f.begin(), f.end()); }
91 
92     FOR_ALL_INPUT_AND_WEIGHT_TENSORS(DefineSetter);
93 
94 #undef DefineSetter
95 
GetOutput() const96     const std::vector<int>& GetOutput() const { return Output_; }
97 
Invoke()98     void Invoke() {
99         ASSERT_TRUE(model_.isValid());
100 
101         Compilation compilation(&model_);
102         compilation.finish();
103         Execution execution(&compilation);
104 
105 #define SetInputOrWeight(X, T)                                                                     \
106     ASSERT_EQ(                                                                                     \
107             execution.setInput(LSHProjection::k##X##Tensor, X##_.data(), sizeof(T) * X##_.size()), \
108             Result::NO_ERROR);
109 
110         FOR_ALL_INPUT_AND_WEIGHT_TENSORS(SetInputOrWeight);
111 
112 #undef SetInputOrWeight
113 
114 #define SetOutput(X, T)                                                     \
115     ASSERT_EQ(execution.setOutput(LSHProjection::k##X##Tensor, X##_.data(), \
116                                   sizeof(T) * X##_.size()),                 \
117               Result::NO_ERROR);
118 
119         FOR_ALL_OUTPUT_TENSORS(SetOutput);
120 
121 #undef SetOutput
122 
123         ASSERT_EQ(execution.setInput(LSHProjection::kTypeParam, &type_, sizeof(type_)),
124                   Result::NO_ERROR);
125 
126         ASSERT_EQ(execution.compute(), Result::NO_ERROR);
127     }
128 
129    private:
130     Model model_;
131     LSHProjectionType type_;
132 
133     std::vector<float> Hash_;
134     std::vector<int> Input_;
135     std::vector<float> Weight_;
136     std::vector<int> Output_;
137 };  // namespace wrapper
138 
TEST(LSHProjectionOpTest2,DenseWithThreeInputs)139 TEST(LSHProjectionOpTest2, DenseWithThreeInputs) {
140     LSHProjectionOpModel m(LSHProjectionType_DENSE, {4, 2}, {3, 2}, {3});
141 
142     m.SetInput({12345, 54321, 67890, 9876, -12345678, -87654321});
143     m.SetHash({0.123, 0.456, -0.321, -0.654, 1.234, 5.678, -4.321, -8.765});
144     m.SetWeight({0.12, 0.34, 0.56});
145 
146     m.Invoke();
147 
148     EXPECT_THAT(m.GetOutput(), ElementsAre(1, 1, 1, 0, 1, 1, 1, 0));
149 }
150 
TEST(LSHProjectionOpTest2,SparseDeprecatedWithTwoInputs)151 TEST(LSHProjectionOpTest2, SparseDeprecatedWithTwoInputs) {
152     LSHProjectionOpModel m(LSHProjectionType_SPARSE_DEPRECATED, {4, 2}, {3, 2}, {0});
153 
154     m.SetInput({12345, 54321, 67890, 9876, -12345678, -87654321});
155     m.SetHash({0.123, 0.456, -0.321, -0.654, 1.234, 5.678, -4.321, -8.765});
156 
157     m.Invoke();
158 
159     EXPECT_THAT(m.GetOutput(), ElementsAre(1, 2, 2, 0));
160 }
161 
TEST(LSHProjectionOpTest2,SparseWithTwoInputs)162 TEST(LSHProjectionOpTest2, SparseWithTwoInputs) {
163     LSHProjectionOpModel m(LSHProjectionType_SPARSE, {4, 2}, {3, 2}, {0});
164 
165     m.SetInput({12345, 54321, 67890, 9876, -12345678, -87654321});
166     m.SetHash({0.123, 0.456, -0.321, -0.654, 1.234, 5.678, -4.321, -8.765});
167 
168     m.Invoke();
169 
170     EXPECT_THAT(m.GetOutput(), ElementsAre(1, 6, 10, 12));
171 }
172 
173 }  // namespace wrapper
174 }  // namespace nn
175 }  // namespace android
176