nnet-descriptor.cc
Go to the documentation of this file.
1 // nnet3/nnet-descriptor.cc
2 
3 // Copyright 2015 Johns Hopkins University (author: Daniel Povey)
4 
5 // See ../../COPYING for clarification regarding multiple authors
6 //
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
10 //
11 // http://www.apache.org/licenses/LICENSE-2.0
12 //
13 // THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
15 // WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
16 // MERCHANTABLITY OR NON-INFRINGEMENT.
17 // See the Apache 2 License for the specific language governing permissions and
18 // limitations under the License.
19 
20 #include <iterator>
21 #include <sstream>
22 #include "nnet3/nnet-descriptor.h"
23 #include "nnet3/nnet-nnet.h"
25 
26 namespace kaldi {
27 namespace nnet3 {
28 
29 static std::string ParsingContext(const std::string *token_ptr) {
30  if (*token_ptr == "end of input")
31  return "";
32  std::string next_few_tokens = ", next part of line is: ";
33  // in the next line, *token_ptr should never equal "" but it's to mitigate the
34  // effect of bugs where we read past the end of the array.
35  while (*token_ptr != "end of input" && *token_ptr != "" &&
36  next_few_tokens.size() < 40) {
37  next_few_tokens = (next_few_tokens + " ") + *token_ptr;
38  token_ptr++;
39  }
40  if (*token_ptr != "end of input")
41  next_few_tokens = next_few_tokens + " ...";
42  return next_few_tokens;
43 }
44 
45 static void ExpectToken(const std::string &token,
46  const std::string &what_we_are_parsing,
47  const std::string **next_token) {
48  if (**next_token != token)
49  KALDI_ERR << "Expected '" << token << "' while parsing "
50  << what_we_are_parsing << ", got "
51  << **next_token << ParsingContext(*next_token);
52  else // advance token pointer.
53  (*next_token)++;
54 }
55 
56 static int32 ReadIntegerToken(const std::string &what_we_are_parsing,
57  const std::string **next_token) {
58  int32 ans;
59  if (!ConvertStringToInteger(**next_token, &ans))
60  KALDI_ERR << "Expected integer while parsing "
61  << what_we_are_parsing << ", got '"
62  << **next_token << "'" << ParsingContext(*next_token);
63  (*next_token)++; // advance token pointer.
64  return ans;
65 }
66 
67 
69  const Index &index,
70  std::vector<Cindex> *dependencies) const {
71  dependencies->clear();
72  std::vector<SumDescriptor*>::const_iterator sum_iter = parts_.begin(),
73  sum_end = parts_.end();
74  std::vector<Cindex> this_part;
75  for (; sum_iter != sum_end; ++sum_iter)
76  (*sum_iter)->GetDependencies(index, dependencies);
77 }
78 
80  return nnet.GetNode(src_node_).Dim(nnet);
81 }
82 
84  if (node_index == src_node_) return scale_;
85  else return std::numeric_limits<BaseFloat>::infinity();
86 }
87 
89  return Cindex(src_node_, index);
90 }
91 
93  return new SimpleForwardingDescriptor(src_node_, scale_);
94 }
95 
97  std::vector<int32> *node_indexes) const {
98  node_indexes->push_back(src_node_);
99 }
100 
102  std::ostream &os,
103  const std::vector<std::string> &node_names) const {
104  KALDI_ASSERT(static_cast<size_t>(src_node_) < node_names.size());
105  if (scale_ == 1.0) {
106  os << node_names[src_node_];
107  } else {
108  os << "Scale(" << scale_ << ", " << node_names[src_node_] << ")";
109  }
110 }
111 
113  std::vector<int32> *node_indexes) const {
114  src_->GetNodeDependencies(node_indexes);
115 }
116 
118  return src_->GetScaleForNode(node_index);
119 }
120 
121 
123  Index ind_mod(ind);
124  ind_mod += offset_;
125  return src_->MapToInput(ind_mod);
126 }
127 
128 
130  return new OffsetForwardingDescriptor(src_->Copy(), offset_);
131 }
132 
134  std::ostream &os,
135  const std::vector<std::string> &node_names) const {
136  KALDI_ASSERT(offset_.n == 0);
137  os << "Offset(";
138  src_->WriteConfig(os, node_names);
139  os << ", " << offset_.t;
140  if (offset_.x != 0)
141  os << ", " << offset_.x;
142  os << ")";
143 }
144 
145 
147  std::vector<int32> *node_indexes) const {
148  for (size_t i = 0; i < src_.size(); i++)
149  src_[i]->GetNodeDependencies(node_indexes);
150 }
151 
153  KALDI_ASSERT(!src_.empty());
154  int32 size = src_.size(), mod = ind.t % size;
155  // next line gets "mathematical" modulus, not broken "C" modulus.
156  if (mod < 0) mod += size;
157  return src_[mod]->MapToInput(ind);
158 }
159 
160 
162  std::vector<ForwardingDescriptor*> src_copy(src_.size());
163  for (size_t i = 0; i < src_.size(); i++)
164  src_copy[i] = src_[i]->Copy();
165  return new SwitchingForwardingDescriptor(src_copy);
166 }
167 
168 
170  std::ostream &os,
171  const std::vector<std::string> &node_names) const {
172  KALDI_ASSERT(!src_.empty());
173  os << "Switch(";
174  for (size_t i = 0; i < src_.size(); i++) {
175  src_[i]->WriteConfig(os, node_names);
176  if (i + 1 < src_.size())
177  os << ", ";
178  }
179  os << ")";
180 }
181 
183  std::vector<int32> *node_indexes) const {
184  src_->GetNodeDependencies(node_indexes);
185 }
186 
188  int32 node_index) const {
189  return src_->GetScaleForNode(node_index);
190 }
191 
193  KALDI_ASSERT(t_modulus_ >= 1);
194  Index ind_mod(ind);
195  // unfortunately doing "mathematical" modulus is a bit painful in C.
196  int32 mod = ind_mod.t % t_modulus_;
197  if (mod < 0)
198  mod += t_modulus_;
199  ind_mod.t -= mod;
200  return src_->MapToInput(ind_mod);
201 }
202 
204  return new RoundingForwardingDescriptor(src_->Copy(), t_modulus_);
205 }
206 
208  std::ostream &os,
209  const std::vector<std::string> &node_names) const {
210  os << "Round(";
211  src_->WriteConfig(os, node_names);
212  os << ", " << t_modulus_ << ")";
213 }
214 
216  std::vector<int32> *node_indexes) const {
217  src_->GetNodeDependencies(node_indexes);
218 }
219 
221  int32 node_index) const {
222  return src_->GetScaleForNode(node_index);
223 }
224 
226  Index ind_mod(ind);
227  switch (variable_name_) {
228  case kT: ind_mod.t = value_; break;
229  case kX: ind_mod.x = value_; break;
230  default: // kN or any other value is not allowed (doesn't make sense
231  // to change the minibatch index in this way).
232  KALDI_ERR << "Invalid variable name";
233  }
234  return src_->MapToInput(ind_mod);
235 }
236 
238  return new ReplaceIndexForwardingDescriptor(src_->Copy(),
239  variable_name_, value_);
240 
241 }
242 
244  std::ostream &os,
245  const std::vector<std::string> &node_names) const {
246  os << "ReplaceIndex(";
247  src_->WriteConfig(os, node_names);
248  KALDI_ASSERT(variable_name_ == kT || variable_name_ == kX);
249  os << ", " << (variable_name_ == kT ? "t" : "x") << ", "
250  << value_ << ")";
251 }
252 
254  return new OptionalSumDescriptor(src_->Copy());
255 }
256 
258  const Index &ind,
259  std::vector<Cindex> *dependencies) const {
260  src_->GetDependencies(ind, dependencies);
261 }
262 
264  std::ostream &os,
265  const std::vector<std::string> &node_names) const {
266  os << "IfDefined(";
267  src_->WriteConfig(os, node_names);
268  os << ")";
269 }
270 
272  return src_->Dim(nnet);
273 }
274 
276  BaseFloat ans = src_->GetScaleForNode(node_index);
277  if (node_index < 0 && ans != 0.0) {
278  // node_index < 0 means that the user is querying about the scale this
279  // expression puts on the constant value. If there is a nonzero scale
280  // (i.e. a Const() expression) inside an IfDefined() expression, which
281  // is what OptionalSumDescriptor handles, then it is an error: the user
282  // is trying to code something that we do not currently support.
283  KALDI_ERR << "Illegal combination of IfDefined() expression and "
284  "Const() expression encountered.";
285  }
286  return ans;
287 }
288 
290  std::vector<int32> *node_indexes) const {
291  src_->GetNodeDependencies(node_indexes);
292 }
293 
295  return new SimpleSumDescriptor(src_->Copy());
296 }
297 
299  std::vector<Cindex> *dependencies) const {
300  dependencies->push_back(src_->MapToInput(ind));
301 }
302 
304  const Index &ind,
305  const CindexSet &cindex_set,
306  std::vector<Cindex> *used_inputs) const {
307  Cindex c = src_->MapToInput(ind);
308  bool src_present = cindex_set(c);
309  if (src_present && used_inputs != NULL)
310  used_inputs->push_back(c);
311  return src_present;
312 }
313 
315  std::ostream &os,
316  const std::vector<std::string> &node_names) const {
317  src_->WriteConfig(os, node_names);
318 }
319 
320 int32 SimpleSumDescriptor::Dim(const Nnet &nnet) const {
321  return src_->Dim(nnet);
322 }
323 
325  if (node_index >= 0) return src_->GetScaleForNode(node_index);
326  else return 0.0; // scale of constant term, which does not appear in
327  // ForwardingDescriptors, hence 0.0.
328 }
329 
331  std::vector<int32> *node_indexes) const {
332  src_->GetNodeDependencies(node_indexes);
333 }
334 
336  if (node_index < 0) return value_;
337  else return std::numeric_limits<BaseFloat>::infinity();
338 }
339 
341  std::ostream &os, const std::vector<std::string> &node_names) const {
342  os << "Const(" << value_ << ", " << dim_ << ')';
343 }
344 
346  return new ConstantSumDescriptor(value_, dim_);
347 }
348 
350  int32 dim):
351  value_(value), dim_(dim) {
352  KALDI_ASSERT(dim > 0 && (value - value == 0.0));
353 }
354 
356  const Index &ind, std::vector<Cindex> *dependencies) const {
357  src1_->GetDependencies(ind, dependencies);
358  src2_->GetDependencies(ind, dependencies);
359 }
360 
362  const Index &ind,
363  const CindexSet &cindex_set,
364  std::vector<Cindex> *used_inputs) const {
365  std::vector<Cindex> src1_inputs, src2_inputs;
366  bool r = (used_inputs != NULL);
367  bool src1_computable = src1_->IsComputable(ind, cindex_set,
368  r ? &src1_inputs: NULL),
369  src2_computable = src2_->IsComputable(ind, cindex_set,
370  r ? &src2_inputs : NULL);
371  if (op_ == kSumOperation) {
372  if (src1_computable && src2_computable) {
373  if (r) {
374  used_inputs->insert(used_inputs->end(),
375  src1_inputs.begin(), src1_inputs.end());
376  used_inputs->insert(used_inputs->end(),
377  src2_inputs.begin(), src2_inputs.end());
378  }
379  return true;
380  } else {
381  return false;
382  }
383  } else {
384  KALDI_ASSERT(op_ == kFailoverOperation);
385  if (src1_computable) {
386  if (r)
387  used_inputs->insert(used_inputs->end(),
388  src1_inputs.begin(), src1_inputs.end());
389  return true;
390  } else if (src2_computable) {
391  if (r)
392  used_inputs->insert(used_inputs->end(),
393  src2_inputs.begin(), src2_inputs.end());
394  return true;
395  } else {
396  return false;
397  }
398  }
399 }
400 
401 int32 BinarySumDescriptor::Dim(const Nnet &nnet) const {
402  int32 dim1 = src1_->Dim(nnet), dim2 = src2_->Dim(nnet);
403  if (dim1 != dim2)
404  KALDI_ERR << "Neural net contains " << (op_ == kSumOperation ? "Sum" :
405  "Failover")
406  << " expression with inconsistent dimension: " << dim1
407  << " vs. " << dim2;
408  return dim1;
409 }
410 
412  BaseFloat ans1 = src1_->GetScaleForNode(node_index),
413  ans2 = src2_->GetScaleForNode(node_index);
414  bool ans1_valid = (ans1 - ans1 == 0),
415  ans2_valid = (ans2 - ans2 == 0); // Test for infinity.
416  if (node_index < 0) { // the query is about the constant offset, not for a
417  // specific node.
418  KALDI_ASSERT(ans1_valid && ans2_valid);
419  if (op_ == kSumOperation) {
420  // For a sum operation, if there were more than one Const(..) expression,
421  // they would logically add together (even though it would be redundant to
422  // write such a thing).
423  return ans1 + ans2;
424  } else if (ans1 != ans2) {
425  KALDI_ERR << "Illegal combination of Failover operation with Const() "
426  "expression encountered in Descriptor (this is not supported).";
427  }
428  }
429  if (ans1_valid && ans2_valid && ans1 != ans2) {
430  // this would be a code error so don't print a very informative message.
431  KALDI_ERR << "Inconsistent value for sum descriptor: for node "
432  << node_index << ", it can have scales "
433  << ans1 << " vs. " << ans2 << " (you have used unsupported "
434  "combinations of descriptors).";
435  }
436  if (!ans2_valid) return ans1;
437  else return ans2;
438 }
439 
441  std::vector<int32> *node_indexes) const {
442  src1_->GetNodeDependencies(node_indexes);
443  src2_->GetNodeDependencies(node_indexes);
444 }
445 
447  return Lcm(src1_->Modulus(), src2_->Modulus());
448 }
449 
451  return new BinarySumDescriptor(op_, src1_->Copy(), src2_->Copy());
452 }
453 
455  std::ostream &os,
456  const std::vector<std::string> &node_names) const {
457  KALDI_ASSERT(op_ == kSumOperation || op_ == kFailoverOperation);
458  if (op_ == kSumOperation) os << "Sum(";
459  if (op_ == kFailoverOperation) os << "Failover(";
460  src1_->WriteConfig(os, node_names);
461  os << ", ";
462  src2_->WriteConfig(os, node_names);
463  os << ")";
464 }
465 
467  int32 ans = src_.size();;
468  for (size_t i = 0; i < src_.size(); i++)
469  ans = Lcm(ans, src_[i]->Modulus());
470  return ans;
471 }
472 
474  int32 node_index) const {
475  BaseFloat inf = std::numeric_limits<BaseFloat>::infinity(),
476  ans = inf;
477  for (size_t i = 0; i < src_.size(); i++) {
478  BaseFloat this_ans = src_[i]->GetScaleForNode(node_index);
479  if (this_ans != inf) {
480  if (ans != inf && ans != this_ans)
481  KALDI_ERR << "Invalid Descriptor encountered: for node-index "
482  << node_index << ", got two different scales "
483  << this_ans << " vs. " << ans;
484  ans = this_ans;
485  }
486  }
487  return ans;
488 }
489 
490 
491 bool Descriptor::Parse(const std::vector<std::string> &node_names,
492  const std::string **next_token) {
493  GeneralDescriptor *gen_desc;
494  try {
495  gen_desc = GeneralDescriptor::Parse(node_names,
496  next_token);
497  } catch (...) {
498  return false;
499  }
500  if (**next_token != "end of input")
501  KALDI_ERR << "Parsing Descriptor, expected end of input but got "
502  << "'" << **next_token << "'";
503  Descriptor *desc = gen_desc->ConvertToDescriptor();
504  *this = *desc;
505  delete desc;
506  delete gen_desc;
507  return true;
508 }
509 
510 void Descriptor::WriteConfig(std::ostream &os,
511  const std::vector<std::string> &node_names) const {
512  KALDI_ASSERT(parts_.size() > 0);
513  if (parts_.size() == 1)
514  parts_[0]->WriteConfig(os, node_names);
515  else {
516  os << "Append(";
517  for (size_t i = 0; i < parts_.size(); i++) {
518  parts_[i]->WriteConfig(os, node_names);
519  if (i + 1 < parts_.size())
520  os << ", ";
521  }
522  os << ")";
523  }
524 }
526  for (size_t i = 0; i < parts_.size(); i++)
527  delete parts_[i];
528  parts_.clear();
529 }
530 
531 int32 Descriptor::Dim(const Nnet &nnet) const {
532  int32 num_parts = parts_.size();
533  int32 dim = 0;
534  for (int32 part = 0; part < num_parts; part++)
535  dim += parts_[part]->Dim(nnet);
536  KALDI_ASSERT(dim > 0);
537  return dim;
538 }
539 
540 
542  Destroy();
543  for (size_t i = 0; i < other.parts_.size(); i++)
544  parts_.push_back(other.parts_[i]->Copy());
545  return *this;
546 }
547 
549  int32 ans = 1;
550  for (size_t i = 0; i < parts_.size(); i++)
551  ans = Lcm(ans, parts_[i]->Modulus());
552  return ans;
553 }
554 
555 
557  const CindexSet &cindex_set,
558  std::vector<Cindex> *input_terms) const {
559  if (input_terms)
560  input_terms->clear();
561  for (size_t i = 0; i < parts_.size(); i++) {
562  // if any of the parts is not computable, the whole is not computable.
563  if (!parts_[i]->IsComputable(ind, cindex_set, input_terms)) {
564  if (input_terms)
565  input_terms->clear();
566  return false;
567  }
568  }
569  return true;
570 }
571 
573  KALDI_ASSERT(static_cast<size_t>(n) < parts_.size());
574  return *(parts_[n]);
575 }
576 
577 void Descriptor::GetNodeDependencies(std::vector<int32> *node_indexes) const {
578  node_indexes->clear();
579  for (size_t i = 0; i < parts_.size(); i++)
580  parts_[i]->GetNodeDependencies(node_indexes);
581 }
582 
583 
584 // static
586  const std::vector<std::string> &node_names,
587  const std::string **next_token) {
588 
589  DescriptorType t;
590  if (**next_token == "Append") {
591  t = kAppend;
592  } else if (**next_token == "Sum") {
593  t = kSum;
594  } else if (**next_token == "Failover") {
595  t = kFailover;
596  } else if (**next_token == "IfDefined") {
597  t = kIfDefined;
598  } else if (**next_token == "Offset") {
599  t = kOffset;
600  } else if (**next_token == "Switch") {
601  t = kSwitch;
602  } else if (**next_token == "Scale") {
603  t = kScale;
604  } else if (**next_token == "Const") {
605  t = kConst;
606  } else if (**next_token == "Round") {
607  t = kRound;
608  } else if (**next_token == "ReplaceIndex") {
609  t = kReplaceIndex;
610  } else {
611  // what we read wasn't a reserved name like Offset, etc.
612  // We expect a node name in that case.
613  for (size_t i = 0; i < node_names.size(); i++) {
614  if (**next_token == node_names[i]) {
615  GeneralDescriptor *ans = new GeneralDescriptor(kNodeName, i);
616  (*next_token)++;
617  return ans;
618  }
619  }
620  KALDI_ERR << "Expected a Descriptor, got instead "
621  << **next_token;
622  t = kNodeName; // suppress compiler warning.
623  }
624  (*next_token)++;
625  ExpectToken("(", "Descriptor", next_token);
626  GeneralDescriptor *ans = new GeneralDescriptor(t);
627  switch (t) {
628  case kAppend: case kSum: case kSwitch:
629  ans->ParseAppendOrSumOrSwitch(node_names, next_token); break;
630  case kFailover: ans->ParseFailover(node_names, next_token); break;
631  case kIfDefined: ans->ParseIfDefined(node_names, next_token); break;
632  case kOffset: ans->ParseOffset(node_names, next_token); break;
633  case kRound: ans->ParseRound(node_names, next_token); break;
634  case kReplaceIndex: ans->ParseReplaceIndex(node_names, next_token); break;
635  case kScale: ans->ParseScale(node_names, next_token); break;
636  case kConst: ans->ParseConst(node_names, next_token); break;
637  default:
638  KALDI_ERR << "Code error";
639  }
640  return ans;
641 }
642 
644  const std::vector<std::string> &node_names,
645  const std::string **next_token) {
646  descriptors_.push_back(Parse(node_names, next_token));
647  while (true) {
648  if (**next_token == ")") {
649  (*next_token)++;
650  return;
651  } else if (**next_token == ",") {
652  (*next_token)++;
653  descriptors_.push_back(Parse(node_names, next_token));
654  } else {
655  KALDI_ERR << "Expected ',' or ')', got "
656  << **next_token;
657  }
658  }
659 }
660 
662  const std::vector<std::string> &node_names,
663  const std::string **next_token) {
664  descriptors_.push_back(Parse(node_names, next_token));
665  ExpectToken(")", "IfDefined", next_token);
666 }
667 
669  const std::vector<std::string> &node_names,
670  const std::string **next_token) {
671  descriptors_.push_back(Parse(node_names, next_token));
672  ExpectToken(",", "Failover", next_token);
673  descriptors_.push_back(Parse(node_names, next_token));
674  ExpectToken(")", "Failover", next_token);
675 }
676 
678  const std::vector<std::string> &node_names,
679  const std::string **next_token) {
680  if (!ConvertStringToReal(**next_token, &alpha_)) {
681  KALDI_ERR << "Parsing Scale() in descriptor: expected floating-point scale"
682  ", got: " << **next_token;
683  }
684  (*next_token)++; // Consume the float.
685  ExpectToken(",", "Scale", next_token);
686  descriptors_.push_back(Parse(node_names, next_token));
687  ExpectToken(")", "Scale", next_token);
688 }
689 
691  const std::vector<std::string> &node_names,
692  const std::string **next_token) {
693  if (!ConvertStringToReal(**next_token, &alpha_)) {
694  KALDI_ERR << "Parsing Const() in descriptor: expected floating-point value"
695  ", got: " << **next_token;
696  }
697  (*next_token)++; // Consume the float.
698  ExpectToken(",", "Const", next_token);
699  if (!ConvertStringToInteger(**next_token, &value1_) ||
700  value1_ <= 0) {
701  KALDI_ERR << "Parsing Const() in descriptor: expected nonnegative integer, "
702  "got: " << **next_token;
703  }
704  (*next_token)++; // Consume the int.
705  ExpectToken(")", "Const", next_token);
706 }
707 
708 
709 
711  const std::vector<std::string> &node_names,
712  const std::string **next_token) {
713  descriptors_.push_back(Parse(node_names, next_token));
714  ExpectToken(",", "Offset", next_token);
715  value1_ = ReadIntegerToken("Offset", next_token);
716  if (**next_token == ",") {
717  (*next_token)++;
718  value2_ = ReadIntegerToken("Offset", next_token);
719  } else {
720  value2_ = 0;
721  }
722  ExpectToken(")", "Offset", next_token);
723 }
724 
725 
727  const std::vector<std::string> &node_names,
728  const std::string **next_token) {
729  descriptors_.push_back(Parse(node_names, next_token));
730  ExpectToken(",", "Round", next_token);
731  value1_ = ReadIntegerToken("Round", next_token);
732  ExpectToken(")", "Round", next_token);
733 }
734 
735 
737  const std::vector<std::string> &node_names,
738  const std::string **next_token) {
739  descriptors_.push_back(Parse(node_names, next_token));
740  ExpectToken(",", "ReplaceIndex", next_token);
741  if (**next_token == "t") {
743  (*next_token)++;
744  } else if (**next_token == "x") {
746  (*next_token)++;
747  } else {
748  KALDI_ERR << "Expected 't' or 'x', got " << **next_token;
749  }
750  ExpectToken(",", "ReplaceIndex", next_token);
751  value2_ = ReadIntegerToken("Replace", next_token);
752  ExpectToken(")", "ReplaceIndex", next_token);
753 }
754 
755 
757  int32 ans = 0;
758  switch (descriptor_type_) {
759  case kNodeName: ans = 1; break;
760  case kConst: ans = 1; break;
761  case kAppend: {
762  for (size_t i = 0; i < descriptors_.size(); i++)
763  ans += descriptors_[i]->NumAppendTerms();
764  break;
765  }
766  default:
767  KALDI_ASSERT(descriptors_.size() > 0);
768  ans = descriptors_[0]->NumAppendTerms();
769  for (size_t i = 1; i < descriptors_.size(); i++)
770  KALDI_ASSERT(descriptors_[i]->NumAppendTerms() == ans);
771  }
772  return ans;
773 }
774 
776  switch (descriptor_type_) {
777  case kNodeName:
778  KALDI_ASSERT(term == 0);
779  return new GeneralDescriptor(kNodeName, value1_);
780  case kAppend: {
781  int32 cur_term = term;
782  for (size_t i = 0; i < descriptors_.size(); i++) {
783  int32 this_num_terms = descriptors_[i]->NumAppendTerms();
784  if (cur_term < this_num_terms)
785  return descriptors_[i]->GetAppendTerm(cur_term);
786  else
787  cur_term -= this_num_terms;
788  }
789  KALDI_ERR << "Code error, getting append term.";
790  return NULL; // avoid compiler warning
791  }
792  default: {
793  GeneralDescriptor *ans = new GeneralDescriptor(descriptor_type_,
794  value1_, value2_,
795  alpha_);
796  ans->descriptors_.resize(descriptors_.size());
797  for (size_t i = 0; i < descriptors_.size(); i++)
798  ans->descriptors_[i] = descriptors_[i]->GetAppendTerm(term);
799  return ans;
800  }
801  }
802 }
803 
804 
805 // this is only called at the top level.
807  int32 num_terms = NumAppendTerms();
808  KALDI_ASSERT(num_terms > 0);
809  if (num_terms == 1) {
810  return GetAppendTerm(0);
811  } else {
812  GeneralDescriptor *ans = new GeneralDescriptor(kAppend);
813  ans->descriptors_.resize(num_terms);
814  for (size_t i = 0; i < num_terms; i++) {
815  ans->descriptors_[i] = GetAppendTerm(i);
816  }
817  return ans;
818  }
819 }
820 
821 
822 // static
824  bool changed = false;
825  switch (desc->descriptor_type_) {
826  case kOffset: { // this block combines Offset(Offset(x, ..), ..).
827  KALDI_ASSERT(desc->descriptors_.size() == 1);
828  GeneralDescriptor *child = desc->descriptors_[0];
829  if (child->descriptor_type_ == kOffset) {
830  KALDI_ASSERT(child->descriptors_.size() == 1);
831  GeneralDescriptor *grandchild = child->descriptors_[0];
832  desc->value1_ += child->value1_;
833  desc->value2_ += child->value2_;
834  child->descriptors_.clear(); // avoid delete in destructor.
835  delete child;
836  desc->descriptors_[0] = grandchild;
837  changed = true;
838  } else if (desc->value1_ == 0 && desc->value2_ == 0) {
839  // remove redundant Offset expression like Offset(x, 0).
840  desc->descriptors_.swap(child->descriptors_);
841  desc->descriptor_type_ = child->descriptor_type_;
842  desc->value1_ = child->value1_;
843  desc->value2_ = child->value2_;
844  desc->alpha_ = child->alpha_;
845  child->descriptors_.clear(); // avoid delete in destructor.
846  delete child;
847  changed = true;
848  break; // break from the switch ('desc' is no longer of type
849  // kOffset)', so we don't want to carry through.
850  }
851  }
852  // ... and continue through to the next case statement.
853  case kSwitch: case kRound: case kReplaceIndex: { // ..and kOffset:
854  KALDI_ASSERT(desc->descriptors_.size() >= 1);
855  GeneralDescriptor *child = desc->descriptors_[0];
856  // If child->descriptor_type_ == kAppend, it would be code error since we
857  // already did NormalizeAppend().
858  KALDI_ASSERT(child->descriptor_type_ != kAppend);
859  if (child->descriptor_type_ == kSum ||
860  child->descriptor_type_ == kFailover ||
861  child->descriptor_type_ == kIfDefined) {
862  if (desc->descriptors_.size() > 1) {
863  KALDI_ASSERT(desc->descriptor_type_ == kSwitch);
864  KALDI_ERR << "Sum(), Failover() or IfDefined() expression inside Switch(), "
865  << "we can't currently normalize this.";
866  }
867  // this is a forbidden case of a sum descriptor inside a forwarding
868  // descriptor. we need to rearrange. E.g. Offset(Sum(x, y), 1) becomes
869  // Sum(Offset(x, 1), Offset(y, 1)).
870  for (size_t i = 0; i < child->descriptors_.size(); i++) {
871  GeneralDescriptor *grandchild = child->descriptors_[i];
872  GeneralDescriptor *modified_grandchild =
874  desc->value1_,
875  desc->value2_,
876  desc->alpha_);
877  // modified_grandchild takes ownership of grandchild.
878  modified_grandchild->descriptors_.push_back(grandchild);
879  child->descriptors_[i] = modified_grandchild;
880  }
881  // copy all members from child to desc.
882  desc->descriptor_type_ = child->descriptor_type_;
883  desc->value1_ = child->value1_;
884  desc->value2_ = child->value2_;
885  desc->descriptors_.swap(child->descriptors_);
886  child->descriptors_.clear(); // avoid delete in destructor of 'child'
887  delete child;
888  changed = true;
889  }
890  break;
891  }
892  case kSum: {
893  KALDI_ASSERT(!desc->descriptors_.empty());
894  if (desc->descriptors_.size() == 1) {
895  // convert Sum(x) to just x.
896  GeneralDescriptor *child = desc->descriptors_[0];
897  desc->descriptor_type_ = child->descriptor_type_;
898  desc->descriptors_.swap(child->descriptors_);
899  desc->value1_ = child->value1_;
900  desc->value2_ = child->value2_;
901  desc->alpha_ = child->alpha_;
902  child->descriptors_.clear(); // avoid delete in destructor.
903  delete child;
904  changed = true;
905  } else if (desc->descriptors_.size() > 2) {
906  // convert Sum(a, b, c, ...) to Sum(a, Sum(b, c, ...)).
907  GeneralDescriptor *new_child = new GeneralDescriptor(kSum);
908  // assign b, c, .. to the descriptors of new_child.
909  new_child->descriptors_.insert(new_child->descriptors_.begin(),
910  desc->descriptors_.begin() + 1,
911  desc->descriptors_.end());
912  desc->descriptors_.erase(desc->descriptors_.begin() + 1,
913  desc->descriptors_.end());
914  desc->descriptors_.push_back(new_child);
915  changed = true;
916  }
917  break;
918  }
919  case kScale: {
920  KALDI_ASSERT(desc->descriptors_.size() == 1);
921  GeneralDescriptor *child = desc->descriptors_[0];
922  if (child->descriptor_type_ == kOffset ||
923  child->descriptor_type_ == kReplaceIndex ||
924  child->descriptor_type_ == kRound) {
925  // push the Scale() inside those expressions.
926  std::swap(desc->descriptor_type_, child->descriptor_type_);
927  std::swap(desc->alpha_, child->alpha_);
928  std::swap(desc->value1_, child->value1_);
929  std::swap(desc->value2_, child->value2_);
930  changed = true;
931  } else if (child->descriptor_type_ == kSum) {
932  // Push the Scale() inside the sum expression.
933  desc->descriptors_.clear();
934  for (size_t i = 0; i < child->descriptors_.size(); i++) {
935  GeneralDescriptor *new_child =
936  new GeneralDescriptor(kScale, -1, -1, desc->alpha_);
937  new_child->descriptors_.push_back(child->descriptors_[i]);
938  desc->descriptors_.push_back(new_child);
939  }
940  desc->descriptor_type_ = kSum;
941  desc->alpha_ = 0.0;
942  child->descriptors_.clear(); // prevent them being freed.
943  delete child;
944  changed = true;
945  } else if (child->descriptor_type_ == kScale) {
946  // Combine the 'scale' expressions.
947  KALDI_ASSERT(child->descriptors_.size() == 1);
948  GeneralDescriptor *grandchild = child->descriptors_[0];
949  desc->alpha_ *= child->alpha_;
950  desc->descriptors_[0] = grandchild;
951  child->descriptors_.clear(); // prevent them being freed.
952  delete child;
953  changed = true;
954  } else if (child->descriptor_type_ != kNodeName) {
955  KALDI_ERR << "Unhandled case encountered when normalizing Descriptor; "
956  "you can work around this by pushing Scale() inside "
957  "other expressions.";
958  }
959  break;
960  }
961  default: { } // empty statement
962  }
963  // ... and recurse.
964  for (size_t i = 0; i < desc->descriptors_.size(); i++)
965  changed = changed || Normalize(desc->descriptors_[i]);
966  return changed;
967 }
968 
970  GeneralDescriptor *ans = NormalizeAppend();
971  while (Normalize(ans)); // keep normalizing as long as it changes.
972  return ans;
973 }
974 
975 void GeneralDescriptor::Print(const std::vector<std::string> &node_names,
976  std::ostream &os) {
977  switch (descriptor_type_) {
978  // first handle all the expressions of the form "Operator(<desc1>, ... <descN>)".
979  case kAppend: os << "Append("; break;
980  case kSum: os << "Sum("; break;
981  case kFailover: os << "Failover("; break;
982  case kIfDefined: os << "IfDefined("; break;
983  case kSwitch: os << "Switch("; break;
984  // Scale() ends in a descriptor, so we also break and let the generic code
985  // handle that.
986  case kScale: os << "Scale(" << alpha_ << ", "; break;
987  // now handle the exceptions.
988  case kOffset: case kRound: {
989  os << "Offset(";
990  KALDI_ASSERT(descriptors_.size() == 1);
991  descriptors_[0]->Print(node_names, os);
992  os << ", " << value1_;
993  if (descriptor_type_ == kOffset && value2_ != 0) os << ", " << value2_;
994  os << ")";
995  return;
996  }
997  case kReplaceIndex: {
998  os << "ReplaceIndex(";
999  KALDI_ASSERT(descriptors_.size() == 1);
1000  descriptors_[0]->Print(node_names, os);
1003  if (value1_ == int32(ReplaceIndexForwardingDescriptor::kT)) {
1004  os << ", t, ";
1005  } else {
1006  os << ", x, ";
1007  }
1008  os << value2_ << ")";
1009  return;
1010  }
1011  case kNodeName: {
1012  KALDI_ASSERT(static_cast<size_t>(value1_) < node_names.size());
1013  os << node_names[value1_];
1014  return;
1015  }
1016  case kConst: {
1017  os << "Const(" << alpha_ << ", " << value1_ << ")";
1018  return;
1019  }
1020  }
1021  for (size_t i = 0; i < descriptors_.size(); i++) {
1022  if (i > 0) os << ", ";
1023  descriptors_[i]->Print(node_names, os);
1024  }
1025  os << ")";
1026 }
1027 
1028 
1030  GeneralDescriptor *normalized = GetNormalizedDescriptor();
1031  std::vector<SumDescriptor*> sum_descriptors;
1032  if (normalized->descriptor_type_ == kAppend) {
1033  for (size_t i = 0; i < normalized->descriptors_.size(); i++)
1034  sum_descriptors.push_back(
1035  normalized->descriptors_[i]->ConvertToSumDescriptor());
1036  } else {
1037  sum_descriptors.push_back(normalized->ConvertToSumDescriptor());
1038  }
1039  Descriptor *ans = new Descriptor(sum_descriptors);
1040  delete normalized;
1041  return ans;
1042 }
1043 
1045  KALDI_ASSERT(descriptor_type_ != kAppend &&
1046  "Badly normalized descriptor");
1047  switch (descriptor_type_) {
1048  case kAppend:
1049  KALDI_ERR << "Badly normalized descriptor";
1050  case kSum: case kFailover: {
1051  KALDI_ASSERT(descriptors_.size() == 2 && "Bad descriptor");
1052  return new BinarySumDescriptor(
1053  descriptor_type_ == kSum ?
1056  descriptors_[0]->ConvertToSumDescriptor(),
1057  descriptors_[1]->ConvertToSumDescriptor());
1058  }
1059  case kIfDefined: {
1060  KALDI_ASSERT(descriptors_.size() == 1 && "Bad descriptor");
1061  return new OptionalSumDescriptor(
1062  descriptors_[0]->ConvertToSumDescriptor());
1063  }
1064  case kConst: {
1065  KALDI_ASSERT(descriptors_.empty() && value1_ > 0);
1066  return new ConstantSumDescriptor(alpha_, value1_);
1067  }
1068  default: {
1069  return new SimpleSumDescriptor(this->ConvertToForwardingDescriptor());
1070  }
1071  }
1072 }
1073 
1074 
1076  switch (this->descriptor_type_) {
1077  case kNodeName: return new SimpleForwardingDescriptor(value1_);
1078  case kOffset: {
1079  KALDI_ASSERT(descriptors_.size() == 1 && "bad descriptor");
1080  return new OffsetForwardingDescriptor(
1081  descriptors_[0]->ConvertToForwardingDescriptor(),
1082  Index(0, value1_, value2_));
1083  }
1084  case kSwitch: {
1085  std::vector<ForwardingDescriptor*> descriptors;
1086  for (size_t i = 0; i < descriptors_.size(); i++)
1087  descriptors.push_back(descriptors_[i]->ConvertToForwardingDescriptor());
1088  return new SwitchingForwardingDescriptor(descriptors);
1089  }
1090  case kRound: {
1091  KALDI_ASSERT(descriptors_.size() == 1 && "bad descriptor");
1092  return new RoundingForwardingDescriptor(
1093  descriptors_[0]->ConvertToForwardingDescriptor(),
1094  value1_);
1095  }
1096  case kReplaceIndex: {
1097  KALDI_ASSERT(descriptors_.size() == 1 && "bad descriptor");
1101  descriptors_[0]->ConvertToForwardingDescriptor(),
1105  value2_);
1106  }
1107  case kScale: {
1108  if (!(descriptors_.size() == 1 &&
1109  descriptors_[0]->descriptor_type_ == kNodeName)) {
1110  KALDI_ERR << "Invalid combination of Scale() expression and other "
1111  "expressions encountered in descriptor.";
1112  }
1113  return new SimpleForwardingDescriptor(descriptors_[0]->value1_,
1114  alpha_);
1115  }
1116  case kConst: {
1117  KALDI_ERR << "Error in Descriptor: Const() "
1118  "appeared too deep in the expression.";
1119  }
1120  default:
1121  KALDI_ERR << "Invalid descriptor type (failure in normalization?)";
1122  return NULL;
1123  }
1124 }
1125 
1126 
1127 } // namespace nnet3
1128 } // namespace kaldi
virtual ForwardingDescriptor * Copy() const
void GetDependencies(const Index &index, std::vector< Cindex > *used_inputs) const
This function exists to enable us to manage optional dependencies, i.e.
This code computes Goodness of Pronunciation (GOP) and extracts phone-level pronunciation feature for...
Definition: chain.dox:20
virtual int32 Dim(const Nnet &nnet) const
virtual Cindex MapToInput(const Index &ind) const
bool ConvertStringToInteger(const std::string &str, Int *out)
Converts a string into an integer via strtoll and returns false if there was any kind of problem (i...
Definition: text-utils.h:118
void GetNodeDependencies(std::vector< int32 > *node_indexes) const
virtual BaseFloat GetScaleForNode(int32 node_index) const
This function returns the scale on the node-index &#39;node_index&#39; when it appears in expressions inside ...
virtual BaseFloat GetScaleForNode(int32 node_index) const
This function returns the scale on the node-index &#39;node_index&#39; when it appears in expressions inside ...
static bool Normalize(GeneralDescriptor *ptr)
SumDescriptor * ConvertToSumDescriptor() const
This is the case of class SumDescriptor, in which we contain just one term, and that term is optional...
ConstantSumDescriptor(BaseFloat value, int32 dim)
void ParseScale(const std::vector< std::string > &node_names, const std::string **next_token)
static int32 ReadIntegerToken(const std::string &what_we_are_parsing, const std::string **next_token)
virtual void GetDependencies(const Index &ind, std::vector< Cindex > *dependencies) const
Given an Index at the output of this Descriptor, append to "dependencies" a list of Cindexes that des...
virtual int32 Dim(const Nnet &nnet) const
virtual ForwardingDescriptor * Copy() const
bool Parse(const std::vector< std::string > &node_names, const std::string **next_token)
virtual void GetNodeDependencies(std::vector< int32 > *node_indexes) const
This function appends to "node_indexes" all the node indexes.
This class is only used when parsing Descriptors.
void ParseConst(const std::vector< std::string > &node_names, const std::string **next_token)
void ParseRound(const std::vector< std::string > &node_names, const std::string **next_token)
std::vector< GeneralDescriptor * > descriptors_
This is an alternative base-case of SumDescriptor (an alternative to SimpleSumDescriptor) which repre...
virtual bool IsComputable(const Index &ind, const CindexSet &cindex_set, std::vector< Cindex > *used_inputs) const
This function exists to enable us to manage optional dependencies, i.e.
SimpleForwardingDescriptor is the base-case of ForwardingDescriptor, consisting of a source node in t...
virtual void WriteConfig(std::ostream &os, const std::vector< std::string > &node_names) const
virtual void WriteConfig(std::ostream &os, const std::vector< std::string > &node_names) const
The written form is: `Const(, <dimension>)`, e.g.
virtual int32 Modulus() const
This function is for use in things like clockwork RNNs, where shifting the time of the inputs and out...
virtual void WriteConfig(std::ostream &os, const std::vector< std::string > &node_names) const
std::vector< SumDescriptor * > parts_
BinarySumDescriptor can represent either A + B, or (A if defined, else B).
void swap(basic_filebuf< CharT, Traits > &x, basic_filebuf< CharT, Traits > &y)
kaldi::int32 int32
virtual void GetDependencies(const Index &ind, std::vector< Cindex > *dependencies) const
Given an Index at the output of this Descriptor, append to "dependencies" a list of Cindexes that des...
void ParseReplaceIndex(const std::vector< std::string > &node_names, const std::string **next_token)
virtual void GetNodeDependencies(std::vector< int32 > *node_indexes) const
This function appends to "node_indexes" all the node indexes.
void ParseAppendOrSumOrSwitch(const std::vector< std::string > &node_names, const std::string **next_token)
virtual Cindex MapToInput(const Index &index) const
static std::string ParsingContext(const std::string *token_ptr)
For use in clockwork RNNs and the like, this forwarding-descriptor rounds the time-index t down to th...
virtual void WriteConfig(std::ostream &os, const std::vector< std::string > &node_names) const
written form is: if required_ == true, "<written-form-of-src>" else "IfDefined(<written-form-of-src>)...
static GeneralDescriptor * Parse(const std::vector< std::string > &node_names, const std::string **next_token)
virtual SumDescriptor * Copy() const
struct Index is intended to represent the various indexes by which we number the rows of the matrices...
Definition: nnet-common.h:44
I Lcm(I m, I n)
Returns the least common multiple of two integers.
Definition: kaldi-math.h:318
virtual int32 Dim(const Nnet &nnet) const
std::pair< int32, Index > Cindex
Definition: nnet-common.h:115
virtual Cindex MapToInput(const Index &ind) const
virtual void GetNodeDependencies(std::vector< int32 > *node_indexes) const
This function appends to "node_indexes" a list (not necessarily sorted or unique) of all the node ind...
virtual void WriteConfig(std::ostream &os, const std::vector< std::string > &node_names) const
written form is: if required_ == true, "<written-form-of-src>" else "IfDefined(<written-form-of-src>)...
This is an abstract base-class.
virtual void GetNodeDependencies(std::vector< int32 > *node_indexes) const
This function appends to "node_indexes" all the node indexes.
virtual BaseFloat GetScaleForNode(int32 node_index) const
This function returns the scale on the node-index &#39;node_index&#39; when it appears in expressions inside ...
const NetworkNode & GetNode(int32 node) const
returns const reference to a particular numbered network node.
Definition: nnet-nnet.h:146
virtual bool IsComputable(const Index &ind, const CindexSet &cindex_set, std::vector< Cindex > *used_inputs) const
This function exists to enable us to manage optional dependencies, i.e.
virtual void WriteConfig(std::ostream &os, const std::vector< std::string > &node_names) const
Written form is: if op_ == kSum then "Sum(<src1>, <src2>)"; if op_ == kFailover, then "Failover(<src1...
Chooses from different inputs based on the the time index modulo (the number of ForwardingDescriptors...
virtual BaseFloat GetScaleForNode(int32 node_index) const
This function returns the scale on the node-index &#39;node_index&#39; when it appears in expressions inside ...
int32 Dim(const Nnet &nnet) const
Definition: nnet-nnet.cc:33
static void ExpectToken(const std::string &token, const std::string &what_we_are_parsing, const std::string **next_token)
virtual int32 Dim(const Nnet &nnet) const
virtual ForwardingDescriptor * Copy() const
const SumDescriptor & Part(int32 n) const
returns the n&#39;th part.
struct rnnlm::@11::@12 n
void ParseFailover(const std::vector< std::string > &node_names, const std::string **next_token)
void ParseOffset(const std::vector< std::string > &node_names, const std::string **next_token)
void ParseIfDefined(const std::vector< std::string > &node_names, const std::string **next_token)
#define KALDI_ERR
Definition: kaldi-error.h:147
bool ConvertStringToReal(const std::string &str, T *out)
ConvertStringToReal converts a string into either float or double and returns false if there was any ...
Definition: text-utils.cc:238
This is the normal base-case of SumDescriptor which just wraps a ForwardingDescriptor.
virtual BaseFloat GetScaleForNode(int32 node_index) const
This function returns the scale on the node-index &#39;node_index&#39; when it appears in expressions inside ...
virtual Cindex MapToInput(const Index &ind) const
A ForwardingDescriptor describes how we copy data from another NetworkNode, or from multiple other Ne...
virtual void WriteConfig(std::ostream &os, const std::vector< std::string > &node_names) const
GeneralDescriptor * NormalizeAppend() const
ForwardingDescriptor * ConvertToForwardingDescriptor() const
bool IsComputable(const Index &ind, const CindexSet &cindex_set, std::vector< Cindex > *used_inputs) const
Has the same purpose and interface as the IsComputable function of the SumDescriptor function...
virtual BaseFloat GetScaleForNode(int32 node_index) const
This function returns the scale on the node-index &#39;node_index&#39; when it appears in expressions inside ...
Descriptor & operator=(const Descriptor &other)
Assignment operator.
virtual SumDescriptor * Copy() const
Offsets in &#39;t&#39; and &#39;x&#39; values of other ForwardingDescriptors.
void WriteConfig(std::ostream &os, const std::vector< std::string > &node_names) const
virtual BaseFloat GetScaleForNode(int32 node_index) const
This function returns the scale on the node-index &#39;node_index&#39; when it appears in expressions inside ...
virtual ForwardingDescriptor * Copy() const
#define KALDI_ASSERT(cond)
Definition: kaldi-error.h:185
This ForwardingDescriptor modifies the indexes (n, t, x) by replacing one of them (normally t) with a...
virtual Cindex MapToInput(const Index &ind) const
GeneralDescriptor * GetAppendTerm(int32 term) const
GeneralDescriptor * GetNormalizedDescriptor() const
virtual void WriteConfig(std::ostream &os, const std::vector< std::string > &node_names) const
virtual int32 Dim(const Nnet &nnet) const
virtual SumDescriptor * Copy() const
virtual void GetNodeDependencies(std::vector< int32 > *node_indexes) const
This function appends to "node_indexes" a list (not necessarily sorted or unique) of all the node ind...
void Print(const std::vector< std::string > &node_names, std::ostream &os)
virtual void GetNodeDependencies(std::vector< int32 > *node_indexes) const
This function appends to "node_indexes" all the node indexes.
virtual void GetNodeDependencies(std::vector< int32 > *node_indexes) const
This function appends to "node_indexes" a list (not necessarily sorted or unique) of all the node ind...
virtual void GetDependencies(const Index &ind, std::vector< Cindex > *dependencies) const
Given an Index at the output of this Descriptor, append to "dependencies" a list of Cindexes that des...
virtual void WriteConfig(std::ostream &os, const std::vector< std::string > &node_names) const
virtual BaseFloat GetScaleForNode(int32 node_index) const
This function returns the scale on the node-index &#39;node_index&#39; when it appears in expressions inside ...
virtual bool IsComputable(const Index &ind, const CindexSet &cindex_set, std::vector< Cindex > *used_inputs) const
This function exists to enable us to manage optional dependencies, i.e.
This file contains class definitions for classes ForwardingDescriptor, SumDescriptor and Descriptor...
virtual BaseFloat GetScaleForNode(int32 node_index) const
This function returns the scale on the node-index &#39;node_index&#39; when it appears in expressions inside ...
int32 Dim(const Nnet &nnet) const
virtual ForwardingDescriptor * Copy() const
virtual SumDescriptor * Copy() const
virtual void GetNodeDependencies(std::vector< int32 > *node_indexes) const
This function appends to "node_indexes" a list (not necessarily sorted or unique) of all the node ind...
virtual void GetNodeDependencies(std::vector< int32 > *node_indexes) const
This function appends to "node_indexes" all the node indexes.
void Copy(const CuMatrixBase< Real > &src, const CuArray< int32 > &copy_from_indices, CuMatrixBase< Real > *tgt)
Copies elements from src into tgt as given by copy_from_indices.
Definition: cu-math.cc:173