knowrob  2.1.0
A Knowledge Base System for Cognition-enabled Robots
QueryableStorage.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of KnowRob, please consult
3  * https://github.com/knowrob/knowrob for license details.
4  */
5 
6 #include "knowrob/storage/QueryableStorage.h"
7 #include "knowrob/ThreadPool.h"
8 #include "knowrob/queries/AnswerNo.h"
9 #include "knowrob/queries/AnswerYes.h"
10 #include "knowrob/semweb/GraphBuiltin.h"
11 #include "knowrob/semweb/ImportHierarchy.h"
12 #include "knowrob/semweb/GraphSequence.h"
13 #include "knowrob/semweb/GraphUnion.h"
14 #include "knowrob/integration/python/utils.h"
15 
16 using namespace knowrob;
17 
18 AtomPtr QueryableStorage::versionProperty = IRIAtom::Tabled("http://knowrob.org/kb/knowrob.owl#hasVersionOfOrigin");
19 
21  : Storage(features) {
22 }
23 
24 std::vector<VersionedOriginPtr> QueryableStorage::getOrigins() {
25  static auto v_origin = std::make_shared<Variable>("Origin");
26  static auto v_version = std::make_shared<Variable>("Version");
27  std::vector<VersionedOriginPtr> origins;
28  match(TriplePattern(v_origin, versionProperty, v_version),
29  [&](const TriplePtr &triple) {
30  origins.push_back(std::make_shared<VersionedOrigin>(triple->subject(), triple->valueAsString()));
31  });
32  return origins;
33 }
34 
35 void QueryableStorage::setVersionOfOrigin(std::string_view origin, std::string_view version) {
36  TripleView triple;
37  triple.setSubject(origin);
38  triple.setPredicate(versionProperty->stringForm());
39  triple.setStringValue(version);
40  triple.setGraph(origin);
41  insertOne(triple);
42 }
43 
44 std::optional<std::string> QueryableStorage::getVersionOfOrigin(std::string_view origin) {
45  static auto v_version = std::make_shared<Variable>("Version");
46  std::optional<std::string> version;
47  match(TriplePattern(std::make_shared<Atom>(origin), versionProperty, v_version),
48  [&](const TriplePtr &triple) { version = triple->createStringValue(); });
49  return version;
50 }
51 
56 }
57 
58 namespace knowrob {
59  class TripleView_withBindings : public TripleView {
60  public:
61  explicit TripleView_withBindings(const BindingsPtr &bindings) : bindings_(bindings) {}
62 
63  private:
64  const BindingsPtr bindings_;
65  };
66 }
67 
68 void QueryableStorage::match(const TriplePattern &q, const TripleVisitor &visitor) {
69  auto graph_query = std::make_shared<GraphQuery>(
70  std::make_shared<GraphPattern>(std::make_shared<TriplePattern>(q)),
72  query(graph_query, [&](const BindingsPtr &bindings) {
73  TriplePtr triplePtr;
74  // create a triple view that holds a reference to the bindings.
75  // this is done to allow the visitor to take over the ownership of the triple.
76  triplePtr.ptr = new TripleView_withBindings(bindings);
77  triplePtr.owned = true;
78  q.instantiateInto(*triplePtr, bindings);
79  visitor(triplePtr);
80  });
81 }
82 
83 bool QueryableStorage::contains(const Triple &triple) {
84  bool hasTriple = false;
85  match(TriplePattern(triple), [&hasTriple](const TriplePtr &) {
86  hasTriple = true;
87  });
88  return hasTriple;
89 }
90 
91 void QueryableStorage::foreach(const TripleVisitor &visitor) const {
92  batch([&](const TripleContainerPtr &container) {
93  for (auto &triple: *container) {
94  visitor(triple);
95  }
96  });
97 }
98 
99 std::shared_ptr<AnswerYes> QueryableStorage::yes(const GraphPathQueryPtr &original,
100  const GraphQueryExpansionPtr &expanded,
101  const BindingsPtr &bindings) {
102  static const auto edbTerm = Atom::Tabled("EDB");
103 
104  auto positiveAnswer = std::make_shared<AnswerYes>(bindings);
105  // Indicate that EDB has computed the grounding.
106  positiveAnswer->setReasonerTerm(edbTerm);
107  // Apply query context to the answer for some parameters.
108  positiveAnswer->applyFrame(original->ctx()->selector);
109 
110  // Add predicate groundings to the answer
111  for (auto &rdfLiteral: original->path()) {
112  auto p = rdfLiteral->predicate();
113  auto p_instance = applyBindings(p, *positiveAnswer->substitution());
114  positiveAnswer->addGrounding(
115  std::static_pointer_cast<Predicate>(p_instance),
116  rdfLiteral->isNegated(),
117  positiveAnswer->frame());
118  }
119 
120  // The answer is uncertain if any of the groundings is uncertain.
121  positiveAnswer->setIsUncertain(
122  std::any_of(expanded->u_vars.begin(), expanded->u_vars.end(), [&](const VariablePtr &v) {
123  auto &u_v = bindings->get(v->name());
124  return (u_v && u_v->isNumeric() && std::static_pointer_cast<Numeric>(u_v)->asBoolean());
125  }), std::nullopt);
126 
127  // The answer is occasional if any of the groundings has occasional=true flag
128  positiveAnswer->setIsOccasionallyTrue(
129  std::any_of(expanded->o_vars.begin(), expanded->o_vars.end(), [&](const VariablePtr &v) {
130  auto &o_v = bindings->get(v->name());
131  return (o_v && o_v->isNumeric() && std::static_pointer_cast<Numeric>(o_v)->asBoolean());
132  }));
133 
134  return positiveAnswer;
135 }
136 
137 std::shared_ptr<AnswerNo> QueryableStorage::no(const GraphPathQueryPtr &q) {
138  static const auto edbTerm = Atom::Tabled("EDB");
139 
140  // send one negative answer if no positive answer was found
141  auto negativeAnswer = std::make_shared<AnswerNo>();
142  negativeAnswer->setReasonerTerm(edbTerm);
143  // Apply query context "origin" and "perspective" to the answer if any
144  negativeAnswer->applyFrame(q->ctx()->selector);
145  // the answer is uncertain as we only were not able to obtain a positive answer
146  // which does not mean that there is no positive answer.
147  negativeAnswer->setIsUncertain(true, std::nullopt);
148 
149  // Add ungrounded literals to negative answer.
150  // But at the moment the information is not provided by EDBs, would be difficult to implement e.g. in MongoDB.
151  // Well at least we know if the query is only a single triple pattern.
152  if (q->path().size() == 1) {
153  negativeAnswer->addUngrounded(q->path().front()->predicate(),
154  q->path().front()->isNegated());
155  }
156  return negativeAnswer;
157 }
158 
159 static std::shared_ptr<GraphTerm>
160 expand_pattern(const std::shared_ptr<GraphPattern> &q, GraphQueryExpansion &ctx) {
161  const auto &p = q->value();
162  ctx.counter += 1;
163 
164  // Find out if an additional variable must be added for the "isUncertain" flag.
165  // If a variable exists, rather use the existing one.
166  bool needsUncertainVar = false;
167  if (p->isUncertainTerm()) {
168  if (**p->isUncertainTerm() == *Numeric::trueAtom()) {
169  // For each possibly uncertain triple, add a variable to the query for the isUncertain flag.
170  needsUncertainVar = true;
171  } else if (p->isUncertainTerm()->isVariable()) {
172  // In case uncertain term is already a variable, remember it.
173  ctx.u_vars.emplace_back(std::static_pointer_cast<Variable>(*p->isUncertainTerm()));
174  }
175  }
176 
177  // Find out if an additional variable must be added for the "isOccasional" flag.
178  // If a variable exists, rather use the existing one.
179  bool needsOccasionalVar = false;
180  if (p->isOccasionalTerm()) {
181  if (**p->isOccasionalTerm() == *Numeric::trueAtom()) {
182  // For each possibly occasional triple, add a variable to the query for the isOccasional flag.
183  needsOccasionalVar = true;
184  } else if (p->isOccasionalTerm()->isVariable()) {
185  // In case occasional term is already a variable, remember it.
186  ctx.o_vars.emplace_back(std::static_pointer_cast<Variable>(*p->isOccasionalTerm()));
187  }
188  }
189 
190  // Find out if begin/end variables must be added for the computation of the time interval.
191  // This is always the case if the query uses SOMETIMES operator.
192  bool needsIntervalComputation = (ctx.query_ctx->selector.occasional);
193 
194  bool needsRewrite = needsUncertainVar || needsOccasionalVar || needsIntervalComputation;
195  if (!needsRewrite) return q;
196 
197  auto pat_expanded = std::make_shared<TriplePattern>(*p);
198 
199  if (needsUncertainVar) {
200  static const std::string varPrefix = "_uncertain";
201  // insert a fresh variable for the isUncertain flag
202  auto u_var = std::make_shared<Variable>(varPrefix + std::to_string(ctx.counter));
203  pat_expanded->setIsUncertainTerm(groundable<Numeric>(u_var));
204  // also remember the variable in the context
205  ctx.u_vars.emplace_back(u_var);
206  }
207 
208  if (needsOccasionalVar) {
209  static const std::string varPrefix = "_occasional";
210  // insert a fresh variable for the isOccasional flag
211  auto o_var = std::make_shared<Variable>(varPrefix + std::to_string(ctx.counter));
212  pat_expanded->setIsOccasionalTerm(groundable<Numeric>(o_var));
213  // also remember the variable in the context
214  ctx.o_vars.emplace_back(o_var);
215  }
216 
217  VariablePtr triple_begin, triple_end;
218  if (needsIntervalComputation) {
219  // Add begin/end variables possibly replacing fixed values for begin/end in the query.
220  // This is ok assuming each pattern has the same query frame.
221  // FILTER will be performed through a builtin instead, so deleting the values is ok,
222  // SPARQL would insert ad-hoc variables anyway.
223  auto g_begin = p->beginTerm();
224  auto g_end = p->endTerm();
225 
226  if (g_begin.has_variable()) {
227  triple_begin = g_begin.variable();
228  } else {
229  static const std::string varPrefix = "_begin";
230  triple_begin = std::make_shared<Variable>(varPrefix + std::to_string(ctx.counter));
231  pat_expanded->setBeginTerm(groundable<Double>(triple_begin));
232  }
233 
234  if (g_end.has_variable()) {
235  triple_end = g_end.variable();
236  } else {
237  static const std::string varPrefix = "_end";
238  triple_end = std::make_shared<Variable>(varPrefix + std::to_string(ctx.counter));
239  pat_expanded->setEndTerm(groundable<Double>(triple_end));
240  }
241  }
242 
243  auto q_expanded = std::make_shared<GraphPattern>(pat_expanded);
244  std::shared_ptr<GraphTerm> outer_term = q_expanded;
245 
246  // If the query uses SOMETIMES operator, then we need to insert builtins to the query for
247  // the computation of the time interval.
248  if (needsIntervalComputation) {
249  auto seq = std::make_shared<GraphSequence>();
250  seq->addMember(q_expanded);
251 
252  // FILTER all triples that do not intersect with begin/end of query frame
253  if (ctx.query_ctx->selector.begin) {
254  auto ctx_begin = std::make_shared<Double>(ctx.query_ctx->selector.begin.value());
255  seq->addMember(GraphBuiltin::lessOrEqual(ctx_begin, triple_end));
256  }
257  if (ctx.query_ctx->selector.end) {
258  auto ctx_end = std::make_shared<Double>(ctx.query_ctx->selector.end.value());
259  seq->addMember(GraphBuiltin::greaterOrEqual(ctx_end, triple_begin));
260  }
261 
262  // Set accumulated begin and end time.
263  // But not all backends support variable re-assignment.
264  // For the ones that don't support it like (SPARQL), we need to introduce a new variable for each intersection computed.
265  VariablePtr next_i_begin, next_i_end;
266  if (ctx.with_reassignment) {
267  next_i_begin = ctx.accumulated_begin;
268  next_i_end = ctx.accumulated_end;
269  } else {
270  static const std::string varPrefix_begin = "_i_begin";
271  static const std::string varPrefix_end = "_i_end";
272  next_i_begin = std::make_shared<Variable>(varPrefix_begin + std::to_string(ctx.counter));
273  next_i_end = std::make_shared<Variable>(varPrefix_end + std::to_string(ctx.counter));
274  }
275  seq->addMember(GraphBuiltin::max(next_i_begin, ctx.accumulated_begin, triple_begin));
276  seq->addMember(GraphBuiltin::min(next_i_end, ctx.accumulated_end, triple_end));
277  ctx.accumulated_begin = next_i_begin;
278  ctx.accumulated_end = next_i_end;
279 
280  // Ensure that begin < end
281  seq->addMember(GraphBuiltin::less(ctx.accumulated_begin, ctx.accumulated_end));
282 
283  // Update outer term
284  outer_term = seq;
285  }
286 
287  return outer_term;
288 }
289 
290 static std::shared_ptr<GraphTerm>
291 expand_term(const std::shared_ptr<GraphTerm> &q, GraphQueryExpansion &ctx) { // NOLINT
292  switch (q->termType()) {
294  return expand_pattern(std::static_pointer_cast<GraphPattern>(q), ctx);
297  auto connective = std::static_pointer_cast<GraphConnective>(q);
298  std::vector<std::shared_ptr<GraphTerm>> expandedTerms(connective->terms().size());
299 
300  bool hasExpansion = false;
301  for (size_t i = 0; i < connective->terms().size(); ++i) {
302  auto expandedTerm = expand_term(connective->terms()[i], ctx);
303  expandedTerms[i] = expandedTerm;
304  hasExpansion = hasExpansion || (expandedTerm != connective->terms()[i]);
305  }
306 
307  if (hasExpansion) {
308  if (q->termType() == GraphTermType::Union)
309  return std::make_shared<GraphUnion>(expandedTerms);
310  else
311  return std::make_shared<GraphSequence>(expandedTerms);
312  }
313  break;
314  }
316  break;
317  }
318  return q;
319 }
320 
321 static GraphQueryPtr expand_query(const GraphQueryPtr &q, GraphQueryExpansion &ctx) {
322  // Initialize begin/end variables for the computation of the time interval.
323  static const auto var_begin = std::make_shared<Variable>("_begin");
324  static const auto var_end = std::make_shared<Variable>("_end");
325  ctx.accumulated_begin = var_begin;
326  ctx.accumulated_end = var_end;
327 
328  // Expand the query
329  auto expandedTerm = expand_term(q->term(), ctx);
330 
331  // If the query uses SOMETIMES operator, prepend initialization of the accumulated_begin and accumulated_end variables
332  // used to compute the intersection of triple time intervals.
333  if (ctx.query_ctx->selector.occasional) {
334  double b_min = ctx.query_ctx->selector.begin.value_or(0.0);
335  double e_max = ctx.query_ctx->selector.end.value_or(std::numeric_limits<double>::max());
336  auto set_b = GraphBuiltin::bind(var_begin, std::make_shared<Double>(b_min));
337  auto set_e = GraphBuiltin::bind(var_end, std::make_shared<Double>(e_max));
338 
339  if (expandedTerm->termType() == GraphTermType::Sequence) {
340  // Prepend to existing sequence
341  auto seq_terms = std::static_pointer_cast<GraphSequence>(expandedTerm)->terms();
342  seq_terms.insert(seq_terms.begin(), set_e);
343  seq_terms.insert(seq_terms.begin(), set_b);
344  expandedTerm = std::make_shared<GraphSequence>(seq_terms);
345  } else {
346  // Create a new sequence
347  auto seq = std::make_shared<GraphSequence>();
348  seq->addMember(set_b);
349  seq->addMember(set_e);
350  seq->addMember(expandedTerm);
351  expandedTerm = seq;
352  }
353  }
354 
355  if (expandedTerm == q->term()) {
356  return q;
357  } else {
358  return std::make_shared<GraphQuery>(expandedTerm, q->ctx());
359  }
360 }
361 
363  // Expand the query. Currently, this is mainly used to compute the answer frame here.
364  // But also to insert some builtins for occasional triples.
365  auto exp_ctx = std::make_shared<GraphQueryExpansion>();
366  exp_ctx->query_ctx = q->ctx();
367  exp_ctx->with_reassignment = supports(StorageFeature::ReAssignment);
368  exp_ctx->expanded = expand_query(q, *exp_ctx);
369  return exp_ctx;
370 }
371 
372 namespace knowrob::py {
373  struct QueryableStorageWrap : public QueryableStorage, boost::python::wrapper<QueryableStorage> {
374  explicit QueryableStorageWrap(PyObject *p, const StorageFeatures features)
375  : QueryableStorage(features), self(p) {}
376 
377  // virtual
378  void foreach(const TripleVisitor &visitor) const override {
379  call_method<void>(self, "foreach", visitor);
380  }
381 
382  void foreach_default(const TripleVisitor &visitor) const {
383  this->QueryableStorage::foreach(visitor);
384  }
385 
386  bool contains(const Triple &triple) override {
387  return call_method<bool, const Triple &>(self, "contains", triple);
388  }
389 
390  bool contains_default(const Triple &triple) {
391  return this->QueryableStorage::contains(triple);
392  }
393 
394  void match(const TriplePattern &query, const TripleVisitor &visitor) override {
395  call_method<void>(self, "match", query, visitor);
396  }
397 
398  void match_default(const TriplePattern &query, const TripleVisitor &visitor) {
399  this->QueryableStorage::match(query, visitor);
400  }
401 
402  // pure virtual
403  bool isPersistent() const override {
404  return call_method<bool>(self, "isPersistent");
405  }
406 
407  // pure virtual
408  void batch(const TripleHandler &callback) const override {
409  call_method<void>(self, "batch", callback);
410  }
411 
412  // pure virtual
413  void batchOrigin(std::string_view origin, const TripleHandler &callback) override {
414  call_method<void>(self, "batchOrigin", origin, callback);
415  }
416 
417  // pure virtual
418  void query(const GraphQueryPtr &query, const BindingsHandler &callback) override {
419  call_method<void>(self, "query", query, callback);
420  }
421 
422  // pure virtual
423  void count(const ResourceCounter &callback) const override {
424  call_method<void>(self, "count", callback);
425  }
426 
427  // pure virtual
428  bool initializeBackend(const PropertyTree &config) override {
429  return call_method<bool>(self, "initializeBackend", config);
430  }
431 
432  // pure virtual
433  bool insertOne(const Triple &triple) override {
434  return call_method<bool>(self, "insertOne", &triple);
435  }
436 
437  // pure virtual
438  bool insertAll(const TripleContainerPtr &triples) override {
439  return call_method<bool>(self, "insertAll", triples);
440  }
441 
442  // pure virtual
443  bool removeOne(const Triple &triple) override {
444  return call_method<bool>(self, "removeOne", &triple);
445  }
446 
447  // pure virtual
448  bool removeAll(const TripleContainerPtr &triples) override {
449  return call_method<bool>(self, "removeAll", triples);
450  }
451 
452  // pure virtual
453  bool removeAllWithOrigin(std::string_view origin) override {
454  return call_method<bool>(self, "removeAllWithOrigin", origin.data());
455  }
456 
457  private:
458  PyObject *self;
459  };
460 
461  template<>
463  using namespace boost::python;
464  class_<QueryableStorage, std::shared_ptr<QueryableStorageWrap>, bases<Storage>, boost::noncopyable>
465  ("QueryableStorage", init<StorageFeatures>())
466  .def("setVersionOfOrigin", &QueryableStorage::setVersionOfOrigin)
467  .def("getVersionOfOrigin", &QueryableStorage::getVersionOfOrigin)
468  .def("dropSessionOrigins", &QueryableStorage::dropSessionOrigins)
469  .def("yes", &QueryableStorage::yes).staticmethod("yes")
470  .def("no", &QueryableStorage::no).staticmethod("no")
471  // methods that must be implemented by backend plugins
472  .def("foreach", &QueryableStorageWrap::foreach, &QueryableStorageWrap::foreach_default)
473  .def("contains", &QueryableStorageWrap::contains, &QueryableStorageWrap::contains_default)
474  .def("match", &QueryableStorageWrap::match, &QueryableStorageWrap::match_default)
475  .def("isPersistent", pure_virtual(&QueryableStorageWrap::isPersistent))
476  .def("batch", pure_virtual(&QueryableStorageWrap::batch))
477  .def("batchOrigin", pure_virtual(&QueryableStorageWrap::batchOrigin))
478  .def("query", +[](QueryableStorage &x, const GraphQueryPtr &query, object &fn) { x.query(query, fn); })
479  .def("count", pure_virtual(&QueryableStorageWrap::count));
480  register_ptr_to_python< std::shared_ptr< QueryableStorage > >();
481  }
482 }
static std::shared_ptr< knowrob::Atom > Tabled(std::string_view stringForm)
Definition: Atom.cpp:40
static BuiltinPtr greaterOrEqual(const TermPtr &a, const TermPtr &b)
Definition: GraphBuiltin.h:210
static BuiltinPtr max(const VariablePtr &var, const TermPtr &a, const TermPtr &b)
Definition: GraphBuiltin.h:157
static BuiltinPtr lessOrEqual(const TermPtr &a, const TermPtr &b)
Definition: GraphBuiltin.h:184
static BuiltinPtr less(const TermPtr &a, const TermPtr &b)
Definition: GraphBuiltin.h:171
static BuiltinPtr bind(const VariablePtr &var, const TermPtr &val)
Definition: GraphBuiltin.h:127
static BuiltinPtr min(const VariablePtr &var, const TermPtr &a, const TermPtr &b)
Definition: GraphBuiltin.h:142
static std::shared_ptr< IRIAtom > Tabled(std::string_view stringForm)
Definition: IRIAtom.cpp:25
static constexpr std::string_view ORIGIN_SESSION
static constexpr std::string_view ORIGIN_USER
static constexpr std::string_view ORIGIN_REASONER
static std::shared_ptr< Numeric > trueAtom()
Definition: Numeric.cpp:15
std::optional< std::string > getVersionOfOrigin(std::string_view origin)
static std::shared_ptr< AnswerNo > no(const GraphPathQueryPtr &q)
virtual void match(const TriplePattern &query, const TripleVisitor &visitor)
QueryableStorage(StorageFeatures features=StorageFeature::NothingSpecial)
static std::shared_ptr< AnswerYes > yes(const GraphPathQueryPtr &original, const GraphQueryExpansionPtr &expansion, const BindingsPtr &bindings)
virtual void foreach(const TripleVisitor &visitor) const
GraphQueryExpansionPtr expand(const GraphQueryPtr &q)
virtual void batch(const TripleHandler &callback) const =0
void setVersionOfOrigin(std::string_view origin, std::string_view version)
virtual bool contains(const Triple &triple)
static AtomPtr versionProperty
std::vector< VersionedOriginPtr > getOrigins()
virtual void query(const GraphQueryPtr &query, const BindingsHandler &callback)=0
bool supports(StorageFeature feature) const
Definition: Storage.h:84
virtual bool removeAllWithOrigin(std::string_view origin)=0
virtual bool insertOne(const Triple &triple)=0
virtual std::string_view valueAsString() const =0
virtual std::string_view subject() const =0
std::string createStringValue() const
Definition: Triple.cpp:71
bool instantiateInto(Triple &triple, const std::shared_ptr< const Bindings > &bindings=Bindings::emptyBindings()) const
void setStringValue(std::string_view v) override
Definition: Triple.h:464
void setPredicate(std::string_view predicate) override
Definition: Triple.h:449
void setGraph(std::string_view graph) override
Definition: Triple.h:497
void setSubject(std::string_view subject) override
Definition: Triple.h:446
TermRule & string()
Definition: terms.cpp:63
void createType< QueryableStorage >()
std::function< void(const TriplePtr &)> TripleVisitor
StorageFeature
Definition: Storage.h:24
std::shared_ptr< TripleContainer > TripleContainerPtr
std::shared_ptr< GraphQueryExpansion > GraphQueryExpansionPtr
std::shared_ptr< const Bindings > BindingsPtr
Definition: Bindings.h:151
std::function< void(std::string_view, uint64_t)> ResourceCounter
QueryContextPtr DefaultQueryContext()
Definition: Query.cpp:11
std::function< void(const BindingsPtr &)> BindingsHandler
Definition: Bindings.h:152
std::shared_ptr< Atom > AtomPtr
Definition: Atom.h:69
std::function< void(const TripleContainerPtr &)> TripleHandler
std::shared_ptr< GraphQuery > GraphQueryPtr
Definition: GraphQuery.h:65
std::shared_ptr< GraphPathQuery > GraphPathQueryPtr
std::shared_ptr< Variable > VariablePtr
Definition: Variable.h:60
FirstOrderLiteralPtr applyBindings(const FirstOrderLiteralPtr &lit, const Bindings &bindings)
std::vector< VariablePtr > o_vars
std::vector< VariablePtr > u_vars
Triple * ptr
Definition: Triple.h:590