deal.II version GIT relicensing-6834-g5b78e6bcdf 2026-10-01 11:20:01+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
symengine_optimizer.h
Go to the documentation of this file.
1// -----------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later
4// Copyright (C) 2020 - 2026 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Detailed license information governing the source code and contributions
9// can be found in LICENSE.md and CONTRIBUTING.md at the top level directory.
10//
11// -----------------------------------------------------------------------------
12
13#ifndef dealii_differentiation_sd_symengine_optimizer_h
14#define dealii_differentiation_sd_symengine_optimizer_h
15
16#include <deal.II/base/config.h>
17
18#ifdef DEAL_II_WITH_SYMENGINE
19
20// Low level
21# include <symengine/basic.h>
22# include <symengine/dict.h>
23# include <symengine/symengine_exception.h>
24# include <symengine/symengine_rcp.h>
25
26// Optimization
27# include <symengine/lambda_double.h>
28# include <symengine/visitor.h>
29# ifdef HAVE_SYMENGINE_LLVM
30# include <symengine/llvm_double.h>
31# endif
32
35
41
42# include <boost/serialization/split_member.hpp>
43# include <boost/type_traits.hpp>
44
45# include <algorithm>
46# include <map>
47# include <memory>
48# include <type_traits>
49# include <utility>
50# include <vector>
51
52#endif // DEAL_II_WITH_SYMENGINE
53
55
56#ifdef DEAL_II_WITH_SYMENGINE
57namespace Differentiation
58{
59 namespace SD
60 {
71 "SymEngine has not been built with LLVM support.");
72
78 "The SymEngine LLVM optimizer does not (yet) support the "
79 "selected return type.");
80
84 // Forward declarations
85 template <typename ReturnType>
86 class BatchOptimizer;
87
88
94 enum class OptimizerType
95 {
104 lambda,
109 llvm
110 };
111
112
116 template <typename StreamType>
117 inline StreamType &
118 operator<<(StreamType &s, OptimizerType o)
119 {
121 s << "dictionary";
122 else if (o == OptimizerType::lambda)
123 s << "lambda";
124 else if (o == OptimizerType::llvm)
125 s << "llvm";
126 else
127 {
128 Assert(false, ExcMessage("Unknown optimization method."));
129 }
130
131 return s;
132 }
133
134
140 enum class OptimizationFlags : unsigned char
141 {
149 optimize_cse = 0x0001,
154 optimize_aggressive = 0x0002,
159 };
160
161
170 // This operator exists since if it did not then the result of the bit-or
171 // <tt>operator |</tt> would be an integer which would in turn trigger a
172 // compiler warning when we tried to assign it to an object of type
173 // OptimizationFlags.
174 inline OptimizationFlags
176 {
177 return static_cast<OptimizationFlags>(static_cast<unsigned int>(f1) |
178 static_cast<unsigned int>(f2));
179 }
180
181
186 inline OptimizationFlags &
188 {
189 f1 = f1 | f2;
190 return f1;
191 }
192
193
202 // This operator exists since if it did not then the result of the bit-or
203 // <tt>operator |</tt> would be an integer which would in turn trigger a
204 // compiler warning when we tried to assign it to an object of type
205 // OptimizationFlags.
206 inline OptimizationFlags
208 {
209 return static_cast<OptimizationFlags>(static_cast<unsigned int>(f1) &
210 static_cast<unsigned int>(f2));
211 }
212
213
218 inline OptimizationFlags &
220 {
221 f1 = f1 & f2;
222 return f1;
223 }
224
225
226 namespace internal
227 {
232 inline bool
234 {
235 return static_cast<int>(flags & OptimizationFlags::optimize_cse);
236 }
237
242 inline int
244 {
245 // With the LLVM compiler there exists the opportunity to tune
246 // the level of optimizations performed during compilation.
247 // By default SymEngine sets this at "opt_level=2", which one
248 // presumes targets -O2. Here we are a bit more specific about
249 // want we want it to do:
250 // - Normal compilation: -02 (default settings)
251 // - Aggressive mode: -03 (the whole lot!)
252 // In theory we could also target
253 // - Debug mode: -O0 (no optimizations)
254 // but this doesn't make much sense since SymEngine is a
255 // tested external library.
256 const bool use_agg_opt =
257 static_cast<int>(flags & OptimizationFlags::optimize_aggressive);
258 const int opt_level = (use_agg_opt ? 3 : 2);
259 return opt_level;
260 }
261 } // namespace internal
262
263
268 template <typename StreamType>
269 inline StreamType &
270 operator<<(StreamType &s, OptimizationFlags o)
271 {
272 s << " OptimizationFlags|";
273 if (static_cast<unsigned int>(o & OptimizationFlags::optimize_cse))
274 s << "cse|";
275
276 // LLVM optimization level
277 s << "-O" + std::to_string(internal::get_LLVM_optimization_level(o)) +
278 "|";
279
280 return s;
281 }
282
283
284 namespace internal
285 {
295 template <typename ReturnType, typename T = void>
297
298
308 template <typename ReturnType, typename T = void>
310
311
312# ifdef HAVE_SYMENGINE_LLVM
322 template <typename ReturnType, typename T = void>
323 struct LLVMOptimizer;
324# endif // HAVE_SYMENGINE_LLVM
325
326
342 template <typename ReturnType, typename Optimizer, typename T = void>
344
345
346# ifndef DOXYGEN
347
348
349 /* ----------- Specializations for the Optimizers ----------- */
350
351
352 // A helper struct to type trait detection for the optimizers that
353 // will be defined next.
354 template <typename ReturnType_, typename T = void>
355 struct SupportedOptimizerTypeTraits
356 {
357 static const bool is_supported = false;
358
359 using ReturnType = void;
360 };
361
362
363
364 // Specialization for arithmetic types
365 template <typename ReturnType_>
366 struct SupportedOptimizerTypeTraits<
367 ReturnType_,
368 std::enable_if_t<std::is_arithmetic_v<ReturnType_>>>
369 {
370 static const bool is_supported = true;
371
372 using ReturnType =
373 std::conditional_t<std::is_same_v<ReturnType_, float>, float, double>;
374 };
375
376
377
378 // Specialization for complex arithmetic types
379 template <typename ReturnType_>
380 struct SupportedOptimizerTypeTraits<
381 ReturnType_,
382 std::enable_if_t<
383 boost::is_complex<ReturnType_>::value &&
384 std::is_arithmetic_v<typename ReturnType_::value_type>>>
385 {
386 static const bool is_supported = true;
387
388 using ReturnType =
389 std::conditional_t<std::is_same_v<ReturnType_, std::complex<float>>,
390 std::complex<float>,
391 std::complex<double>>;
392 };
393
394
395
396 template <typename ReturnType_>
397 struct DictionaryOptimizer<ReturnType_,
398 std::enable_if_t<SupportedOptimizerTypeTraits<
399 ReturnType_>::is_supported>>
400 {
401 using ReturnType =
402 typename SupportedOptimizerTypeTraits<ReturnType_>::ReturnType;
403 using OptimizerType =
404 internal::DictionarySubstitutionVisitor<ReturnType, SD::Expression>;
405
406
415 static void
416 initialize(OptimizerType &optimizer,
417 const SymEngine::vec_basic &independent_symbols,
418 const SymEngine::vec_basic &dependent_functions,
419 const enum OptimizationFlags &optimization_flags)
420 {
421 const bool use_symbolic_cse = use_symbolic_CSE(optimization_flags);
422 optimizer.init(independent_symbols,
423 dependent_functions,
424 use_symbolic_cse);
425 }
426
427
428
433 template <class Archive>
434 static void
435 save(Archive &archive,
436 const unsigned int version,
437 OptimizerType &optimizer)
438 {
439 optimizer.save(archive, version);
440 }
441
442
443
448 template <class Archive>
449 static void
450 load(Archive &archive,
451 const unsigned int version,
452 OptimizerType &optimizer,
453 const SymEngine::vec_basic & /*independent_symbols*/,
454 const SymEngine::vec_basic & /*dependent_functions*/,
455 const enum OptimizationFlags & /*optimization_flags*/)
456 {
457 optimizer.load(archive, version);
458 }
459
460
461
477 template <typename Stream>
478 static void
479 print(Stream &stream,
480 const OptimizerType &optimizer,
481 const bool print_independent_symbols = false,
482 const bool print_dependent_functions = false,
483 const bool print_cse_reductions = true)
484 {
485 optimizer.print(stream,
486 print_independent_symbols,
487 print_dependent_functions,
488 print_cse_reductions);
489 }
490 };
491
492
493
494 template <typename ReturnType_>
495 struct LambdaOptimizer<ReturnType_,
496 std::enable_if_t<SupportedOptimizerTypeTraits<
497 ReturnType_>::is_supported>>
498 {
499 using ReturnType =
500 std::conditional_t<!boost::is_complex<ReturnType_>::value,
501 double,
502 std::complex<double>>;
503 using OptimizerType =
504 std::conditional_t<!boost::is_complex<ReturnType_>::value,
505 SymEngine::LambdaRealDoubleVisitor,
506 SymEngine::LambdaComplexDoubleVisitor>;
507
508
517 static void
518 initialize(OptimizerType &optimizer,
519 const SymEngine::vec_basic &independent_symbols,
520 const SymEngine::vec_basic &dependent_functions,
521 const enum OptimizationFlags &optimization_flags)
522 {
523 const bool use_symbolic_cse = use_symbolic_CSE(optimization_flags);
524 optimizer.init(independent_symbols,
525 dependent_functions,
526 use_symbolic_cse);
527 }
528
529
530
535 template <class Archive>
536 static void
537 save(Archive & /*archive*/,
538 const unsigned int /*version*/,
539 OptimizerType & /*optimizer*/)
540 {}
541
542
547 template <class Archive>
548 static void
549 load(Archive & /*archive*/,
550 const unsigned int /*version*/,
551 OptimizerType &optimizer,
552 const SymEngine::vec_basic &independent_symbols,
553 const SymEngine::vec_basic &dependent_functions,
554 const enum OptimizationFlags &optimization_flags)
555 {
556 initialize(optimizer,
557 independent_symbols,
558 dependent_functions,
559 optimization_flags);
560 }
561
562
563
579 template <typename StreamType>
580 static void
581 print(StreamType & /*stream*/,
582 const OptimizerType & /*optimizer*/,
583 const bool /*print_independent_symbols*/ = false,
584 const bool /*print_dependent_functions*/ = false,
585 const bool /*print_cse_reductions*/ = true)
586 {
587 // No built-in print function
588 }
589 };
590
591
592
593# ifdef HAVE_SYMENGINE_LLVM
594 template <typename ReturnType_>
595 struct LLVMOptimizer<ReturnType_,
596 std::enable_if_t<std::is_arithmetic_v<ReturnType_>>>
597 {
598 using ReturnType =
599 std::conditional_t<std::is_same_v<ReturnType_, float>, float, double>;
600 using OptimizerType =
601 std::conditional_t<std::is_same_v<ReturnType_, float>,
602 SymEngine::LLVMFloatVisitor,
603 SymEngine::LLVMDoubleVisitor>;
604
609 static const bool supported_by_LLVM = true;
610
611
620 static void
621 initialize(OptimizerType &optimizer,
622 const SymEngine::vec_basic &independent_symbols,
623 const SymEngine::vec_basic &dependent_functions,
624 const enum OptimizationFlags &optimization_flags)
625 {
626 const int opt_level = get_LLVM_optimization_level(optimization_flags);
627 const bool use_symbolic_cse = use_symbolic_CSE(optimization_flags);
628 optimizer.init(independent_symbols,
629 dependent_functions,
630 use_symbolic_cse,
631 opt_level);
632 }
633
634
635
640 template <class Archive>
641 static void
642 save(Archive &archive,
643 const unsigned int /*version*/,
644 OptimizerType &optimizer)
645 {
646 const std::string llvm_compiled_function = optimizer.dumps();
647 archive &llvm_compiled_function;
648 }
649
650
651
656 template <class Archive>
657 static void
658 load(Archive &archive,
659 const unsigned int /*version*/,
660 OptimizerType &optimizer,
661 const SymEngine::vec_basic & /*independent_symbols*/,
662 const SymEngine::vec_basic & /*dependent_functions*/,
663 const enum OptimizationFlags & /*optimization_flags*/)
664 {
665 std::string llvm_compiled_function;
666 archive &llvm_compiled_function;
667 optimizer.loads(llvm_compiled_function);
668 }
669
670
671
687 template <typename StreamType>
688 static void
689 print(StreamType & /*stream*/,
690 const OptimizerType & /*optimizer*/,
691 const bool /*print_independent_symbols*/ = false,
692 const bool /*print_dependent_functions*/ = false,
693 const bool /*print_cse_reductions*/ = true)
694 {
695 // No built-in print function
696 }
697 };
698
699
700 // There is no LLVM optimizer built with complex number support.
701 // So we fall back to the LambdaDouble case as a type (required
702 // at compile time), but offer no implementation. We expect that
703 // the calling class does not create this type: This can be done by
704 // checking the `supported_by_LLVM` flag.
705 template <typename ReturnType_>
706 struct LLVMOptimizer<
707 ReturnType_,
708 std::enable_if_t<
709 boost::is_complex<ReturnType_>::value &&
710 std::is_arithmetic_v<typename ReturnType_::value_type>>>
711 {
712 // Since there is no working implementation, these are dummy types
713 // that help with templating in the calling function.
714 using ReturnType = typename LambdaOptimizer<ReturnType_>::ReturnType;
715 using OptimizerType =
716 typename LambdaOptimizer<ReturnType_>::OptimizerType;
717
722 static const bool supported_by_LLVM = false;
723
724
733 static void
734 initialize(OptimizerType & /*optimizer*/,
735 const SymEngine::vec_basic & /*independent_symbols*/,
736 const SymEngine::vec_basic & /*dependent_functions*/,
737 const enum OptimizationFlags & /*optimization_flags*/)
738 {
740 }
741
742
743
748 template <class Archive>
749 static void
750 save(Archive & /*archive*/,
751 const unsigned int /*version*/,
752 OptimizerType & /*optimizer*/)
753 {
755 }
756
757
758
763 template <class Archive>
764 static void
765 load(Archive & /*archive*/,
766 const unsigned int /*version*/,
767 OptimizerType & /*optimizer*/,
768 const SymEngine::vec_basic & /*independent_symbols*/,
769 const SymEngine::vec_basic & /*dependent_functions*/,
770 const enum OptimizationFlags & /*optimization_flags*/)
771 {
773 }
774
775
776
792 template <typename StreamType>
793 static void
794 print(StreamType & /*stream*/,
795 const OptimizerType & /*optimizer*/,
796 const bool /*print_independent_symbols*/ = false,
797 const bool /*print_dependent_functions*/ = false,
798 const bool /*print_cse_reductions*/ = true)
799 {
801 }
802 };
803# endif // HAVE_SYMENGINE_LLVM
804
805
806 /* ----------- Specializations for OptimizerHelper ----------- */
807
808
809 template <typename ReturnType, typename Optimizer>
810 struct OptimizerHelper<
811 ReturnType,
812 Optimizer,
813 std::enable_if_t<
814 std::is_same_v<ReturnType, typename Optimizer::ReturnType>>>
815 {
824 static void
825 initialize(typename Optimizer::OptimizerType *optimizer,
826 const SymEngine::vec_basic &independent_symbols,
827 const SymEngine::vec_basic &dependent_functions,
828 const enum OptimizationFlags &optimization_flags)
829 {
830 Assert(optimizer, ExcNotInitialized());
831
832 // Some optimizers don't have the same interface for
833 // initialization, we filter them out through the specializations
834 // of the Optimizer class
835 Optimizer::initialize(*optimizer,
836 independent_symbols,
837 dependent_functions,
838 optimization_flags);
839 }
840
841
842
856 static void
857 substitute(typename Optimizer::OptimizerType *optimizer,
858 std::vector<ReturnType> &output_values,
859 const std::vector<ReturnType> &substitution_values)
860 {
861 Assert(optimizer, ExcNotInitialized());
862 optimizer->call(output_values.data(), substitution_values.data());
863 }
864
865
866
871 template <class Archive>
872 static void
873 save(Archive &archive,
874 const unsigned int version,
875 typename Optimizer::OptimizerType *optimizer)
876 {
877 Assert(optimizer, ExcNotInitialized());
878
879 // Some optimizers don't have the same interface for
880 // serialization, we filter them out through the specializations
881 // of the Optimizer class
882 Optimizer::save(archive, version, *optimizer);
883 }
884
885
886
891 template <class Archive>
892 static void
893 load(Archive &archive,
894 const unsigned int version,
895 typename Optimizer::OptimizerType *optimizer,
896 const SymEngine::vec_basic &independent_symbols,
897 const SymEngine::vec_basic &dependent_functions,
898 const enum OptimizationFlags &optimization_flags)
899 {
900 Assert(optimizer, ExcNotInitialized());
901
902 // Some optimizers don't have the same interface for
903 // serialization, we filter them out through the specializations
904 // of the Optimizer class
905 Optimizer::load(archive,
906 version,
907 *optimizer,
908 independent_symbols,
909 dependent_functions,
910 optimization_flags);
911 }
912
913
914
930 template <typename Stream>
931 static void
932 print(Stream &stream,
933 typename Optimizer::OptimizerType *optimizer,
934 const bool print_independent_symbols = false,
935 const bool print_dependent_functions = false,
936 const bool print_cse_reductions = true)
937 {
938 Assert(optimizer, ExcNotInitialized());
939
940 // Some optimizers don't have a print function, so
941 // we filter them out through the specializations of
942 // the Optimizer class
943 Optimizer::print(stream,
944 *optimizer,
945 print_independent_symbols,
946 print_dependent_functions,
947 print_cse_reductions);
948 }
949 };
950
951 template <typename ReturnType, typename Optimizer>
952 struct OptimizerHelper<
953 ReturnType,
954 Optimizer,
955 std::enable_if_t<
956 !std::is_same_v<ReturnType, typename Optimizer::ReturnType>>>
957 {
966 static void
967 initialize(typename Optimizer::OptimizerType *optimizer,
968 const SymEngine::vec_basic &independent_symbols,
969 const SymEngine::vec_basic &dependent_functions,
970 const enum OptimizationFlags &optimization_flags)
971 {
972 Assert(optimizer, ExcNotInitialized());
973
974 const bool use_symbolic_cse = use_symbolic_CSE(optimization_flags);
975 optimizer->init(independent_symbols,
976 dependent_functions,
977 use_symbolic_cse);
978 }
979
980
981
995 static void
996 substitute(typename Optimizer::OptimizerType *optimizer,
997 std::vector<ReturnType> &output_values,
998 const std::vector<ReturnType> &substitution_values)
999 {
1000 Assert(optimizer, ExcNotInitialized());
1001
1002 // Intermediate values to accommodate the difference in
1003 // value types.
1004 std::vector<typename Optimizer::ReturnType> int_outputs(
1005 output_values.size());
1006 std::vector<typename Optimizer::ReturnType> int_inputs(
1007 substitution_values.size());
1008
1009 std::copy(substitution_values.begin(),
1010 substitution_values.end(),
1011 int_inputs.begin());
1012 optimizer->call(int_outputs.data(), int_inputs.data());
1013 std::copy(int_outputs.begin(),
1014 int_outputs.end(),
1015 output_values.begin());
1016 }
1017
1018
1019
1024 template <class Archive>
1025 static void
1026 save(Archive &archive,
1027 const unsigned int version,
1028 typename Optimizer::OptimizerType *optimizer)
1029 {
1030 Assert(optimizer, ExcNotInitialized());
1031 Optimizer::save(archive, version, *optimizer);
1032 }
1033
1034
1035
1040 template <class Archive>
1041 static void
1042 load(Archive &archive,
1043 const unsigned int version,
1044 typename Optimizer::OptimizerType *optimizer,
1045 const SymEngine::vec_basic &independent_symbols,
1046 const SymEngine::vec_basic &dependent_functions,
1047 const enum OptimizationFlags &optimization_flags)
1048 {
1049 Assert(optimizer, ExcNotInitialized());
1050
1051 // Some optimizers don't have the same interface for
1052 // serialization, we filter them out through the specializations
1053 // of the Optimizer class
1054 Optimizer::load(archive,
1055 version,
1056 *optimizer,
1057 independent_symbols,
1058 dependent_functions,
1059 optimization_flags);
1060 }
1061
1062
1063
1079 template <typename Stream>
1080 static void
1081 print(Stream &stream,
1082 typename Optimizer::OptimizerType *optimizer,
1083 const bool print_cse_reductions = true,
1084 const bool print_independent_symbols = false,
1085 const bool print_dependent_functions = false)
1086 {
1087 Assert(optimizer, ExcNotInitialized());
1088
1089 optimizer->print(stream,
1090 print_independent_symbols,
1091 print_dependent_functions,
1092 print_cse_reductions);
1093 }
1094 };
1095
1096# endif // DOXYGEN
1097
1098
1099 /* -------------------- Utility functions ---------------------- */
1100
1101
1123 template <typename NumberType,
1124 int rank,
1125 int dim,
1126 template <int, int, typename>
1127 class TensorType>
1128 TensorType<rank, dim, NumberType>
1130 const TensorType<rank, dim, Expression> &symbol_tensor,
1131 const std::vector<NumberType> &cached_evaluation,
1132 const BatchOptimizer<NumberType> &optimizer)
1133 {
1134 TensorType<rank, dim, NumberType> out;
1135 for (unsigned int i = 0; i < out.n_independent_components; ++i)
1136 {
1137 const TableIndices<rank> indices(
1138 out.unrolled_to_component_indices(i));
1139 out[indices] =
1140 optimizer.extract(symbol_tensor[indices], cached_evaluation);
1141 }
1142 return out;
1143 }
1144
1145
1168 template <typename NumberType, int dim>
1171 const SymmetricTensor<4, dim, Expression> &symbol_tensor,
1172 const std::vector<NumberType> &cached_evaluation,
1173 const BatchOptimizer<NumberType> &optimizer)
1174 {
1176 for (unsigned int i = 0;
1177 i < SymmetricTensor<2, dim>::n_independent_components;
1178 ++i)
1179 for (unsigned int j = 0;
1180 j < SymmetricTensor<2, dim>::n_independent_components;
1181 ++j)
1182 {
1183 const TableIndices<4> indices =
1184 make_rank_4_tensor_indices<dim>(i, j);
1185 out[indices] =
1186 optimizer.extract(symbol_tensor[indices], cached_evaluation);
1187 }
1188 return out;
1189 }
1190
1191
1209 template <typename NumberType, typename T>
1210 void
1212 const T &function)
1213 {
1214 optimizer.register_function(function);
1215 }
1216
1217
1235 template <typename NumberType, typename T>
1236 void
1238 const std::vector<T> &functions)
1239 {
1240 for (const auto &function : functions)
1241 register_functions(optimizer, function);
1242 }
1243
1244
1264 template <typename NumberType, typename T, typename... Args>
1265 void
1267 const T &function,
1268 const Args &...other_functions)
1269 {
1270 register_functions(optimizer, function);
1271 register_functions(optimizer, other_functions...);
1272 }
1273
1274
1286 template <int rank,
1287 int dim,
1288 template <int, int, typename>
1289 class TensorType>
1292 const TensorType<rank, dim, Expression> &symbol_tensor)
1293 {
1295 out.reserve(symbol_tensor.n_independent_components);
1296 for (unsigned int i = 0; i < symbol_tensor.n_independent_components;
1297 ++i)
1298 {
1299 const TableIndices<rank> indices(
1300 symbol_tensor.unrolled_to_component_indices(i));
1301 out.push_back(symbol_tensor[indices].get_RCP());
1302 }
1303 return out;
1304 }
1305
1306
1316 template <int dim>
1319 const SymmetricTensor<4, dim, Expression> &symbol_tensor)
1320 {
1322 out.reserve(symbol_tensor.n_independent_components);
1323 for (unsigned int i = 0;
1324 i < SymmetricTensor<2, dim>::n_independent_components;
1325 ++i)
1326 for (unsigned int j = 0;
1327 j < SymmetricTensor<2, dim>::n_independent_components;
1328 ++j)
1329 {
1330 const TableIndices<4> indices =
1331 make_rank_4_tensor_indices<dim>(i, j);
1332 out.push_back(symbol_tensor[indices].get_RCP());
1333 }
1334 return out;
1335 }
1336
1337 } // namespace internal
1338
1339
1340
1431 template <typename ReturnType>
1433 {
1434 public:
1443
1464
1474 BatchOptimizer(const BatchOptimizer &other);
1475
1479 BatchOptimizer(BatchOptimizer &&) noexcept = default;
1480
1484 ~BatchOptimizer() = default;
1485
1496 void
1497 copy_from(const BatchOptimizer &other);
1498
1508 template <typename Stream>
1509 void
1510 print(Stream &stream, const bool print_cse = false) const;
1511
1520 template <class Archive>
1521 void
1522 save(Archive &archive, const unsigned int version) const;
1523
1537 template <class Archive>
1538 void
1539 load(Archive &archive, const unsigned int version);
1540
1541# ifdef DOXYGEN
1563 template <class Archive>
1564 void
1565 serialize(Archive &archive, const unsigned int version);
1566# else
1567 // This macro defines the serialize() method that is compatible with
1568 // the templated save() and load() method that have been implemented.
1569 BOOST_SERIALIZATION_SPLIT_MEMBER()
1570# endif
1571
1582 void
1583 register_symbols(const types::substitution_map &substitution_map);
1584
1590 void
1591 register_symbols(const SymEngine::map_basic_basic &substitution_map);
1592
1603 void
1604 register_symbols(const types::symbol_vector &symbols);
1605
1616 void
1617 register_symbols(const SymEngine::vec_basic &symbols);
1618
1625
1631 std::size_t
1633
1645 void
1646 register_function(const Expression &function);
1647
1652 template <int rank, int dim>
1653 void
1655
1660 template <int rank, int dim>
1661 void
1663 const SymmetricTensor<rank, dim, Expression> &function_tensor);
1664
1669 void
1670 register_functions(const types::symbol_vector &functions);
1671
1676 void
1677 register_functions(const SymEngine::vec_basic &functions);
1678
1688 template <typename T>
1689 void
1690 register_functions(const std::vector<T> &functions);
1691
1705 template <typename T, typename... Args>
1706 void
1707 register_functions(const T &functions, const Args &...other_functions);
1708
1713 const types::symbol_vector &
1715
1722 std::size_t
1723 n_dependent_variables() const;
1724
1743 void
1747
1752 enum OptimizerType
1753 optimization_method() const;
1754
1760 optimization_flags() const;
1761
1767 bool
1768 use_symbolic_CSE() const;
1769
1785 void
1786 optimize();
1787
1792 bool
1793 optimized() const;
1794
1811 void
1812 substitute(const types::substitution_map &substitution_map) const;
1813
1823 void
1824 substitute(const SymEngine::map_basic_basic &substitution_map) const;
1825
1836 void
1837 substitute(const types::symbol_vector &symbols,
1838 const std::vector<ReturnType> &values) const;
1839
1850 void
1851 substitute(const SymEngine::vec_basic &symbols,
1852 const std::vector<ReturnType> &values) const;
1853
1859 bool
1860 values_substituted() const;
1861
1891 const std::vector<ReturnType> &
1892 evaluate() const;
1893
1901 ReturnType
1902 evaluate(const Expression &func) const;
1903
1912 std::vector<ReturnType>
1913 evaluate(const std::vector<Expression> &funcs) const;
1914
1923 template <int rank, int dim>
1926
1927
1936 template <int rank, int dim>
1939
1940
1948 ReturnType
1949 extract(const Expression &func,
1950 const std::vector<ReturnType> &cached_evaluation) const;
1951
1952
1960 std::vector<ReturnType>
1961 extract(const std::vector<Expression> &funcs,
1962 const std::vector<ReturnType> &cached_evaluation) const;
1963
1964
1972 template <int rank, int dim>
1975 const std::vector<ReturnType> &cached_evaluation) const;
1976
1977
1985 template <int rank, int dim>
1988 const std::vector<ReturnType> &cached_evaluation) const;
1989
1992 private:
1997
2003
2014
2021
2026 bool
2028 const SD::Expression &function) const;
2029
2034 bool
2036 const SymEngine::RCP<const SymEngine::Basic> &function) const;
2037
2052 mutable std::vector<ReturnType> dependent_variables_output;
2053
2063 std::map<SD::Expression,
2064 std::size_t,
2066
2072
2079 mutable std::unique_ptr<SymEngine::Visitor> optimizer;
2080
2090
2096
2100 void
2102
2107 void
2109
2113 void
2114 create_optimizer(std::unique_ptr<SymEngine::Visitor> &optimizer);
2115
2132 void
2133 substitute(const std::vector<ReturnType> &substitution_values) const;
2134 };
2135
2136
2137
2138 /* -------------------- inline and template functions ------------------ */
2139
2140
2141# ifndef DOXYGEN
2142
2143
2144 template <typename ReturnType>
2145 template <typename Stream>
2146 void
2147 BatchOptimizer<ReturnType>::print(Stream &stream,
2148 const bool /*print_cse*/) const
2149 {
2150 // Settings
2151 stream << "Method? " << optimization_method() << '\n';
2152 stream << "Flags: " << optimization_flags() << '\n';
2153 stream << "Optimized? " << (optimized() ? "Yes" : "No") << '\n';
2154 stream << "Values substituted? " << values_substituted() << "\n\n";
2155
2156 // Independent variables
2157 stream << "Symbols (" << n_independent_variables()
2158 << " independent variables):" << '\n';
2159 int cntr = 0;
2160 for (SD::types::substitution_map::const_iterator it =
2161 independent_variables_symbols.begin();
2162 it != independent_variables_symbols.end();
2163 ++it, ++cntr)
2164 {
2165 stream << cntr << ": " << it->first << '\n';
2166 }
2167 stream << '\n' << std::flush;
2168
2169 // Dependent functions
2170 stream << "Functions (" << n_dependent_variables()
2171 << " dependent variables):" << '\n';
2172 cntr = 0;
2173 for (typename SD::types::symbol_vector::const_iterator it =
2174 dependent_variables_functions.begin();
2175 it != dependent_variables_functions.end();
2176 ++it, ++cntr)
2177 {
2178 stream << cntr << ": " << (*it) << '\n';
2179 }
2180 stream << '\n' << std::flush;
2181
2182 // Common subexpression
2183 if (optimized() == true && use_symbolic_CSE() == true)
2184 {
2185 Assert(optimizer, ExcNotInitialized());
2186 const bool print_cse_reductions = true;
2187 const bool print_independent_symbols = false;
2188 const bool print_dependent_functions = false;
2189
2190 if (optimization_method() == OptimizerType::dictionary)
2191 {
2192 Assert(dynamic_cast<typename internal::DictionaryOptimizer<
2193 ReturnType>::OptimizerType *>(optimizer.get()),
2194 ExcMessage("Cannot cast optimizer to Dictionary type."));
2195
2196 internal::OptimizerHelper<
2197 ReturnType,
2198 internal::DictionaryOptimizer<ReturnType>>::
2199 print(stream,
2200 dynamic_cast<typename internal::DictionaryOptimizer<
2201 ReturnType>::OptimizerType *>(optimizer.get()),
2202 print_independent_symbols,
2203 print_dependent_functions,
2204 print_cse_reductions);
2205
2206 stream << '\n' << std::flush;
2207 }
2208 else if (optimization_method() == OptimizerType::lambda)
2209 {
2210 Assert(dynamic_cast<typename internal::LambdaOptimizer<
2211 ReturnType>::OptimizerType *>(optimizer.get()),
2212 ExcMessage("Cannot cast optimizer to Lambda type."));
2213
2214 internal::OptimizerHelper<ReturnType,
2215 internal::LambdaOptimizer<ReturnType>>::
2216 print(stream,
2217 dynamic_cast<typename internal::LambdaOptimizer<
2218 ReturnType>::OptimizerType *>(optimizer.get()),
2219 print_independent_symbols,
2220 print_dependent_functions,
2221 print_cse_reductions);
2222 }
2223# ifdef HAVE_SYMENGINE_LLVM
2224 else if (optimization_method() == OptimizerType::llvm)
2225 {
2226 Assert(dynamic_cast<typename internal::LLVMOptimizer<
2227 ReturnType>::OptimizerType *>(optimizer.get()),
2228 ExcMessage("Cannot cast optimizer to LLVM type."));
2229
2230 internal::OptimizerHelper<ReturnType,
2231 internal::LLVMOptimizer<ReturnType>>::
2232 print(stream,
2233 dynamic_cast<typename internal::LLVMOptimizer<
2234 ReturnType>::OptimizerType *>(optimizer.get()),
2235 print_independent_symbols,
2236 print_dependent_functions,
2237 print_cse_reductions);
2238 }
2239# endif // HAVE_SYMENGINE_LLVM
2240 else
2241 {
2242 AssertThrow(false, ExcMessage("Unknown optimizer type."));
2243 }
2244 }
2245
2246 if (values_substituted())
2247 {
2248 stream << "Evaluated functions:" << '\n';
2249 stream << std::flush;
2250 cntr = 0;
2251 for (typename std::vector<ReturnType>::const_iterator it =
2252 dependent_variables_output.begin();
2253 it != dependent_variables_output.end();
2254 ++it, ++cntr)
2255 {
2256 stream << cntr << ": " << (*it) << '\n';
2257 }
2258 stream << '\n' << std::flush;
2259 }
2260 }
2261
2262
2263
2264 template <typename ReturnType>
2265 template <class Archive>
2266 void
2268 const unsigned int version) const
2269 {
2270 // Serialize enum classes...
2271 {
2272 const auto m =
2273 static_cast<std::underlying_type_t<OptimizerType>>(method);
2274 ar &m;
2275 }
2276 {
2277 const auto f =
2278 static_cast<std::underlying_type_t<OptimizationFlags>>(flags);
2279 ar &f;
2280 }
2281
2282 // Important: Independent variables must always be
2283 // serialized before the dependent variables.
2284 ar &independent_variables_symbols;
2285 ar &dependent_variables_functions;
2286
2287 ar &dependent_variables_output;
2288 ar &map_dep_expr_vec_entry;
2289 ar &ready_for_value_extraction;
2290
2291 // Mark that we've saved this class at some point.
2292 has_been_serialized = true;
2293 ar &has_been_serialized;
2294
2295 // When we serialize the optimizer itself, we have to (unfortunately)
2296 // provide it with sufficient information to rebuild itself from scratch.
2297 // This is because only two of the three optimization classes support
2298 // real serialization (i.e. have save/load capability).
2299 const SD::types::symbol_vector symbol_vec =
2300 Utilities::extract_symbols(independent_variables_symbols);
2301 if (typename internal::DictionaryOptimizer<ReturnType>::OptimizerType
2302 *opt = dynamic_cast<typename internal::DictionaryOptimizer<
2303 ReturnType>::OptimizerType *>(optimizer.get()))
2304 {
2305 Assert(optimization_method() == OptimizerType::dictionary,
2307 internal::OptimizerHelper<
2308 ReturnType,
2309 internal::DictionaryOptimizer<ReturnType>>::save(ar, version, opt);
2310 }
2311 else if (typename internal::LambdaOptimizer<ReturnType>::OptimizerType
2312 *opt = dynamic_cast<typename internal::LambdaOptimizer<
2313 ReturnType>::OptimizerType *>(optimizer.get()))
2314 {
2315 Assert(optimization_method() == OptimizerType::lambda,
2317 internal::OptimizerHelper<
2318 ReturnType,
2319 internal::LambdaOptimizer<ReturnType>>::save(ar, version, opt);
2320 }
2321# ifdef HAVE_SYMENGINE_LLVM
2322 else if (typename internal::LLVMOptimizer<ReturnType>::OptimizerType
2323 *opt = dynamic_cast<typename internal::LLVMOptimizer<
2324 ReturnType>::OptimizerType *>(optimizer.get()))
2325 {
2326 Assert(optimization_method() == OptimizerType::llvm,
2328 internal::OptimizerHelper<
2329 ReturnType,
2330 internal::LLVMOptimizer<ReturnType>>::save(ar, version, opt);
2331 }
2332# endif
2333 else
2334 {
2335 AssertThrow(false, ExcMessage("Unknown optimizer type."));
2336 }
2337 }
2338
2339
2340
2341 template <typename ReturnType>
2342 template <class Archive>
2343 void
2344 BatchOptimizer<ReturnType>::load(Archive &ar, const unsigned int version)
2345 {
2346 Assert(independent_variables_symbols.empty(), ExcInternalError());
2347 Assert(dependent_variables_functions.empty(), ExcInternalError());
2348 Assert(dependent_variables_output.empty(), ExcInternalError());
2349 Assert(map_dep_expr_vec_entry.empty(), ExcInternalError());
2350 Assert(ready_for_value_extraction == false, ExcInternalError());
2351
2352 // Deserialize enum classes...
2353 {
2354 std::underlying_type_t<OptimizerType> m;
2355 ar &m;
2356 method = static_cast<OptimizerType>(m);
2357 }
2358 {
2359 std::underlying_type_t<OptimizationFlags> f;
2360 ar &f;
2361 flags = static_cast<OptimizationFlags>(f);
2362 }
2363
2364 // Important: Independent variables must always be
2365 // deserialized before the dependent variables.
2366 ar &independent_variables_symbols;
2367 ar &dependent_variables_functions;
2368
2369 ar &dependent_variables_output;
2370 ar &map_dep_expr_vec_entry;
2371 ar &ready_for_value_extraction;
2372
2373 ar &has_been_serialized;
2374
2375 // If we're reading in data, then create the optimizer
2376 // and then deserialize it.
2377 Assert(!optimizer, ExcInternalError());
2378
2379 // Create and configure the optimizer
2380 create_optimizer(optimizer);
2381 Assert(optimizer, ExcNotInitialized());
2382
2383 // When we deserialize the optimizer itself, we have to (unfortunately)
2384 // provide it with sufficient information to rebuild itself from scratch.
2385 // This is because only two of the three optimization classes support
2386 // real serialization (i.e. have save/load capability).
2387 const SD::types::symbol_vector symbol_vec =
2388 Utilities::extract_symbols(independent_variables_symbols);
2389 if (typename internal::DictionaryOptimizer<ReturnType>::OptimizerType
2390 *opt = dynamic_cast<typename internal::DictionaryOptimizer<
2391 ReturnType>::OptimizerType *>(optimizer.get()))
2392 {
2393 Assert(optimization_method() == OptimizerType::dictionary,
2395 internal::OptimizerHelper<ReturnType,
2396 internal::DictionaryOptimizer<ReturnType>>::
2397 load(ar,
2398 version,
2399 opt,
2401 symbol_vec),
2403 dependent_variables_functions),
2404 optimization_flags());
2405 }
2406 else if (typename internal::LambdaOptimizer<ReturnType>::OptimizerType
2407 *opt = dynamic_cast<typename internal::LambdaOptimizer<
2408 ReturnType>::OptimizerType *>(optimizer.get()))
2409 {
2410 Assert(optimization_method() == OptimizerType::lambda,
2412 internal::OptimizerHelper<ReturnType,
2413 internal::LambdaOptimizer<ReturnType>>::
2414 load(ar,
2415 version,
2416 opt,
2418 symbol_vec),
2420 dependent_variables_functions),
2421 optimization_flags());
2422 }
2423# ifdef HAVE_SYMENGINE_LLVM
2424 else if (typename internal::LLVMOptimizer<ReturnType>::OptimizerType
2425 *opt = dynamic_cast<typename internal::LLVMOptimizer<
2426 ReturnType>::OptimizerType *>(optimizer.get()))
2427 {
2428 Assert(optimization_method() == OptimizerType::llvm,
2430 internal::OptimizerHelper<ReturnType,
2431 internal::LLVMOptimizer<ReturnType>>::
2432 load(ar,
2433 version,
2434 opt,
2436 symbol_vec),
2438 dependent_variables_functions),
2439 optimization_flags());
2440 }
2441# endif
2442 else
2443 {
2444 AssertThrow(false, ExcMessage("Unknown optimizer type."));
2445 }
2446 }
2447
2448
2449
2450 template <typename ReturnType>
2451 template <int rank, int dim>
2452 void
2454 const Tensor<rank, dim, Expression> &function_tensor)
2455 {
2456 Assert(optimized() == false,
2457 ExcMessage(
2458 "Cannot register functions once the optimizer is finalised."));
2459
2460 register_vector_functions(
2461 internal::unroll_to_expression_vector(function_tensor));
2462 }
2463
2464
2465
2466 template <typename ReturnType>
2467 template <int rank, int dim>
2468 void
2470 const SymmetricTensor<rank, dim, Expression> &function_tensor)
2471 {
2472 Assert(optimized() == false,
2473 ExcMessage(
2474 "Cannot register functions once the optimizer is finalised."));
2475
2476 register_vector_functions(
2477 internal::unroll_to_expression_vector(function_tensor));
2478 }
2479
2480
2481
2482 template <typename ReturnType>
2483 template <typename T, typename... Args>
2484 void
2486 const T &functions,
2487 const Args &...other_functions)
2488 {
2489 internal::register_functions(*this, functions);
2490 internal::register_functions(*this, other_functions...);
2491 }
2492
2493
2494
2495 template <typename ReturnType>
2496 template <typename T>
2497 void
2499 const std::vector<T> &functions)
2500 {
2501 internal::register_functions(*this, functions);
2502 }
2503
2504
2505
2506 template <typename ReturnType>
2507 template <int rank, int dim>
2510 const Tensor<rank, dim, Expression> &funcs,
2511 const std::vector<ReturnType> &cached_evaluation) const
2512 {
2514 cached_evaluation,
2515 *this);
2516 }
2517
2518
2519
2520 template <typename ReturnType>
2521 template <int rank, int dim>
2524 const Tensor<rank, dim, Expression> &funcs) const
2525 {
2526 Assert(
2527 values_substituted() == true,
2528 ExcMessage(
2529 "The optimizer is not configured to perform evaluation. "
2530 "This action can only performed after substitute() has been called."));
2531
2532 return extract(funcs, dependent_variables_output);
2533 }
2534
2535
2536
2537 template <typename ReturnType>
2538 template <int rank, int dim>
2542 const std::vector<ReturnType> &cached_evaluation) const
2543 {
2545 cached_evaluation,
2546 *this);
2547 }
2548
2549
2550
2551 template <typename ReturnType>
2552 template <int rank, int dim>
2555 const SymmetricTensor<rank, dim, Expression> &funcs) const
2556 {
2557 Assert(
2558 values_substituted() == true,
2559 ExcMessage(
2560 "The optimizer is not configured to perform evaluation. "
2561 "This action can only performed after substitute() has been called."));
2562
2563 return extract(funcs, dependent_variables_output);
2564 }
2565
2566# endif // DOXYGEN
2567
2568 } // namespace SD
2569} // namespace Differentiation
2570
2571#endif // DEAL_II_WITH_SYMENGINE
2572
2574
2575#endif
SymmetricTensor< rank, dim, ReturnType > extract(const SymmetricTensor< rank, dim, Expression > &funcs, const std::vector< ReturnType > &cached_evaluation) const
types::substitution_map independent_variables_symbols
void substitute(const types::substitution_map &substitution_map) const
void register_functions(const T &functions, const Args &...other_functions)
void register_scalar_function(const SD::Expression &function)
const types::symbol_vector & get_dependent_functions() const
void create_optimizer(std::unique_ptr< SymEngine::Visitor > &optimizer)
void print(Stream &stream, const bool print_cse=false) const
enum OptimizerType optimization_method() const
void copy_from(const BatchOptimizer &other)
void register_function(const Tensor< rank, dim, Expression > &function_tensor)
void set_optimization_method(const enum OptimizerType &optimization_method, const enum OptimizationFlags &optimization_flags=OptimizationFlags::optimize_all)
SymmetricTensor< rank, dim, ReturnType > evaluate(const SymmetricTensor< rank, dim, Expression > &funcs) const
void save(Archive &archive, const unsigned int version) const
enum OptimizationFlags optimization_flags() const
void register_functions(const types::symbol_vector &functions)
std::vector< ReturnType > dependent_variables_output
Tensor< rank, dim, ReturnType > extract(const Tensor< rank, dim, Expression > &funcs, const std::vector< ReturnType > &cached_evaluation) const
void serialize(Archive &archive, const unsigned int version)
void register_symbols(const types::substitution_map &substitution_map)
const std::vector< ReturnType > & evaluate() const
std::map< SD::Expression, std::size_t, SD::types::internal::ExpressionKeyLess > map_dependent_expression_to_vector_entry_t
void register_function(const Expression &function)
Tensor< rank, dim, ReturnType > evaluate(const Tensor< rank, dim, Expression > &funcs) const
std::unique_ptr< SymEngine::Visitor > optimizer
ReturnType extract(const Expression &func, const std::vector< ReturnType > &cached_evaluation) const
BatchOptimizer(BatchOptimizer &&) noexcept=default
void load(Archive &archive, const unsigned int version)
void register_functions(const std::vector< T > &functions)
bool is_valid_nonunique_dependent_variable(const SD::Expression &function) const
void register_vector_functions(const types::symbol_vector &functions)
types::symbol_vector get_independent_symbols() const
void register_function(const SymmetricTensor< rank, dim, Expression > &function_tensor)
map_dependent_expression_to_vector_entry_t map_dep_expr_vec_entry
static constexpr unsigned int n_independent_components
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
static ::ExceptionBase & ExcNotImplemented()
#define Assert(cond, exc)
static ::ExceptionBase & ExcSymEngineLLVMReturnTypeNotSupported()
#define DeclExceptionMsg(Exception, defaulttext)
static ::ExceptionBase & ExcSymEngineLLVMNotAvailable()
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcNotInitialized()
static ::ExceptionBase & ExcMessage(std::string arg1)
#define AssertThrow(cond, exc)
SD::types::symbol_vector extract_symbols(const SD::types::substitution_map &substitution_values)
SymEngine::vec_basic convert_expression_vector_to_basic_vector(const SD::types::symbol_vector &symbol_vector)
TensorType< rank, dim, NumberType > tensor_evaluate_optimized(const TensorType< rank, dim, Expression > &symbol_tensor, const std::vector< NumberType > &cached_evaluation, const BatchOptimizer< NumberType > &optimizer)
bool use_symbolic_CSE(const enum OptimizationFlags &flags)
types::symbol_vector unroll_to_expression_vector(const TensorType< rank, dim, Expression > &symbol_tensor)
int get_LLVM_optimization_level(const enum OptimizationFlags &flags)
void register_functions(BatchOptimizer< NumberType > &optimizer, const T &function)
std::vector< SD::Expression > symbol_vector
std::map< SD::Expression, SD::Expression, internal::ExpressionKeyLess > substitution_map
OptimizationFlags & operator|=(OptimizationFlags &f1, const OptimizationFlags f2)
Expression operator|(const Expression &lhs, const Expression &rhs)
Expression operator&(const Expression &lhs, const Expression &rhs)
Expression substitute(const Expression &expression, const types::substitution_map &substitution_map)
std::ostream & operator<<(std::ostream &stream, const Expression &expression)
OptimizationFlags & operator&=(OptimizationFlags &f1, const OptimizationFlags f2)
constexpr char T
constexpr ReturnType< rank, T >::value_type & extract(T &t, const ArrayType &indices)
void load(Archive &ar, ::std_cxx26::inplace_vector< T, N > &vec, const unsigned int)
void save(Archive &ar, const ::std_cxx26::inplace_vector< T, N > &vec, const unsigned int)
STL namespace.