BmnRoot 26.10.0-18-g4e3414f9
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
BmnGemStripFdeepModel.cxx
Go to the documentation of this file.
2
3#include "FairLogger.h"
4#include "TString.h"
5#include "TSystem.h"
6
7#include <algorithm>
8#include <map>
9#include <mutex>
10#include <set>
11
12// frugally-deep (optional) ------------------------------------------------------
13// BMN_WITH_FDEEP = 1 : fdeep models can be loaded (fdeep found by include)
14// BMN_WITH_FDEEP = 0 : built without fdeep, no model can be loaded (the ANN methods use the standard method)
15// Can be forced from the build: -DBMN_WITH_FDEEP=0 (or =1)
16#ifndef BMN_WITH_FDEEP
17#if __has_include("fdeep/fdeep.hpp")
18#define BMN_WITH_FDEEP 1
19#else
20#define BMN_WITH_FDEEP 0
21#endif
22#endif
23
24#if BMN_WITH_FDEEP
25#define FDEEP_FLOAT_TYPE float
26#include "fdeep/fdeep.hpp"
27#endif
28//------------------------------------------------------------------------------
29
30namespace
31{
32const char* const kBuiltinDefaultPath = "$VMCWORKDIR/parameters/gem/ANNModels/GEM_DefaultModel.json";
33
34// Path with environment variables ($VMCWORKDIR, ~, ...) -> real path (ROOT: gSystem->ExpandPathName).
35// ok = false if it cannot be expanded (an environment variable is not set); the path is returned as given then.
36std::string ExpandPathName(const std::string& path, bool* ok = nullptr)
37{
38 TString expanded(path.c_str());
39 const bool failed = gSystem->ExpandPathName(expanded); // kTRUE in case of error
40 if (ok)
41 *ok = !failed;
42 return failed ? path : std::string(expanded.Data());
43}
44
45// Shared models ---------------------------------------------------------------
46struct SharedState
47{
48 std::mutex mutex;
49 std::string default_path = kBuiltinDefaultPath;
50 std::map<std::string, std::shared_ptr<const BmnGemStripFdeepModel>> models; // key - expanded path
51 std::set<std::string> failed; // paths that failed to load
52 std::map<std::string, std::string> expanded; // path as given -> expanded path
53};
54
55SharedState& Shared()
56{
57 static SharedState state;
58 return state;
59}
60//==============================================================================
61} // namespace
62//------------------------------------------------------------------------------
63
64// fdeep model and the shape of its input (only here: the header does not need fdeep)
66{
67#if BMN_WITH_FDEEP
68 Impl(fdeep::model&& m, const fdeep::tensor_shape& shape)
69 : model(std::move(m))
70 , input_shape(shape)
71 {}
72 fdeep::model model;
73 fdeep::tensor_shape input_shape;
74#endif
75};
76//------------------------------------------------------------------------------
77
83
84BmnGemStripFdeepModel::BmnGemStripFdeepModel(const std::string& path, bool verbose)
86{
87 Load(path, verbose);
88}
89
91
93{
94 fImpl.reset();
95 fLoaded = false;
96 fInputDim = 0;
97 fOutputDim = 0;
98 fFilePath.clear();
99}
100
105//------------------------------------------------------------------------------
106
107bool BmnGemStripFdeepModel::Load(const std::string& path, bool verbose)
108{
109 Clear();
110
111 bool expanded_ok = true;
112 const std::string full_path = ExpandPathName(path, &expanded_ok);
113
114 auto fail = [&](const std::string& reason) {
115 if (verbose)
116 LOG(error) << "BmnGemStripFdeepModel: cannot load model " << full_path << ": " << reason;
117 Clear();
118 return false;
119 };
120
121#if BMN_WITH_FDEEP
122 if (gSystem->AccessPathName(full_path.c_str(), kFileExists)) {
123 std::string reason = "file not found";
124 if (!expanded_ok)
125 reason += " (an environment variable in the path is not set)";
126 return fail(reason);
127 }
128
129 try {
130 // verify = false: the test data of the file are not checked (fast loading)
131 fdeep::model model = fdeep::load_model(full_path, false, fdeep::dev_null_logger);
132
133 if (model.get_input_shapes().size() != 1 || model.get_output_shapes().size() != 1)
134 return fail("the model must have one input and one output");
135
136 // Sizes of the input and the output: one prediction on zero input
137 const fdeep::tensors dummy = model.generate_dummy_inputs();
138 const fdeep::tensors out = model.predict(dummy);
139 if (dummy.size() != 1 || out.size() != 1)
140 return fail("unexpected number of input / output tensors");
141
142 const fdeep::tensor_shape input_shape = dummy.front().shape();
143 fInputDim = static_cast<int>(input_shape.volume());
144 fOutputDim = static_cast<int>(out.front().as_vector()->size());
145 fImpl.reset(new Impl(std::move(model), input_shape));
146 } catch (const std::exception& e) {
147 return fail(e.what());
148 }
149
150 fFilePath = full_path;
151 fLoaded = true;
152 return true;
153#else
154 (void)expanded_ok;
155 return fail("the library is built without frugally-deep");
156#endif
157}
158//------------------------------------------------------------------------------
159
160bool BmnGemStripFdeepModel::Predict(const float* input, float* output) const
161{
162 if (!fLoaded || !fImpl || !input || !output)
163 return false;
164
165#if BMN_WITH_FDEEP
166 try {
167 fdeep::tensors in;
168 in.emplace_back(fImpl->input_shape, std::vector<float>(input, input + fInputDim));
169 const fdeep::tensors out = fImpl->model.predict(in);
170 if (out.empty())
171 return false;
172 const auto& result = *out.front().as_vector(); // fdeep vector may use its own allocator
173 if (static_cast<int>(result.size()) != fOutputDim)
174 return false;
175 std::copy(result.begin(), result.end(), output);
176 return true;
177 } catch (const std::exception& e) {
178 LOG(error) << "BmnGemStripFdeepModel: prediction failed (" << fFilePath << "): " << e.what();
179 return false;
180 }
181#else
182 return false;
183#endif
184}
185
186std::vector<float> BmnGemStripFdeepModel::Predict(const std::vector<float>& input) const
187{
188 if (!fLoaded || static_cast<int>(input.size()) != fInputDim)
189 return {};
190 std::vector<float> output(fOutputDim);
191 if (!Predict(input.data(), output.data()))
192 return {};
193 return output;
194}
195//------------------------------------------------------------------------------
196
197bool BmnGemStripFdeepModel::PredictSplit(const float* common,
198 int n_common,
199 const float* specific,
200 int n_rows,
201 float* output) const
202{
203 if (!fLoaded || !output || n_rows < 0 || n_common < 0 || n_common > fInputDim)
204 return false;
205 const int n_spec = fInputDim - n_common;
206 if ((n_common > 0 && !common) || (n_spec > 0 && n_rows > 0 && !specific))
207 return false;
208
209 // Each row separately: [common, specific of the row]
210 std::vector<float> input(fInputDim);
211 if (n_common > 0)
212 std::copy(common, common + n_common, input.begin());
213 for (int r = 0; r < n_rows; ++r) {
214 if (n_spec > 0)
215 std::copy(specific + static_cast<size_t>(r) * n_spec, specific + static_cast<size_t>(r + 1) * n_spec,
216 input.begin() + n_common);
217 if (!Predict(input.data(), output + static_cast<size_t>(r) * fOutputDim))
218 return false;
219 }
220 return true;
221}
222//------------------------------------------------------------------------------
223
224void BmnGemStripFdeepModel::Print(std::ostream& os) const
225{
226 if (!fLoaded) {
227 os << "BmnGemStripFdeepModel: not loaded" << (IsBuiltWithFdeep() ? "" : " (built without frugally-deep)")
228 << "\n";
229 return;
230 }
231 os << "BmnGemStripFdeepModel: " << fFilePath << "\n"
232 << " input " << fInputDim << " -> output " << fOutputDim << "\n";
233}
234//------------------------------------------------------------------------------
235
236std::shared_ptr<const BmnGemStripFdeepModel> BmnGemStripFdeepModel::GetShared(const std::string& path)
237{
238 SharedState& state = Shared();
239 std::lock_guard<std::mutex> lock(state.mutex);
240
241 // Cache key - the expanded path (the same file given by different strings is loaded once);
242 // the expansion is done once for each path string (GetShared() can be called for each point)
243 const std::string& given = path.empty() ? state.default_path : path;
244 auto iexp = state.expanded.find(given);
245 if (iexp == state.expanded.end())
246 iexp = state.expanded.emplace(given, ExpandPathName(given)).first;
247 const std::string key = iexp->second;
248
249 auto it = state.models.find(key);
250 if (it != state.models.end())
251 return it->second;
252 if (state.failed.count(key))
253 return nullptr;
254
255 auto model = std::make_shared<BmnGemStripFdeepModel>();
256 if (!model->Load(key, true)) {
257 LOG(warn) << "BmnGemStripFdeepModel: the model " << key
258 << " is not available (this warning is printed once for the path)";
259 state.failed.insert(key);
260 return nullptr;
261 }
262 std::shared_ptr<const BmnGemStripFdeepModel> shared = model;
263 state.models[key] = shared;
264 return shared;
265}
266
267void BmnGemStripFdeepModel::SetDefaultPath(const std::string& path)
268{
269 SharedState& state = Shared();
270 std::lock_guard<std::mutex> lock(state.mutex);
271 state.default_path = path.empty() ? std::string(kBuiltinDefaultPath) : path;
272}
273
275{
276 SharedState& state = Shared();
277 std::lock_guard<std::mutex> lock(state.mutex);
278 return state.default_path;
279}
280
282{
283 return kBuiltinDefaultPath;
284}
285
287{
288 SharedState& state = Shared();
289 std::lock_guard<std::mutex> lock(state.mutex);
290 state.models.clear(); // models in use stay alive while someone holds them
291 state.failed.clear();
292 state.expanded.clear();
293}
#define BMN_WITH_FDEEP
__m128 m
Definition P4_F32vec4.h:27
kFileExists
R__EXTERN TSystem * gSystem
std::unique_ptr< Impl > fImpl
static std::shared_ptr< const BmnGemStripFdeepModel > GetShared(const std::string &path="")
static void SetDefaultPath(const std::string &path)
static std::string GetBuiltinDefaultPath()
bool Predict(const float *input, float *output) const
bool PredictSplit(const float *common, int n_common, const float *specific, int n_rows, float *output) const
bool Load(const std::string &path, bool verbose=true)
void Print(std::ostream &os=std::cout) const
static std::string GetDefaultPath()
const char * Data() const