Antares Simulator
Power System Simulator
matrix.hxx
1 /*
2 ** Copyright 2007-2025, RTE (https://www.rte-france.com)
3 ** See AUTHORS.txt
4 ** SPDX-License-Identifier: MPL-2.0
5 ** This file is part of Antares-Simulator,
6 ** Adequacy and Performance assessment for interconnected energy networks.
7 **
8 ** Antares_Simulator is free software: you can redistribute it and/or modify
9 ** it under the terms of the Mozilla Public Licence 2.0 as published by
10 ** the Mozilla Foundation, either version 2 of the License, or
11 ** (at your option) any later version.
12 **
13 ** Antares_Simulator is distributed in the hope that it will be useful,
14 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ** Mozilla Public Licence 2.0 for more details.
17 **
18 ** You should have received a copy of the Mozilla Public Licence 2.0
19 ** along with Antares_Simulator. If not, see <https://opensource.org/license/mpl-2-0/>.
20 */
21 #ifndef __ANTARES_LIBS_ARRAY_MATRIX_HXX__
22 #define __ANTARES_LIBS_ARRAY_MATRIX_HXX__
23 
24 #include <cmath>
25 #include <cstdlib>
26 #include <utility>
27 
28 #include <yuni/yuni.h>
29 #include <yuni/core/string.h>
30 
31 #include <antares/io/statistics.h>
32 #include <antares/logs/logs.h>
33 
34 #include "matrix-to-buffer.h"
35 
36 #define ANTARES_MATRIX_CSV_COMMA "\t;,"
37 #define ANTARES_MATRIX_CSV_SEPARATORS "\t\r\n;,"
38 
39 namespace Antares
40 {
41 namespace // anonymous
42 {
43 template<class T>
44 class MatrixData final
45 {
46 public:
47  inline static void Init(T& data)
48  {
49  data = T();
50  }
51 
52  template<class U>
53  inline static void Copy(T& data, const U& value)
54  {
55  data = static_cast<T>(value);
56  }
57 
58  inline static void Copy(T&, const AnyString&)
59  {
60  // to avoid gcc warnings
61  // must never be called
62  logs.error() << "internal error: matrix data conversion";
63  }
64 };
65 
66 template<uint ChunkSizeT, bool ExpandableT>
67 class MatrixData<Yuni::CString<ChunkSizeT, ExpandableT>> final
68 {
69 public:
70  using StringT = Yuni::CString<ChunkSizeT, ExpandableT>;
71 
72 public:
73  inline static void Init(StringT& data)
74  {
75  data.clear();
76  }
77 
78  template<class U>
79  inline static void Copy(StringT& data, const U& value)
80  {
81  // we must avoid stupid static cast (stupid in this case)
82  data = value;
83  }
84 };
85 
86 template<class ReadWriteType>
87 class MatrixStringConverter final
88 {
89 public:
90  enum
91  {
92  direct = 0
93  };
94 
95 public:
96  inline static bool Do(const AnyString& str, ReadWriteType& out)
97  {
98  return str.to(out);
99  }
100 };
101 
102 template<>
103 class MatrixStringConverter<double> final
104 {
105 public:
106  enum
107  {
108  direct = 0
109  };
110 
111 public:
112  inline static bool Do(const AnyString& str, double& out)
113  {
114  char* pend;
115  out = ::strtod(str.c_str(), &pend);
116  return (NULL != pend and '\0' == *pend);
117  }
118 };
119 
120 template<>
121 class MatrixStringConverter<float> final
122 {
123 public:
124  enum
125  {
126  direct = 0
127  };
128 
129 public:
130  inline static bool Do(const AnyString& str, float& out)
131  {
132  char* pend;
133  out = static_cast<float>(::strtod(str.c_str(), &pend));
134  return (NULL != pend and '\0' == *pend);
135  }
136 };
137 
138 template<uint ChunkSizeT, bool ExpandableT>
139 class MatrixStringConverter<Yuni::CString<ChunkSizeT, ExpandableT>> final
140 {
141 public:
142  enum
143  {
144  direct = 1,
145  };
146 
147  using StringT = Yuni::CString<ChunkSizeT, ExpandableT>;
148 
149 public:
150  inline static bool Do(const AnyString& str, StringT& out)
151  {
152  out.assign(str);
153  return true;
154  }
155 };
156 
157 template<unsigned A, bool B>
158 Yuni::CString<A, B> trunc(Yuni::CString<A, B>& str)
159 {
160  return str;
161 }
162 
163 template<class T>
164 static T trunc(T& in)
165 {
166  return static_cast<T>(std::trunc(in));
167 }
168 
169 template<class T, class P>
170 class MatrixRound final
171 {
172 public:
173  static T Value(P value)
174  {
175  return static_cast<T>(trunc(value));
176  }
177 };
178 
179 template<class T>
180 class MatrixRound<T, double> final
181 {
182 public:
183  static T Value(double value)
184  {
185  return static_cast<T>(value);
186  }
187 };
188 
189 template<class T>
190 class MatrixRound<T, float> final
191 {
192 public:
193  static T Value(float value)
194  {
195  return static_cast<T>(value);
196  }
197 };
198 } // anonymous namespace
199 
200 template<class T, class ReadWriteT>
202  width(0),
203  height(0),
204  entry(nullptr),
205  jit(nullptr)
206 {
207 }
208 
209 template<class T, class ReadWriteT>
211  width(w),
212  height(h),
213  jit(nullptr)
214 {
215  if (0 == width or 0 == height)
216  {
217  entry = nullptr;
218  }
219  else
220  {
221  entry = new typename Antares::Memory::Stored<T>::Type[w + 1];
222  entry[w] = nullptr;
223 
224  for (uint i = 0; i != w; ++i)
225  {
226  Antares::Memory::Allocate<T>(entry[i], h);
227  }
228  }
229 }
230 
231 template<class T, class ReadWriteT>
233  width(rhs.width),
234  height(rhs.height),
235  jit(nullptr)
236 {
237  if (0 == width or 0 == height)
238  {
239  entry = nullptr;
240  width = 0;
241  height = 0;
242  }
243  else
244  {
245  entry = new typename Antares::Memory::Stored<T>::Type[width + 1];
246  entry[width] = nullptr;
247 
248  for (uint i = 0; i != rhs.width; ++i)
249  {
250  Antares::Memory::Allocate<T>(entry[i], height);
251  memcpy(entry[i], rhs.entry[i], sizeof(T) * height);
252  }
253  }
254 }
255 
256 template<class T, class ReadWriteT>
258 {
259  // use Matrix::operator=(Matrix&& rhs)
260  *this = std::move(rhs);
261 }
262 
263 template<class T, class ReadWriteT>
264 template<class U, class V>
266  width(0),
267  height(0),
268  entry(nullptr),
269  jit(nullptr)
270 {
271  copyFrom(rhs);
272 }
273 
274 template<class T, class ReadWriteT>
276 {
277  assert((JIT::enabled or (jit == NULL))
278  and "Internal variable jit is set but JIT is not globally enabled (overflow?)");
279  delete jit;
280 
281  if (entry)
282  {
283  for (uint i = 0; i != width; ++i)
284  {
286  }
287  delete[] entry;
288  }
289 }
290 
291 template<class T, class ReadWriteT>
293 {
294  for (uint i = 0; i != width; ++i)
295  {
296  ColumnType& column = entry[i];
297  (void)::memset((void*)column, 0, sizeof(T) * height);
298  }
299 }
300 
301 template<class T, class ReadWriteT>
303 {
304  if (width > 1)
305  {
306  ColumnType& first = entry[0];
307 
308  // add the values of each timeseries to the first one
309  for (uint i = 1; i != width; ++i)
310  {
311  ColumnType& column = entry[i];
312  for (uint j = 0; j != height; ++j)
313  {
314  first[j] += column[j];
315  }
316  }
317 
318  // average
319  double coeff = 1. / width;
320  if (roundValues)
321  {
322  for (uint j = 0; j != height; ++j)
323  {
324  const double d = first[j] * coeff;
325  first[j] = std::round(d);
326  }
327  }
328  else
329  {
330  for (uint j = 0; j != height; ++j)
331  {
332  first[j] *= coeff;
333  }
334  }
335 
336  // Release all timeseries no longer needed
337  for (uint i = 1; i != width; ++i)
338  {
339  Antares::Memory::Release(entry[i]);
340  }
341  // reset the width to 1
342  width = 1;
343  }
344 }
345 
346 template<class T, class ReadWriteT>
348 {
349  for (uint i = 0; i != width; ++i)
350  {
351  ColumnType& column = entry[i];
352 
353  for (uint j = 0; j != height; ++j)
354  {
355  column[j] = v;
356  }
357  }
358 }
359 
360 template<class T, class ReadWriteT>
362 {
363  for (uint i = 0; i != width; ++i)
364  {
365  ColumnType& column = entry[i];
366 
367  (void)::memset((void*)column, 0, sizeof(T) * height);
368 
369  column[i] = T(1);
370  }
371 }
372 
373 template<class T, class ReadWriteT>
374 bool Matrix<T, ReadWriteT>::forceReload(bool reload) const
375 {
377  return (reload) ? loadAllJITData() : true;
378 }
379 
380 template<class T, class ReadWriteT>
381 inline void Matrix<T, ReadWriteT>::reset(uint w, uint h, bool fixedSize)
382 {
383  resize(w, h, fixedSize);
384  zero();
385 }
386 
387 template<class T, class ReadWriteT>
388 bool Matrix<T, ReadWriteT>::internalLoadJITData(const AnyString& filename,
389  uint minWidth,
390  uint maxHeight,
391  uint options)
392 {
393  // To avoid undefined behavior when filename is jit->sourceFilename
394  // we have to make a copy first.
395  jit = JIT::Reset(jit, YString(filename));
396 
398  clear();
399  jit->minWidth = minWidth;
400  jit->maxHeight = maxHeight;
401  jit->options = options;
402  return true;
403 }
404 
405 template<class T, class ReadWriteT>
406 inline bool Matrix<T, ReadWriteT>::loadFromCSVFile(const AnyString& filename)
407 {
408  return loadFromCSVFile(filename,
409  1,
410  0,
411  optImmediate | optNoWarnIfEmpty | optNeverFails | optQuiet);
412 }
413 
414 template<class T, class ReadWriteT>
415 inline bool Matrix<T, ReadWriteT>::loadFromCSVFile(const AnyString& filename,
416  uint minWidth,
417  uint maxHeight,
418  BufferType* buffer)
419 {
420  return loadFromCSVFile(filename, minWidth, maxHeight, optNone, buffer);
421 }
422 
423 template<class T, class ReadWriteT>
424 bool Matrix<T, ReadWriteT>::loadFromCSVFile(const AnyString& filename,
425  uint minWidth,
426  uint maxHeight,
427  uint options,
428  BufferType* buffer)
429 {
430  assert(not filename.empty() and "Matrix<>:: loadFromCSVFile: empty filename");
431  // As the loading might be expensive, especially when dealing with
432  // numerous matrices, we may want to delay this loading (a `lazy` mode)
433  if (JIT::enabled and not(options & optImmediate))
434  {
435  return internalLoadJITData(filename, minWidth, maxHeight, options);
436  }
437  else
438  {
439  // Reading data from file
440  return internalLoadCSVFile(filename, minWidth, maxHeight, options, buffer);
441  }
442 }
443 
444 template<class T, class ReadWriteT>
445 bool Matrix<T, ReadWriteT>::saveToCSVFile(const AnyString& filename,
446  uint precision,
447  bool print_dimensions,
448  bool saveEvenIfAllZero) const
449 {
450  PredicateIdentity predicate;
451  return internalSaveCSVFile(filename, precision, print_dimensions, predicate, saveEvenIfAllZero);
452 }
453 
454 template<class T, class ReadWriteT>
455 template<class PredicateT>
456 bool Matrix<T, ReadWriteT>::saveToCSVFile(const AnyString& filename,
457  uint precision,
458  bool print_dimensions,
459  PredicateT& predicate,
460  bool saveEvenIfAllZero) const
461 {
462  return internalSaveCSVFile(filename, precision, print_dimensions, predicate, saveEvenIfAllZero);
463 }
464 
465 template<class T, class ReadWriteT>
466 template<class U>
467 void Matrix<T, ReadWriteT>::pasteToColumn(uint x, const U* data)
468 {
469  assert(x < width and "Invalid column index (bigger than `this->width`)");
470  ColumnType& column = entry[x];
471 
472  // if the two types are strictly equal, we can perform some major
473  // optimisations
474  if (Yuni::Static::Type::StrictlyEqual<T, U>::Yes)
475  {
476  (void)::memcpy(column, data, sizeof(T) * height);
477  }
478  else
479  {
480  // ...otherwise we have to copy each item by hand in any cases
481  for (uint y = 0; y != height; ++y)
482  {
483  column[y] = (T)data[y];
484  }
485  }
486 
487  markAsModified();
488 }
489 
490 template<class T, class ReadWriteT>
491 void Matrix<T, ReadWriteT>::fillColumn(uint x, const T& value)
492 {
493  assert(x < width and "Invalid column index (bigger than `this->width`)");
494  ColumnType& column = entry[x];
495 
496  for (uint y = 0; y != height; ++y)
497  {
498  column[y] = value;
499  }
500 
501  markAsModified();
502 }
503 
504 template<class T, class ReadWriteT>
506 {
507  assert(x < width and "Invalid column index (bigger than `this->width`)");
508  ColumnType& column = entry[x];
509 
510  (void)::memset((void*)column, 0, sizeof(T) * height);
511 
512  markAsModified();
513 }
514 
515 template<class T, class ReadWriteT>
517 {
518  if (jit)
519  {
520  jit->markAsModified();
521  }
522 }
523 
524 template<class T, class ReadWriteT>
525 inline bool Matrix<T, ReadWriteT>::empty() const
526 {
527  return (!width) or (!height);
528 }
529 
530 template<class T, class ReadWriteT>
532 {
533  if (entry)
534  {
535  for (uint i = 0; i != width; ++i)
536  {
537  Antares::Memory::Release(entry[i]);
538  }
539  delete[] entry;
540  entry = nullptr;
541  }
542  width = 0;
543  height = 0;
544 }
545 
546 template<class T, class ReadWriteT>
548 {
549  clear();
550  markAsModified();
551 }
552 
553 template<class T, class ReadWriteT>
554 void Matrix<T, ReadWriteT>::resize(uint w, uint h, bool fixedSize)
555 {
556  // Asserts
557  // This limit is correlated with the maximal amount of years
558  // See the routine GeneralData::fixBadValues() if some changes are needed
559  assert(w <= 50000 and "The new width seems a bit excessive");
560  assert(h <= 50000 and "The new height seems a bit excessive");
561 
562  // Checking if the matrix really needs to be resized
563  if (w != width or h != height)
564  {
565  if (!w or !h)
566  {
567  clear();
568  }
569  else
570  {
571  if (entry)
572  {
573  for (uint i = 0; i != width; ++i)
574  {
575  Antares::Memory::Release(entry[i]);
576  }
577  delete[] entry;
578  }
579 
580  // Assigning the new size
581  width = w;
582  height = h;
583 
584  // Allocating the entry for the matrix
585  entry = new typename Antares::Memory::Stored<T>::Type[width + 1];
586  entry[width] = nullptr;
587 
588  for (uint i = 0; i != w; ++i)
589  {
590  Antares::Memory::Allocate<T>(entry[i], height);
591  }
592  }
593  }
594 
595  // JIT Update
596  if (JIT::enabled and not jit and fixedSize)
597  {
598  jit = JIT::Reset(jit);
599  }
600 
601  if (jit and w != 0 and h != 0)
602  {
603  jit->minWidth = w;
604  jit->maxHeight = h;
605  if (fixedSize)
606  {
607  jit->options = jit->options | optFixedSize;
608  }
609  }
610  markAsModified();
611 }
612 
613 namespace // anonymous
614 {
615 static inline bool DetectEncoding(const AnyString& filename, const AnyString& data, size_t& offset)
616 {
617  if (data.size() > 1)
618  {
619  // UTF-16 Big endian
620  if ((unsigned char)data[0] == 0xFE and ((unsigned char)data[1]) == 0xFF)
621  {
622  if (data.size() > 3 and !data[2] and !data[3])
623  {
624  logs.error() << '`' << filename
625  << "`: UTF-32 Little Endian encoding detected. ASCII/UTF-8 required.";
626  }
627  else
628  {
629  logs.error() << '`' << filename
630  << "`: UTF-16 Big Endian encoding detected. ASCII/UTF-8 required.";
631  }
632  return false;
633  }
634  // UTF-16 Little endian
635  if ((unsigned char)data[0] == 0xFF and ((unsigned char)data[1]) == 0xFE)
636  {
637  logs.error() << '`' << filename
638  << "`: UTF-16 Little Endian encoding detected. ASCII/UTF-8 required.";
639  return false;
640  }
641  // UTF-8 Little endian
642  if ((unsigned char)data[0] == 0xEF and ((unsigned char)data[1]) == 0xBB and data.size() > 2
643  and ((unsigned char)data[2]) == 0xBF)
644  {
645  // Slipping the byte-order mark
646  offset = 3;
647  }
648  if (data.size() > 3)
649  {
650  if (!data[0] and !data[1] and ((unsigned char)data[2]) == 0xFE
651  and ((unsigned char)data[3]) == 0xFF)
652  {
653  logs.error() << '`' << filename
654  << "`: UTF-32 Big Endian encoding detected. ASCII/UTF-8 required.";
655  return false;
656  }
657  }
658  }
659  return true;
660 }
661 } // anonymous namespace
662 
663 template<class T, class ReadWriteT>
664 bool Matrix<T, ReadWriteT>::loadFromBuffer(const AnyString& filename,
665  BufferType& data,
666  uint minWidth,
667  uint maxHeight,
668  const int fixedSize,
669  uint options)
670 {
671  using namespace Yuni;
672 
673  logs.debug() << " :: loading `" << filename << "`";
674 
675  // Detecting BOM
676  // Antares currently only accepts ASCII and/or UTF-8 encodings.
677  size_t bom = 0;
678  if (not DetectEncoding(filename, data, bom))
679  {
680  // gp : dead code - can never be reached
681  reset((minWidth > 0 ? minWidth : 1), maxHeight);
682  return false;
683  }
684 
685  uint offset = (uint)bom;
686  uint x = 0;
687 
688  // Properly resizing the matrix
689  // Directly resizing the matrix when its size is well-known
690  if (fixedSize)
691  {
692  reset(minWidth, maxHeight, true);
693  }
694  else
695  {
696  // Autodetection of the number of lines
697  if (!maxHeight)
698  {
699  assert(not data.empty());
700  maxHeight = data.countChar('\n');
701  if (data.last() == '\n')
702  {
703  --maxHeight;
704  }
705  else
706  {
707  ++maxHeight;
708  }
709  }
710  // gp : dead code - can never be reached
711  // The first occurence of the carriage return
712  YString::Size max = data.find('\n');
713  if (max == BufferType::npos)
714  {
715  logs.error() << filename << ": At least one line-return must be available";
716  reset(minWidth, maxHeight);
717  return false;
718  }
719  offset = max + 1;
720  // remove all final trailing whitespaces
721  if (max > 0)
722  {
723  do
724  {
725  char c = data[max - 1];
726  if (c == '\r' or c == '\t' or c == ' ')
727  {
728  if (!(--max))
729  {
730  break;
731  }
732  }
733  else
734  {
735  break;
736  }
737  } while (true);
738  }
739 
740  if (max > 7 /* ex: size:0x0 */ and data.startsWith("size:"))
741  {
742  CString<64, false> buffer;
743  buffer.assign(data.c_str(), max);
744  int x, y;
745 #ifdef YUNI_OS_MSVC
746  sscanf_s(buffer.c_str(), "size:%dx%d", &x, &y);
747 #else
748  sscanf(buffer.c_str(), "size:%dx%d", &x, &y);
749 #endif
750  if (x < 1)
751  {
752  if (!(options & optQuiet))
753  {
754  logs.warning() << '`' << filename << "`: Invalid header";
755  }
756  x = 1;
757  }
758  if (y < 1)
759  {
760  if (!(options & optQuiet))
761  {
762  logs.warning() << '`' << filename << "`: Invalid header";
763  }
764  y = maxHeight;
765  }
766  maxHeight = y;
767  resize((uint)x, (uint)y);
768  }
769  else
770  {
771  offset = 0;
772  x = ((max > 0) ? 1 : 0);
773 
774  if (max > 0)
775  {
776  while ((offset = (uint)data.find_first_of(ANTARES_MATRIX_CSV_COMMA, offset)) < max)
777  {
778  ++offset;
779  ++x;
780  }
781  }
782  resize(((x < minWidth) ? minWidth : x), maxHeight);
783  if (!x)
784  {
785  if (not(options & optQuiet) and not(options & optNoWarnIfEmpty))
786  {
787  logs.warning() << "`" << filename << "`: Invalid format: The file seems empty";
788  }
789  zero();
790  return false;
791  }
792 
793  offset = (uint)bom;
794  }
795  }
796 
797  uint y = 0;
798  uint pos;
799  int errorCount = 6;
800  char separator = '\0';
801  AnyString converter;
802  ReadWriteType cellValue;
803  bool result = true;
804 
805  while (y < maxHeight and offset < (uint)data.size())
806  {
807  // Starting the reading of the begining of the line
808  x = 0;
809  pos = offset;
810  uint lineOffset = (uint)offset;
811 
812  while ((offset = data.find_first_of(ANTARES_MATRIX_CSV_SEPARATORS, offset))
813  != BufferType::npos)
814  {
815  assert(offset != BufferType::npos);
816 
817  separator = data[offset];
818  // the final zero is mandatory for string-to-double convertions
819  data[offset] = '\0';
820  // Adding the value
821  converter = (const char*)((const char*)data.c_str() + pos);
822 
823  // Convert string into double or something else
824  if (not converter.empty())
825  {
826  if (x >= width) // Out of bounds
827  {
828  if ((options & optNeverFails))
829  {
830  if (separator == '\n')
831  {
832  resizeWithoutDataLost(x + 1, height);
833  }
834  else
835  {
836  // gp : dead code - can never be reached
837  // Looking for the new matrix width
838  uint newOffset = offset;
839  uint newWidth = width + 1;
840 
841  while ((newOffset = data.find_first_of(ANTARES_MATRIX_CSV_SEPARATORS,
842  (String::Size)newOffset))
843  != BufferType::npos)
844  {
845  if (data[newOffset] == '\n')
846  {
847  ++newWidth;
848  break;
849  }
850  if (data[newOffset] != '\r')
851  {
852  ++newWidth;
853  }
854  ++newOffset;
855  }
856  resizeWithoutDataLost(newWidth, height);
857  }
858  // logs.debug() << " :: dynamic resize (" << width << 'x' << height << ')';
859  }
860  else
861  {
862  result = false;
863  if (not(options & optQuiet) and errorCount > 0)
864  {
865  logs.warning()
866  << '`' << filename << "`: Invalid format: Too many entry for the row "
867  << y << " (offset: " << (uint)pos << "byte)";
868  if (!(--errorCount))
869  {
870  logs.warning() << " ... (skipped)";
871  }
872  }
873  break;
874  }
875  }
876 
877  if (MatrixStringConverter<ReadWriteType>::direct)
878  {
879  // We can perform a direct copy, instead of using a temporary buffer
880  // for complex conversions
881  // This should considerably reduced the loading time
882  MatrixData<T>::Copy(entry[x][y], converter);
883  }
884  else
885  {
886  // We will try to convert the string value into the corresponding
887  // type.
888  if (not MatrixStringConverter<ReadWriteType>::Do(converter, cellValue))
889  {
890  // We may fail in some special cases, like when using old studies
891  // where decimal values can be found instead of integer
892  double fallback;
893  if (not MatrixStringConverter<double>::Do(converter, fallback))
894  {
895  result = false;
896  if (not(options & optQuiet) and errorCount)
897  {
898  logs.warning()
899  << '`' << filename << "`: Invalid numeric value (x:" << x
900  << ",y:" << y << ", offset: " << (uint)pos << "byte), text: `"
901  << converter << " read:" << entry[x][y] << '`';
902  if (not(--errorCount))
903  {
904  logs.warning() << " ... (skipped)";
905  }
906  }
907  MatrixData<T>::Init(entry[x][y]);
908  }
909  else
910  {
911  entry[x][y] = MatrixRound<T, ReadWriteType>::Value(
912  static_cast<ReadWriteType>(fallback));
913  }
914  }
915  else
916  {
917  // Ok, the conversion succeeded
918  MatrixData<T>::Copy(entry[x][y], cellValue);
919  }
920  }
921  }
922  else
923  {
924  // We may encounter final tabs, which must not be managed as an error
925  if (x < width)
926  {
927  MatrixData<T>::Init(entry[x][y]);
928  if (not(options & optQuiet))
929  {
930  logs.debug() << " empty value at " << (x + 1) << 'x' << (y + 1)
931  << " (line offset: " << (offset - lineOffset) << ")";
932  }
933  }
934  }
935 
936  // Offset for the next token
937  pos = ++offset;
938 
939  // new column
940  ++x;
941 
942  // next column
943  if (separator == '\r')
944  {
945  if (data[offset] == '\n')
946  {
947  pos = ++offset;
948  break;
949  }
950  }
951  else
952  {
953  if (separator == '\n')
954  {
955  break;
956  }
957  }
958  }
959 
960  // Not enough columns to describe the row of the matrix
961  if (x < width)
962  {
963  if (not(options & optNeverFails))
964  {
965  result = false;
966  if (not(options & optQuiet) and errorCount)
967  {
968  logs.warning()
969  << filename << ": at line " << (y + 1) << ", not enough columns (expected "
970  << width << ", got " << x << ')';
971  if (not(--errorCount))
972  {
973  logs.warning() << " ... (skipped)";
974  }
975  }
976  }
977  while (x < width) // Init for missing entry
978  {
979  MatrixData<T>::Init(entry[x][y]);
980  ++x;
981  }
982  }
983 
984  // Go to the next line
985  ++y;
986  } // while (y ...)
987 
988  // Not enough lines to describe our matrix
989  if (y < height)
990  {
991  result = false;
992  if (!(options & optQuiet))
993  {
994  logs.warning() << filename << ": not enough rows (expected " << height << ", got " << y
995  << ')';
996  }
997  // Initialize missing entry
998  while (y < height)
999  {
1000  for (x = 0; x < width; ++x)
1001  {
1002  MatrixData<T>::Init(entry[x][y]);
1003  }
1004  ++y;
1005  }
1006  }
1007 
1008  return ((0 != (options & optNeverFails)) ? true : result);
1009 }
1010 
1011 template<class T, class ReadWriteT>
1012 bool Matrix<T, ReadWriteT>::internalLoadCSVFile(const AnyString& filename,
1013  uint minWidth,
1014  uint maxHeight,
1015  uint options,
1016  BufferType* buffer)
1017 {
1018  // Status
1019  bool result = false;
1020 
1021  const bool hasOwnership = (NULL == buffer);
1022  if (not buffer)
1023  {
1024  buffer = new BufferType();
1025  }
1026 
1027  switch (loadFromFileToBuffer(*buffer, filename))
1028  {
1029  case Yuni::IO::errNone:
1030  {
1031  // Empty files
1032  if (buffer->empty())
1033  {
1034  if (maxHeight and minWidth)
1035  {
1036  reset((minWidth != 0 ? minWidth : 1),
1037  maxHeight); // gp : minWidth always != 0 here ==> Refactoring
1038  }
1039  else
1040  {
1041  clear();
1042  }
1043  result = true;
1044  break;
1045  }
1046 
1047  // IO statistics
1048  Statistics::HasReadFromDisk(buffer->size());
1049 
1050  // Adding a final \n to make sure we have a line return at the end of the file
1051  *buffer += '\n';
1052  // Load the data
1053  result = loadFromBuffer(filename,
1054  *buffer,
1055  minWidth,
1056  maxHeight,
1057  (options & optFixedSize),
1058  options);
1059 
1060  // Mark as modified
1061  if (0 != (options & optMarkAsModified))
1062  {
1063  if (jit)
1064  {
1065  jit->markAsModified();
1066  }
1067  }
1068  break;
1069  }
1070  case Yuni::IO::errNotFound:
1071  {
1072  if (not(options & optQuiet))
1073  {
1074  logs.error() << "I/O Error: not found: '" << filename << "'";
1075  }
1076  break;
1077  }
1078  case Yuni::IO::errMemoryLimit:
1079  {
1080  if (not(options & optQuiet))
1081  {
1082  logs.error() << filename << ": The file is too large (>"
1083  << (filesizeHardLimit / 1024 / 1024) << "Mo)";
1084  }
1085  break;
1086  }
1087  default:
1088  {
1089  if (not(options & optQuiet))
1090  {
1091  logs.error() << "I/O Error: failed to load '" << filename << "'";
1092  }
1093  }
1094  }
1095 
1096  if (hasOwnership)
1097  {
1098  delete buffer;
1099  }
1100 
1101  // The matrix may not be loaded but we have to initialize it to avoid
1102  // further SegV if the application decides to continue anyway.
1103  if (not result)
1104  {
1105  reset(minWidth, maxHeight);
1106  }
1107 
1108  // Post-processing when the Load-on-Demand is enabled
1109  if (JIT::enabled and not jit and (0 != (options & optFixedSize)))
1110  {
1111  jit = JIT::Reset(jit, filename);
1112  }
1113 
1114  if (jit)
1115  {
1116  jit->alreadyLoaded = true;
1117  jit->modified = false;
1118  jit->minWidth = (options & optFixedSize) ? (!width ? minWidth : width) : 1;
1119  jit->maxHeight = height;
1120  jit->options = options;
1121  if (jit->sourceFilename.empty())
1122  {
1123  jit->sourceFilename = filename;
1124  assert(not jit->sourceFilename.empty());
1125  }
1126  }
1127 
1128  // We return `true` in any cases to not stop the execution of the solver, since
1129  // it may not be a fatal error
1130  return result;
1131 }
1132 
1133 template<class T, class ReadWriteT>
1135 {
1136  if (width and height)
1137  {
1138  for (uint x = 0; x != width; ++x)
1139  {
1140  auto& column = entry[x];
1141  for (uint y = 0; y != height; ++y)
1142  {
1143  if (!Utils::isZero((T)column[y]))
1144  {
1145  return false;
1146  }
1147  }
1148  }
1149  }
1150  return true;
1151 }
1152 
1153 template<class T, class ReadWriteT>
1154 template<class PredicateT>
1155 bool Matrix<T, ReadWriteT>::containsOnlyZero(PredicateT& predicate) const
1156 {
1157  if (width and height)
1158  {
1159  for (uint x = 0; x != width; ++x)
1160  {
1161  auto& column = entry[x];
1162  for (uint y = 0; y != height; ++y)
1163  {
1164  if (!Utils::isZero((T)predicate(column[y])))
1165  {
1166  return false;
1167  }
1168  }
1169  }
1170  }
1171  return true;
1172 }
1173 
1174 template<class T, class ReadWriteT>
1175 template<class PredicateT>
1176 void Matrix<T, ReadWriteT>::saveToBuffer(std::string& data,
1177  uint precision,
1178  bool print_dimensions,
1179  PredicateT& predicate,
1180  bool saveEvenIfAllZero) const
1181 {
1182  using namespace Yuni;
1183 
1184  enum
1185  {
1186  // Get if the read/write type is a decimal type (e.g. double/float/long double)
1187  isDecimal = Static::Type::IsDecimal<ReadWriteType>::Yes,
1188  };
1189 
1190  if (not print_dimensions and (containsOnlyZero(predicate) and not saveEvenIfAllZero))
1191  {
1192  // Does nothing if the matrix only contains zero
1193  return;
1194  }
1195 
1196  matrix_to_buffer_dumper_factory mtx_to_buffer_dumper_factory;
1197 
1198  auto mtx_to_buffer_dpr = mtx_to_buffer_dumper_factory
1199  .get_dumper<T, ReadWriteT, PredicateT>(this, data, predicate);
1200 
1201  // Determining the string format to use according the given precision
1202  mtx_to_buffer_dpr->set_print_format(isDecimal, precision);
1203 
1204  // Pre-allocate memory in the buffer. It should be enough in nearly all cases.
1205  data.reserve(width * height * 6);
1206 
1207  if (print_dimensions)
1208  {
1209  data += "size:" + std::to_string(width) + 'x' + std::to_string(height) + '\n';
1210  }
1211 
1212  mtx_to_buffer_dpr->run();
1213 }
1214 
1215 template<class T, class ReadWriteT>
1216 void Matrix<T, ReadWriteT>::saveToBuffer(std::string& data, uint precision) const
1217 {
1218  PredicateIdentity identity;
1219  this->saveToBuffer(data, precision, false, identity, true);
1220 }
1221 
1222 template<class T, class ReadWriteT>
1223 bool Matrix<T, ReadWriteT>::openFile(Yuni::IO::File::Stream& file, const AnyString& filename) const
1224 {
1225  if (not file.openRW(filename))
1226  {
1227  logs.error() << "I/O error: " << filename
1228  << ": Impossible to write the file (not enough permission ?)";
1229  return false;
1230  }
1231  return true;
1232 }
1233 
1234 template<class T, class ReadWriteT>
1235 void Matrix<T, ReadWriteT>::saveBufferToFile(std::string& buffer, Yuni::IO::File::Stream& f) const
1236 {
1237  f << buffer;
1238 }
1239 
1240 template<class T, class ReadWriteT>
1241 template<class PredicateT>
1242 bool Matrix<T, ReadWriteT>::internalSaveCSVFile(const AnyString& filename,
1243  uint precision,
1244  bool print_dimensions,
1245  PredicateT& predicate,
1246  bool saveEvenIfAllZero) const
1247 {
1248  JIT::just_in_time_manager jit_mgr(jit, filename);
1249 
1250  jit_mgr.record_current_jit_state(width, height);
1251 
1252  if (jit_mgr.jit_activated() && jit_mgr.matrix_content_in_memory_is_same_as_on_disk())
1253  {
1254  // No difference between actual matrix content in memory and matrix on disk, so we don't
1255  // need to save on disk. Besides, as jit is on, we do not need it in memory, and matrix is
1256  // cleared.
1257  jit_mgr.clear_matrix(this);
1258  return true;
1259  }
1260 
1261  if (jit_mgr.jit_activated() && jit_mgr.do_we_force_matrix_load_from_disk())
1262  {
1263  jit_mgr.load_matrix(this);
1264  }
1265 
1266  // Attempt to open the file, and to write data
1267  // We have write access to the file
1268  logs.debug() << " :: writing `" << filename << "' (" << width << 'x' << height << ')';
1269 
1270  Yuni::IO::File::Stream file;
1271  if (not openFile(file, filename))
1272  {
1273  return false;
1274  }
1275 
1276  if (height and width)
1277  {
1278  std::string buffer;
1279 
1280  saveToBuffer(buffer, precision, print_dimensions, predicate, saveEvenIfAllZero);
1281  Statistics::HasWrittenToDisk(buffer.size());
1282 
1283  saveBufferToFile(buffer, file);
1284  }
1285 
1286  // Attempt to open the file, and to write data
1287  // We have write access to the file
1288  logs.debug() << " :: [end] writing `" << filename << "' (" << width << 'x' << height << ')';
1289 
1290  jit_mgr.unload_matrix_properly_from_memory(this);
1291 
1292  return true;
1293 }
1294 
1295 template<class T, class ReadWriteT>
1296 void Matrix<T, ReadWriteT>::resizeWithoutDataLost(uint x, uint y, const T& defVal)
1297 {
1298  if (!x or !y)
1299  {
1300  clear();
1301  }
1302  else
1303  {
1304  if (x <= width and y <= height) // shrinking
1305  {
1306  for (uint i = x; i < width; ++i)
1307  {
1308  Antares::Memory::Release(entry[i]);
1309  }
1310 
1311  // Update the matrix size
1312  width = x;
1313  height = y;
1314  }
1315  else
1316  {
1317  const Matrix<T, ReadWriteT> copy(*this);
1318  resize(x, y);
1319  // Copy values
1320  uint minW = (x < copy.width) ? x : copy.width;
1321  uint minH = (y < copy.height) ? y : copy.height;
1322 
1323  for (uint i = 0; i < minW; ++i)
1324  {
1325  ColumnType& column = entry[i];
1326 
1327  (void)::memcpy(column, copy.entry[i], sizeof(T) * minH);
1328 
1329  for (uint j = minH; j < y; ++j)
1330  {
1331  column[j] = defVal;
1332  }
1333  }
1334 
1335  if (defVal == T())
1336  {
1337  for (uint i = minW; i < x; ++i)
1338  {
1339  Memory::Zero(y, entry[i]);
1340  }
1341  }
1342  else
1343  {
1344  for (uint i = minW; i < x; ++i)
1345  {
1346  Memory::Assign(y, entry[i], defVal);
1347  }
1348  }
1349  }
1350  }
1351  markAsModified();
1352  logs.debug() << " :: end resizeWithoutDataLost";
1353 }
1354 
1355 template<class T, class ReadWriteT>
1357 {
1358  if (jit and not JIT::IsReady(jit))
1359  {
1360  return (const_cast<Matrix<T, ReadWriteT>*>(this))
1361  ->loadFromCSVFile(jit->sourceFilename,
1362  jit->minWidth,
1363  jit->maxHeight,
1364  jit->options | optImmediate);
1365  }
1366  return true;
1367 }
1368 
1369 template<class T, class ReadWriteT>
1370 template<class U>
1372 {
1373  if (!entry)
1374  {
1375  return;
1376  }
1377 
1378  if (!Utils::isZero(c))
1379  {
1380  for (uint x = 0; x != width; ++x)
1381  {
1382  ColumnType& column = entry[x];
1383 
1384  for (uint y = 0; y != height; ++y)
1385  {
1386  column[y] *= (T)c;
1387  }
1388  }
1389  }
1390  else
1391  {
1392  zero();
1393  }
1394 }
1395 
1396 template<class T, class ReadWriteT>
1397 template<class U>
1399 {
1400  assert(x < width and "Invalid column index (bigger than `this->width`)");
1401  ColumnType& column = entry[x];
1402  for (uint y = 0; y != height; ++y)
1403  {
1404  column[y] *= (T)c;
1405  }
1406 }
1407 
1408 template<class T, class ReadWriteT>
1409 template<class U>
1410 void Matrix<T, ReadWriteT>::divideColumnBy(uint x, const U& c)
1411 {
1412  assert(x < width and "Invalid column index (bigger than `this->width`)");
1413  assert(c != (T)0 && "Dividing by zero");
1414  ColumnType& column = entry[x];
1415  for (uint y = 0; y != height; ++y)
1416  {
1417  column[y] /= (T)c;
1418  }
1419 }
1420 
1421 template<class T, class ReadWriteT>
1423 {
1424  for (uint x = 0; x != width; ++x)
1425  {
1426  ColumnType& col = entry[x];
1427  for (uint y = 0; y != height; ++y)
1428  {
1429  col[y] = (T)std::round(col[y]);
1430  }
1431  }
1432 }
1433 
1434 template<class T, class ReadWriteT>
1436 {
1437  for (uint x = 0; x != width; ++x)
1438  {
1439  ColumnType& col = entry[x];
1440  for (uint y = 0; y != height; ++y)
1441  {
1442  col[y] = std::abs(col[y]);
1443  }
1444  }
1445 }
1446 
1447 template<class T, class ReadWriteT>
1449 {
1450  double r = +1e30;
1451  for (uint x = 0; x != width; ++x)
1452  {
1453  ColumnType& col = entry[x];
1454  for (uint y = 0; y != height; ++y)
1455  {
1456  if (col[y] < r)
1457  {
1458  r = col[y];
1459  }
1460  }
1461  }
1462  return (T)r;
1463 }
1464 
1465 template<class T, class ReadWriteT>
1467 {
1468  double r = -1e30;
1469  for (uint x = 0; x != width; ++x)
1470  {
1471  ColumnType& col = entry[x];
1472  for (uint y = 0; y != height; ++y)
1473  {
1474  if (col[y] > r)
1475  {
1476  r = col[y];
1477  }
1478  }
1479  }
1480  return (T)r;
1481 }
1482 
1483 template<class T, class ReadWriteT>
1484 template<class U, class V>
1486 {
1487  assert((void*)(&rhs) != (void*)this and "Undefined behavior");
1488 
1489  rhs.forceReload(true);
1490  if (rhs.empty())
1491  {
1492  clear();
1493  }
1494  else
1495  {
1496  // resize the matrix
1497  resize(rhs.width, rhs.height);
1498  // copy raw values
1499  for (uint x = 0; x != rhs.width; ++x)
1500  {
1501  auto& column = entry[x];
1502  const auto& src = rhs.entry[x];
1503 
1504  // if the two types are strictly equal, we can perform some major
1505  // optimisations
1506  if (Yuni::Static::Type::StrictlyEqual<T, U>::Yes)
1507  {
1508  (void)::memcpy((void*)column, (void*)src, sizeof(T) * height);
1509  }
1510  else
1511  {
1512  // ...otherwise we have to copy each item by hand in any cases
1513  for (uint y = 0; y != height; ++y)
1514  {
1515  column[y] = (T)src[y];
1516  }
1517  }
1518  }
1519  }
1520 
1521  if (rhs.jit)
1522  {
1523  if (not jit)
1524  {
1525  jit = new JIT::Informations(*rhs.jit);
1526  }
1527  else
1528  {
1529  jit->options = rhs.jit->options;
1530  jit->minWidth = rhs.jit->minWidth;
1531  jit->maxHeight = rhs.jit->maxHeight;
1532  }
1533  }
1534  // mark the matrix as modified
1535  markAsModified();
1536 }
1537 
1538 template<class T, class ReadWriteT>
1539 template<class U, class V>
1540 inline void Matrix<T, ReadWriteT>::copyFrom(const Matrix<U, V>* rhs)
1541 {
1542  if (rhs)
1543  {
1544  copyFrom(*rhs);
1545  }
1546 }
1547 
1548 template<class T, class ReadWriteT>
1550 {
1551  // argument deduction lookup (ADL)
1552  using std::swap;
1553  swap(this->width, rhs.width);
1554  swap(this->height, rhs.height);
1555  swap(this->entry, rhs.entry);
1556  swap(this->jit, rhs.jit);
1557 }
1558 
1559 template<class T, class ReadWriteT>
1561 {
1562  copyFrom(rhs);
1563  return *this;
1564 }
1565 
1566 template<class T, class ReadWriteT>
1568 {
1569  width = rhs.width;
1570  height = rhs.height;
1571  jit = rhs.jit;
1572  if (0 == width || 0 == height)
1573  {
1574  entry = nullptr;
1575  width = 0;
1576  height = 0;
1577  }
1578  else
1579  {
1580  entry = rhs.entry;
1581  }
1582  // Prevent spurious de-allocation from rhs's destructor
1583  rhs.entry = nullptr;
1584  return *this;
1585 }
1586 
1587 template<class T, class ReadWriteT>
1588 template<class U>
1589 inline Matrix<T, ReadWriteT>& Matrix<T, ReadWriteT>::operator=(const Matrix<U>& rhs)
1590 {
1591  copyFrom(rhs);
1592  return *this;
1593 }
1594 
1595 template<class T, class ReadWriteT>
1597 {
1598  std::cout << "DUMP:\n";
1599  if (empty())
1600  {
1601  std::cout << "\tempty\n";
1602  return;
1603  }
1604  for (uint y = 0; y != height; ++y)
1605  {
1606  std::cout << "\t[";
1607  for (uint x = 0; x != width; ++x)
1608  {
1609  if (x)
1610  {
1611  std::cout << ",\t";
1612  }
1613  else
1614  {
1615  std::cout << '\t';
1616  }
1617  std::cout << entry[x][y];
1618  }
1619  std::cout << "]\n";
1620  }
1621 }
1622 
1623 template<class T, class ReadWriteT>
1625 {
1626  if (jit)
1627  {
1628  if (jit->alreadyLoaded and not jit->modified and not jit->sourceFilename.empty())
1629  {
1630  // ugly, but to not break the whole code design
1631  auto& thisNotConst = const_cast<Matrix&>(*this);
1632 
1633  thisNotConst.clear();
1634  JIT::MarkAsNotLoaded(thisNotConst.jit);
1635  }
1636  }
1637 }
1638 
1639 template<class T1, class T2>
1640 bool MatrixTestForAtLeastOnePositiveValue(const Matrix<T1, T2>& m)
1641 {
1642  if (m.width and m.height)
1643  {
1644  uint y;
1645  for (uint x = 0; x < m.width; ++x)
1646  {
1647  auto& col = m.entry[x];
1648  for (y = 0; y < m.height; ++y)
1649  {
1650  if (col[y] > T1(0))
1651  {
1652  return true;
1653  }
1654  }
1655  }
1656  }
1657  return false;
1658 }
1659 
1660 template<class T, class ReadWriteT>
1662 {
1663  if (count != 0)
1664  {
1665  for (uint column = 0; column != width; ++column)
1666  {
1667  circularShiftRows(column, count);
1668  }
1669  markAsModified();
1670  }
1671 }
1672 
1673 template<class T, class ReadWriteT>
1674 void Matrix<T, ReadWriteT>::reverseRows(uint column, uint start, uint end)
1675 {
1676  if (height <= 1 or !(column < width) or !(start < end))
1677  {
1678  return;
1679  }
1680 
1681  // The values of the selected column
1682  auto& values = entry[column];
1683  // temporary value
1684  T swap;
1685  for (uint y = start; y < --end; ++y)
1686  {
1687  swap = values[y];
1688  values[y] = values[end];
1689  values[end] = swap;
1690  }
1691 }
1692 
1693 template<class T, class ReadWriteT>
1694 void Matrix<T, ReadWriteT>::circularShiftRows(uint column, uint count)
1695 {
1696  assert(column < width and "Column out of bounds");
1697  if (height <= 1 or !(column < width) or !count)
1698  {
1699  return;
1700  }
1701 
1702  // fits \p count into [0..height[
1703  count = (count % height + height) % height;
1704  if (count == 0 or (uint) count == height)
1705  {
1706  return;
1707  }
1708 
1709  // Algorithm in O(N)
1710  reverseRows(column, 0, count);
1711  reverseRows(column, count, height);
1712  reverseRows(column, 0, height);
1713  markAsModified();
1714 }
1715 
1716 template<class T, class ReadWriteT>
1718  uint column) const
1719 {
1720  assert(column < width);
1721  assert(Memory::RawPointer(entry[column]));
1722  return entry[column];
1723 }
1724 
1725 template<class T, class ReadWriteT>
1727 {
1728  assert(column < width);
1729  assert(Memory::RawPointer(entry[column]));
1730  return entry[column];
1731 }
1732 
1733 template<class T, class ReadWriteT>
1735 {
1736  assert(n < width);
1737  assert(Memory::RawPointer(entry[n]));
1738  return entry[n];
1739 }
1740 
1741 template<class T, class ReadWriteT>
1743 {
1744  assert(n < width);
1745  assert(Memory::RawPointer(entry[n]));
1746  return entry[n];
1747 }
1748 } // namespace Antares
1749 
1750 #endif // __ANTARES_LIBS_ARRAY_MATRIX_HXX__
JIT::Informations * jit
Just-in-time informations.
Definition: matrix.h:447
uint height
Height of the matrix.
Definition: matrix.h:443
void markAsModified() const
Mark the matrix as modified.
Definition: matrix.hxx:516
Matrix()
Default Constructor.
Definition: matrix.hxx:201
Matrix & operator=(const Matrix &rhs)
Assignement.
Definition: matrix.hxx:1560
bool forceReload(bool reload=false) const
Force the Load of data (if not done) for the next save and mark the matrix as modified.
Definition: matrix.hxx:374
typename Antares::Memory::Stored< uint32_t >::Type ColumnType
Column type.
Definition: matrix.h:62
bool empty() const
Get if the matrix is empty.
Definition: matrix.hxx:525
ColumnType * entry
All entries of the matrix (bidimensional array)
Definition: matrix.h:445
virtual ~Matrix()
Destructor.
Definition: matrix.hxx:275
void clear()
Empty the matrix.
Definition: matrix.hxx:531
Yuni::Clob BufferType
A buffer, for large amount of data.
Definition: matrix.h:65
uint width
Width of the matrix.
Definition: matrix.h:441
virtual bool loadFromCSVFile(const AnyString &filename, uint minWidth, uint maxHeight, uint options=optNone, BufferType *buffer=NULL)
Load entries from a CSV file.
Definition: matrix.hxx:424
static void Release(T *&pointer)
Release a raw pointer.
Definition: memory.hxx:32
Definition: jit.h:118
unsigned options
The option to the matrix.
Definition: jit.h:150
unsigned minWidth
Minimum width expected (for Matrices)
Definition: jit.h:156
unsigned maxHeight
Minimum height expected (for Matrices)
Definition: jit.h:162
Definition: jit.h:66
static Informations * Reset(Informations *jit, const AnyString &filename)
Reset the source filename.
Definition: jit.cpp:80
static bool IsReady(Informations *j)
Get if the data has been loaded.
Definition: jit.hxx:28
static void Invalidate(Informations *j)
Mark the attached object as modified.
Definition: jit.cpp:110
static bool enabled
Flag to enable/disable JIT informations.
Definition: jit.h:179
static void MarkAsNotLoaded(Informations *j)
Mark the attached object as not loaded.
Definition: jit.cpp:101
Definition: matrix.h:450
Definition: tests-matrix-save.h:39