passagemath-categories 10.6.32__cp314-cp314t-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_categories-10.6.32.dist-info/METADATA +156 -0
- passagemath_categories-10.6.32.dist-info/RECORD +719 -0
- passagemath_categories-10.6.32.dist-info/WHEEL +5 -0
- passagemath_categories-10.6.32.dist-info/top_level.txt +2 -0
- passagemath_categories.libs/libgcc_s-2d945d6c.so.1 +0 -0
- passagemath_categories.libs/libgmp-28992bcb.so.10.5.0 +0 -0
- passagemath_categories.libs/libstdc++-85f2cd6d.so.6.0.33 +0 -0
- sage/all__sagemath_categories.py +28 -0
- sage/arith/all.py +38 -0
- sage/arith/constants.pxd +27 -0
- sage/arith/functions.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/functions.pxd +4 -0
- sage/arith/functions.pyx +221 -0
- sage/arith/misc.py +6552 -0
- sage/arith/multi_modular.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/multi_modular.pxd +39 -0
- sage/arith/multi_modular.pyx +994 -0
- sage/arith/rational_reconstruction.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/rational_reconstruction.pxd +4 -0
- sage/arith/rational_reconstruction.pyx +115 -0
- sage/arith/srange.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/arith/srange.pyx +571 -0
- sage/calculus/all__sagemath_categories.py +2 -0
- sage/calculus/functional.py +481 -0
- sage/calculus/functions.py +151 -0
- sage/categories/additive_groups.py +73 -0
- sage/categories/additive_magmas.py +1044 -0
- sage/categories/additive_monoids.py +114 -0
- sage/categories/additive_semigroups.py +184 -0
- sage/categories/affine_weyl_groups.py +238 -0
- sage/categories/algebra_ideals.py +95 -0
- sage/categories/algebra_modules.py +96 -0
- sage/categories/algebras.py +349 -0
- sage/categories/algebras_with_basis.py +377 -0
- sage/categories/all.py +160 -0
- sage/categories/aperiodic_semigroups.py +29 -0
- sage/categories/associative_algebras.py +47 -0
- sage/categories/bialgebras.py +101 -0
- sage/categories/bialgebras_with_basis.py +414 -0
- sage/categories/bimodules.py +206 -0
- sage/categories/chain_complexes.py +268 -0
- sage/categories/classical_crystals.py +480 -0
- sage/categories/coalgebras.py +405 -0
- sage/categories/coalgebras_with_basis.py +232 -0
- sage/categories/coercion_methods.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/categories/coercion_methods.pyx +52 -0
- sage/categories/commutative_additive_groups.py +104 -0
- sage/categories/commutative_additive_monoids.py +45 -0
- sage/categories/commutative_additive_semigroups.py +48 -0
- sage/categories/commutative_algebra_ideals.py +87 -0
- sage/categories/commutative_algebras.py +94 -0
- sage/categories/commutative_ring_ideals.py +58 -0
- sage/categories/commutative_rings.py +736 -0
- sage/categories/complete_discrete_valuation.py +293 -0
- sage/categories/complex_reflection_groups.py +145 -0
- sage/categories/complex_reflection_or_generalized_coxeter_groups.py +1249 -0
- sage/categories/coxeter_group_algebras.py +186 -0
- sage/categories/coxeter_groups.py +3402 -0
- sage/categories/crystals.py +2628 -0
- sage/categories/cw_complexes.py +216 -0
- sage/categories/dedekind_domains.py +137 -0
- sage/categories/discrete_valuation.py +325 -0
- sage/categories/distributive_magmas_and_additive_magmas.py +100 -0
- sage/categories/division_rings.py +114 -0
- sage/categories/domains.py +95 -0
- sage/categories/drinfeld_modules.py +789 -0
- sage/categories/dual.py +42 -0
- sage/categories/enumerated_sets.py +1146 -0
- sage/categories/euclidean_domains.py +271 -0
- sage/categories/examples/algebras_with_basis.py +102 -0
- sage/categories/examples/all.py +1 -0
- sage/categories/examples/commutative_additive_monoids.py +130 -0
- sage/categories/examples/commutative_additive_semigroups.py +199 -0
- sage/categories/examples/coxeter_groups.py +8 -0
- sage/categories/examples/crystals.py +236 -0
- sage/categories/examples/cw_complexes.py +163 -0
- sage/categories/examples/facade_sets.py +187 -0
- sage/categories/examples/filtered_algebras_with_basis.py +204 -0
- sage/categories/examples/filtered_modules_with_basis.py +154 -0
- sage/categories/examples/finite_coxeter_groups.py +252 -0
- sage/categories/examples/finite_dimensional_algebras_with_basis.py +148 -0
- sage/categories/examples/finite_dimensional_lie_algebras_with_basis.py +495 -0
- sage/categories/examples/finite_enumerated_sets.py +208 -0
- sage/categories/examples/finite_monoids.py +150 -0
- sage/categories/examples/finite_semigroups.py +190 -0
- sage/categories/examples/finite_weyl_groups.py +191 -0
- sage/categories/examples/graded_connected_hopf_algebras_with_basis.py +152 -0
- sage/categories/examples/graded_modules_with_basis.py +168 -0
- sage/categories/examples/graphs.py +122 -0
- sage/categories/examples/hopf_algebras_with_basis.py +145 -0
- sage/categories/examples/infinite_enumerated_sets.py +190 -0
- sage/categories/examples/lie_algebras.py +352 -0
- sage/categories/examples/lie_algebras_with_basis.py +196 -0
- sage/categories/examples/magmas.py +162 -0
- sage/categories/examples/manifolds.py +94 -0
- sage/categories/examples/monoids.py +144 -0
- sage/categories/examples/posets.py +178 -0
- sage/categories/examples/semigroups.py +580 -0
- sage/categories/examples/semigroups_cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/categories/examples/semigroups_cython.pyx +221 -0
- sage/categories/examples/semirings.py +249 -0
- sage/categories/examples/sets_cat.py +706 -0
- sage/categories/examples/sets_with_grading.py +101 -0
- sage/categories/examples/with_realizations.py +542 -0
- sage/categories/fields.py +991 -0
- sage/categories/filtered_algebras.py +63 -0
- sage/categories/filtered_algebras_with_basis.py +548 -0
- sage/categories/filtered_hopf_algebras_with_basis.py +138 -0
- sage/categories/filtered_modules.py +210 -0
- sage/categories/filtered_modules_with_basis.py +1209 -0
- sage/categories/finite_complex_reflection_groups.py +1506 -0
- sage/categories/finite_coxeter_groups.py +1138 -0
- sage/categories/finite_crystals.py +103 -0
- sage/categories/finite_dimensional_algebras_with_basis.py +1860 -0
- sage/categories/finite_dimensional_bialgebras_with_basis.py +33 -0
- sage/categories/finite_dimensional_coalgebras_with_basis.py +33 -0
- sage/categories/finite_dimensional_graded_lie_algebras_with_basis.py +231 -0
- sage/categories/finite_dimensional_hopf_algebras_with_basis.py +38 -0
- sage/categories/finite_dimensional_lie_algebras_with_basis.py +2774 -0
- sage/categories/finite_dimensional_modules_with_basis.py +1407 -0
- sage/categories/finite_dimensional_nilpotent_lie_algebras_with_basis.py +167 -0
- sage/categories/finite_dimensional_semisimple_algebras_with_basis.py +270 -0
- sage/categories/finite_enumerated_sets.py +769 -0
- sage/categories/finite_fields.py +252 -0
- sage/categories/finite_groups.py +256 -0
- sage/categories/finite_lattice_posets.py +242 -0
- sage/categories/finite_monoids.py +316 -0
- sage/categories/finite_permutation_groups.py +339 -0
- sage/categories/finite_posets.py +1994 -0
- sage/categories/finite_semigroups.py +136 -0
- sage/categories/finite_sets.py +93 -0
- sage/categories/finite_weyl_groups.py +39 -0
- sage/categories/finitely_generated_lambda_bracket_algebras.py +112 -0
- sage/categories/finitely_generated_lie_conformal_algebras.py +114 -0
- sage/categories/finitely_generated_magmas.py +57 -0
- sage/categories/finitely_generated_semigroups.py +214 -0
- sage/categories/function_fields.py +76 -0
- sage/categories/g_sets.py +77 -0
- sage/categories/gcd_domains.py +65 -0
- sage/categories/generalized_coxeter_groups.py +94 -0
- sage/categories/graded_algebras.py +85 -0
- sage/categories/graded_algebras_with_basis.py +258 -0
- sage/categories/graded_bialgebras.py +32 -0
- sage/categories/graded_bialgebras_with_basis.py +32 -0
- sage/categories/graded_coalgebras.py +65 -0
- sage/categories/graded_coalgebras_with_basis.py +51 -0
- sage/categories/graded_hopf_algebras.py +41 -0
- sage/categories/graded_hopf_algebras_with_basis.py +169 -0
- sage/categories/graded_lie_algebras.py +91 -0
- sage/categories/graded_lie_algebras_with_basis.py +44 -0
- sage/categories/graded_lie_conformal_algebras.py +74 -0
- sage/categories/graded_modules.py +133 -0
- sage/categories/graded_modules_with_basis.py +329 -0
- sage/categories/graphs.py +138 -0
- sage/categories/group_algebras.py +430 -0
- sage/categories/groupoid.py +94 -0
- sage/categories/groups.py +667 -0
- sage/categories/h_trivial_semigroups.py +64 -0
- sage/categories/hecke_modules.py +185 -0
- sage/categories/highest_weight_crystals.py +980 -0
- sage/categories/hopf_algebras.py +219 -0
- sage/categories/hopf_algebras_with_basis.py +309 -0
- sage/categories/infinite_enumerated_sets.py +115 -0
- sage/categories/integral_domains.py +203 -0
- sage/categories/j_trivial_semigroups.py +29 -0
- sage/categories/kac_moody_algebras.py +82 -0
- sage/categories/kahler_algebras.py +203 -0
- sage/categories/l_trivial_semigroups.py +63 -0
- sage/categories/lambda_bracket_algebras.py +280 -0
- sage/categories/lambda_bracket_algebras_with_basis.py +107 -0
- sage/categories/lattice_posets.py +89 -0
- sage/categories/left_modules.py +49 -0
- sage/categories/lie_algebras.py +1070 -0
- sage/categories/lie_algebras_with_basis.py +261 -0
- sage/categories/lie_conformal_algebras.py +350 -0
- sage/categories/lie_conformal_algebras_with_basis.py +147 -0
- sage/categories/lie_groups.py +73 -0
- sage/categories/loop_crystals.py +1290 -0
- sage/categories/magmas.py +1189 -0
- sage/categories/magmas_and_additive_magmas.py +149 -0
- sage/categories/magmatic_algebras.py +365 -0
- sage/categories/manifolds.py +352 -0
- sage/categories/matrix_algebras.py +40 -0
- sage/categories/metric_spaces.py +387 -0
- sage/categories/modular_abelian_varieties.py +78 -0
- sage/categories/modules.py +989 -0
- sage/categories/modules_with_basis.py +2794 -0
- sage/categories/monoid_algebras.py +38 -0
- sage/categories/monoids.py +739 -0
- sage/categories/noetherian_rings.py +87 -0
- sage/categories/number_fields.py +242 -0
- sage/categories/ore_modules.py +189 -0
- sage/categories/partially_ordered_monoids.py +49 -0
- sage/categories/permutation_groups.py +63 -0
- sage/categories/pointed_sets.py +42 -0
- sage/categories/polyhedra.py +74 -0
- sage/categories/poor_man_map.py +270 -0
- sage/categories/posets.py +722 -0
- sage/categories/principal_ideal_domains.py +270 -0
- sage/categories/quantum_group_representations.py +543 -0
- sage/categories/quotient_fields.py +728 -0
- sage/categories/r_trivial_semigroups.py +45 -0
- sage/categories/regular_crystals.py +898 -0
- sage/categories/regular_supercrystals.py +170 -0
- sage/categories/right_modules.py +49 -0
- sage/categories/ring_ideals.py +74 -0
- sage/categories/rings.py +1904 -0
- sage/categories/rngs.py +175 -0
- sage/categories/schemes.py +393 -0
- sage/categories/semigroups.py +1060 -0
- sage/categories/semirings.py +71 -0
- sage/categories/semisimple_algebras.py +114 -0
- sage/categories/sets_with_grading.py +235 -0
- sage/categories/shephard_groups.py +43 -0
- sage/categories/signed_tensor.py +120 -0
- sage/categories/simplicial_complexes.py +134 -0
- sage/categories/simplicial_sets.py +1206 -0
- sage/categories/super_algebras.py +149 -0
- sage/categories/super_algebras_with_basis.py +144 -0
- sage/categories/super_hopf_algebras_with_basis.py +126 -0
- sage/categories/super_lie_conformal_algebras.py +193 -0
- sage/categories/super_modules.py +229 -0
- sage/categories/super_modules_with_basis.py +193 -0
- sage/categories/supercommutative_algebras.py +99 -0
- sage/categories/supercrystals.py +406 -0
- sage/categories/tensor.py +110 -0
- sage/categories/topological_spaces.py +170 -0
- sage/categories/triangular_kac_moody_algebras.py +439 -0
- sage/categories/tutorial.py +58 -0
- sage/categories/unique_factorization_domains.py +318 -0
- sage/categories/unital_algebras.py +426 -0
- sage/categories/vector_bundles.py +159 -0
- sage/categories/vector_spaces.py +357 -0
- sage/categories/weyl_groups.py +853 -0
- sage/combinat/all__sagemath_categories.py +34 -0
- sage/combinat/backtrack.py +180 -0
- sage/combinat/combinat.py +2269 -0
- sage/combinat/combinat_cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/combinat_cython.pxd +6 -0
- sage/combinat/combinat_cython.pyx +390 -0
- sage/combinat/combination.py +796 -0
- sage/combinat/combinatorial_map.py +416 -0
- sage/combinat/composition.py +2192 -0
- sage/combinat/dlx.py +510 -0
- sage/combinat/integer_lists/__init__.py +7 -0
- sage/combinat/integer_lists/base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/integer_lists/base.pxd +16 -0
- sage/combinat/integer_lists/base.pyx +713 -0
- sage/combinat/integer_lists/invlex.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/integer_lists/invlex.pxd +4 -0
- sage/combinat/integer_lists/invlex.pyx +1650 -0
- sage/combinat/integer_lists/lists.py +328 -0
- sage/combinat/integer_lists/nn.py +48 -0
- sage/combinat/integer_vector.py +1818 -0
- sage/combinat/integer_vector_weighted.py +413 -0
- sage/combinat/matrices/all__sagemath_categories.py +5 -0
- sage/combinat/matrices/dancing_links.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/matrices/dancing_links.pyx +1159 -0
- sage/combinat/matrices/dancing_links_c.h +380 -0
- sage/combinat/matrices/dlxcpp.py +136 -0
- sage/combinat/partition.py +10070 -0
- sage/combinat/partitions.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/partitions.pyx +743 -0
- sage/combinat/permutation.py +10168 -0
- sage/combinat/permutation_cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/combinat/permutation_cython.pxd +11 -0
- sage/combinat/permutation_cython.pyx +407 -0
- sage/combinat/q_analogues.py +1090 -0
- sage/combinat/ranker.py +268 -0
- sage/combinat/subset.py +1561 -0
- sage/combinat/subsets_hereditary.py +202 -0
- sage/combinat/subsets_pairwise.py +184 -0
- sage/combinat/tools.py +63 -0
- sage/combinat/tuple.py +348 -0
- sage/data_structures/all.py +2 -0
- sage/data_structures/all__sagemath_categories.py +2 -0
- sage/data_structures/binary_matrix.pxd +138 -0
- sage/data_structures/binary_search.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/binary_search.pxd +3 -0
- sage/data_structures/binary_search.pyx +66 -0
- sage/data_structures/bitset.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/bitset.pxd +40 -0
- sage/data_structures/bitset.pyx +2385 -0
- sage/data_structures/bitset_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/bitset_base.pxd +926 -0
- sage/data_structures/bitset_base.pyx +117 -0
- sage/data_structures/bitset_intrinsics.h +487 -0
- sage/data_structures/blas_dict.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/blas_dict.pxd +12 -0
- sage/data_structures/blas_dict.pyx +469 -0
- sage/data_structures/list_of_pairs.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/list_of_pairs.pxd +16 -0
- sage/data_structures/list_of_pairs.pyx +122 -0
- sage/data_structures/mutable_poset.py +3312 -0
- sage/data_structures/pairing_heap.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/data_structures/pairing_heap.h +346 -0
- sage/data_structures/pairing_heap.pxd +88 -0
- sage/data_structures/pairing_heap.pyx +1464 -0
- sage/data_structures/sparse_bitset.pxd +62 -0
- sage/data_structures/stream.py +5070 -0
- sage/databases/all__sagemath_categories.py +7 -0
- sage/databases/sql_db.py +2236 -0
- sage/ext/all__sagemath_categories.py +3 -0
- sage/ext/fast_callable.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/fast_callable.pxd +4 -0
- sage/ext/fast_callable.pyx +2746 -0
- sage/ext/fast_eval.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/fast_eval.pxd +1 -0
- sage/ext/fast_eval.pyx +102 -0
- sage/ext/interpreters/__init__.py +1 -0
- sage/ext/interpreters/all__sagemath_categories.py +2 -0
- sage/ext/interpreters/wrapper_el.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/interpreters/wrapper_el.pxd +18 -0
- sage/ext/interpreters/wrapper_el.pyx +148 -0
- sage/ext/interpreters/wrapper_py.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/ext/interpreters/wrapper_py.pxd +17 -0
- sage/ext/interpreters/wrapper_py.pyx +133 -0
- sage/functions/airy.py +937 -0
- sage/functions/all.py +97 -0
- sage/functions/bessel.py +2102 -0
- sage/functions/error.py +784 -0
- sage/functions/exp_integral.py +1529 -0
- sage/functions/gamma.py +1087 -0
- sage/functions/generalized.py +672 -0
- sage/functions/hyperbolic.py +747 -0
- sage/functions/hypergeometric.py +1156 -0
- sage/functions/jacobi.py +1705 -0
- sage/functions/log.py +1402 -0
- sage/functions/min_max.py +338 -0
- sage/functions/orthogonal_polys.py +3106 -0
- sage/functions/other.py +2303 -0
- sage/functions/piecewise.py +1505 -0
- sage/functions/prime_pi.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/functions/prime_pi.pyx +262 -0
- sage/functions/special.py +1212 -0
- sage/functions/spike_function.py +278 -0
- sage/functions/transcendental.py +690 -0
- sage/functions/trig.py +1062 -0
- sage/functions/wigner.py +726 -0
- sage/geometry/abc.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/geometry/abc.pyx +82 -0
- sage/geometry/all__sagemath_categories.py +1 -0
- sage/groups/all__sagemath_categories.py +11 -0
- sage/groups/generic.py +1733 -0
- sage/groups/groups_catalog.py +113 -0
- sage/groups/perm_gps/all__sagemath_categories.py +1 -0
- sage/groups/perm_gps/partn_ref/all.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_categories.py +1 -0
- sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pxd +52 -0
- sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pyx +906 -0
- sage/groups/perm_gps/partn_ref/canonical_augmentation.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/canonical_augmentation.pxd +85 -0
- sage/groups/perm_gps/partn_ref/canonical_augmentation.pyx +534 -0
- sage/groups/perm_gps/partn_ref/data_structures.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/data_structures.pxd +576 -0
- sage/groups/perm_gps/partn_ref/data_structures.pyx +1792 -0
- sage/groups/perm_gps/partn_ref/double_coset.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/double_coset.pxd +45 -0
- sage/groups/perm_gps/partn_ref/double_coset.pyx +739 -0
- sage/groups/perm_gps/partn_ref/refinement_lists.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_lists.pxd +18 -0
- sage/groups/perm_gps/partn_ref/refinement_lists.pyx +82 -0
- sage/groups/perm_gps/partn_ref/refinement_python.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_python.pxd +16 -0
- sage/groups/perm_gps/partn_ref/refinement_python.pyx +564 -0
- sage/groups/perm_gps/partn_ref/refinement_sets.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_sets.pxd +60 -0
- sage/groups/perm_gps/partn_ref/refinement_sets.pyx +858 -0
- sage/interfaces/abc.py +140 -0
- sage/interfaces/all.py +58 -0
- sage/interfaces/all__sagemath_categories.py +1 -0
- sage/interfaces/expect.py +1643 -0
- sage/interfaces/interface.py +1682 -0
- sage/interfaces/process.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/interfaces/process.pxd +5 -0
- sage/interfaces/process.pyx +288 -0
- sage/interfaces/quit.py +167 -0
- sage/interfaces/sage0.py +604 -0
- sage/interfaces/sagespawn.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/interfaces/sagespawn.pyx +308 -0
- sage/interfaces/tab_completion.py +101 -0
- sage/misc/all__sagemath_categories.py +78 -0
- sage/misc/allocator.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/allocator.pxd +6 -0
- sage/misc/allocator.pyx +47 -0
- sage/misc/binary_tree.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/binary_tree.pxd +29 -0
- sage/misc/binary_tree.pyx +537 -0
- sage/misc/callable_dict.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/callable_dict.pyx +89 -0
- sage/misc/citation.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/citation.pyx +159 -0
- sage/misc/converting_dict.py +293 -0
- sage/misc/defaults.py +129 -0
- sage/misc/derivative.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/derivative.pyx +223 -0
- sage/misc/functional.py +2005 -0
- sage/misc/html.py +589 -0
- sage/misc/latex.py +2673 -0
- sage/misc/latex_macros.py +236 -0
- sage/misc/latex_standalone.py +1833 -0
- sage/misc/map_threaded.py +38 -0
- sage/misc/mathml.py +76 -0
- sage/misc/method_decorator.py +88 -0
- sage/misc/mrange.py +755 -0
- sage/misc/multireplace.py +41 -0
- sage/misc/object_multiplexer.py +92 -0
- sage/misc/parser.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/parser.pyx +1107 -0
- sage/misc/random_testing.py +264 -0
- sage/misc/rest_index_of_methods.py +377 -0
- sage/misc/search.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/search.pxd +2 -0
- sage/misc/search.pyx +68 -0
- sage/misc/stopgap.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/misc/stopgap.pyx +95 -0
- sage/misc/table.py +853 -0
- sage/monoids/all__sagemath_categories.py +1 -0
- sage/monoids/indexed_free_monoid.py +1071 -0
- sage/monoids/monoid.py +82 -0
- sage/numerical/all__sagemath_categories.py +1 -0
- sage/numerical/backends/all__sagemath_categories.py +1 -0
- sage/numerical/backends/generic_backend.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/generic_backend.pxd +61 -0
- sage/numerical/backends/generic_backend.pyx +1893 -0
- sage/numerical/backends/generic_sdp_backend.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/numerical/backends/generic_sdp_backend.pxd +38 -0
- sage/numerical/backends/generic_sdp_backend.pyx +755 -0
- sage/parallel/all.py +6 -0
- sage/parallel/decorate.py +575 -0
- sage/parallel/map_reduce.py +1997 -0
- sage/parallel/multiprocessing_sage.py +76 -0
- sage/parallel/ncpus.py +35 -0
- sage/parallel/parallelism.py +364 -0
- sage/parallel/reference.py +47 -0
- sage/parallel/use_fork.py +333 -0
- sage/rings/abc.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/abc.pxd +31 -0
- sage/rings/abc.pyx +526 -0
- sage/rings/algebraic_closure_finite_field.py +1154 -0
- sage/rings/all__sagemath_categories.py +91 -0
- sage/rings/big_oh.py +227 -0
- sage/rings/continued_fraction.py +2754 -0
- sage/rings/continued_fraction_gosper.py +220 -0
- sage/rings/factorint.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/factorint.pyx +295 -0
- sage/rings/fast_arith.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/fast_arith.pxd +21 -0
- sage/rings/fast_arith.pyx +535 -0
- sage/rings/finite_rings/all__sagemath_categories.py +9 -0
- sage/rings/finite_rings/conway_polynomials.py +542 -0
- sage/rings/finite_rings/element_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/element_base.pxd +12 -0
- sage/rings/finite_rings/element_base.pyx +1176 -0
- sage/rings/finite_rings/finite_field_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/finite_field_base.pxd +7 -0
- sage/rings/finite_rings/finite_field_base.pyx +2171 -0
- sage/rings/finite_rings/finite_field_constructor.py +827 -0
- sage/rings/finite_rings/finite_field_prime_modn.py +372 -0
- sage/rings/finite_rings/galois_group.py +154 -0
- sage/rings/finite_rings/hom_finite_field.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/hom_finite_field.pxd +23 -0
- sage/rings/finite_rings/hom_finite_field.pyx +856 -0
- sage/rings/finite_rings/hom_prime_finite_field.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/hom_prime_finite_field.pxd +15 -0
- sage/rings/finite_rings/hom_prime_finite_field.pyx +164 -0
- sage/rings/finite_rings/homset.py +357 -0
- sage/rings/finite_rings/integer_mod.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/integer_mod.pxd +56 -0
- sage/rings/finite_rings/integer_mod.pyx +4586 -0
- sage/rings/finite_rings/integer_mod_limits.h +11 -0
- sage/rings/finite_rings/integer_mod_ring.py +2044 -0
- sage/rings/finite_rings/residue_field.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/finite_rings/residue_field.pxd +30 -0
- sage/rings/finite_rings/residue_field.pyx +1811 -0
- sage/rings/finite_rings/stdint.pxd +19 -0
- sage/rings/fraction_field.py +1452 -0
- sage/rings/fraction_field_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/fraction_field_element.pyx +1357 -0
- sage/rings/function_field/all.py +7 -0
- sage/rings/function_field/all__sagemath_categories.py +2 -0
- sage/rings/function_field/constructor.py +218 -0
- sage/rings/function_field/element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/function_field/element.pxd +11 -0
- sage/rings/function_field/element.pyx +1008 -0
- sage/rings/function_field/element_rational.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/function_field/element_rational.pyx +513 -0
- sage/rings/function_field/extensions.py +230 -0
- sage/rings/function_field/function_field.py +1468 -0
- sage/rings/function_field/function_field_rational.py +1005 -0
- sage/rings/function_field/ideal.py +1155 -0
- sage/rings/function_field/ideal_rational.py +629 -0
- sage/rings/function_field/jacobian_base.py +826 -0
- sage/rings/function_field/jacobian_hess.py +1053 -0
- sage/rings/function_field/jacobian_khuri_makdisi.py +1027 -0
- sage/rings/function_field/maps.py +1039 -0
- sage/rings/function_field/order.py +281 -0
- sage/rings/function_field/order_basis.py +586 -0
- sage/rings/function_field/order_rational.py +576 -0
- sage/rings/function_field/place.py +426 -0
- sage/rings/function_field/place_rational.py +181 -0
- sage/rings/generic.py +320 -0
- sage/rings/homset.py +332 -0
- sage/rings/ideal.py +1885 -0
- sage/rings/ideal_monoid.py +215 -0
- sage/rings/infinity.py +1890 -0
- sage/rings/integer.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/integer.pxd +45 -0
- sage/rings/integer.pyx +7874 -0
- sage/rings/integer_ring.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/integer_ring.pxd +8 -0
- sage/rings/integer_ring.pyx +1693 -0
- sage/rings/laurent_series_ring.py +931 -0
- sage/rings/laurent_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/laurent_series_ring_element.pxd +11 -0
- sage/rings/laurent_series_ring_element.pyx +1927 -0
- sage/rings/lazy_series.py +7815 -0
- sage/rings/lazy_series_ring.py +4356 -0
- sage/rings/localization.py +1043 -0
- sage/rings/morphism.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/morphism.pxd +39 -0
- sage/rings/morphism.pyx +3299 -0
- sage/rings/multi_power_series_ring.py +1145 -0
- sage/rings/multi_power_series_ring_element.py +2184 -0
- sage/rings/noncommutative_ideals.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/noncommutative_ideals.pyx +423 -0
- sage/rings/number_field/all__sagemath_categories.py +1 -0
- sage/rings/number_field/number_field_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/number_field/number_field_base.pxd +8 -0
- sage/rings/number_field/number_field_base.pyx +507 -0
- sage/rings/number_field/number_field_element_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/number_field/number_field_element_base.pxd +6 -0
- sage/rings/number_field/number_field_element_base.pyx +36 -0
- sage/rings/number_field/number_field_ideal.py +3550 -0
- sage/rings/padics/all__sagemath_categories.py +4 -0
- sage/rings/padics/local_generic.py +1670 -0
- sage/rings/padics/local_generic_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/padics/local_generic_element.pxd +5 -0
- sage/rings/padics/local_generic_element.pyx +1017 -0
- sage/rings/padics/misc.py +256 -0
- sage/rings/padics/padic_generic.py +1911 -0
- sage/rings/padics/pow_computer.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/padics/pow_computer.pxd +38 -0
- sage/rings/padics/pow_computer.pyx +671 -0
- sage/rings/padics/precision_error.py +24 -0
- sage/rings/polynomial/all__sagemath_categories.py +25 -0
- sage/rings/polynomial/commutative_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/commutative_polynomial.pxd +6 -0
- sage/rings/polynomial/commutative_polynomial.pyx +24 -0
- sage/rings/polynomial/cyclotomic.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/cyclotomic.pyx +404 -0
- sage/rings/polynomial/flatten.py +711 -0
- sage/rings/polynomial/ideal.py +102 -0
- sage/rings/polynomial/infinite_polynomial_element.py +1768 -0
- sage/rings/polynomial/infinite_polynomial_ring.py +1653 -0
- sage/rings/polynomial/laurent_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/laurent_polynomial.pxd +18 -0
- sage/rings/polynomial/laurent_polynomial.pyx +2190 -0
- sage/rings/polynomial/laurent_polynomial_ideal.py +590 -0
- sage/rings/polynomial/laurent_polynomial_ring.py +832 -0
- sage/rings/polynomial/laurent_polynomial_ring_base.py +708 -0
- sage/rings/polynomial/multi_polynomial.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/multi_polynomial.pxd +12 -0
- sage/rings/polynomial/multi_polynomial.pyx +3082 -0
- sage/rings/polynomial/multi_polynomial_element.py +2570 -0
- sage/rings/polynomial/multi_polynomial_ideal.py +5771 -0
- sage/rings/polynomial/multi_polynomial_ring.py +947 -0
- sage/rings/polynomial/multi_polynomial_ring_base.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/multi_polynomial_ring_base.pxd +15 -0
- sage/rings/polynomial/multi_polynomial_ring_base.pyx +1855 -0
- sage/rings/polynomial/multi_polynomial_sequence.py +2204 -0
- sage/rings/polynomial/polydict.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polydict.pxd +45 -0
- sage/rings/polynomial/polydict.pyx +2701 -0
- sage/rings/polynomial/polynomial_compiled.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polynomial_compiled.pxd +59 -0
- sage/rings/polynomial/polynomial_compiled.pyx +509 -0
- sage/rings/polynomial/polynomial_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polynomial_element.pxd +64 -0
- sage/rings/polynomial/polynomial_element.pyx +13255 -0
- sage/rings/polynomial/polynomial_element_generic.py +1637 -0
- sage/rings/polynomial/polynomial_fateman.py +97 -0
- sage/rings/polynomial/polynomial_quotient_ring.py +2465 -0
- sage/rings/polynomial/polynomial_quotient_ring_element.py +779 -0
- sage/rings/polynomial/polynomial_ring.py +3784 -0
- sage/rings/polynomial/polynomial_ring_constructor.py +1051 -0
- sage/rings/polynomial/polynomial_ring_homomorphism.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polynomial_ring_homomorphism.pxd +5 -0
- sage/rings/polynomial/polynomial_ring_homomorphism.pyx +121 -0
- sage/rings/polynomial/polynomial_singular_interface.py +549 -0
- sage/rings/polynomial/symmetric_ideal.py +989 -0
- sage/rings/polynomial/symmetric_reduction.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/symmetric_reduction.pxd +8 -0
- sage/rings/polynomial/symmetric_reduction.pyx +669 -0
- sage/rings/polynomial/term_order.py +2279 -0
- sage/rings/polynomial/toy_buchberger.py +449 -0
- sage/rings/polynomial/toy_d_basis.py +387 -0
- sage/rings/polynomial/toy_variety.py +362 -0
- sage/rings/power_series_mpoly.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/power_series_mpoly.pxd +9 -0
- sage/rings/power_series_mpoly.pyx +161 -0
- sage/rings/power_series_poly.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/power_series_poly.pxd +10 -0
- sage/rings/power_series_poly.pyx +1317 -0
- sage/rings/power_series_ring.py +1441 -0
- sage/rings/power_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/power_series_ring_element.pxd +12 -0
- sage/rings/power_series_ring_element.pyx +3028 -0
- sage/rings/puiseux_series_ring.py +487 -0
- sage/rings/puiseux_series_ring_element.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/puiseux_series_ring_element.pxd +7 -0
- sage/rings/puiseux_series_ring_element.pyx +1055 -0
- sage/rings/qqbar_decorators.py +167 -0
- sage/rings/quotient_ring.py +1598 -0
- sage/rings/quotient_ring_element.py +979 -0
- sage/rings/rational.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/rational.pxd +20 -0
- sage/rings/rational.pyx +4284 -0
- sage/rings/rational_field.py +1730 -0
- sage/rings/real_double.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/real_double.pxd +16 -0
- sage/rings/real_double.pyx +2218 -0
- sage/rings/real_lazy.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/real_lazy.pxd +30 -0
- sage/rings/real_lazy.pyx +1773 -0
- sage/rings/ring.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/ring.pxd +30 -0
- sage/rings/ring.pyx +850 -0
- sage/rings/semirings/all.py +3 -0
- sage/rings/semirings/non_negative_integer_semiring.py +107 -0
- sage/rings/semirings/tropical_mpolynomial.py +972 -0
- sage/rings/semirings/tropical_polynomial.py +997 -0
- sage/rings/semirings/tropical_semiring.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/semirings/tropical_semiring.pyx +676 -0
- sage/rings/semirings/tropical_variety.py +1701 -0
- sage/rings/sum_of_squares.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/rings/sum_of_squares.pxd +3 -0
- sage/rings/sum_of_squares.pyx +336 -0
- sage/rings/tests.py +504 -0
- sage/schemes/affine/affine_homset.py +508 -0
- sage/schemes/affine/affine_morphism.py +1574 -0
- sage/schemes/affine/affine_point.py +460 -0
- sage/schemes/affine/affine_rational_point.py +308 -0
- sage/schemes/affine/affine_space.py +1264 -0
- sage/schemes/affine/affine_subscheme.py +592 -0
- sage/schemes/affine/all.py +25 -0
- sage/schemes/all__sagemath_categories.py +5 -0
- sage/schemes/generic/algebraic_scheme.py +2092 -0
- sage/schemes/generic/all.py +5 -0
- sage/schemes/generic/ambient_space.py +400 -0
- sage/schemes/generic/divisor.py +465 -0
- sage/schemes/generic/divisor_group.py +313 -0
- sage/schemes/generic/glue.py +84 -0
- sage/schemes/generic/homset.py +820 -0
- sage/schemes/generic/hypersurface.py +234 -0
- sage/schemes/generic/morphism.py +2107 -0
- sage/schemes/generic/point.py +237 -0
- sage/schemes/generic/scheme.py +1190 -0
- sage/schemes/generic/spec.py +199 -0
- sage/schemes/product_projective/all.py +6 -0
- sage/schemes/product_projective/homset.py +236 -0
- sage/schemes/product_projective/morphism.py +517 -0
- sage/schemes/product_projective/point.py +568 -0
- sage/schemes/product_projective/rational_point.py +550 -0
- sage/schemes/product_projective/space.py +1301 -0
- sage/schemes/product_projective/subscheme.py +466 -0
- sage/schemes/projective/all.py +24 -0
- sage/schemes/projective/proj_bdd_height.py +453 -0
- sage/schemes/projective/projective_homset.py +718 -0
- sage/schemes/projective/projective_morphism.py +2792 -0
- sage/schemes/projective/projective_point.py +1484 -0
- sage/schemes/projective/projective_rational_point.py +569 -0
- sage/schemes/projective/projective_space.py +2571 -0
- sage/schemes/projective/projective_subscheme.py +1574 -0
- sage/sets/all.py +17 -0
- sage/sets/cartesian_product.py +376 -0
- sage/sets/condition_set.py +525 -0
- sage/sets/disjoint_set.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/disjoint_set.pxd +36 -0
- sage/sets/disjoint_set.pyx +998 -0
- sage/sets/disjoint_union_enumerated_sets.py +625 -0
- sage/sets/family.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/family.pxd +12 -0
- sage/sets/family.pyx +1556 -0
- sage/sets/finite_enumerated_set.py +406 -0
- sage/sets/finite_set_map_cy.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/finite_set_map_cy.pxd +34 -0
- sage/sets/finite_set_map_cy.pyx +708 -0
- sage/sets/finite_set_maps.py +591 -0
- sage/sets/image_set.py +448 -0
- sage/sets/integer_range.py +829 -0
- sage/sets/non_negative_integers.py +241 -0
- sage/sets/positive_integers.py +93 -0
- sage/sets/primes.py +188 -0
- sage/sets/real_set.py +2760 -0
- sage/sets/recursively_enumerated_set.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/sets/recursively_enumerated_set.pxd +31 -0
- sage/sets/recursively_enumerated_set.pyx +2082 -0
- sage/sets/set.py +2083 -0
- sage/sets/set_from_iterator.py +1021 -0
- sage/sets/totally_ordered_finite_set.py +329 -0
- sage/symbolic/all__sagemath_categories.py +1 -0
- sage/symbolic/function.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/symbolic/function.pxd +29 -0
- sage/symbolic/function.pyx +1488 -0
- sage/symbolic/symbols.py +56 -0
- sage/tests/all__sagemath_categories.py +1 -0
- sage/tests/cython.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/tests/cython.pyx +37 -0
- sage/tests/stl_vector.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/tests/stl_vector.pyx +171 -0
- sage/typeset/all.py +6 -0
- sage/typeset/ascii_art.py +295 -0
- sage/typeset/character_art.py +789 -0
- sage/typeset/character_art_factory.py +572 -0
- sage/typeset/symbols.py +334 -0
- sage/typeset/unicode_art.py +183 -0
- sage/typeset/unicode_characters.py +101 -0
|
@@ -0,0 +1,2746 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-categories
|
|
2
|
+
r"""
|
|
3
|
+
Fast Expression Evaluation
|
|
4
|
+
|
|
5
|
+
For many applications such as numerical integration, differential
|
|
6
|
+
equation approximation, plotting a 3d surface, optimization problems,
|
|
7
|
+
Monte-Carlo simulations, etc., one wishes to pass around and evaluate
|
|
8
|
+
a single algebraic expression many, many times at various floating
|
|
9
|
+
point values. Other applications may need to evaluate an expression
|
|
10
|
+
many times in interval arithmetic, or in a finite field. Doing this
|
|
11
|
+
via recursive calls over a python representation of the object (even
|
|
12
|
+
if Maxima or other outside packages are not involved) is extremely
|
|
13
|
+
inefficient.
|
|
14
|
+
|
|
15
|
+
This module provides a function, :func:`fast_callable`, to
|
|
16
|
+
transform such expressions into a form where they can be evaluated
|
|
17
|
+
quickly::
|
|
18
|
+
|
|
19
|
+
sage: # needs sage.symbolic
|
|
20
|
+
sage: f = sin(x) + 3*x^2
|
|
21
|
+
sage: ff = fast_callable(f, vars=[x])
|
|
22
|
+
sage: ff(3.5)
|
|
23
|
+
36.3992167723104
|
|
24
|
+
sage: ff(RIF(3.5))
|
|
25
|
+
36.39921677231038?
|
|
26
|
+
|
|
27
|
+
By default, :func:`fast_callable` only removes some interpretive
|
|
28
|
+
overhead from the evaluation, but all of the individual arithmetic
|
|
29
|
+
operations are done using standard Sage arithmetic. This is still a
|
|
30
|
+
huge win over :mod:`sage.calculus`, which evidently has a lot of overhead.
|
|
31
|
+
Compare the cost of evaluating Wilkinson's polynomial (in unexpanded
|
|
32
|
+
form) at `x = 30`::
|
|
33
|
+
|
|
34
|
+
sage: # needs sage.symbolic
|
|
35
|
+
sage: wilk = prod((x-i) for i in [1 .. 20]); wilk
|
|
36
|
+
(x - 1)*(x - 2)*(x - 3)*(x - 4)*(x - 5)*(x - 6)*(x - 7)*(x - 8)*(x - 9)*(x - 10)*(x - 11)*(x - 12)*(x - 13)*(x - 14)*(x - 15)*(x - 16)*(x - 17)*(x - 18)*(x - 19)*(x - 20)
|
|
37
|
+
sage: timeit('wilk.subs(x=30)') # random # long time
|
|
38
|
+
625 loops, best of 3: 1.43 ms per loop
|
|
39
|
+
sage: fc_wilk = fast_callable(wilk, vars=[x])
|
|
40
|
+
sage: timeit('fc_wilk(30)') # random # long time
|
|
41
|
+
625 loops, best of 3: 9.72 us per loop
|
|
42
|
+
|
|
43
|
+
You can specify a particular domain for the evaluation using
|
|
44
|
+
``domain=``::
|
|
45
|
+
|
|
46
|
+
sage: fc_wilk_zz = fast_callable(wilk, vars=[x], domain=ZZ) # needs sage.symbolic
|
|
47
|
+
|
|
48
|
+
The meaning of ``domain=D`` is that each intermediate and final result
|
|
49
|
+
is converted to type ``D``. For instance, the previous example of
|
|
50
|
+
``sin(x) + 3*x^2`` with ``domain=D`` would be equivalent to
|
|
51
|
+
``D(D(sin(D(x))) + D(D(3)*D(D(x)^2)))``. (This example also
|
|
52
|
+
demonstrates the one exception to the general rule: if an exponent is an
|
|
53
|
+
integral constant, then it is not wrapped with ``D()``.)
|
|
54
|
+
|
|
55
|
+
At first glance, this seems like a very bad idea if you want to
|
|
56
|
+
compute quickly. And it is a bad idea, for types where we don't
|
|
57
|
+
have a special interpreter. It's not too bad of a slowdown, though.
|
|
58
|
+
To mitigate the costs, we check whether the value already has
|
|
59
|
+
the correct parent before we call ``D``.
|
|
60
|
+
|
|
61
|
+
We don't yet have a special interpreter with domain ``ZZ``, so we can see
|
|
62
|
+
how that compares to the generic ``fc_wilk`` example above::
|
|
63
|
+
|
|
64
|
+
sage: timeit('fc_wilk_zz(30)') # random # long time # needs sage.symbolic
|
|
65
|
+
625 loops, best of 3: 15.4 us per loop
|
|
66
|
+
|
|
67
|
+
However, for other types, using ``domain=D`` will get a large speedup,
|
|
68
|
+
because we have special-purpose interpreters for those types. One
|
|
69
|
+
example is ``RDF``. Since with ``domain=RDF`` we know that every single
|
|
70
|
+
operation will be floating-point, we can just execute the
|
|
71
|
+
floating-point operations directly and skip all the Python object
|
|
72
|
+
creations that you would get from actually using ``RDF`` objects::
|
|
73
|
+
|
|
74
|
+
sage: fc_wilk_rdf = fast_callable(wilk, vars=[x], domain=RDF) # needs sage.symbolic
|
|
75
|
+
sage: timeit('fc_wilk_rdf(30.0)') # random # long time # needs sage.symbolic
|
|
76
|
+
625 loops, best of 3: 7 us per loop
|
|
77
|
+
|
|
78
|
+
The domain does not need to be a Sage type; for instance, ``domain=float``
|
|
79
|
+
also works. (We actually use the same fast interpreter for ``domain=float``
|
|
80
|
+
and ``domain=RDF``; the only difference is that when ``domain=RDF`` is used,
|
|
81
|
+
the return value is an ``RDF`` element, and when ``domain=float`` is used,
|
|
82
|
+
the return value is a Python :class:`float`.) ::
|
|
83
|
+
|
|
84
|
+
sage: fc_wilk_float = fast_callable(wilk, vars=[x], domain=float) # needs sage.symbolic
|
|
85
|
+
sage: timeit('fc_wilk_float(30.0)') # random # long time # needs sage.symbolic
|
|
86
|
+
625 loops, best of 3: 5.04 us per loop
|
|
87
|
+
|
|
88
|
+
We also have support for ``RR``::
|
|
89
|
+
|
|
90
|
+
sage: fc_wilk_rr = fast_callable(wilk, vars=[x], domain=RR) # needs sage.symbolic
|
|
91
|
+
sage: timeit('fc_wilk_rr(30.0)') # random # long time # needs sage.symbolic
|
|
92
|
+
625 loops, best of 3: 13 us per loop
|
|
93
|
+
|
|
94
|
+
For ``CC``::
|
|
95
|
+
|
|
96
|
+
sage: fc_wilk_cc = fast_callable(wilk, vars=[x], domain=CC) # needs sage.symbolic
|
|
97
|
+
sage: timeit('fc_wilk_cc(30.0)') # random # long time # needs sage.symbolic
|
|
98
|
+
625 loops, best of 3: 23 us per loop
|
|
99
|
+
|
|
100
|
+
And support for ``CDF``::
|
|
101
|
+
|
|
102
|
+
sage: fc_wilk_cdf = fast_callable(wilk, vars=[x], domain=CDF) # needs sage.symbolic
|
|
103
|
+
sage: timeit('fc_wilk_cdf(30.0)') # random # long time # needs sage.symbolic
|
|
104
|
+
625 loops, best of 3: 10.2 us per loop
|
|
105
|
+
|
|
106
|
+
Currently, :func:`fast_callable` can accept two kinds of objects:
|
|
107
|
+
polynomials (univariate and multivariate) and symbolic expressions
|
|
108
|
+
(elements of the Symbolic Ring). For polynomials, you can omit the
|
|
109
|
+
``vars`` argument; the variables will default to the ring generators (in
|
|
110
|
+
the order used when creating the ring). ::
|
|
111
|
+
|
|
112
|
+
sage: K.<x,y,z> = QQ[]
|
|
113
|
+
sage: p = 10*y + 100*z + x
|
|
114
|
+
sage: fp = fast_callable(p)
|
|
115
|
+
sage: fp(1,2,3)
|
|
116
|
+
321
|
|
117
|
+
|
|
118
|
+
But you can also specify the variable names to override the default
|
|
119
|
+
ordering (you can include extra variable names here, too). ::
|
|
120
|
+
|
|
121
|
+
sage: fp = fast_callable(p, vars=('x','w','z','y'))
|
|
122
|
+
|
|
123
|
+
For symbolic expressions, you need to specify the variable names, so
|
|
124
|
+
that :func:`fast_callable` knows what order to use. ::
|
|
125
|
+
|
|
126
|
+
sage: # needs sage.symbolic
|
|
127
|
+
sage: var('y,z,x')
|
|
128
|
+
(y, z, x)
|
|
129
|
+
sage: f = 10*y + 100*z + x
|
|
130
|
+
sage: ff = fast_callable(f, vars=(x,y,z))
|
|
131
|
+
sage: ff(1,2,3)
|
|
132
|
+
321
|
|
133
|
+
|
|
134
|
+
You can also specify extra variable names::
|
|
135
|
+
|
|
136
|
+
sage: ff = fast_callable(f, vars=('x','w','z','y')) # needs sage.symbolic
|
|
137
|
+
sage: ff(1,2,3,4) # needs sage.symbolic
|
|
138
|
+
341
|
|
139
|
+
|
|
140
|
+
This should be enough for normal use of :func:`fast_callable`; let's
|
|
141
|
+
discuss some more advanced topics.
|
|
142
|
+
|
|
143
|
+
Sometimes it may be useful to create a fast version of an expression
|
|
144
|
+
without going through symbolic expressions or polynomials; perhaps
|
|
145
|
+
because you want to describe to :func:`fast_callable` an expression
|
|
146
|
+
with common subexpressions.
|
|
147
|
+
|
|
148
|
+
Internally, :func:`fast_callable` works in two stages: it constructs
|
|
149
|
+
an expression tree from its argument, and then it builds a
|
|
150
|
+
fast evaluator from that expression tree. You can bypass the first phase
|
|
151
|
+
by building your own expression tree and passing that directly to
|
|
152
|
+
:func:`fast_callable`, using an :class:`ExpressionTreeBuilder`. ::
|
|
153
|
+
|
|
154
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
155
|
+
sage: etb = ExpressionTreeBuilder(vars=('x','y','z'))
|
|
156
|
+
|
|
157
|
+
An :class:`ExpressionTreeBuilder` has three interesting methods:
|
|
158
|
+
:meth:`constant`, :meth:`var`, and :meth:`call`.
|
|
159
|
+
All of these methods return :class:`ExpressionTree` objects.
|
|
160
|
+
|
|
161
|
+
The :meth:`var` method takes a string, and returns an expression tree
|
|
162
|
+
for the corresponding variable. ::
|
|
163
|
+
|
|
164
|
+
sage: x = etb.var('x')
|
|
165
|
+
sage: y = etb.var('y')
|
|
166
|
+
sage: z = etb.var('y')
|
|
167
|
+
|
|
168
|
+
Expression trees support Python's numeric operators, so you can easily
|
|
169
|
+
build expression trees representing arithmetic expressions. ::
|
|
170
|
+
|
|
171
|
+
sage: v1 = (x+y)*(y+z) + (y//z)
|
|
172
|
+
|
|
173
|
+
The :meth:`constant` method takes a Sage value, and returns an
|
|
174
|
+
expression tree representing that value. ::
|
|
175
|
+
|
|
176
|
+
sage: v2 = etb.constant(3.14159) * x + etb.constant(1729) * y
|
|
177
|
+
|
|
178
|
+
The :meth:`call` method takes a sage/Python function and zero or more
|
|
179
|
+
expression trees, and returns an expression tree representing
|
|
180
|
+
the function call. ::
|
|
181
|
+
|
|
182
|
+
sage: v3 = etb.call(sin, v1+v2)
|
|
183
|
+
sage: v3 # needs sage.symbolic
|
|
184
|
+
sin(add(add(mul(add(v_0, v_1), add(v_1, v_1)), floordiv(v_1, v_1)),
|
|
185
|
+
add(mul(3.14159000000000, v_0), mul(1729, v_1))))
|
|
186
|
+
|
|
187
|
+
Many sage/Python built-in functions are specially handled; for instance,
|
|
188
|
+
when evaluating an expression involving :func:`sin` over ``RDF``,
|
|
189
|
+
the C math library function :func:`sin` is called. Arbitrary functions
|
|
190
|
+
are allowed, but will be much slower since they will call back to
|
|
191
|
+
Python code on every call; for example, the following will work. ::
|
|
192
|
+
|
|
193
|
+
sage: def my_sqrt(x): return pow(x, 0.5)
|
|
194
|
+
sage: e = etb.call(my_sqrt, v1); e
|
|
195
|
+
{my_sqrt}(add(mul(add(v_0, v_1), add(v_1, v_1)), floordiv(v_1, v_1)))
|
|
196
|
+
sage: fast_callable(e)(1, 2, 3)
|
|
197
|
+
3.60555127546399
|
|
198
|
+
|
|
199
|
+
To provide :func:`fast_callable` for your own class (so that
|
|
200
|
+
``fast_callable(x)`` works when ``x`` is an instance of your
|
|
201
|
+
class), implement a method ``_fast_callable_(self, etb)`` for your class.
|
|
202
|
+
This method takes an :class:`ExpressionTreeBuilder`, and returns an
|
|
203
|
+
expression tree built up using the methods described above.
|
|
204
|
+
|
|
205
|
+
EXAMPLES::
|
|
206
|
+
|
|
207
|
+
sage: var('x') # needs sage.symbolic
|
|
208
|
+
x
|
|
209
|
+
sage: f = fast_callable(sqrt(x^7+1), vars=[x], domain=float) # needs sage.symbolic
|
|
210
|
+
|
|
211
|
+
::
|
|
212
|
+
|
|
213
|
+
sage: f(1) # needs sage.symbolic
|
|
214
|
+
1.4142135623730951
|
|
215
|
+
sage: f.op_list() # needs sage.symbolic
|
|
216
|
+
[('load_arg', 0), ('ipow', 7), ('load_const', 1.0), 'add', 'sqrt', 'return']
|
|
217
|
+
|
|
218
|
+
To interpret that last line, we load argument 0 (``x`` in this case) onto
|
|
219
|
+
the stack, push the constant `7.0` onto the stack, call the :func:`pow`
|
|
220
|
+
functionn(which takes 2 arguments from the stack), push the constant `1.0`,
|
|
221
|
+
add the top two arguments of the stack, and then call :func:`sqrt`.
|
|
222
|
+
|
|
223
|
+
Here we take :func:`sin` of the first argument and add it to ``f``::
|
|
224
|
+
|
|
225
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
226
|
+
sage: etb = ExpressionTreeBuilder('x')
|
|
227
|
+
sage: x = etb.var('x')
|
|
228
|
+
sage: f = etb.call(sqrt, x^7 + 1)
|
|
229
|
+
sage: g = etb.call(sin, x)
|
|
230
|
+
sage: fast_callable(f+g).op_list()
|
|
231
|
+
[('load_arg', 0), ('ipow', 7), ('load_const', 1), 'add',
|
|
232
|
+
('py_call', <function sqrt at ...>, 1), ('load_arg', 0), ('py_call', sin, 1),
|
|
233
|
+
'add', 'return']
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
AUTHOR:
|
|
237
|
+
|
|
238
|
+
- Carl Witty (2009-02): initial version (heavily inspired by
|
|
239
|
+
Robert Bradshaw's fast_eval.pyx)
|
|
240
|
+
|
|
241
|
+
.. TODO::
|
|
242
|
+
|
|
243
|
+
The following bits of text were written for the module docstring.
|
|
244
|
+
They are not true yet, but I hope they will be true someday, at
|
|
245
|
+
which point I will move them into the main text.
|
|
246
|
+
|
|
247
|
+
The final interesting method of :class:`ExpressionTreeBuilder` is
|
|
248
|
+
:meth:`choice`. This produces conditional expressions, like the C
|
|
249
|
+
``COND ? T : F`` expression or the Python ``T if COND else F``.
|
|
250
|
+
This lets you define piecewise functions using :func:`fast_callable`. ::
|
|
251
|
+
|
|
252
|
+
sage: v4 = etb.choice(v3 >= etb.constant(0), v1, v2) # not tested
|
|
253
|
+
|
|
254
|
+
The arguments are ``(COND, T, F)`` (the same order as in C), so the
|
|
255
|
+
above means that if ``v3`` evaluates to a nonnegative number,
|
|
256
|
+
then ``v4`` will evaluate to the result of ``v1``;
|
|
257
|
+
otherwise, ``v4`` will evaluate to the result of ``v2``.
|
|
258
|
+
|
|
259
|
+
Let's see an example where we see that :func:`fast_callable` does not
|
|
260
|
+
evaluate common subexpressions more than once. We'll make a
|
|
261
|
+
:func:`fast_callable` expression that gives the result
|
|
262
|
+
of 16 iterations of the Mandelbrot function. ::
|
|
263
|
+
|
|
264
|
+
sage: etb = ExpressionTreeBuilder('c')
|
|
265
|
+
sage: z = etb.constant(0)
|
|
266
|
+
sage: c = etb.var('c')
|
|
267
|
+
sage: for i in range(16):
|
|
268
|
+
....: z = z*z + c
|
|
269
|
+
sage: mand = fast_callable(z, domain=CDF) # needs sage.rings.complex_double
|
|
270
|
+
|
|
271
|
+
Now ``ff`` does 32 complex arithmetic operations on each call
|
|
272
|
+
(16 additions and 16 multiplications). However, if ``z*z`` produced
|
|
273
|
+
code that evaluated ``z`` twice, then this would do many
|
|
274
|
+
thousands of arithmetic operations instead.
|
|
275
|
+
|
|
276
|
+
Note that the handling for common subexpressions only checks whether
|
|
277
|
+
expression trees are the same Python object; for instance, the following
|
|
278
|
+
code will evaluate ``x+1`` twice::
|
|
279
|
+
|
|
280
|
+
sage: etb = ExpressionTreeBuilder('x')
|
|
281
|
+
sage: x = etb.var('x')
|
|
282
|
+
sage: (x+1)*(x+1)
|
|
283
|
+
mul(add(v_0, 1), add(v_0, 1))
|
|
284
|
+
|
|
285
|
+
but this code will only evaluate ``x+1`` once::
|
|
286
|
+
|
|
287
|
+
sage: v = x+1; v*v
|
|
288
|
+
mul(add(v_0, 1), add(v_0, 1))
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
#*****************************************************************************
|
|
293
|
+
# Copyright (C) 2008 Robert Bradshaw <robertwb@math.washington.edu>
|
|
294
|
+
# Copyright (C) 2009 Carl Witty <Carl.Witty@gmail.com>
|
|
295
|
+
#
|
|
296
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
297
|
+
#
|
|
298
|
+
# This code is distributed in the hope that it will be useful,
|
|
299
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
300
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
301
|
+
# General Public License for more details.
|
|
302
|
+
#
|
|
303
|
+
# The full text of the GPL is available at:
|
|
304
|
+
#
|
|
305
|
+
# http://www.gnu.org/licenses/
|
|
306
|
+
#*****************************************************************************
|
|
307
|
+
|
|
308
|
+
import operator
|
|
309
|
+
from copy import copy
|
|
310
|
+
from math import isnan, nan
|
|
311
|
+
|
|
312
|
+
import sage.rings.abc
|
|
313
|
+
from sage.rings.integer import Integer
|
|
314
|
+
from sage.rings.integer_ring import ZZ
|
|
315
|
+
from sage.structure.element cimport parent
|
|
316
|
+
from sage.structure.element cimport Expression as Expression_abc
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def fast_callable(x, domain=None, vars=None,
|
|
320
|
+
expect_one_var=False):
|
|
321
|
+
r"""
|
|
322
|
+
Given an expression ``x``, compile it into a form that can be quickly
|
|
323
|
+
evaluated, given values for the variables in ``x``.
|
|
324
|
+
|
|
325
|
+
Currently, ``x`` can be an expression object, an element of ``SR``, or a
|
|
326
|
+
(univariate or multivariate) polynomial; this list will probably
|
|
327
|
+
be extended soon.
|
|
328
|
+
|
|
329
|
+
By default, ``x`` is evaluated the same way that a Python function
|
|
330
|
+
would evaluate it -- addition maps to ``PyNumber_Add``, etc. However,
|
|
331
|
+
you can specify ``domain=D`` where ``D`` is some Sage parent or Python
|
|
332
|
+
type; in this case, all arithmetic is done in that domain. If we
|
|
333
|
+
have a special-purpose interpreter for that parent (like ``RDF`` or
|
|
334
|
+
:class:`float`), ``domain=...`` will trigger the use of that interpreter.
|
|
335
|
+
|
|
336
|
+
If ``vars`` is ``None`` and ``x`` is a polynomial, then we will use the
|
|
337
|
+
generators of ``parent(x)`` as the variables; otherwise, ``vars`` must be
|
|
338
|
+
specified (unless ``x`` is a symbolic expression with only one variable,
|
|
339
|
+
and ``expect_one_var`` is ``True``, in which case we will use that variable).
|
|
340
|
+
|
|
341
|
+
EXAMPLES::
|
|
342
|
+
|
|
343
|
+
sage: # needs sage.symbolic
|
|
344
|
+
sage: var('x')
|
|
345
|
+
x
|
|
346
|
+
sage: expr = sin(x) + 3*x^2
|
|
347
|
+
sage: f = fast_callable(expr, vars=[x])
|
|
348
|
+
sage: f(2)
|
|
349
|
+
sin(2) + 12
|
|
350
|
+
sage: f(2.0)
|
|
351
|
+
12.9092974268257
|
|
352
|
+
|
|
353
|
+
We have special fast interpreters for ``domain=float`` and ``domain=RDF``.
|
|
354
|
+
(Actually it's the same interpreter; only the return type varies.)
|
|
355
|
+
Note that the float interpreter is not actually more accurate than
|
|
356
|
+
the ``RDF`` interpreter; elements of ``RDF`` just don't display all
|
|
357
|
+
their digits. We have special fast interpreter for ``domain=CDF``::
|
|
358
|
+
|
|
359
|
+
sage: # needs sage.symbolic
|
|
360
|
+
sage: f_float = fast_callable(expr, vars=[x], domain=float)
|
|
361
|
+
sage: f_float(2)
|
|
362
|
+
12.909297426825681
|
|
363
|
+
sage: f_rdf = fast_callable(expr, vars=[x], domain=RDF)
|
|
364
|
+
sage: f_rdf(2)
|
|
365
|
+
12.909297426825681
|
|
366
|
+
sage: f_cdf = fast_callable(expr, vars=[x], domain=CDF)
|
|
367
|
+
sage: f_cdf(2)
|
|
368
|
+
12.909297426825681
|
|
369
|
+
sage: f_cdf(2+I)
|
|
370
|
+
10.40311925062204 + 11.510943740958707*I
|
|
371
|
+
sage: f = fast_callable(expr, vars=('z','x','y'))
|
|
372
|
+
sage: f(1, 2, 3)
|
|
373
|
+
sin(2) + 12
|
|
374
|
+
|
|
375
|
+
sage: K.<x> = QQ[]
|
|
376
|
+
sage: p = -1/4*x^6 + 1/2*x^5 - x^4 - 12*x^3 + 1/2*x^2 - 1/95*x - 1/2
|
|
377
|
+
sage: fp = fast_callable(p, domain=RDF)
|
|
378
|
+
sage: fp.op_list()
|
|
379
|
+
[('load_arg', 0), ('load_const', -0.25), 'mul', ('load_const', 0.5), 'add',
|
|
380
|
+
('load_arg', 0), 'mul', ('load_const', -1.0), 'add', ('load_arg', 0), 'mul',
|
|
381
|
+
('load_const', -12.0), 'add', ('load_arg', 0), 'mul', ('load_const', 0.5),
|
|
382
|
+
'add', ('load_arg', 0), 'mul', ('load_const', -0.010526315789473684),
|
|
383
|
+
'add', ('load_arg', 0), 'mul', ('load_const', -0.5), 'add', 'return']
|
|
384
|
+
sage: fp(3.14159)
|
|
385
|
+
-552.4182988917153
|
|
386
|
+
|
|
387
|
+
sage: K.<x,y,z> = QQ[]
|
|
388
|
+
sage: p = x*y^2 + 1/3*y^2 - x*z - y*z
|
|
389
|
+
sage: fp = fast_callable(p, domain=RDF)
|
|
390
|
+
sage: fp.op_list()
|
|
391
|
+
[('load_const', 0.0), ('load_const', 1.0), ('load_arg', 0), ('ipow', 1),
|
|
392
|
+
('load_arg', 1), ('ipow', 2), 'mul', 'mul', 'add',
|
|
393
|
+
('load_const', 0.3333333333333333), ('load_arg', 1), ('ipow', 2), 'mul', 'add',
|
|
394
|
+
('load_const', -1.0), ('load_arg', 0), ('ipow', 1), ('load_arg', 2),
|
|
395
|
+
('ipow', 1), 'mul', 'mul', 'add', ('load_const', -1.0), ('load_arg', 1),
|
|
396
|
+
('ipow', 1), ('load_arg', 2), ('ipow', 1), 'mul', 'mul', 'add', 'return']
|
|
397
|
+
sage: fp(e, pi, sqrt(2)) # abs tol 3e-14 # needs sage.symbolic
|
|
398
|
+
21.831120464939584
|
|
399
|
+
sage: symbolic_result = p(e, pi, sqrt(2)); symbolic_result # needs sage.symbolic
|
|
400
|
+
pi^2*e + 1/3*pi^2 - sqrt(2)*pi - sqrt(2)*e
|
|
401
|
+
sage: n(symbolic_result) # needs sage.symbolic
|
|
402
|
+
21.8311204649396
|
|
403
|
+
|
|
404
|
+
::
|
|
405
|
+
|
|
406
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
407
|
+
sage: etb = ExpressionTreeBuilder(vars=('x','y'), domain=float)
|
|
408
|
+
sage: x = etb.var('x')
|
|
409
|
+
sage: y = etb.var('y')
|
|
410
|
+
sage: expr = etb.call(sin, x^2 + y); expr
|
|
411
|
+
sin(add(ipow(v_0, 2), v_1))
|
|
412
|
+
sage: fc = fast_callable(expr, domain=float)
|
|
413
|
+
sage: fc(5, 7)
|
|
414
|
+
0.5514266812416906
|
|
415
|
+
|
|
416
|
+
Check that :func:`fast_callable` also works for symbolic functions with
|
|
417
|
+
evaluation functions::
|
|
418
|
+
|
|
419
|
+
sage: # needs sage.symbolic
|
|
420
|
+
sage: def evalf_func(self, x, y, parent):
|
|
421
|
+
....: return parent(x*y) if parent is not None else x*y
|
|
422
|
+
sage: x,y = var('x,y')
|
|
423
|
+
sage: f = function('f', evalf_func=evalf_func)
|
|
424
|
+
sage: fc = fast_callable(f(x, y), vars=[x, y])
|
|
425
|
+
sage: fc(3, 4)
|
|
426
|
+
f(3, 4)
|
|
427
|
+
|
|
428
|
+
And also when there are complex values involved::
|
|
429
|
+
|
|
430
|
+
sage: # needs sage.symbolic
|
|
431
|
+
sage: def evalf_func(self, x, y, parent):
|
|
432
|
+
....: return parent(I*x*y) if parent is not None else I*x*y
|
|
433
|
+
sage: g = function('g', evalf_func=evalf_func)
|
|
434
|
+
sage: fc = fast_callable(g(x, y), vars=[x, y])
|
|
435
|
+
sage: fc(3, 4)
|
|
436
|
+
g(3, 4)
|
|
437
|
+
sage: fc2 = fast_callable(g(x, y), domain=complex, vars=[x, y])
|
|
438
|
+
sage: fc2(3, 4)
|
|
439
|
+
12j
|
|
440
|
+
sage: fc3 = fast_callable(g(x, y), domain=float, vars=[x, y])
|
|
441
|
+
sage: fc3(3, 4)
|
|
442
|
+
Traceback (most recent call last):
|
|
443
|
+
...
|
|
444
|
+
TypeError: unable to simplify to float approximation
|
|
445
|
+
"""
|
|
446
|
+
cdef Expression et
|
|
447
|
+
if isinstance(x, Expression):
|
|
448
|
+
et = x
|
|
449
|
+
vars = et._etb._vars
|
|
450
|
+
else:
|
|
451
|
+
if not vars:
|
|
452
|
+
# fast_float passes empty list/tuple
|
|
453
|
+
vars = None
|
|
454
|
+
|
|
455
|
+
if isinstance(x, Expression_abc) and x.is_callable():
|
|
456
|
+
if vars is None:
|
|
457
|
+
vars = x.arguments()
|
|
458
|
+
if expect_one_var and len(vars) != 1:
|
|
459
|
+
raise ValueError(f"passed expect_one_var=True, but the callable expression takes {len(vars)} arguments")
|
|
460
|
+
elif isinstance(x, Expression_abc):
|
|
461
|
+
if vars is None:
|
|
462
|
+
vars = x.variables()
|
|
463
|
+
if expect_one_var and len(vars) <= 1:
|
|
464
|
+
if not vars:
|
|
465
|
+
vars = ['EXTRA_VAR0']
|
|
466
|
+
else:
|
|
467
|
+
raise ValueError("list of variables must be specified for symbolic expressions")
|
|
468
|
+
|
|
469
|
+
def to_var(var):
|
|
470
|
+
if isinstance(var, Expression_abc) and var.is_symbol():
|
|
471
|
+
return var
|
|
472
|
+
from sage.symbolic.ring import SR
|
|
473
|
+
return SR.var(var)
|
|
474
|
+
vars = [to_var(var) for var in vars]
|
|
475
|
+
# Convert to a callable symbolic expression
|
|
476
|
+
x = x.function(*vars)
|
|
477
|
+
|
|
478
|
+
if vars is None:
|
|
479
|
+
from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
|
|
480
|
+
from sage.rings.polynomial.multi_polynomial_ring import MPolynomialRing_base
|
|
481
|
+
P = x.parent()
|
|
482
|
+
if isinstance(P, (PolynomialRing_generic, MPolynomialRing_base)):
|
|
483
|
+
vars = P.variable_names()
|
|
484
|
+
else:
|
|
485
|
+
# constant
|
|
486
|
+
vars = ()
|
|
487
|
+
|
|
488
|
+
etb = ExpressionTreeBuilder(vars=vars, domain=domain)
|
|
489
|
+
et = x._fast_callable_(etb)
|
|
490
|
+
|
|
491
|
+
builder, str = _builder_and_stream(vars=vars, domain=domain)
|
|
492
|
+
|
|
493
|
+
generate_code(et, str)
|
|
494
|
+
str.instr('return')
|
|
495
|
+
return builder(str.get_current())
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _builder_and_stream(vars, domain):
|
|
499
|
+
r"""
|
|
500
|
+
Return a builder and a stream.
|
|
501
|
+
|
|
502
|
+
This is an internal function used only once, by :func:`fast_callable`.
|
|
503
|
+
|
|
504
|
+
INPUT:
|
|
505
|
+
|
|
506
|
+
- ``vars`` -- a sequence of variable names
|
|
507
|
+
|
|
508
|
+
- ``domain`` -- a Sage parent or Python type or ``None``; if non-``None``,
|
|
509
|
+
all arithmetic is done in that domain
|
|
510
|
+
|
|
511
|
+
OUTPUT: a :class:`Wrapper`, an class:`InstructionStream`
|
|
512
|
+
|
|
513
|
+
EXAMPLES::
|
|
514
|
+
|
|
515
|
+
sage: from sage.ext.fast_callable import _builder_and_stream
|
|
516
|
+
sage: _builder_and_stream(["x", "y"], ZZ)
|
|
517
|
+
(<class '...interpreters.wrapper_el.Wrapper_el'>,
|
|
518
|
+
<sage.ext.fast_callable.InstructionStream object at 0x...>)
|
|
519
|
+
sage: _builder_and_stream(["x", "y"], RR) # needs sage.rings.real_mpfr
|
|
520
|
+
(<class '...interpreters.wrapper_rr.Wrapper_rr'>,
|
|
521
|
+
<sage.ext.fast_callable.InstructionStream object at 0x...>)
|
|
522
|
+
|
|
523
|
+
Modularized test with sagemath-categories after :issue:`35095`, which has
|
|
524
|
+
(a basic version of) ``RDF`` but not the specialized interpreter for it.
|
|
525
|
+
In this case, the function falls back to using the :class:`Element`
|
|
526
|
+
interpreter::
|
|
527
|
+
|
|
528
|
+
sage: domain = RDF
|
|
529
|
+
sage: from sage.structure.element import Element as domain
|
|
530
|
+
sage: _builder_and_stream(["x", "y"], domain)
|
|
531
|
+
(<class '...interpreters.wrapper_el.Wrapper_el'>,
|
|
532
|
+
<sage.ext.fast_callable.InstructionStream object at 0x...>)
|
|
533
|
+
"""
|
|
534
|
+
if isinstance(domain, sage.rings.abc.RealField):
|
|
535
|
+
try:
|
|
536
|
+
from sage.ext.interpreters.wrapper_rr import metadata, Wrapper_rr as builder
|
|
537
|
+
except ImportError:
|
|
538
|
+
pass
|
|
539
|
+
else:
|
|
540
|
+
return builder, InstructionStream(metadata, len(vars), domain)
|
|
541
|
+
|
|
542
|
+
if isinstance(domain, sage.rings.abc.ComplexField):
|
|
543
|
+
try:
|
|
544
|
+
from sage.ext.interpreters.wrapper_cc import metadata, Wrapper_cc as builder
|
|
545
|
+
except ImportError:
|
|
546
|
+
pass
|
|
547
|
+
else:
|
|
548
|
+
return builder, InstructionStream(metadata, len(vars), domain)
|
|
549
|
+
|
|
550
|
+
if isinstance(domain, sage.rings.abc.RealDoubleField) or domain is float:
|
|
551
|
+
try:
|
|
552
|
+
from sage.ext.interpreters.wrapper_rdf import metadata, Wrapper_rdf as builder
|
|
553
|
+
except ImportError:
|
|
554
|
+
pass
|
|
555
|
+
else:
|
|
556
|
+
return builder, InstructionStream(metadata, len(vars), domain)
|
|
557
|
+
|
|
558
|
+
if isinstance(domain, sage.rings.abc.ComplexDoubleField):
|
|
559
|
+
try:
|
|
560
|
+
from sage.ext.interpreters.wrapper_cdf import metadata, Wrapper_cdf as builder
|
|
561
|
+
except ImportError:
|
|
562
|
+
pass
|
|
563
|
+
else:
|
|
564
|
+
return builder, InstructionStream(metadata, len(vars), domain)
|
|
565
|
+
|
|
566
|
+
if domain is None:
|
|
567
|
+
from sage.ext.interpreters.wrapper_py import metadata, Wrapper_py as builder
|
|
568
|
+
return builder, InstructionStream(metadata, len(vars))
|
|
569
|
+
|
|
570
|
+
from sage.ext.interpreters.wrapper_el import metadata, Wrapper_el as builder
|
|
571
|
+
return builder, InstructionStream(metadata, len(vars), domain)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def function_name(fn):
|
|
575
|
+
r"""
|
|
576
|
+
Given a function, return a string giving a name for the function.
|
|
577
|
+
|
|
578
|
+
For functions we recognize, we use our standard opcode name for the
|
|
579
|
+
function (so :func:`operator.add` becomes ``'add'``, and
|
|
580
|
+
:func:`sage.functions.trig.sin` becomes ``'sin'``).
|
|
581
|
+
|
|
582
|
+
For functions we don't recognize, we try to come up with a name,
|
|
583
|
+
but the name will be wrapped in braces; this is a signal that
|
|
584
|
+
we'll definitely use a slow Python call to call this function.
|
|
585
|
+
(We may use a slow Python call even for functions we do recognize,
|
|
586
|
+
if we're targeting an interpreter without an opcode for the function.)
|
|
587
|
+
|
|
588
|
+
Only used when printing Expressions.
|
|
589
|
+
|
|
590
|
+
EXAMPLES::
|
|
591
|
+
|
|
592
|
+
sage: from sage.ext.fast_callable import function_name
|
|
593
|
+
sage: function_name(operator.pow)
|
|
594
|
+
'pow'
|
|
595
|
+
sage: function_name(cos) # needs sage.symbolic
|
|
596
|
+
'cos'
|
|
597
|
+
sage: function_name(factorial)
|
|
598
|
+
'{factorial}'
|
|
599
|
+
"""
|
|
600
|
+
from sage.structure.dynamic_class import DynamicMetaclass
|
|
601
|
+
if isinstance(type(fn), DynamicMetaclass):
|
|
602
|
+
return "{%r}" % fn
|
|
603
|
+
builtins = get_builtin_functions()
|
|
604
|
+
if fn in builtins:
|
|
605
|
+
return builtins[fn]
|
|
606
|
+
try:
|
|
607
|
+
return "{%s}" % fn.__name__
|
|
608
|
+
except AttributeError:
|
|
609
|
+
return "{%r}" % fn
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
cdef class ExpressionTreeBuilder:
|
|
613
|
+
r"""
|
|
614
|
+
A class with helper methods for building Expressions.
|
|
615
|
+
|
|
616
|
+
An instance of this class is passed to :meth:`_fast_callable_` methods;
|
|
617
|
+
you can also instantiate it yourself to create your own expressions
|
|
618
|
+
for :func:`fast_callable`, bypassing :meth:`_fast_callable_`.
|
|
619
|
+
|
|
620
|
+
EXAMPLES::
|
|
621
|
+
|
|
622
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
623
|
+
sage: etb = ExpressionTreeBuilder('x')
|
|
624
|
+
sage: x = etb.var('x')
|
|
625
|
+
sage: (x+3)*5
|
|
626
|
+
mul(add(v_0, 3), 5)
|
|
627
|
+
"""
|
|
628
|
+
|
|
629
|
+
cdef readonly object _domain
|
|
630
|
+
cdef readonly object _vars
|
|
631
|
+
|
|
632
|
+
def __init__(self, vars, domain=None):
|
|
633
|
+
r"""
|
|
634
|
+
Initialize an instance of :class:`ExpressionTreeBuilder`.
|
|
635
|
+
|
|
636
|
+
Takes a list or tuple of variable names to use, and also an optional
|
|
637
|
+
``domain``. If a ``domain`` is given, then creating an
|
|
638
|
+
:class:`ExpressionConstant` node with the :meth:`__call__`, make, or
|
|
639
|
+
constant methods will convert the value into the given domain.
|
|
640
|
+
|
|
641
|
+
Note that this is the only effect of the domain parameter. It
|
|
642
|
+
is quite possible to use different domains for
|
|
643
|
+
:class:`ExpressionTreeBuilder` and for :func:`fast_callable`; in that case,
|
|
644
|
+
constants will be converted twice (once when building the
|
|
645
|
+
:class:`Expression`, and once when generating code).
|
|
646
|
+
|
|
647
|
+
EXAMPLES::
|
|
648
|
+
|
|
649
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
650
|
+
sage: etb = ExpressionTreeBuilder('x')
|
|
651
|
+
sage: etb(3^50)
|
|
652
|
+
717897987691852588770249
|
|
653
|
+
sage: etb = ExpressionTreeBuilder('x', domain=RR)
|
|
654
|
+
sage: etb(3^50)
|
|
655
|
+
7.17897987691853e23
|
|
656
|
+
"""
|
|
657
|
+
|
|
658
|
+
if isinstance(vars, tuple):
|
|
659
|
+
vars = list(vars)
|
|
660
|
+
elif not isinstance(vars, list):
|
|
661
|
+
vars = [vars]
|
|
662
|
+
|
|
663
|
+
vars = [self._clean_var(v) for v in vars]
|
|
664
|
+
|
|
665
|
+
self._domain = domain
|
|
666
|
+
self._vars = vars
|
|
667
|
+
|
|
668
|
+
def __call__(self, x):
|
|
669
|
+
r"""
|
|
670
|
+
Try to convert the given value to an :class:`Expression`.
|
|
671
|
+
|
|
672
|
+
If it is already an Expression, just return it. If it has a
|
|
673
|
+
:meth:`_fast_callable_` method, then call the method with ``self`` as
|
|
674
|
+
an argument. Otherwise, use ``self.constant()`` to turn it into a constant.
|
|
675
|
+
|
|
676
|
+
EXAMPLES::
|
|
677
|
+
|
|
678
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
679
|
+
sage: etb = ExpressionTreeBuilder('x')
|
|
680
|
+
sage: v = etb(3); v, type(v)
|
|
681
|
+
(3, <class 'sage.ext.fast_callable.ExpressionConstant'>)
|
|
682
|
+
sage: v = etb(polygen(QQ)); v, type(v)
|
|
683
|
+
(v_0, <class 'sage.ext.fast_callable.ExpressionVariable'>)
|
|
684
|
+
sage: v is etb(v)
|
|
685
|
+
True
|
|
686
|
+
"""
|
|
687
|
+
if isinstance(x, Expression):
|
|
688
|
+
return x
|
|
689
|
+
|
|
690
|
+
try:
|
|
691
|
+
fc = x._fast_callable_
|
|
692
|
+
except AttributeError:
|
|
693
|
+
return self.constant(x)
|
|
694
|
+
|
|
695
|
+
return fc(self)
|
|
696
|
+
|
|
697
|
+
def _clean_var(self, v):
|
|
698
|
+
r"""
|
|
699
|
+
Give a variable name, given a variable.
|
|
700
|
+
|
|
701
|
+
EXAMPLES::
|
|
702
|
+
|
|
703
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
704
|
+
sage: etb = ExpressionTreeBuilder('x')
|
|
705
|
+
sage: var('x') # needs sage.symbolic
|
|
706
|
+
x
|
|
707
|
+
sage: etb._clean_var(x) # needs sage.symbolic
|
|
708
|
+
'x'
|
|
709
|
+
sage: x = polygen(RR); x
|
|
710
|
+
x
|
|
711
|
+
sage: etb._clean_var(x)
|
|
712
|
+
'x'
|
|
713
|
+
"""
|
|
714
|
+
# There should be a better way to do this. (Maybe there is.)
|
|
715
|
+
if not isinstance(v, str):
|
|
716
|
+
v = str(v)
|
|
717
|
+
if '*' in v:
|
|
718
|
+
v = v[v.index('*')+1:]
|
|
719
|
+
return v
|
|
720
|
+
|
|
721
|
+
def constant(self, c):
|
|
722
|
+
r"""
|
|
723
|
+
Turn the argument into an :class:`ExpressionConstant`, converting it to
|
|
724
|
+
our domain if we have one.
|
|
725
|
+
|
|
726
|
+
EXAMPLES::
|
|
727
|
+
|
|
728
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
729
|
+
sage: etb = ExpressionTreeBuilder('x')
|
|
730
|
+
sage: etb.constant(pi) # needs sage.symbolic
|
|
731
|
+
pi
|
|
732
|
+
sage: etb = ExpressionTreeBuilder('x', domain=RealField(200)) # needs sage.rings.real_mpfr
|
|
733
|
+
sage: etb.constant(pi) # needs sage.rings.real_mpfr sage.symbolic
|
|
734
|
+
3.1415926535897932384626433832795028841971693993751058209749
|
|
735
|
+
"""
|
|
736
|
+
if self._domain is not None:
|
|
737
|
+
c = self._domain(c)
|
|
738
|
+
return ExpressionConstant(self, c)
|
|
739
|
+
|
|
740
|
+
def var(self, v):
|
|
741
|
+
r"""
|
|
742
|
+
Turn the argument into an :class:`ExpressionVariable`. Look it up in
|
|
743
|
+
the list of variables. (Variables are matched by name.)
|
|
744
|
+
|
|
745
|
+
EXAMPLES::
|
|
746
|
+
|
|
747
|
+
sage: # needs sage.symbolic
|
|
748
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
749
|
+
sage: var('a,b,some_really_long_name')
|
|
750
|
+
(a, b, some_really_long_name)
|
|
751
|
+
sage: x = polygen(QQ)
|
|
752
|
+
sage: etb = ExpressionTreeBuilder(vars=('a','b',some_really_long_name, x))
|
|
753
|
+
sage: etb.var(some_really_long_name)
|
|
754
|
+
v_2
|
|
755
|
+
sage: etb.var('some_really_long_name')
|
|
756
|
+
v_2
|
|
757
|
+
sage: etb.var(x)
|
|
758
|
+
v_3
|
|
759
|
+
sage: etb.var('y')
|
|
760
|
+
Traceback (most recent call last):
|
|
761
|
+
...
|
|
762
|
+
ValueError: Variable 'y' not found...
|
|
763
|
+
"""
|
|
764
|
+
var_name = self._clean_var(v)
|
|
765
|
+
try:
|
|
766
|
+
ind = self._vars.index(var_name)
|
|
767
|
+
except ValueError:
|
|
768
|
+
raise ValueError(f"Variable '{var_name}' not found in {self._vars}")
|
|
769
|
+
return ExpressionVariable(self, ind)
|
|
770
|
+
|
|
771
|
+
def _var_number(self, n):
|
|
772
|
+
r"""
|
|
773
|
+
Given an integer, return the variable with that index.
|
|
774
|
+
|
|
775
|
+
EXAMPLES::
|
|
776
|
+
|
|
777
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
778
|
+
sage: etb = ExpressionTreeBuilder(vars=('a','b','c','d'))
|
|
779
|
+
sage: etb._var_number(0)
|
|
780
|
+
v_0
|
|
781
|
+
sage: etb._var_number(3)
|
|
782
|
+
v_3
|
|
783
|
+
sage: etb._var_number(4)
|
|
784
|
+
Traceback (most recent call last):
|
|
785
|
+
...
|
|
786
|
+
ValueError: Variable number 4 out of range
|
|
787
|
+
"""
|
|
788
|
+
if 0 <= n < len(self._vars):
|
|
789
|
+
return ExpressionVariable(self, n)
|
|
790
|
+
raise ValueError("Variable number %d out of range" % n)
|
|
791
|
+
|
|
792
|
+
def call(self, fn, *args):
|
|
793
|
+
r"""
|
|
794
|
+
Construct a call node, given a function and a list of arguments.
|
|
795
|
+
|
|
796
|
+
The arguments will be converted to Expressions using
|
|
797
|
+
:meth:`ExpressionTreeBuilder.__call__`.
|
|
798
|
+
|
|
799
|
+
As a special case, notice if the function is :func:`operator.pow` and
|
|
800
|
+
the second argument is integral, and construct an :class:`ExpressionIPow`
|
|
801
|
+
instead.
|
|
802
|
+
|
|
803
|
+
EXAMPLES::
|
|
804
|
+
|
|
805
|
+
sage: # needs sage.symbolic
|
|
806
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
807
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
808
|
+
sage: etb.call(cos, x)
|
|
809
|
+
cos(v_0)
|
|
810
|
+
sage: etb.call(sin, 1)
|
|
811
|
+
sin(1)
|
|
812
|
+
sage: etb.call(sin, etb(1))
|
|
813
|
+
sin(1)
|
|
814
|
+
sage: etb.call(factorial, x+57)
|
|
815
|
+
{factorial}(add(v_0, 57))
|
|
816
|
+
sage: etb.call(operator.pow, x, 543)
|
|
817
|
+
ipow(v_0, 543)
|
|
818
|
+
"""
|
|
819
|
+
if fn is operator.pow:
|
|
820
|
+
base, exponent = args
|
|
821
|
+
return self(base)**exponent
|
|
822
|
+
else:
|
|
823
|
+
return ExpressionCall(self, fn, [self(a) for a in args])
|
|
824
|
+
|
|
825
|
+
def choice(self, cond, iftrue, iffalse):
|
|
826
|
+
r"""
|
|
827
|
+
Construct a choice node (a conditional expression), given the
|
|
828
|
+
condition, and the values for the true and false cases.
|
|
829
|
+
|
|
830
|
+
(It's possible to create choice nodes, but they don't work yet.)
|
|
831
|
+
|
|
832
|
+
EXAMPLES::
|
|
833
|
+
|
|
834
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
835
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
836
|
+
sage: etb.choice(etb.call(operator.eq, x, 0), 0, 1/x) # needs sage.symbolic
|
|
837
|
+
(0 if {eq}(v_0, 0) else div(1, v_0))
|
|
838
|
+
"""
|
|
839
|
+
return ExpressionChoice(self,
|
|
840
|
+
cond,
|
|
841
|
+
self(iftrue),
|
|
842
|
+
self(iffalse))
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
# Cache these values, to make expression building a tiny bit faster
|
|
846
|
+
# (by skipping the hash-table lookup in the operator module).
|
|
847
|
+
cdef op_add = operator.add
|
|
848
|
+
cdef op_sub = operator.sub
|
|
849
|
+
cdef op_mul = operator.mul
|
|
850
|
+
cdef op_div
|
|
851
|
+
try:
|
|
852
|
+
op_div = operator.div
|
|
853
|
+
except AttributeError:
|
|
854
|
+
op_div = object() # Unique object not equal to anything else
|
|
855
|
+
cdef op_truediv = operator.truediv
|
|
856
|
+
cdef op_floordiv = operator.floordiv
|
|
857
|
+
cdef op_pow = operator.pow
|
|
858
|
+
cdef op_neg = operator.neg
|
|
859
|
+
cdef op_abs = operator.abs
|
|
860
|
+
cdef op_inv = operator.inv
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
cdef class Expression:
|
|
864
|
+
r"""
|
|
865
|
+
Represent an expression for :func:`fast_callable`.
|
|
866
|
+
|
|
867
|
+
Supports the standard Python arithmetic operators; if arithmetic
|
|
868
|
+
is attempted between an Expression and a non-Expression, the
|
|
869
|
+
non-Expression is converted to an expression (using the
|
|
870
|
+
:meth:`__call__` method of the Expression's :class:`ExpressionTreeBuilder`).
|
|
871
|
+
|
|
872
|
+
EXAMPLES::
|
|
873
|
+
|
|
874
|
+
sage: # needs sage.symbolic
|
|
875
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
876
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
877
|
+
sage: x = etb.var(x)
|
|
878
|
+
sage: etb(x)
|
|
879
|
+
v_0
|
|
880
|
+
sage: etb(3)
|
|
881
|
+
3
|
|
882
|
+
sage: etb.call(sin, x)
|
|
883
|
+
sin(v_0)
|
|
884
|
+
sage: (x+1)/(x-1)
|
|
885
|
+
div(add(v_0, 1), sub(v_0, 1))
|
|
886
|
+
sage: x//5
|
|
887
|
+
floordiv(v_0, 5)
|
|
888
|
+
sage: -abs(~x)
|
|
889
|
+
neg(abs(inv(v_0)))
|
|
890
|
+
"""
|
|
891
|
+
|
|
892
|
+
cdef ExpressionTreeBuilder _etb
|
|
893
|
+
|
|
894
|
+
def __init__(self, etb):
|
|
895
|
+
r"""
|
|
896
|
+
Initialize an Expression. Sets the _etb member.
|
|
897
|
+
|
|
898
|
+
EXAMPLES::
|
|
899
|
+
|
|
900
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
901
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
902
|
+
sage: v = etb(3); v # indirect doctest # needs sage.symbolic
|
|
903
|
+
3
|
|
904
|
+
sage: v._get_etb() is etb # needs sage.symbolic
|
|
905
|
+
True
|
|
906
|
+
"""
|
|
907
|
+
self._etb = etb
|
|
908
|
+
|
|
909
|
+
def _get_etb(self):
|
|
910
|
+
r"""
|
|
911
|
+
Return the ExpressionTreeBuilder used to build a given expression.
|
|
912
|
+
|
|
913
|
+
EXAMPLES::
|
|
914
|
+
|
|
915
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
916
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
917
|
+
sage: v = etb(3); v # needs sage.symbolic
|
|
918
|
+
3
|
|
919
|
+
sage: v._get_etb() is etb # needs sage.symbolic
|
|
920
|
+
True
|
|
921
|
+
"""
|
|
922
|
+
return self._etb
|
|
923
|
+
|
|
924
|
+
def __add__(s, o):
|
|
925
|
+
r"""
|
|
926
|
+
Compute a sum of two Expressions.
|
|
927
|
+
|
|
928
|
+
EXAMPLES::
|
|
929
|
+
|
|
930
|
+
sage: # needs sage.symbolic
|
|
931
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
932
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
933
|
+
sage: x = etb(x)
|
|
934
|
+
sage: x+x
|
|
935
|
+
add(v_0, v_0)
|
|
936
|
+
sage: x+1
|
|
937
|
+
add(v_0, 1)
|
|
938
|
+
sage: 1+x
|
|
939
|
+
add(1, v_0)
|
|
940
|
+
sage: x.__add__(1)
|
|
941
|
+
add(v_0, 1)
|
|
942
|
+
sage: x.__radd__(1)
|
|
943
|
+
add(1, v_0)
|
|
944
|
+
"""
|
|
945
|
+
return _expression_binop_helper(s, o, op_add)
|
|
946
|
+
|
|
947
|
+
def __sub__(s, o):
|
|
948
|
+
r"""
|
|
949
|
+
Compute a difference of two Expressions.
|
|
950
|
+
|
|
951
|
+
EXAMPLES::
|
|
952
|
+
|
|
953
|
+
sage: # needs sage.symbolic
|
|
954
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
955
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
956
|
+
sage: x = etb(x)
|
|
957
|
+
sage: x-x
|
|
958
|
+
sub(v_0, v_0)
|
|
959
|
+
sage: x-1
|
|
960
|
+
sub(v_0, 1)
|
|
961
|
+
sage: 1-x
|
|
962
|
+
sub(1, v_0)
|
|
963
|
+
sage: x.__sub__(1)
|
|
964
|
+
sub(v_0, 1)
|
|
965
|
+
sage: x.__rsub__(1)
|
|
966
|
+
sub(1, v_0)
|
|
967
|
+
"""
|
|
968
|
+
return _expression_binop_helper(s, o, op_sub)
|
|
969
|
+
|
|
970
|
+
def __mul__(s, o):
|
|
971
|
+
r"""
|
|
972
|
+
Compute a product of two Expressions.
|
|
973
|
+
|
|
974
|
+
EXAMPLES::
|
|
975
|
+
|
|
976
|
+
sage: # needs sage.symbolic
|
|
977
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
978
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
979
|
+
sage: x = etb(x)
|
|
980
|
+
sage: x*x
|
|
981
|
+
mul(v_0, v_0)
|
|
982
|
+
sage: x*1
|
|
983
|
+
mul(v_0, 1)
|
|
984
|
+
sage: 1*x
|
|
985
|
+
mul(1, v_0)
|
|
986
|
+
sage: x.__mul__(1)
|
|
987
|
+
mul(v_0, 1)
|
|
988
|
+
sage: x.__rmul__(1)
|
|
989
|
+
mul(1, v_0)
|
|
990
|
+
"""
|
|
991
|
+
return _expression_binop_helper(s, o, op_mul)
|
|
992
|
+
|
|
993
|
+
def __truediv__(s, o):
|
|
994
|
+
r"""
|
|
995
|
+
Compute a quotient of two Expressions.
|
|
996
|
+
|
|
997
|
+
EXAMPLES::
|
|
998
|
+
|
|
999
|
+
sage: # needs sage.symbolic
|
|
1000
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1001
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1002
|
+
sage: x = etb(x)
|
|
1003
|
+
sage: x/x
|
|
1004
|
+
div(v_0, v_0)
|
|
1005
|
+
sage: x/1
|
|
1006
|
+
div(v_0, 1)
|
|
1007
|
+
sage: 1/x
|
|
1008
|
+
div(1, v_0)
|
|
1009
|
+
sage: x.__truediv__(1)
|
|
1010
|
+
div(v_0, 1)
|
|
1011
|
+
sage: x.__rtruediv__(1)
|
|
1012
|
+
div(1, v_0)
|
|
1013
|
+
"""
|
|
1014
|
+
return _expression_binop_helper(s, o, op_truediv)
|
|
1015
|
+
|
|
1016
|
+
def __floordiv__(s, o):
|
|
1017
|
+
r"""
|
|
1018
|
+
Compute the floordiv (the floor of the quotient) of two Expressions.
|
|
1019
|
+
|
|
1020
|
+
EXAMPLES::
|
|
1021
|
+
|
|
1022
|
+
sage: # needs sage.symbolic
|
|
1023
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1024
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1025
|
+
sage: x = etb(x)
|
|
1026
|
+
sage: x//x
|
|
1027
|
+
floordiv(v_0, v_0)
|
|
1028
|
+
sage: x//1
|
|
1029
|
+
floordiv(v_0, 1)
|
|
1030
|
+
sage: 1//x
|
|
1031
|
+
floordiv(1, v_0)
|
|
1032
|
+
sage: x.__floordiv__(1)
|
|
1033
|
+
floordiv(v_0, 1)
|
|
1034
|
+
sage: x.__rfloordiv__(1)
|
|
1035
|
+
floordiv(1, v_0)
|
|
1036
|
+
"""
|
|
1037
|
+
return _expression_binop_helper(s, o, op_floordiv)
|
|
1038
|
+
|
|
1039
|
+
def __pow__(s, o, dummy):
|
|
1040
|
+
r"""
|
|
1041
|
+
Compute a power expression from two Expressions.
|
|
1042
|
+
|
|
1043
|
+
If the second Expression is a constant integer, then return
|
|
1044
|
+
an :class:`ExpressionIPow` instead of an :class:`ExpressionCall`.
|
|
1045
|
+
|
|
1046
|
+
EXAMPLES::
|
|
1047
|
+
|
|
1048
|
+
sage: # needs sage.symbolic
|
|
1049
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1050
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1051
|
+
sage: x = etb(x)
|
|
1052
|
+
sage: x^x
|
|
1053
|
+
pow(v_0, v_0)
|
|
1054
|
+
sage: x^1
|
|
1055
|
+
ipow(v_0, 1)
|
|
1056
|
+
sage: x.__pow__(1)
|
|
1057
|
+
ipow(v_0, 1)
|
|
1058
|
+
sage: x.__pow__(1.0)
|
|
1059
|
+
pow(v_0, 1.00000000000000)
|
|
1060
|
+
sage: x.__rpow__(1)
|
|
1061
|
+
pow(1, v_0)
|
|
1062
|
+
"""
|
|
1063
|
+
# XXX There is a performance regression from the original
|
|
1064
|
+
# fast_float here; it would replace small integer powers with
|
|
1065
|
+
# multiplication. We can't do this safely until we support
|
|
1066
|
+
# common subexpression elimination (or at least the dup instruction).
|
|
1067
|
+
# (Plus, we should consider how strict a semantics we want;
|
|
1068
|
+
# probably this sort of optimization should be controlled by a
|
|
1069
|
+
# flag.)
|
|
1070
|
+
|
|
1071
|
+
cdef Expression es
|
|
1072
|
+
if isinstance(o, (int, Integer)):
|
|
1073
|
+
es = s
|
|
1074
|
+
return ExpressionIPow(es._etb, s, o)
|
|
1075
|
+
else:
|
|
1076
|
+
# I really don't like this, but I can't think of a better way
|
|
1077
|
+
if isinstance(o, Expression_abc) and o in ZZ:
|
|
1078
|
+
es = s
|
|
1079
|
+
return ExpressionIPow(es._etb, s, ZZ(o))
|
|
1080
|
+
else:
|
|
1081
|
+
return _expression_binop_helper(s, o, op_pow)
|
|
1082
|
+
|
|
1083
|
+
def __neg__(self):
|
|
1084
|
+
r"""
|
|
1085
|
+
Compute the negation of an Expression.
|
|
1086
|
+
|
|
1087
|
+
EXAMPLES::
|
|
1088
|
+
|
|
1089
|
+
sage: # needs sage.symbolic
|
|
1090
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1091
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1092
|
+
sage: x = etb(x)
|
|
1093
|
+
sage: -x
|
|
1094
|
+
neg(v_0)
|
|
1095
|
+
sage: x.__neg__()
|
|
1096
|
+
neg(v_0)
|
|
1097
|
+
"""
|
|
1098
|
+
return ExpressionCall(self._etb, op_neg, [self])
|
|
1099
|
+
|
|
1100
|
+
def __abs__(self):
|
|
1101
|
+
r"""
|
|
1102
|
+
Compute the absolute value of an Expression.
|
|
1103
|
+
|
|
1104
|
+
EXAMPLES::
|
|
1105
|
+
|
|
1106
|
+
sage: # needs sage.symbolic
|
|
1107
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1108
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1109
|
+
sage: x = etb(x)
|
|
1110
|
+
sage: abs(x)
|
|
1111
|
+
abs(v_0)
|
|
1112
|
+
sage: x.abs()
|
|
1113
|
+
abs(v_0)
|
|
1114
|
+
sage: x.__abs__()
|
|
1115
|
+
abs(v_0)
|
|
1116
|
+
"""
|
|
1117
|
+
return ExpressionCall(self._etb, op_abs, [self])
|
|
1118
|
+
|
|
1119
|
+
def abs(self):
|
|
1120
|
+
r"""
|
|
1121
|
+
Compute the absolute value of an Expression.
|
|
1122
|
+
|
|
1123
|
+
EXAMPLES::
|
|
1124
|
+
|
|
1125
|
+
sage: # needs sage.symbolic
|
|
1126
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1127
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1128
|
+
sage: x = etb(x)
|
|
1129
|
+
sage: abs(x)
|
|
1130
|
+
abs(v_0)
|
|
1131
|
+
sage: x.abs()
|
|
1132
|
+
abs(v_0)
|
|
1133
|
+
sage: x.__abs__()
|
|
1134
|
+
abs(v_0)
|
|
1135
|
+
"""
|
|
1136
|
+
return ExpressionCall(self._etb, op_abs, [self])
|
|
1137
|
+
|
|
1138
|
+
def __invert__(self):
|
|
1139
|
+
r"""
|
|
1140
|
+
Compute the inverse of an Expression.
|
|
1141
|
+
|
|
1142
|
+
EXAMPLES::
|
|
1143
|
+
|
|
1144
|
+
sage: # needs sage.symbolic
|
|
1145
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1146
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1147
|
+
sage: x = etb(x)
|
|
1148
|
+
sage: ~x
|
|
1149
|
+
inv(v_0)
|
|
1150
|
+
sage: x.__invert__()
|
|
1151
|
+
inv(v_0)
|
|
1152
|
+
"""
|
|
1153
|
+
return ExpressionCall(self._etb, op_inv, [self])
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
cdef class ExpressionConstant(Expression):
|
|
1157
|
+
r"""
|
|
1158
|
+
An Expression that represents an arbitrary constant.
|
|
1159
|
+
|
|
1160
|
+
EXAMPLES::
|
|
1161
|
+
|
|
1162
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1163
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1164
|
+
sage: type(etb(3)) # needs sage.symbolic
|
|
1165
|
+
<class 'sage.ext.fast_callable.ExpressionConstant'>
|
|
1166
|
+
"""
|
|
1167
|
+
|
|
1168
|
+
cdef object _value
|
|
1169
|
+
|
|
1170
|
+
def __init__(self, etb, c):
|
|
1171
|
+
r"""
|
|
1172
|
+
Initialize an :class:`ExpressionConstant`.
|
|
1173
|
+
|
|
1174
|
+
EXAMPLES::
|
|
1175
|
+
|
|
1176
|
+
sage: # needs sage.symbolic
|
|
1177
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder, ExpressionConstant
|
|
1178
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1179
|
+
sage: etb(3)
|
|
1180
|
+
3
|
|
1181
|
+
sage: v = ExpressionConstant(etb, 3); v
|
|
1182
|
+
3
|
|
1183
|
+
sage: v._get_etb() is etb
|
|
1184
|
+
True
|
|
1185
|
+
sage: v.value()
|
|
1186
|
+
3
|
|
1187
|
+
sage: v.value() == 3
|
|
1188
|
+
True
|
|
1189
|
+
"""
|
|
1190
|
+
Expression.__init__(self, etb)
|
|
1191
|
+
self._value = c
|
|
1192
|
+
|
|
1193
|
+
def value(self):
|
|
1194
|
+
r"""
|
|
1195
|
+
Return the constant value of an :class:`ExpressionConstant`.
|
|
1196
|
+
|
|
1197
|
+
EXAMPLES::
|
|
1198
|
+
|
|
1199
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1200
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1201
|
+
sage: etb(3).value() # needs sage.symbolic
|
|
1202
|
+
3
|
|
1203
|
+
"""
|
|
1204
|
+
return self._value
|
|
1205
|
+
|
|
1206
|
+
def __repr__(self):
|
|
1207
|
+
r"""
|
|
1208
|
+
Give a string representing this :class:`ExpressionConstant`.
|
|
1209
|
+
(We use the repr of its value.)
|
|
1210
|
+
|
|
1211
|
+
EXAMPLES::
|
|
1212
|
+
|
|
1213
|
+
sage: # needs sage.symbolic
|
|
1214
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1215
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1216
|
+
sage: v = etb.constant(pi)
|
|
1217
|
+
sage: v
|
|
1218
|
+
pi
|
|
1219
|
+
sage: repr(v)
|
|
1220
|
+
'pi'
|
|
1221
|
+
sage: v.__repr__()
|
|
1222
|
+
'pi'
|
|
1223
|
+
"""
|
|
1224
|
+
return repr(self._value)
|
|
1225
|
+
|
|
1226
|
+
cdef class ExpressionVariable(Expression):
|
|
1227
|
+
r"""
|
|
1228
|
+
An Expression that represents a variable.
|
|
1229
|
+
|
|
1230
|
+
EXAMPLES::
|
|
1231
|
+
|
|
1232
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1233
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1234
|
+
sage: type(etb.var(x)) # needs sage.symbolic
|
|
1235
|
+
<class 'sage.ext.fast_callable.ExpressionVariable'>
|
|
1236
|
+
"""
|
|
1237
|
+
cdef int _variable_index
|
|
1238
|
+
|
|
1239
|
+
def __init__(self, etb, int n):
|
|
1240
|
+
r"""
|
|
1241
|
+
Initialize an ExpressionVariable.
|
|
1242
|
+
|
|
1243
|
+
EXAMPLES::
|
|
1244
|
+
|
|
1245
|
+
sage: # needs sage.symbolic
|
|
1246
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder, ExpressionVariable
|
|
1247
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1248
|
+
sage: etb(x)
|
|
1249
|
+
v_0
|
|
1250
|
+
sage: v = ExpressionVariable(etb, 0); v
|
|
1251
|
+
v_0
|
|
1252
|
+
sage: v._get_etb() is etb
|
|
1253
|
+
True
|
|
1254
|
+
sage: v.variable_index()
|
|
1255
|
+
0
|
|
1256
|
+
"""
|
|
1257
|
+
Expression.__init__(self, etb)
|
|
1258
|
+
self._variable_index = n
|
|
1259
|
+
|
|
1260
|
+
def variable_index(self):
|
|
1261
|
+
r"""
|
|
1262
|
+
Return the variable index of an :class:`ExpressionVariable`.
|
|
1263
|
+
|
|
1264
|
+
EXAMPLES::
|
|
1265
|
+
|
|
1266
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1267
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1268
|
+
sage: etb(x).variable_index() # needs sage.symbolic
|
|
1269
|
+
0
|
|
1270
|
+
"""
|
|
1271
|
+
return self._variable_index
|
|
1272
|
+
|
|
1273
|
+
def __repr__(self):
|
|
1274
|
+
r"""
|
|
1275
|
+
Give a string representing this :class:`ExpressionVariable`.
|
|
1276
|
+
|
|
1277
|
+
EXAMPLES::
|
|
1278
|
+
|
|
1279
|
+
sage: # needs sage.symbolic
|
|
1280
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1281
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1282
|
+
sage: v = etb._var_number(0)
|
|
1283
|
+
sage: v
|
|
1284
|
+
v_0
|
|
1285
|
+
sage: repr(v)
|
|
1286
|
+
'v_0'
|
|
1287
|
+
sage: v.__repr__()
|
|
1288
|
+
'v_0'
|
|
1289
|
+
"""
|
|
1290
|
+
# Should we look up the variable name in self._etb, instead?
|
|
1291
|
+
# I think not.. I like the emphasis that we're totally removed
|
|
1292
|
+
# from the original expression when we have an Expression.
|
|
1293
|
+
return "v_%d" % self._variable_index
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
cdef class ExpressionCall(Expression):
|
|
1297
|
+
r"""
|
|
1298
|
+
An Expression that represents a function call.
|
|
1299
|
+
|
|
1300
|
+
EXAMPLES::
|
|
1301
|
+
|
|
1302
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1303
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1304
|
+
sage: type(etb.call(sin, x)) # needs sage.symbolic
|
|
1305
|
+
<class 'sage.ext.fast_callable.ExpressionCall'>
|
|
1306
|
+
"""
|
|
1307
|
+
cdef object _function
|
|
1308
|
+
cdef object _arguments
|
|
1309
|
+
|
|
1310
|
+
def __init__(self, etb, fn, args):
|
|
1311
|
+
r"""
|
|
1312
|
+
Initialize an :class:`ExpressionCall`.
|
|
1313
|
+
|
|
1314
|
+
EXAMPLES::
|
|
1315
|
+
|
|
1316
|
+
sage: # needs sage.symbolic
|
|
1317
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder, ExpressionCall
|
|
1318
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1319
|
+
sage: x = etb(x)
|
|
1320
|
+
sage: etb.call(factorial, x)
|
|
1321
|
+
{factorial}(v_0)
|
|
1322
|
+
sage: v = ExpressionCall(etb, factorial, [x]); v
|
|
1323
|
+
{factorial}(v_0)
|
|
1324
|
+
sage: v._get_etb() is etb
|
|
1325
|
+
True
|
|
1326
|
+
sage: v.function()
|
|
1327
|
+
factorial
|
|
1328
|
+
sage: v.arguments()
|
|
1329
|
+
[v_0]
|
|
1330
|
+
"""
|
|
1331
|
+
Expression.__init__(self, etb)
|
|
1332
|
+
self._function = fn
|
|
1333
|
+
self._arguments = args
|
|
1334
|
+
|
|
1335
|
+
def function(self):
|
|
1336
|
+
r"""
|
|
1337
|
+
Return the function from this :class:`ExpressionCall`.
|
|
1338
|
+
|
|
1339
|
+
EXAMPLES::
|
|
1340
|
+
|
|
1341
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1342
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1343
|
+
sage: etb.call(sin, x).function() # needs sage.symbolic
|
|
1344
|
+
sin
|
|
1345
|
+
"""
|
|
1346
|
+
return self._function
|
|
1347
|
+
|
|
1348
|
+
def arguments(self):
|
|
1349
|
+
r"""
|
|
1350
|
+
Return the arguments from this :class:`ExpressionCall`.
|
|
1351
|
+
|
|
1352
|
+
EXAMPLES::
|
|
1353
|
+
|
|
1354
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1355
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1356
|
+
sage: etb.call(sin, x).arguments() # needs sage.symbolic
|
|
1357
|
+
[v_0]
|
|
1358
|
+
"""
|
|
1359
|
+
return copy(self._arguments)
|
|
1360
|
+
|
|
1361
|
+
def __repr__(self):
|
|
1362
|
+
r"""
|
|
1363
|
+
Give a string representing this :class:`ExpressionCall`.
|
|
1364
|
+
|
|
1365
|
+
EXAMPLES::
|
|
1366
|
+
|
|
1367
|
+
sage: # needs sage.symbolic
|
|
1368
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1369
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1370
|
+
sage: x = etb.var(x)
|
|
1371
|
+
sage: etb.call(operator.add, x, 1)
|
|
1372
|
+
add(v_0, 1)
|
|
1373
|
+
sage: etb.call(factorial, x)
|
|
1374
|
+
{factorial}(v_0)
|
|
1375
|
+
sage: v = etb.call(sin, x)
|
|
1376
|
+
sage: v
|
|
1377
|
+
sin(v_0)
|
|
1378
|
+
sage: repr(v)
|
|
1379
|
+
'sin(v_0)'
|
|
1380
|
+
sage: v.__repr__()
|
|
1381
|
+
'sin(v_0)'
|
|
1382
|
+
"""
|
|
1383
|
+
fn = function_name(self._function)
|
|
1384
|
+
return '%s(%s)' % (fn, ', '.join(repr(a) for a in self._arguments))
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
cdef class ExpressionIPow(Expression):
|
|
1388
|
+
r"""
|
|
1389
|
+
A power Expression with an integer exponent.
|
|
1390
|
+
|
|
1391
|
+
EXAMPLES::
|
|
1392
|
+
|
|
1393
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1394
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1395
|
+
sage: type(etb.var('x')^17) # needs sage.symbolic
|
|
1396
|
+
<class 'sage.ext.fast_callable.ExpressionIPow'>
|
|
1397
|
+
"""
|
|
1398
|
+
cdef object _base
|
|
1399
|
+
cdef object _exponent
|
|
1400
|
+
|
|
1401
|
+
def __init__(self, etb, base, exponent):
|
|
1402
|
+
r"""
|
|
1403
|
+
Initialize an ExpressionIPow.
|
|
1404
|
+
|
|
1405
|
+
EXAMPLES::
|
|
1406
|
+
|
|
1407
|
+
sage: # needs sage.symbolic
|
|
1408
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder, ExpressionIPow
|
|
1409
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1410
|
+
sage: x = etb(x)
|
|
1411
|
+
sage: x^(-12)
|
|
1412
|
+
ipow(v_0, -12)
|
|
1413
|
+
sage: v = ExpressionIPow(etb, x, 55); v
|
|
1414
|
+
ipow(v_0, 55)
|
|
1415
|
+
sage: v._get_etb() is etb
|
|
1416
|
+
True
|
|
1417
|
+
sage: v.base()
|
|
1418
|
+
v_0
|
|
1419
|
+
sage: v.exponent()
|
|
1420
|
+
55
|
|
1421
|
+
"""
|
|
1422
|
+
Expression.__init__(self, etb)
|
|
1423
|
+
self._base = base
|
|
1424
|
+
self._exponent = exponent
|
|
1425
|
+
|
|
1426
|
+
def base(self):
|
|
1427
|
+
r"""
|
|
1428
|
+
Return the base from this :class:`ExpressionIPow`.
|
|
1429
|
+
|
|
1430
|
+
EXAMPLES::
|
|
1431
|
+
|
|
1432
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1433
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1434
|
+
sage: (etb(33)^42).base() # needs sage.symbolic
|
|
1435
|
+
33
|
|
1436
|
+
"""
|
|
1437
|
+
return self._base
|
|
1438
|
+
|
|
1439
|
+
def exponent(self):
|
|
1440
|
+
r"""
|
|
1441
|
+
Return the exponent from this :class:`ExpressionIPow`.
|
|
1442
|
+
|
|
1443
|
+
EXAMPLES::
|
|
1444
|
+
|
|
1445
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1446
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1447
|
+
sage: (etb(x)^(-1)).exponent() # needs sage.symbolic
|
|
1448
|
+
-1
|
|
1449
|
+
"""
|
|
1450
|
+
return self._exponent
|
|
1451
|
+
|
|
1452
|
+
def __repr__(self):
|
|
1453
|
+
r"""
|
|
1454
|
+
Give a string representing this :class:`ExpressionIPow`.
|
|
1455
|
+
|
|
1456
|
+
EXAMPLES::
|
|
1457
|
+
|
|
1458
|
+
sage: # needs sage.symbolic
|
|
1459
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1460
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1461
|
+
sage: x = etb.var(x)
|
|
1462
|
+
sage: x^3
|
|
1463
|
+
ipow(v_0, 3)
|
|
1464
|
+
sage: x^(-2)
|
|
1465
|
+
ipow(v_0, -2)
|
|
1466
|
+
sage: v = (x+1)^3
|
|
1467
|
+
sage: v
|
|
1468
|
+
ipow(add(v_0, 1), 3)
|
|
1469
|
+
sage: repr(v)
|
|
1470
|
+
'ipow(add(v_0, 1), 3)'
|
|
1471
|
+
sage: v.__repr__()
|
|
1472
|
+
'ipow(add(v_0, 1), 3)'
|
|
1473
|
+
"""
|
|
1474
|
+
return 'ipow(%s, %d)' % (repr(self._base), self._exponent)
|
|
1475
|
+
|
|
1476
|
+
|
|
1477
|
+
cdef class ExpressionChoice(Expression):
|
|
1478
|
+
r"""
|
|
1479
|
+
A conditional expression.
|
|
1480
|
+
|
|
1481
|
+
(It's possible to create choice nodes, but they don't work yet.)
|
|
1482
|
+
|
|
1483
|
+
EXAMPLES::
|
|
1484
|
+
|
|
1485
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1486
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1487
|
+
sage: etb.choice(etb.call(operator.eq, x, 0), 0, 1/x) # needs sage.symbolic
|
|
1488
|
+
(0 if {eq}(v_0, 0) else div(1, v_0))
|
|
1489
|
+
"""
|
|
1490
|
+
|
|
1491
|
+
cdef object _cond
|
|
1492
|
+
cdef object _iftrue
|
|
1493
|
+
cdef object _iffalse
|
|
1494
|
+
|
|
1495
|
+
def __init__(self, etb, cond, iftrue, iffalse):
|
|
1496
|
+
r"""
|
|
1497
|
+
Initialize an :class:`ExpressionChoice`.
|
|
1498
|
+
|
|
1499
|
+
EXAMPLES::
|
|
1500
|
+
|
|
1501
|
+
sage: # needs sage.symbolic
|
|
1502
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder, ExpressionChoice
|
|
1503
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1504
|
+
sage: x = etb(x)
|
|
1505
|
+
sage: etb.choice(x, ~x, 0)
|
|
1506
|
+
(inv(v_0) if v_0 else 0)
|
|
1507
|
+
sage: v = ExpressionChoice(etb, x, ~x, etb(0)); v
|
|
1508
|
+
(inv(v_0) if v_0 else 0)
|
|
1509
|
+
sage: v._get_etb() is etb
|
|
1510
|
+
True
|
|
1511
|
+
sage: v.condition()
|
|
1512
|
+
v_0
|
|
1513
|
+
sage: v.if_true()
|
|
1514
|
+
inv(v_0)
|
|
1515
|
+
sage: v.if_false()
|
|
1516
|
+
0
|
|
1517
|
+
"""
|
|
1518
|
+
Expression.__init__(self, etb)
|
|
1519
|
+
self._cond = cond
|
|
1520
|
+
self._iftrue = iftrue
|
|
1521
|
+
self._iffalse = iffalse
|
|
1522
|
+
|
|
1523
|
+
def condition(self):
|
|
1524
|
+
r"""
|
|
1525
|
+
Return the condition of an :class:`ExpressionChoice`.
|
|
1526
|
+
|
|
1527
|
+
EXAMPLES::
|
|
1528
|
+
|
|
1529
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1530
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1531
|
+
sage: x = etb(x) # needs sage.symbolic
|
|
1532
|
+
sage: etb.choice(x, ~x, 0).condition() # needs sage.symbolic
|
|
1533
|
+
v_0
|
|
1534
|
+
"""
|
|
1535
|
+
return self._cond
|
|
1536
|
+
|
|
1537
|
+
def if_true(self):
|
|
1538
|
+
r"""
|
|
1539
|
+
Return the true branch of an :class:`ExpressionChoice`.
|
|
1540
|
+
|
|
1541
|
+
EXAMPLES::
|
|
1542
|
+
|
|
1543
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1544
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1545
|
+
sage: x = etb(x) # needs sage.symbolic
|
|
1546
|
+
sage: etb.choice(x, ~x, 0).if_true() # needs sage.symbolic
|
|
1547
|
+
inv(v_0)
|
|
1548
|
+
"""
|
|
1549
|
+
return self._iftrue
|
|
1550
|
+
|
|
1551
|
+
def if_false(self):
|
|
1552
|
+
r"""
|
|
1553
|
+
Return the false branch of an :class:`ExpressionChoice`.
|
|
1554
|
+
|
|
1555
|
+
EXAMPLES::
|
|
1556
|
+
|
|
1557
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1558
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,)) # needs sage.symbolic
|
|
1559
|
+
sage: x = etb(x) # needs sage.symbolic
|
|
1560
|
+
sage: etb.choice(x, ~x, 0).if_false() # needs sage.symbolic
|
|
1561
|
+
0
|
|
1562
|
+
"""
|
|
1563
|
+
return self._iffalse
|
|
1564
|
+
|
|
1565
|
+
def __repr__(self):
|
|
1566
|
+
r"""
|
|
1567
|
+
Give a string representation for this :class:`ExpressionChoice`.
|
|
1568
|
+
(Based on the syntax for Python conditional expressions.)
|
|
1569
|
+
|
|
1570
|
+
EXAMPLES::
|
|
1571
|
+
|
|
1572
|
+
sage: # needs sage.symbolic
|
|
1573
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder
|
|
1574
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,))
|
|
1575
|
+
sage: x = etb(x)
|
|
1576
|
+
sage: v = etb.choice(x, ~x, 0)
|
|
1577
|
+
sage: v
|
|
1578
|
+
(inv(v_0) if v_0 else 0)
|
|
1579
|
+
sage: repr(v)
|
|
1580
|
+
'(inv(v_0) if v_0 else 0)'
|
|
1581
|
+
sage: v.__repr__()
|
|
1582
|
+
'(inv(v_0) if v_0 else 0)'
|
|
1583
|
+
"""
|
|
1584
|
+
return '(%s if %s else %s)' % (repr(self._iftrue),
|
|
1585
|
+
repr(self._cond),
|
|
1586
|
+
repr(self._iffalse))
|
|
1587
|
+
|
|
1588
|
+
|
|
1589
|
+
cpdef _expression_binop_helper(s, o, op):
|
|
1590
|
+
r"""
|
|
1591
|
+
Make an :class:`Expression` for (``s`` ``op`` ``o``).
|
|
1592
|
+
|
|
1593
|
+
Either ``s`` or ``o`` (or both) must already be an :class:`Expression`.
|
|
1594
|
+
|
|
1595
|
+
EXAMPLES::
|
|
1596
|
+
|
|
1597
|
+
sage: from sage.ext.fast_callable import _expression_binop_helper, ExpressionTreeBuilder
|
|
1598
|
+
sage: var('x,y') # needs sage.symbolic
|
|
1599
|
+
(x, y)
|
|
1600
|
+
sage: etb = ExpressionTreeBuilder(vars=(x,y)) # needs sage.symbolic
|
|
1601
|
+
sage: x = etb(x) # needs sage.symbolic
|
|
1602
|
+
|
|
1603
|
+
Now ``x`` is an :class:`Expression`, but ``y`` is not. Still, all the following
|
|
1604
|
+
cases work::
|
|
1605
|
+
|
|
1606
|
+
sage: _expression_binop_helper(x, x, operator.add) # needs sage.symbolic
|
|
1607
|
+
add(v_0, v_0)
|
|
1608
|
+
sage: _expression_binop_helper(x, y, operator.add) # needs sage.symbolic
|
|
1609
|
+
add(v_0, v_1)
|
|
1610
|
+
sage: _expression_binop_helper(y, x, operator.add) # needs sage.symbolic
|
|
1611
|
+
add(v_1, v_0)
|
|
1612
|
+
"""
|
|
1613
|
+
# The Cython way of handling operator overloading on cdef classes
|
|
1614
|
+
# (which is inherited from Python) is quite annoying. Inside the
|
|
1615
|
+
# code for a binary operator, you know that either the first or
|
|
1616
|
+
# second argument (or both) is a member of your class, but you
|
|
1617
|
+
# don't know which.
|
|
1618
|
+
|
|
1619
|
+
# If there is an arithmetic operator between an Expression and
|
|
1620
|
+
# a non-Expression, I want to convert the non-Expression into
|
|
1621
|
+
# an Expression. But to do that, I need the ExpressionTreeBuilder
|
|
1622
|
+
# from the Expression.
|
|
1623
|
+
|
|
1624
|
+
cdef Expression self
|
|
1625
|
+
cdef Expression other
|
|
1626
|
+
|
|
1627
|
+
if not isinstance(o, Expression):
|
|
1628
|
+
self = s
|
|
1629
|
+
other = self._etb(o)
|
|
1630
|
+
elif not isinstance(s, Expression):
|
|
1631
|
+
other = o
|
|
1632
|
+
self = other._etb(s)
|
|
1633
|
+
else:
|
|
1634
|
+
self = s
|
|
1635
|
+
other = o
|
|
1636
|
+
assert self._etb is other._etb
|
|
1637
|
+
|
|
1638
|
+
return ExpressionCall(self._etb, op, [self, other])
|
|
1639
|
+
|
|
1640
|
+
|
|
1641
|
+
class IntegerPowerFunction():
|
|
1642
|
+
r"""
|
|
1643
|
+
This class represents the function `x^n` for an arbitrary integral power `n`.
|
|
1644
|
+
|
|
1645
|
+
That is, ``IntegerPowerFunction(2)`` is the squaring function;
|
|
1646
|
+
``IntegerPowerFunction(-1)`` is the reciprocal function.
|
|
1647
|
+
|
|
1648
|
+
EXAMPLES::
|
|
1649
|
+
|
|
1650
|
+
sage: from sage.ext.fast_callable import IntegerPowerFunction
|
|
1651
|
+
sage: square = IntegerPowerFunction(2)
|
|
1652
|
+
sage: square
|
|
1653
|
+
(^2)
|
|
1654
|
+
sage: square(pi) # needs sage.symbolic
|
|
1655
|
+
pi^2
|
|
1656
|
+
sage: square(I) # needs sage.symbolic
|
|
1657
|
+
-1
|
|
1658
|
+
sage: square(RIF(-1, 1)).str(style='brackets') # needs sage.rings.real_interval_field
|
|
1659
|
+
'[0.0000000000000000 .. 1.0000000000000000]'
|
|
1660
|
+
sage: IntegerPowerFunction(-1)
|
|
1661
|
+
(^(-1))
|
|
1662
|
+
sage: IntegerPowerFunction(-1)(22/7)
|
|
1663
|
+
7/22
|
|
1664
|
+
sage: v = Integers(123456789)(54321)
|
|
1665
|
+
sage: v^9876543210
|
|
1666
|
+
79745229
|
|
1667
|
+
sage: IntegerPowerFunction(9876543210)(v)
|
|
1668
|
+
79745229
|
|
1669
|
+
"""
|
|
1670
|
+
|
|
1671
|
+
def __init__(self, n):
|
|
1672
|
+
r"""
|
|
1673
|
+
Initialize an :class:`IntegerPowerFunction`.
|
|
1674
|
+
|
|
1675
|
+
EXAMPLES::
|
|
1676
|
+
|
|
1677
|
+
sage: from sage.ext.fast_callable import IntegerPowerFunction
|
|
1678
|
+
sage: cube = IntegerPowerFunction(3)
|
|
1679
|
+
sage: cube
|
|
1680
|
+
(^3)
|
|
1681
|
+
sage: cube(AA(7)^(1/3)) # needs sage.rings.number_field
|
|
1682
|
+
7.000000000000000?
|
|
1683
|
+
sage: cube.exponent
|
|
1684
|
+
3
|
|
1685
|
+
"""
|
|
1686
|
+
self.exponent = n
|
|
1687
|
+
|
|
1688
|
+
def __repr__(self):
|
|
1689
|
+
r"""
|
|
1690
|
+
Return a string representing this :class:`IntegerPowerFunction`.
|
|
1691
|
+
|
|
1692
|
+
EXAMPLES::
|
|
1693
|
+
|
|
1694
|
+
sage: from sage.ext.fast_callable import IntegerPowerFunction
|
|
1695
|
+
sage: square = IntegerPowerFunction(2)
|
|
1696
|
+
sage: square
|
|
1697
|
+
(^2)
|
|
1698
|
+
sage: repr(square)
|
|
1699
|
+
'(^2)'
|
|
1700
|
+
sage: square.__repr__()
|
|
1701
|
+
'(^2)'
|
|
1702
|
+
sage: repr(IntegerPowerFunction(-57))
|
|
1703
|
+
'(^(-57))'
|
|
1704
|
+
"""
|
|
1705
|
+
if self.exponent >= 0:
|
|
1706
|
+
return "(^%s)" % self.exponent
|
|
1707
|
+
else:
|
|
1708
|
+
return "(^(%s))" % self.exponent
|
|
1709
|
+
|
|
1710
|
+
def __call__(self, x):
|
|
1711
|
+
r"""
|
|
1712
|
+
Call this :class:`IntegerPowerFunction`, to compute a power of its argument.
|
|
1713
|
+
|
|
1714
|
+
EXAMPLES::
|
|
1715
|
+
|
|
1716
|
+
sage: from sage.ext.fast_callable import IntegerPowerFunction
|
|
1717
|
+
sage: square = IntegerPowerFunction(2)
|
|
1718
|
+
sage: square.__call__(5)
|
|
1719
|
+
25
|
|
1720
|
+
sage: square(5)
|
|
1721
|
+
25
|
|
1722
|
+
"""
|
|
1723
|
+
return x**self.exponent
|
|
1724
|
+
|
|
1725
|
+
|
|
1726
|
+
cdef dict builtin_functions = None
|
|
1727
|
+
cpdef dict get_builtin_functions():
|
|
1728
|
+
r"""
|
|
1729
|
+
Return a dictionary from Sage and Python functions to opcode names.
|
|
1730
|
+
|
|
1731
|
+
The result is cached.
|
|
1732
|
+
|
|
1733
|
+
The dictionary is used in :class:`ExpressionCall`.
|
|
1734
|
+
|
|
1735
|
+
EXAMPLES::
|
|
1736
|
+
|
|
1737
|
+
sage: from sage.ext.fast_callable import get_builtin_functions
|
|
1738
|
+
sage: builtins = get_builtin_functions()
|
|
1739
|
+
sage: sorted(set(builtins.values())) # needs sage.symbolic
|
|
1740
|
+
['abs', 'acos', 'acosh', 'add', 'asin', 'asinh', 'atan', 'atanh', 'ceil',
|
|
1741
|
+
'cos', 'cosh', 'cot', 'csc', 'div', 'exp', 'floor', 'floordiv', 'inv', 'log',
|
|
1742
|
+
'mul', 'neg', 'pow', 'sec', 'sin', 'sinh', 'sqrt', 'sub', 'tan', 'tanh']
|
|
1743
|
+
sage: builtins[sin] # needs sage.symbolic
|
|
1744
|
+
'sin'
|
|
1745
|
+
sage: builtins[ln] # needs sage.symbolic
|
|
1746
|
+
'log'
|
|
1747
|
+
"""
|
|
1748
|
+
# We delay building builtin_functions to break a circular import
|
|
1749
|
+
# between sage.functions and this file.
|
|
1750
|
+
global builtin_functions
|
|
1751
|
+
if builtin_functions is not None:
|
|
1752
|
+
return builtin_functions
|
|
1753
|
+
builtin_functions = {
|
|
1754
|
+
op_add: 'add',
|
|
1755
|
+
op_sub: 'sub',
|
|
1756
|
+
op_mul: 'mul',
|
|
1757
|
+
op_div: 'div',
|
|
1758
|
+
op_truediv: 'div',
|
|
1759
|
+
op_floordiv: 'floordiv',
|
|
1760
|
+
op_abs: 'abs',
|
|
1761
|
+
op_neg: 'neg',
|
|
1762
|
+
op_inv: 'inv',
|
|
1763
|
+
op_pow: 'pow',
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
try:
|
|
1767
|
+
import sage.functions.all as func_all
|
|
1768
|
+
except ImportError:
|
|
1769
|
+
pass
|
|
1770
|
+
else:
|
|
1771
|
+
# not handled: atan2, log2, log10
|
|
1772
|
+
for fn in ('sqrt', 'ceil', 'floor',
|
|
1773
|
+
'sin', 'cos', 'tan', 'sec', 'csc', 'cot',
|
|
1774
|
+
'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh',
|
|
1775
|
+
'asinh', 'acosh', 'atanh', 'exp', 'log'):
|
|
1776
|
+
builtin_functions[getattr(func_all, fn)] = fn
|
|
1777
|
+
builtin_functions[func_all.abs_symbolic] = 'abs'
|
|
1778
|
+
builtin_functions[func_all.ln] = 'log'
|
|
1779
|
+
return builtin_functions
|
|
1780
|
+
|
|
1781
|
+
|
|
1782
|
+
cdef class InstructionStream # forward declaration
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
cpdef generate_code(Expression expr, InstructionStream stream):
|
|
1786
|
+
r"""
|
|
1787
|
+
Generate code from an :class:`Expression` tree; write the result into an
|
|
1788
|
+
:class:`InstructionStream`.
|
|
1789
|
+
|
|
1790
|
+
In :func:`fast_callable`, first we create an :class:`Expression`, either directly
|
|
1791
|
+
with an :class:`ExpressionTreeBuilder` or with :meth:`_fast_callable_` methods.
|
|
1792
|
+
Then we optimize the :class:`Expression` in tree form. (Unfortunately,
|
|
1793
|
+
this step is currently missing -- we do no optimizations.)
|
|
1794
|
+
|
|
1795
|
+
Then we linearize the :class:`Expression` into a sequence of instructions,
|
|
1796
|
+
by walking the :class:`Expression` and sending the corresponding stack
|
|
1797
|
+
instructions to an :class:`InstructionStream`.
|
|
1798
|
+
|
|
1799
|
+
EXAMPLES::
|
|
1800
|
+
|
|
1801
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder, generate_code, InstructionStream
|
|
1802
|
+
|
|
1803
|
+
sage: # needs sage.symbolic
|
|
1804
|
+
sage: etb = ExpressionTreeBuilder('x')
|
|
1805
|
+
sage: x = etb.var('x')
|
|
1806
|
+
sage: expr = (x+pi) * (x+1)
|
|
1807
|
+
sage: from sage.ext.interpreters.wrapper_py import metadata, Wrapper_py
|
|
1808
|
+
sage: instr_stream = InstructionStream(metadata, 1)
|
|
1809
|
+
sage: generate_code(expr, instr_stream)
|
|
1810
|
+
sage: instr_stream.instr('return')
|
|
1811
|
+
sage: v = Wrapper_py(instr_stream.get_current())
|
|
1812
|
+
sage: type(v)
|
|
1813
|
+
<class '...interpreters.wrapper_py.Wrapper_py'>
|
|
1814
|
+
sage: v(7)
|
|
1815
|
+
8*pi + 56
|
|
1816
|
+
|
|
1817
|
+
TESTS::
|
|
1818
|
+
|
|
1819
|
+
sage: def my_sin(x): return sin(x)
|
|
1820
|
+
sage: def my_norm(x, y): return x*x + y*y
|
|
1821
|
+
sage: def my_sqrt(x):
|
|
1822
|
+
....: if x < 0: raise ValueError("sqrt of negative number")
|
|
1823
|
+
....: return sqrt(x, extend=False)
|
|
1824
|
+
|
|
1825
|
+
sage: # needs sage.symbolic
|
|
1826
|
+
sage: fc = fast_callable(expr, domain=RealField(130))
|
|
1827
|
+
sage: fc(0)
|
|
1828
|
+
3.1415926535897932384626433832795028842
|
|
1829
|
+
sage: fc(1)
|
|
1830
|
+
8.2831853071795864769252867665590057684
|
|
1831
|
+
sage: fc = fast_callable(expr, domain=RDF)
|
|
1832
|
+
sage: fc(0)
|
|
1833
|
+
3.141592653589793
|
|
1834
|
+
sage: fc(1)
|
|
1835
|
+
8.283185307179586
|
|
1836
|
+
sage: fc.op_list()
|
|
1837
|
+
[('load_arg', 0), ('load_const', pi), 'add', ('load_arg', 0), ('load_const', 1), 'add', 'mul', 'return']
|
|
1838
|
+
sage: fc = fast_callable(etb.call(sin, x) + etb.call(sqrt, x), domain=RDF)
|
|
1839
|
+
sage: fc(1)
|
|
1840
|
+
1.8414709848078965
|
|
1841
|
+
sage: fc.op_list()
|
|
1842
|
+
[('load_arg', 0), 'sin', ('load_arg', 0), 'sqrt', 'add', 'return']
|
|
1843
|
+
sage: fc = fast_callable(etb.call(sin, x) + etb.call(sqrt, x))
|
|
1844
|
+
sage: fc(1)
|
|
1845
|
+
sin(1) + 1
|
|
1846
|
+
sage: fc.op_list()
|
|
1847
|
+
[('load_arg', 0), ('py_call', sin, 1), ('load_arg', 0), ('py_call', <function sqrt at ...>, 1), 'add', 'return']
|
|
1848
|
+
sage: fc = fast_callable(etb.call(my_sin, x), domain=RDF)
|
|
1849
|
+
sage: fc(3)
|
|
1850
|
+
0.1411200080598672
|
|
1851
|
+
|
|
1852
|
+
sage: # needs sage.rings.real_mpfr sage.symbolic
|
|
1853
|
+
sage: fc = fast_callable(etb.call(my_sin, x), domain=RealField(100))
|
|
1854
|
+
sage: fc(3)
|
|
1855
|
+
0.14112000805986722210074480281
|
|
1856
|
+
sage: fc.op_list()
|
|
1857
|
+
[('load_arg', 0), ('py_call', <function my_sin at 0x...>, 1), 'return']
|
|
1858
|
+
|
|
1859
|
+
sage: # needs sage.symbolic
|
|
1860
|
+
sage: fc = fast_callable(etb.call(my_sqrt, x), domain=RDF)
|
|
1861
|
+
sage: fc(3)
|
|
1862
|
+
1.7320508075688772
|
|
1863
|
+
sage: parent(fc(3))
|
|
1864
|
+
Real Double Field
|
|
1865
|
+
sage: fc(-3)
|
|
1866
|
+
Traceback (most recent call last):
|
|
1867
|
+
...
|
|
1868
|
+
ValueError: sqrt of negative number
|
|
1869
|
+
sage: fc = fast_callable(etb.call(my_sqrt, x), domain=RR)
|
|
1870
|
+
sage: fc(3)
|
|
1871
|
+
1.73205080756888
|
|
1872
|
+
sage: fc(-3)
|
|
1873
|
+
Traceback (most recent call last):
|
|
1874
|
+
...
|
|
1875
|
+
ValueError: sqrt of negative number
|
|
1876
|
+
|
|
1877
|
+
sage: etb2 = ExpressionTreeBuilder(('y','z'))
|
|
1878
|
+
sage: y = etb2.var('y')
|
|
1879
|
+
sage: z = etb2.var('z')
|
|
1880
|
+
sage: fc = fast_callable(etb2.call(sqrt, etb2.call(my_norm, y, z)), domain=RDF)
|
|
1881
|
+
sage: fc(3, 4)
|
|
1882
|
+
5.0
|
|
1883
|
+
sage: fc.op_list()
|
|
1884
|
+
[('load_arg', 0), ('load_arg', 1), ('py_call', <function my_norm at 0x...>, 2), 'sqrt', 'return']
|
|
1885
|
+
sage: fc.python_calls()
|
|
1886
|
+
[<function my_norm at 0x...>]
|
|
1887
|
+
sage: fc = fast_callable(etb2.call(sqrt, etb2.call(my_norm, y, z)), domain=RR) # needs sage.rings.real_mpfr
|
|
1888
|
+
sage: fc(3, 4) # needs sage.rings.real_mpfr
|
|
1889
|
+
5.00000000000000
|
|
1890
|
+
sage: fc = fast_callable(etb2.call(my_norm, y, z), domain=ZZ)
|
|
1891
|
+
sage: fc(3, 4)
|
|
1892
|
+
25
|
|
1893
|
+
sage: fc.op_list()
|
|
1894
|
+
[('load_arg', 0), ('load_arg', 1), ('py_call', <function my_norm at 0x...>, 2), 'return']
|
|
1895
|
+
|
|
1896
|
+
sage: # needs sage.symbolic
|
|
1897
|
+
sage: fc = fast_callable(expr)
|
|
1898
|
+
sage: fc(3.0r)
|
|
1899
|
+
4.0*pi + 12.0
|
|
1900
|
+
sage: fc = fast_callable(x+3, domain=ZZ)
|
|
1901
|
+
sage: fc(4)
|
|
1902
|
+
7
|
|
1903
|
+
sage: fc = fast_callable(x/3, domain=ZZ)
|
|
1904
|
+
sage: fc(4)
|
|
1905
|
+
Traceback (most recent call last):
|
|
1906
|
+
...
|
|
1907
|
+
TypeError: no conversion of this rational to integer
|
|
1908
|
+
sage: fc(6)
|
|
1909
|
+
2
|
|
1910
|
+
sage: fc = fast_callable(etb.call(sin, x), domain=ZZ)
|
|
1911
|
+
sage: fc(0)
|
|
1912
|
+
0
|
|
1913
|
+
sage: fc(3)
|
|
1914
|
+
Traceback (most recent call last):
|
|
1915
|
+
...
|
|
1916
|
+
TypeError: unable to convert sin(3) to an integer
|
|
1917
|
+
|
|
1918
|
+
::
|
|
1919
|
+
|
|
1920
|
+
sage: # needs sage.symbolic
|
|
1921
|
+
sage: fc = fast_callable(etb(x)^100)
|
|
1922
|
+
sage: fc(pi)
|
|
1923
|
+
pi^100
|
|
1924
|
+
sage: fc = fast_callable(etb(x)^100, domain=ZZ)
|
|
1925
|
+
sage: fc(2)
|
|
1926
|
+
1267650600228229401496703205376
|
|
1927
|
+
sage: fc = fast_callable(etb(x)^100, domain=RIF)
|
|
1928
|
+
sage: fc(RIF(-2))
|
|
1929
|
+
1.2676506002282295?e30
|
|
1930
|
+
sage: fc = fast_callable(etb(x)^100, domain=RDF)
|
|
1931
|
+
sage: fc.op_list()
|
|
1932
|
+
[('load_arg', 0), ('ipow', 100), 'return']
|
|
1933
|
+
sage: fc(1.1)
|
|
1934
|
+
13780.61233982...
|
|
1935
|
+
sage: fc = fast_callable(etb(x)^100, domain=RR) # needs sage.rings.real_mpfr
|
|
1936
|
+
sage: fc.op_list() # needs sage.rings.real_mpfr
|
|
1937
|
+
[('load_arg', 0), ('ipow', 100), 'return']
|
|
1938
|
+
sage: fc(1.1) # needs sage.rings.real_mpfr
|
|
1939
|
+
13780.6123398224
|
|
1940
|
+
sage: fc = fast_callable(etb(x)^(-100), domain=RDF)
|
|
1941
|
+
sage: fc.op_list()
|
|
1942
|
+
[('load_arg', 0), ('ipow', -100), 'return']
|
|
1943
|
+
sage: fc(1.1)
|
|
1944
|
+
7.25657159014...e-05
|
|
1945
|
+
sage: fc = fast_callable(etb(x)^(-100), domain=RR) # needs sage.rings.real_mpfr
|
|
1946
|
+
sage: fc(1.1) # needs sage.rings.real_mpfr
|
|
1947
|
+
0.0000725657159014814
|
|
1948
|
+
sage: expo = 2^32
|
|
1949
|
+
sage: base = (1.0).nextabove()
|
|
1950
|
+
sage: fc = fast_callable(etb(x)^expo, domain=RDF)
|
|
1951
|
+
sage: fc.op_list()
|
|
1952
|
+
[('load_arg', 0), ('py_call', (^4294967296), 1), 'return']
|
|
1953
|
+
sage: fc(base) # rel tol 1e-15
|
|
1954
|
+
1.0000009536747712
|
|
1955
|
+
sage: RDF(base)^expo
|
|
1956
|
+
1.0000009536747712
|
|
1957
|
+
sage: fc = fast_callable(etb(x)^expo, domain=RR) # needs sage.rings.real_mpfr
|
|
1958
|
+
sage: fc.op_list() # needs sage.rings.real_mpfr
|
|
1959
|
+
[('load_arg', 0), ('py_call', (^4294967296), 1), 'return']
|
|
1960
|
+
sage: fc(base) # needs sage.rings.real_mpfr
|
|
1961
|
+
1.00000095367477
|
|
1962
|
+
sage: base^expo
|
|
1963
|
+
1.00000095367477
|
|
1964
|
+
|
|
1965
|
+
Make sure we do not overflow the stack with highly nested expressions
|
|
1966
|
+
(:issue:`11766`)::
|
|
1967
|
+
|
|
1968
|
+
sage: # needs sage.rings.real_mpfr
|
|
1969
|
+
sage: R.<x> = CC[]
|
|
1970
|
+
sage: f = R(list(range(100000)))
|
|
1971
|
+
sage: ff = fast_callable(f)
|
|
1972
|
+
sage: f(0.5)
|
|
1973
|
+
2.00000000000000
|
|
1974
|
+
sage: ff(0.5)
|
|
1975
|
+
2.00000000000000
|
|
1976
|
+
sage: f(0.9), ff(0.9)
|
|
1977
|
+
(90.0000000000000, 90.0000000000000)
|
|
1978
|
+
"""
|
|
1979
|
+
cdef ExpressionConstant econst
|
|
1980
|
+
cdef ExpressionVariable evar
|
|
1981
|
+
cdef ExpressionCall ecall
|
|
1982
|
+
cdef ExpressionChoice echoice
|
|
1983
|
+
|
|
1984
|
+
# Maintain our own stack to avoid crashing on deeply-nested expressions.
|
|
1985
|
+
cdef list todo = [expr]
|
|
1986
|
+
do_call = Expression(None)
|
|
1987
|
+
while len(todo):
|
|
1988
|
+
expr = todo.pop()
|
|
1989
|
+
if isinstance(expr, ExpressionConstant):
|
|
1990
|
+
econst = expr
|
|
1991
|
+
stream.load_const(econst._value)
|
|
1992
|
+
elif isinstance(expr, ExpressionVariable):
|
|
1993
|
+
evar = expr
|
|
1994
|
+
stream.load_arg(evar._variable_index)
|
|
1995
|
+
elif isinstance(expr, ExpressionCall):
|
|
1996
|
+
ecall = expr
|
|
1997
|
+
todo.append(expr)
|
|
1998
|
+
todo.append(do_call)
|
|
1999
|
+
for arg in reversed(ecall._arguments):
|
|
2000
|
+
todo.append(arg)
|
|
2001
|
+
continue
|
|
2002
|
+
elif expr is do_call:
|
|
2003
|
+
# arguments already evaluated, make the call
|
|
2004
|
+
ecall = todo.pop()
|
|
2005
|
+
fn = ecall._function
|
|
2006
|
+
opname = get_builtin_functions().get(fn)
|
|
2007
|
+
if opname is not None:
|
|
2008
|
+
if stream.has_instr(opname):
|
|
2009
|
+
stream.instr0(opname, ())
|
|
2010
|
+
continue
|
|
2011
|
+
if stream.has_instr('py_call'):
|
|
2012
|
+
stream.instr('py_call', fn, len(ecall._arguments))
|
|
2013
|
+
else:
|
|
2014
|
+
raise ValueError("Unhandled function %s in generate_code" % fn)
|
|
2015
|
+
elif isinstance(expr, ExpressionIPow):
|
|
2016
|
+
base = expr.base()
|
|
2017
|
+
exponent = expr.exponent()
|
|
2018
|
+
metadata = stream.get_metadata()
|
|
2019
|
+
ipow_range = metadata.ipow_range
|
|
2020
|
+
if ipow_range is True:
|
|
2021
|
+
use_ipow = True
|
|
2022
|
+
elif isinstance(ipow_range, tuple):
|
|
2023
|
+
a,b = ipow_range
|
|
2024
|
+
use_ipow = (a <= exponent <= b)
|
|
2025
|
+
else:
|
|
2026
|
+
use_ipow = False
|
|
2027
|
+
generate_code(base, stream)
|
|
2028
|
+
if use_ipow:
|
|
2029
|
+
stream.instr('ipow', exponent)
|
|
2030
|
+
else:
|
|
2031
|
+
stream.instr('py_call', IntegerPowerFunction(exponent), 1)
|
|
2032
|
+
else:
|
|
2033
|
+
raise ValueError("Unhandled expression kind %s in generate_code" % type(expr))
|
|
2034
|
+
|
|
2035
|
+
|
|
2036
|
+
cdef class InterpreterMetadata # forward declaration
|
|
2037
|
+
|
|
2038
|
+
|
|
2039
|
+
cdef class InstructionStream:
|
|
2040
|
+
r"""
|
|
2041
|
+
An :class:`InstructionStream` takes a sequence of instructions (passed in by
|
|
2042
|
+
a series of method calls) and computes the data structures needed
|
|
2043
|
+
by the interpreter. This is the stage where we switch from operating
|
|
2044
|
+
on :class:`Expression` trees to a linear representation. If we had a peephole
|
|
2045
|
+
optimizer (we don't) it would go here.
|
|
2046
|
+
|
|
2047
|
+
Currently, this class is not very general; it only works for
|
|
2048
|
+
interpreters with a fixed set of memory chunks (with fixed names).
|
|
2049
|
+
Basically, it only works for stack-based expression interpreters.
|
|
2050
|
+
It should be generalized, so that the interpreter metadata includes
|
|
2051
|
+
a description of the memory chunks involved and the instruction stream
|
|
2052
|
+
can handle any interpreter.
|
|
2053
|
+
|
|
2054
|
+
Once you're done adding instructions, you call :meth:`get_current` to retrieve
|
|
2055
|
+
the information needed by the interpreter (as a Python dictionary).
|
|
2056
|
+
"""
|
|
2057
|
+
|
|
2058
|
+
cdef InterpreterMetadata _metadata
|
|
2059
|
+
cdef list _instrs
|
|
2060
|
+
cdef list _bytecode
|
|
2061
|
+
cdef list _constants
|
|
2062
|
+
cdef object _constant_locs
|
|
2063
|
+
cdef object _py_constants
|
|
2064
|
+
cdef object _py_constant_locs
|
|
2065
|
+
cdef int _stack_cur_size
|
|
2066
|
+
cdef int _stack_max_size
|
|
2067
|
+
cdef int _n_args
|
|
2068
|
+
cdef object _domain
|
|
2069
|
+
|
|
2070
|
+
def __init__(self, metadata, n_args, domain=None):
|
|
2071
|
+
r"""
|
|
2072
|
+
Initialize an :class:`InstructionStream`.
|
|
2073
|
+
|
|
2074
|
+
INPUT:
|
|
2075
|
+
|
|
2076
|
+
- ``metadata`` -- the ``metadata_by_opname`` from a wrapper module
|
|
2077
|
+
|
|
2078
|
+
- ``n_args`` -- the number of arguments accessible by the generated code
|
|
2079
|
+
(this is just passed to the wrapper class)
|
|
2080
|
+
|
|
2081
|
+
- ``domain`` -- the domain of interpretation (this is just passed to the
|
|
2082
|
+
wrapper class)
|
|
2083
|
+
|
|
2084
|
+
EXAMPLES::
|
|
2085
|
+
|
|
2086
|
+
sage: from sage.ext.interpreters.wrapper_el import metadata
|
|
2087
|
+
sage: from sage.ext.interpreters.wrapper_rdf import metadata # needs sage.modules
|
|
2088
|
+
sage: from sage.ext.fast_callable import InstructionStream
|
|
2089
|
+
sage: instr_stream = InstructionStream(metadata, 1)
|
|
2090
|
+
sage: instr_stream.get_current()
|
|
2091
|
+
{'args': 1,
|
|
2092
|
+
'code': [],
|
|
2093
|
+
'constants': [],
|
|
2094
|
+
'domain': None,
|
|
2095
|
+
'py_constants': [],
|
|
2096
|
+
'stack': 0}
|
|
2097
|
+
sage: md = instr_stream.get_metadata()
|
|
2098
|
+
sage: type(md)
|
|
2099
|
+
<class 'sage.ext.fast_callable.InterpreterMetadata'>
|
|
2100
|
+
sage: md.by_opname['py_call']
|
|
2101
|
+
(CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs']), 3)
|
|
2102
|
+
sage: md.by_opcode[3]
|
|
2103
|
+
('py_call', CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs']))
|
|
2104
|
+
"""
|
|
2105
|
+
self._metadata = metadata
|
|
2106
|
+
self._instrs = []
|
|
2107
|
+
self._bytecode = []
|
|
2108
|
+
self._constants = []
|
|
2109
|
+
self._constant_locs = {}
|
|
2110
|
+
self._py_constants = []
|
|
2111
|
+
self._py_constant_locs = {}
|
|
2112
|
+
self._stack_cur_size = 0
|
|
2113
|
+
self._stack_max_size = 0
|
|
2114
|
+
self._domain = domain
|
|
2115
|
+
self._n_args = n_args
|
|
2116
|
+
|
|
2117
|
+
def load_const(self, c):
|
|
2118
|
+
r"""
|
|
2119
|
+
Add a ``'load_const'`` instruction to this :class:`InstructionStream`.
|
|
2120
|
+
|
|
2121
|
+
EXAMPLES::
|
|
2122
|
+
|
|
2123
|
+
sage: from sage.ext.interpreters.wrapper_el import metadata
|
|
2124
|
+
sage: from sage.ext.interpreters.wrapper_rdf import metadata # needs sage.modules
|
|
2125
|
+
sage: from sage.ext.fast_callable import InstructionStream, op_list
|
|
2126
|
+
sage: instr_stream = InstructionStream(metadata, 1)
|
|
2127
|
+
sage: instr_stream.load_const(5)
|
|
2128
|
+
sage: instr_stream.current_op_list()
|
|
2129
|
+
[('load_const', 5)]
|
|
2130
|
+
sage: instr_stream.load_const(7)
|
|
2131
|
+
sage: instr_stream.load_const(5)
|
|
2132
|
+
sage: instr_stream.current_op_list()
|
|
2133
|
+
[('load_const', 5), ('load_const', 7), ('load_const', 5)]
|
|
2134
|
+
|
|
2135
|
+
Note that constants are shared: even though we load 5 twice, it
|
|
2136
|
+
only appears once in the constant table. ::
|
|
2137
|
+
|
|
2138
|
+
sage: instr_stream.get_current()['constants']
|
|
2139
|
+
[5, 7]
|
|
2140
|
+
"""
|
|
2141
|
+
self.instr('load_const', c)
|
|
2142
|
+
|
|
2143
|
+
def load_arg(self, n):
|
|
2144
|
+
r"""
|
|
2145
|
+
Add a ``'load_arg'`` instruction to this :class:`InstructionStream`.
|
|
2146
|
+
|
|
2147
|
+
EXAMPLES::
|
|
2148
|
+
|
|
2149
|
+
sage: from sage.ext.interpreters.wrapper_el import metadata
|
|
2150
|
+
sage: from sage.ext.interpreters.wrapper_rdf import metadata # needs sage.modules
|
|
2151
|
+
sage: from sage.ext.fast_callable import InstructionStream
|
|
2152
|
+
sage: instr_stream = InstructionStream(metadata, 12)
|
|
2153
|
+
sage: instr_stream.load_arg(5)
|
|
2154
|
+
sage: instr_stream.current_op_list()
|
|
2155
|
+
[('load_arg', 5)]
|
|
2156
|
+
sage: instr_stream.load_arg(3)
|
|
2157
|
+
sage: instr_stream.current_op_list()
|
|
2158
|
+
[('load_arg', 5), ('load_arg', 3)]
|
|
2159
|
+
"""
|
|
2160
|
+
self.instr('load_arg', n)
|
|
2161
|
+
|
|
2162
|
+
cpdef bint has_instr(self, opname) noexcept:
|
|
2163
|
+
r"""
|
|
2164
|
+
Check whether this :class:`InstructionStream` knows how to generate code
|
|
2165
|
+
for a given instruction.
|
|
2166
|
+
|
|
2167
|
+
EXAMPLES::
|
|
2168
|
+
|
|
2169
|
+
sage: from sage.ext.interpreters.wrapper_el import metadata
|
|
2170
|
+
sage: from sage.ext.interpreters.wrapper_rdf import metadata # needs sage.modules
|
|
2171
|
+
sage: from sage.ext.fast_callable import InstructionStream
|
|
2172
|
+
sage: instr_stream = InstructionStream(metadata, 1)
|
|
2173
|
+
sage: instr_stream.has_instr('return')
|
|
2174
|
+
True
|
|
2175
|
+
sage: instr_stream.has_instr('factorial')
|
|
2176
|
+
False
|
|
2177
|
+
sage: instr_stream.has_instr('abs')
|
|
2178
|
+
True
|
|
2179
|
+
"""
|
|
2180
|
+
return (opname in self._metadata.by_opname)
|
|
2181
|
+
|
|
2182
|
+
def instr(self, opname, *args):
|
|
2183
|
+
r"""
|
|
2184
|
+
Generate code in this :class:`InstructionStream` for the given instruction
|
|
2185
|
+
and arguments.
|
|
2186
|
+
|
|
2187
|
+
The opname is used to look up a :class:`CompilerInstrSpec`; the
|
|
2188
|
+
:class:`CompilerInstrSpec` describes how to interpret the arguments.
|
|
2189
|
+
(This is documented in the class docstring for :class:`CompilerInstrSpec`.)
|
|
2190
|
+
|
|
2191
|
+
EXAMPLES::
|
|
2192
|
+
|
|
2193
|
+
sage: from sage.ext.interpreters.wrapper_el import metadata
|
|
2194
|
+
sage: from sage.ext.interpreters.wrapper_rdf import metadata # needs sage.modules
|
|
2195
|
+
sage: from sage.ext.fast_callable import InstructionStream
|
|
2196
|
+
sage: instr_stream = InstructionStream(metadata, 1)
|
|
2197
|
+
sage: instr_stream.instr('load_arg', 0)
|
|
2198
|
+
sage: instr_stream.instr('sin') # needs sage.symbolic
|
|
2199
|
+
sage: instr_stream.instr('py_call', math.sin, 1)
|
|
2200
|
+
sage: instr_stream.instr('abs') # needs sage.symbolic
|
|
2201
|
+
sage: instr_stream.instr('factorial')
|
|
2202
|
+
Traceback (most recent call last):
|
|
2203
|
+
...
|
|
2204
|
+
KeyError: 'factorial'
|
|
2205
|
+
sage: instr_stream.instr('return')
|
|
2206
|
+
sage: instr_stream.current_op_list() # needs sage.symbolic
|
|
2207
|
+
[('load_arg', 0), 'sin', ('py_call', <built-in function sin>, 1), 'abs', 'return']
|
|
2208
|
+
"""
|
|
2209
|
+
self.instr0(opname, args)
|
|
2210
|
+
|
|
2211
|
+
cdef instr0(self, opname, tuple args):
|
|
2212
|
+
"""
|
|
2213
|
+
Cdef version of instr. (Can't cpdef because of star args.)
|
|
2214
|
+
"""
|
|
2215
|
+
cdef int i
|
|
2216
|
+
|
|
2217
|
+
spec, opcode = self._metadata.by_opname[opname]
|
|
2218
|
+
assert len(spec.parameters) == len(args)
|
|
2219
|
+
|
|
2220
|
+
cdef int n_inputs = spec.n_inputs
|
|
2221
|
+
cdef int n_outputs = spec.n_outputs
|
|
2222
|
+
|
|
2223
|
+
self._bytecode.append(opcode)
|
|
2224
|
+
for i in range(len(args)):
|
|
2225
|
+
if spec.parameters[i] == 'constants':
|
|
2226
|
+
# XXX bad for strict-mode floating-point constants
|
|
2227
|
+
# (doesn't handle signed 0, NaN)
|
|
2228
|
+
arg = args[i]
|
|
2229
|
+
if (arg,parent(arg)) in self._constant_locs:
|
|
2230
|
+
self._bytecode.append(self._constant_locs[(arg,parent(arg))])
|
|
2231
|
+
else:
|
|
2232
|
+
loc = len(self._constants)
|
|
2233
|
+
self._constants.append(arg)
|
|
2234
|
+
self._constant_locs[(arg,parent(arg))] = loc
|
|
2235
|
+
self._bytecode.append(loc)
|
|
2236
|
+
elif spec.parameters[i] == 'args':
|
|
2237
|
+
self._bytecode.append(args[i])
|
|
2238
|
+
elif spec.parameters[i] == 'code':
|
|
2239
|
+
self._bytecode.append(args[i])
|
|
2240
|
+
elif spec.parameters[i] == 'n_inputs':
|
|
2241
|
+
self._bytecode.append(args[i])
|
|
2242
|
+
n_inputs = args[i]
|
|
2243
|
+
elif spec.parameters[i] == 'n_outputs':
|
|
2244
|
+
self._bytecode.append(args[i])
|
|
2245
|
+
n_outputs = args[i]
|
|
2246
|
+
elif spec.parameters[i] == 'py_constants':
|
|
2247
|
+
arg = args[i]
|
|
2248
|
+
if arg in self._py_constant_locs:
|
|
2249
|
+
self._bytecode.append(self._py_constant_locs[arg])
|
|
2250
|
+
else:
|
|
2251
|
+
loc = len(self._py_constants)
|
|
2252
|
+
self._py_constants.append(arg)
|
|
2253
|
+
self._py_constant_locs[arg] = loc
|
|
2254
|
+
self._bytecode.append(loc)
|
|
2255
|
+
else:
|
|
2256
|
+
raise ValueError
|
|
2257
|
+
|
|
2258
|
+
self._stack_cur_size -= n_inputs
|
|
2259
|
+
self._stack_cur_size += n_outputs
|
|
2260
|
+
self._stack_max_size = max(self._stack_max_size, self._stack_cur_size)
|
|
2261
|
+
|
|
2262
|
+
def get_metadata(self):
|
|
2263
|
+
r"""
|
|
2264
|
+
Return the interpreter metadata being used by the current :class:`InstructionStream`.
|
|
2265
|
+
|
|
2266
|
+
The code generator sometimes uses this to decide which code
|
|
2267
|
+
to generate.
|
|
2268
|
+
|
|
2269
|
+
EXAMPLES::
|
|
2270
|
+
|
|
2271
|
+
sage: from sage.ext.interpreters.wrapper_el import metadata
|
|
2272
|
+
sage: from sage.ext.interpreters.wrapper_rdf import metadata # needs sage.modules
|
|
2273
|
+
sage: from sage.ext.fast_callable import InstructionStream
|
|
2274
|
+
sage: instr_stream = InstructionStream(metadata, 1)
|
|
2275
|
+
sage: md = instr_stream.get_metadata()
|
|
2276
|
+
sage: type(md)
|
|
2277
|
+
<class 'sage.ext.fast_callable.InterpreterMetadata'>
|
|
2278
|
+
"""
|
|
2279
|
+
return self._metadata
|
|
2280
|
+
|
|
2281
|
+
def current_op_list(self):
|
|
2282
|
+
r"""
|
|
2283
|
+
Return the list of instructions that have been added to this
|
|
2284
|
+
:class:`InstructionStream` so far.
|
|
2285
|
+
|
|
2286
|
+
It's OK to call this, then add more instructions.
|
|
2287
|
+
|
|
2288
|
+
EXAMPLES::
|
|
2289
|
+
|
|
2290
|
+
sage: from sage.ext.interpreters.wrapper_el import metadata
|
|
2291
|
+
sage: from sage.ext.interpreters.wrapper_rdf import metadata # needs sage.modules
|
|
2292
|
+
sage: from sage.ext.fast_callable import InstructionStream
|
|
2293
|
+
sage: instr_stream = InstructionStream(metadata, 1)
|
|
2294
|
+
sage: instr_stream.instr('load_arg', 0)
|
|
2295
|
+
sage: instr_stream.instr('py_call', math.sin, 1)
|
|
2296
|
+
sage: instr_stream.instr('abs')
|
|
2297
|
+
sage: instr_stream.instr('return')
|
|
2298
|
+
sage: instr_stream.current_op_list()
|
|
2299
|
+
[('load_arg', 0), ('py_call', <built-in function sin>, 1), 'abs', 'return']
|
|
2300
|
+
"""
|
|
2301
|
+
return op_list(self.get_current(), self._metadata)
|
|
2302
|
+
|
|
2303
|
+
def get_current(self):
|
|
2304
|
+
r"""
|
|
2305
|
+
Return the current state of the :class:`InstructionStream`, as a dictionary
|
|
2306
|
+
suitable for passing to a wrapper class.
|
|
2307
|
+
|
|
2308
|
+
NOTE: The dictionary includes internal data structures of the
|
|
2309
|
+
:class:`InstructionStream`; you must not modify it.
|
|
2310
|
+
|
|
2311
|
+
EXAMPLES::
|
|
2312
|
+
|
|
2313
|
+
sage: from sage.ext.interpreters.wrapper_el import metadata
|
|
2314
|
+
sage: from sage.ext.interpreters.wrapper_rdf import metadata # needs sage.modules
|
|
2315
|
+
sage: from sage.ext.fast_callable import InstructionStream
|
|
2316
|
+
sage: instr_stream = InstructionStream(metadata, 1)
|
|
2317
|
+
sage: instr_stream.get_current()
|
|
2318
|
+
{'args': 1,
|
|
2319
|
+
'code': [],
|
|
2320
|
+
'constants': [],
|
|
2321
|
+
'domain': None,
|
|
2322
|
+
'py_constants': [],
|
|
2323
|
+
'stack': 0}
|
|
2324
|
+
sage: instr_stream.instr('load_arg', 0)
|
|
2325
|
+
sage: instr_stream.instr('py_call', math.sin, 1)
|
|
2326
|
+
sage: instr_stream.instr('abs')
|
|
2327
|
+
sage: instr_stream.instr('return')
|
|
2328
|
+
sage: instr_stream.current_op_list()
|
|
2329
|
+
[('load_arg', 0), ('py_call', <built-in function sin>, 1), 'abs', 'return']
|
|
2330
|
+
sage: instr_stream.get_current()
|
|
2331
|
+
{'args': 1,
|
|
2332
|
+
'code': [0, 0, 3, 0, 1, 12, 2],
|
|
2333
|
+
'constants': [],
|
|
2334
|
+
'domain': None,
|
|
2335
|
+
'py_constants': [<built-in function sin>],
|
|
2336
|
+
'stack': 1}
|
|
2337
|
+
"""
|
|
2338
|
+
d = {'args': self._n_args,
|
|
2339
|
+
'constants': self._constants,
|
|
2340
|
+
'py_constants': self._py_constants,
|
|
2341
|
+
'stack': self._stack_max_size,
|
|
2342
|
+
'code': self._bytecode,
|
|
2343
|
+
'domain': self._domain}
|
|
2344
|
+
return d
|
|
2345
|
+
|
|
2346
|
+
|
|
2347
|
+
cdef class InterpreterMetadata():
|
|
2348
|
+
r"""
|
|
2349
|
+
The interpreter metadata for a :func:`fast_callable` interpreter.
|
|
2350
|
+
|
|
2351
|
+
Currently consists of a dictionary mapping instruction names to
|
|
2352
|
+
(:class:`CompilerInstrSpec`, opcode) pairs, a list mapping opcodes to
|
|
2353
|
+
(instruction name, :class:`CompilerInstrSpec`) pairs, and a range of exponents
|
|
2354
|
+
for which the ``'ipow'`` instruction can be used. This range can be
|
|
2355
|
+
``False`` (if the ``'ipow'`` instruction should never be used), a pair of
|
|
2356
|
+
two integers `(a, b)`, if ``'ipow'`` should be used for `a \le n \le b`, or
|
|
2357
|
+
``True``, if ``'ipow'`` should always be used. When ``'ipow'`` cannot be
|
|
2358
|
+
used, then we fall back on calling :class:`IntegerPowerFunction`.
|
|
2359
|
+
|
|
2360
|
+
See the class docstring for :class:`CompilerInstrSpec` for more information.
|
|
2361
|
+
|
|
2362
|
+
NOTE: You must not modify the metadata.
|
|
2363
|
+
"""
|
|
2364
|
+
cdef public dict by_opname
|
|
2365
|
+
cdef public list by_opcode
|
|
2366
|
+
cdef public ipow_range
|
|
2367
|
+
|
|
2368
|
+
def __init__(self, by_opname, by_opcode, ipow_range):
|
|
2369
|
+
r"""
|
|
2370
|
+
Initialize an InterpreterMetadata object.
|
|
2371
|
+
|
|
2372
|
+
EXAMPLES::
|
|
2373
|
+
|
|
2374
|
+
sage: from sage.ext.fast_callable import InterpreterMetadata
|
|
2375
|
+
sage: metadata = InterpreterMetadata(by_opname={'opname dict goes here': True},
|
|
2376
|
+
....: by_opcode=['opcode list goes here'],
|
|
2377
|
+
....: ipow_range=(2, 57))
|
|
2378
|
+
sage: metadata.by_opname
|
|
2379
|
+
{'opname dict goes here': True}
|
|
2380
|
+
sage: metadata.by_opcode
|
|
2381
|
+
['opcode list goes here']
|
|
2382
|
+
sage: metadata.ipow_range
|
|
2383
|
+
(2, 57)
|
|
2384
|
+
"""
|
|
2385
|
+
self.by_opname = by_opname
|
|
2386
|
+
self.by_opcode = by_opcode
|
|
2387
|
+
self.ipow_range = ipow_range
|
|
2388
|
+
|
|
2389
|
+
|
|
2390
|
+
class CompilerInstrSpec():
|
|
2391
|
+
r"""
|
|
2392
|
+
Describe a single instruction to the :func:`fast_callable` code generator.
|
|
2393
|
+
|
|
2394
|
+
An instruction has a number of stack inputs, a number of stack
|
|
2395
|
+
outputs, and a parameter list describing extra arguments that
|
|
2396
|
+
must be passed to the :meth:`InstructionStream.instr` method (that end up
|
|
2397
|
+
as extra words in the code).
|
|
2398
|
+
|
|
2399
|
+
The parameter list is a list of strings. Each string is one of
|
|
2400
|
+
the following:
|
|
2401
|
+
|
|
2402
|
+
- ``'args'`` -- the instruction argument refers to an input argument of the
|
|
2403
|
+
wrapper class; it is just appended to the code
|
|
2404
|
+
|
|
2405
|
+
- ``'constants'``, ``'py_constants'`` -- the instruction argument is a value; the
|
|
2406
|
+
value is added to the corresponding list (if it's not already there) and
|
|
2407
|
+
the index is appended to the code.
|
|
2408
|
+
|
|
2409
|
+
- ``'n_inputs'``, ``'n_outputs'`` -- the instruction actually takes a variable
|
|
2410
|
+
number of inputs or outputs (the ``n_inputs`` and ``n_outputs`` attributes of
|
|
2411
|
+
this instruction are ignored). The instruction argument specifies the
|
|
2412
|
+
number of inputs or outputs (respectively); it is just appended to the
|
|
2413
|
+
code.
|
|
2414
|
+
"""
|
|
2415
|
+
|
|
2416
|
+
def __init__(self, n_inputs, n_outputs, parameters):
|
|
2417
|
+
r"""
|
|
2418
|
+
Initialize a :class:`CompilerInstrSpec`.
|
|
2419
|
+
|
|
2420
|
+
EXAMPLES::
|
|
2421
|
+
|
|
2422
|
+
sage: from sage.ext.fast_callable import CompilerInstrSpec
|
|
2423
|
+
sage: CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs'])
|
|
2424
|
+
CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs'])
|
|
2425
|
+
"""
|
|
2426
|
+
self.n_inputs = n_inputs
|
|
2427
|
+
self.n_outputs = n_outputs
|
|
2428
|
+
self.parameters = parameters
|
|
2429
|
+
|
|
2430
|
+
def __repr__(self):
|
|
2431
|
+
r"""
|
|
2432
|
+
Give a string representation for this :class:`CompilerInstrSpec`.
|
|
2433
|
+
|
|
2434
|
+
EXAMPLES::
|
|
2435
|
+
|
|
2436
|
+
sage: from sage.ext.fast_callable import CompilerInstrSpec
|
|
2437
|
+
sage: v = CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs'])
|
|
2438
|
+
sage: v
|
|
2439
|
+
CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs'])
|
|
2440
|
+
sage: repr(v)
|
|
2441
|
+
"CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs'])"
|
|
2442
|
+
sage: v.__repr__()
|
|
2443
|
+
"CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs'])"
|
|
2444
|
+
"""
|
|
2445
|
+
return "CompilerInstrSpec(%d, %d, %s)" % (self.n_inputs, self.n_outputs, self.parameters)
|
|
2446
|
+
|
|
2447
|
+
|
|
2448
|
+
def op_list(args, metadata):
|
|
2449
|
+
r"""
|
|
2450
|
+
Given a dictionary with the result of calling :meth:`get_current` on an
|
|
2451
|
+
:class:`InstructionStream`, and the corresponding interpreter metadata,
|
|
2452
|
+
return a list of the instructions, in a simple somewhat
|
|
2453
|
+
human-readable format.
|
|
2454
|
+
|
|
2455
|
+
For debugging only. (That is, it's probably not a good idea to
|
|
2456
|
+
try to programmatically manipulate the result of this function;
|
|
2457
|
+
the expected use is just to print the returned list to the
|
|
2458
|
+
screen.)
|
|
2459
|
+
|
|
2460
|
+
There's probably no reason to call this directly; if you
|
|
2461
|
+
have a wrapper object, call :func:`op_list` on it; if you have an
|
|
2462
|
+
:class:`InstructionStream` object, call :meth:`current_op_list` on it.
|
|
2463
|
+
|
|
2464
|
+
EXAMPLES::
|
|
2465
|
+
|
|
2466
|
+
sage: from sage.ext.interpreters.wrapper_el import metadata
|
|
2467
|
+
sage: from sage.ext.interpreters.wrapper_rdf import metadata # needs sage.modules
|
|
2468
|
+
sage: from sage.ext.fast_callable import InstructionStream, op_list
|
|
2469
|
+
sage: instr_stream = InstructionStream(metadata, 1)
|
|
2470
|
+
sage: instr_stream.instr('load_arg', 0)
|
|
2471
|
+
sage: instr_stream.instr('abs')
|
|
2472
|
+
sage: instr_stream.instr('return')
|
|
2473
|
+
sage: instr_stream.current_op_list()
|
|
2474
|
+
[('load_arg', 0), 'abs', 'return']
|
|
2475
|
+
sage: op_list(instr_stream.get_current(), metadata)
|
|
2476
|
+
[('load_arg', 0), 'abs', 'return']
|
|
2477
|
+
"""
|
|
2478
|
+
ops = []
|
|
2479
|
+
code = args['code']
|
|
2480
|
+
while len(code):
|
|
2481
|
+
opcode = code[0]
|
|
2482
|
+
code = code[1:]
|
|
2483
|
+
(opname, instr) = metadata.by_opcode[opcode]
|
|
2484
|
+
if len(instr.parameters):
|
|
2485
|
+
op = [opname]
|
|
2486
|
+
for p in instr.parameters:
|
|
2487
|
+
p_loc = code[0]
|
|
2488
|
+
code = code[1:]
|
|
2489
|
+
if p in ('args', 'code', 'n_inputs', 'n_outputs'):
|
|
2490
|
+
op.append(p_loc)
|
|
2491
|
+
else:
|
|
2492
|
+
op.append(args[p][p_loc])
|
|
2493
|
+
ops.append(tuple(op))
|
|
2494
|
+
else:
|
|
2495
|
+
ops.append(opname)
|
|
2496
|
+
return ops
|
|
2497
|
+
|
|
2498
|
+
|
|
2499
|
+
cdef class Wrapper:
|
|
2500
|
+
r"""
|
|
2501
|
+
The parent class for all :func:`fast_callable` wrappers.
|
|
2502
|
+
|
|
2503
|
+
Implements shared behavior (currently only debugging).
|
|
2504
|
+
"""
|
|
2505
|
+
|
|
2506
|
+
def __init__(self, args, metadata):
|
|
2507
|
+
r"""
|
|
2508
|
+
Initialize a Wrapper object.
|
|
2509
|
+
|
|
2510
|
+
EXAMPLES::
|
|
2511
|
+
|
|
2512
|
+
sage: # needs sage.symbolic
|
|
2513
|
+
sage: from sage.ext.fast_callable import ExpressionTreeBuilder, generate_code, InstructionStream
|
|
2514
|
+
sage: etb = ExpressionTreeBuilder('x')
|
|
2515
|
+
sage: x = etb.var('x')
|
|
2516
|
+
sage: expr = (x+pi) * (x+1)
|
|
2517
|
+
sage: from sage.ext.interpreters.wrapper_py import metadata, Wrapper_py
|
|
2518
|
+
sage: instr_stream = InstructionStream(metadata, 1)
|
|
2519
|
+
sage: generate_code(expr, instr_stream)
|
|
2520
|
+
sage: instr_stream.instr('return')
|
|
2521
|
+
sage: v = Wrapper_py(instr_stream.get_current())
|
|
2522
|
+
sage: v.get_orig_args()
|
|
2523
|
+
{'args': 1,
|
|
2524
|
+
'code': [0, 0, 1, 0, 4, 0, 0, 1, 1, 4, 6, 2],
|
|
2525
|
+
'constants': [pi, 1],
|
|
2526
|
+
'domain': None,
|
|
2527
|
+
'py_constants': [],
|
|
2528
|
+
'stack': 3}
|
|
2529
|
+
sage: v.op_list()
|
|
2530
|
+
[('load_arg', 0), ('load_const', pi), 'add', ('load_arg', 0), ('load_const', 1), 'add', 'mul', 'return']
|
|
2531
|
+
"""
|
|
2532
|
+
|
|
2533
|
+
# We only keep the original arguments for debugging (op_list(), etc.);
|
|
2534
|
+
# is it worth the memory cost? (Note that we may be holding on to
|
|
2535
|
+
# large objects that could otherwise be garbage collected, for
|
|
2536
|
+
# instance.)
|
|
2537
|
+
self._orig_args = args
|
|
2538
|
+
self._metadata = metadata
|
|
2539
|
+
|
|
2540
|
+
def get_orig_args(self):
|
|
2541
|
+
r"""
|
|
2542
|
+
Get the original arguments used when initializing this wrapper.
|
|
2543
|
+
|
|
2544
|
+
(Probably only useful when writing doctests.)
|
|
2545
|
+
|
|
2546
|
+
EXAMPLES::
|
|
2547
|
+
|
|
2548
|
+
sage: fast_callable(sin(x)/x, vars=[x], domain=RDF).get_orig_args() # needs sage.symbolic
|
|
2549
|
+
{'args': 1,
|
|
2550
|
+
'code': [0, 0, 16, 0, 0, 8, 2],
|
|
2551
|
+
'constants': [],
|
|
2552
|
+
'domain': Real Double Field,
|
|
2553
|
+
'py_constants': [],
|
|
2554
|
+
'stack': 2}
|
|
2555
|
+
"""
|
|
2556
|
+
return self._orig_args
|
|
2557
|
+
|
|
2558
|
+
def op_list(self):
|
|
2559
|
+
r"""
|
|
2560
|
+
Return the list of instructions in this wrapper.
|
|
2561
|
+
|
|
2562
|
+
EXAMPLES::
|
|
2563
|
+
|
|
2564
|
+
sage: fast_callable(cos(x) * x, vars=[x], domain=RDF).op_list() # needs sage.symbolic
|
|
2565
|
+
[('load_arg', 0), ('load_arg', 0), 'cos', 'mul', 'return']
|
|
2566
|
+
"""
|
|
2567
|
+
return op_list(self._orig_args, self._metadata)
|
|
2568
|
+
|
|
2569
|
+
def python_calls(self):
|
|
2570
|
+
r"""
|
|
2571
|
+
List the Python functions that are called in this wrapper.
|
|
2572
|
+
|
|
2573
|
+
(Python function calls are slow, so ideally this list would
|
|
2574
|
+
be empty. If it is not empty, then perhaps there is an
|
|
2575
|
+
optimization opportunity where a Sage developer could speed
|
|
2576
|
+
this up by adding a new instruction to the interpreter.)
|
|
2577
|
+
|
|
2578
|
+
EXAMPLES::
|
|
2579
|
+
|
|
2580
|
+
sage: fast_callable(abs(sin(x)), vars=[x], domain=RDF).python_calls() # needs sage.symbolic
|
|
2581
|
+
[]
|
|
2582
|
+
sage: fast_callable(abs(sin(factorial(x))), vars=[x]).python_calls() # needs sage.symbolic
|
|
2583
|
+
[factorial, sin]
|
|
2584
|
+
"""
|
|
2585
|
+
ops = self.op_list()
|
|
2586
|
+
py_calls = []
|
|
2587
|
+
for op in ops:
|
|
2588
|
+
if isinstance(op, tuple) and op[0] == 'py_call':
|
|
2589
|
+
py_calls.append(op[1])
|
|
2590
|
+
return py_calls
|
|
2591
|
+
|
|
2592
|
+
|
|
2593
|
+
class FastCallableFloatWrapper:
|
|
2594
|
+
r"""
|
|
2595
|
+
A class to alter the return types of the fast-callable functions.
|
|
2596
|
+
|
|
2597
|
+
When applying numerical routines (including plotting) to symbolic
|
|
2598
|
+
expressions and functions, we generally first convert them to a
|
|
2599
|
+
faster form with :func:`fast_callable`. That function takes a
|
|
2600
|
+
``domain`` parameter that forces the end (and all intermediate)
|
|
2601
|
+
results of evaluation to a specific type. Though usually always
|
|
2602
|
+
want the end result to be of type :class:`float`, correctly choosing
|
|
2603
|
+
the ``domain`` presents some problems:
|
|
2604
|
+
|
|
2605
|
+
* :class:`float` is a bad choice because it's common for real
|
|
2606
|
+
functions to have complex terms in them. Moreover precision
|
|
2607
|
+
issues can produce terms like ``1.0 + 1e-12*I`` that are hard
|
|
2608
|
+
to avoid if calling ``real()`` on everything is infeasible.
|
|
2609
|
+
|
|
2610
|
+
* :class:`complex` has essentially the same problem as :class:`float`.
|
|
2611
|
+
There are several symbolic functions like :func:`min_symbolic`,
|
|
2612
|
+
:func:`max_symbolic`, and :func:`floor` that are unable to
|
|
2613
|
+
operate on complex numbers.
|
|
2614
|
+
|
|
2615
|
+
* ``None`` leaves the types of the inputs/outputs alone, but due
|
|
2616
|
+
to the lack of a specialized interpreter, slows down evaluation
|
|
2617
|
+
by an unacceptable amount.
|
|
2618
|
+
|
|
2619
|
+
* ``CDF`` has none of the other issues, because ``CDF`` has its
|
|
2620
|
+
own specialized interpreter, a lexicographic ordering (for
|
|
2621
|
+
min/max), and supports :func:`floor`. However, most numerical
|
|
2622
|
+
functions cannot handle complex numbers, so using ``CDF``
|
|
2623
|
+
would require us to wrap every evaluation in a
|
|
2624
|
+
``CDF``-to-:class:`float` conversion routine. That would slow
|
|
2625
|
+
things down less than a domain of ``None`` would, but is
|
|
2626
|
+
unattractive mainly because of how invasive it would be to
|
|
2627
|
+
"fix" the output everywhere.
|
|
2628
|
+
|
|
2629
|
+
Creating a new fast-callable interpreter that has different input
|
|
2630
|
+
and output types solves most of the problems with a ``CDF``
|
|
2631
|
+
domain, but :func:`fast_callable` and the interpreter classes in
|
|
2632
|
+
:mod:`sage.ext.interpreters` are not really written with that in
|
|
2633
|
+
mind. The ``domain`` parameter to :func:`fast_callable`, for
|
|
2634
|
+
example, is expecting a single Sage ring that corresponds to one
|
|
2635
|
+
interpreter. You can make it accept, for example, a string like
|
|
2636
|
+
"CDF-to-float", but the hacks required to make that work feel
|
|
2637
|
+
wrong.
|
|
2638
|
+
|
|
2639
|
+
Thus we arrive at this solution: a class to wrap the result of
|
|
2640
|
+
:func:`fast_callable`. Whenever we need to support intermediate
|
|
2641
|
+
complex terms in a numerical routine, we can set ``domain=CDF``
|
|
2642
|
+
while creating its fast-callable incarnation, and then wrap the
|
|
2643
|
+
result in this class. The :meth:`__call__` method of this class then
|
|
2644
|
+
ensures that the ``CDF`` output is converted to a :class:`float` if
|
|
2645
|
+
its imaginary part is within an acceptable tolerance.
|
|
2646
|
+
|
|
2647
|
+
EXAMPLES:
|
|
2648
|
+
|
|
2649
|
+
An error is thrown if the answer is complex::
|
|
2650
|
+
|
|
2651
|
+
sage: # needs sage.symbolic
|
|
2652
|
+
sage: from sage.ext.fast_callable import FastCallableFloatWrapper
|
|
2653
|
+
sage: f = sqrt(x)
|
|
2654
|
+
sage: ff = fast_callable(f, vars=[x], domain=CDF)
|
|
2655
|
+
sage: fff = FastCallableFloatWrapper(ff, imag_tol=1e-8)
|
|
2656
|
+
sage: fff(1)
|
|
2657
|
+
1.0
|
|
2658
|
+
sage: fff(-1)
|
|
2659
|
+
Traceback (most recent call last):
|
|
2660
|
+
...
|
|
2661
|
+
ValueError: complex fast-callable function result
|
|
2662
|
+
1.0*I for arguments (-1,)
|
|
2663
|
+
"""
|
|
2664
|
+
def __init__(self, ff, imag_tol):
|
|
2665
|
+
r"""
|
|
2666
|
+
Construct a :class:`FastCallableFloatWrapper`.
|
|
2667
|
+
|
|
2668
|
+
INPUT:
|
|
2669
|
+
|
|
2670
|
+
- ``ff`` -- a fast-callable wrapper over ``CDF``; an instance of
|
|
2671
|
+
:class:`sage.ext.interpreters.Wrapper_cdf`, usually constructed
|
|
2672
|
+
with :func:`fast_callable`.
|
|
2673
|
+
|
|
2674
|
+
- ``imag_tol`` -- float; how big of an imaginary part we're willing
|
|
2675
|
+
to ignore before raising an error
|
|
2676
|
+
|
|
2677
|
+
OUTPUT:
|
|
2678
|
+
|
|
2679
|
+
An instance of :class:`FastCallableFloatWrapper` that can be
|
|
2680
|
+
called just like ``ff``, but that always returns a :class:`float`
|
|
2681
|
+
if no error is raised. A :exc:`ValueError` is raised if the
|
|
2682
|
+
imaginary part of the result exceeds ``imag_tol``.
|
|
2683
|
+
|
|
2684
|
+
EXAMPLES:
|
|
2685
|
+
|
|
2686
|
+
The wrapper will ignore an imaginary part smaller in magnitude
|
|
2687
|
+
than ``imag_tol``, but not one larger::
|
|
2688
|
+
|
|
2689
|
+
sage: # needs sage.symbolic
|
|
2690
|
+
sage: from sage.ext.fast_callable import FastCallableFloatWrapper
|
|
2691
|
+
sage: f = x
|
|
2692
|
+
sage: ff = fast_callable(f, vars=[x], domain=CDF)
|
|
2693
|
+
sage: fff = FastCallableFloatWrapper(ff, imag_tol=1e-8)
|
|
2694
|
+
sage: fff(I*1e-9)
|
|
2695
|
+
0.0
|
|
2696
|
+
sage: fff = FastCallableFloatWrapper(ff, imag_tol=1e-12)
|
|
2697
|
+
sage: fff(I*1e-9)
|
|
2698
|
+
Traceback (most recent call last):
|
|
2699
|
+
...
|
|
2700
|
+
ValueError: complex fast-callable function result 1e-09*I for
|
|
2701
|
+
arguments (1.00000000000000e-9*I,)
|
|
2702
|
+
"""
|
|
2703
|
+
self._ff = ff
|
|
2704
|
+
self._imag_tol = imag_tol
|
|
2705
|
+
|
|
2706
|
+
def __call__(self, *args):
|
|
2707
|
+
r"""
|
|
2708
|
+
Evaluate the underlying fast-callable and convert the result to :class:`float`.
|
|
2709
|
+
|
|
2710
|
+
TESTS:
|
|
2711
|
+
|
|
2712
|
+
Evaluation either returns a :class:`float`, or raises a :exc:`ValueError`::
|
|
2713
|
+
|
|
2714
|
+
sage: # needs sage.symbolic
|
|
2715
|
+
sage: from sage.ext.fast_callable import FastCallableFloatWrapper
|
|
2716
|
+
sage: f = x
|
|
2717
|
+
sage: ff = fast_callable(f, vars=[x], domain=CDF)
|
|
2718
|
+
sage: fff = FastCallableFloatWrapper(ff, imag_tol=0.1)
|
|
2719
|
+
sage: try:
|
|
2720
|
+
....: result = fff(CDF.random_element())
|
|
2721
|
+
....: except ValueError:
|
|
2722
|
+
....: result = float(0)
|
|
2723
|
+
sage: type(result) is float
|
|
2724
|
+
True
|
|
2725
|
+
"""
|
|
2726
|
+
z = self._ff(*args)
|
|
2727
|
+
|
|
2728
|
+
if abs(z.imag()) < self._imag_tol:
|
|
2729
|
+
return float(z.real())
|
|
2730
|
+
elif isnan(z.real()) and isnan(z.imag()):
|
|
2731
|
+
# A fast-callable with domain=float or domain=RDF will
|
|
2732
|
+
# return NaN if that's what the underlying function itself
|
|
2733
|
+
# returns. When performing the same computation over CDF,
|
|
2734
|
+
# however, we get something special; witness:
|
|
2735
|
+
#
|
|
2736
|
+
# sage: RDF(0)*math.inf
|
|
2737
|
+
# NaN
|
|
2738
|
+
# sage: CDF(0)*math.inf
|
|
2739
|
+
# NaN + NaN*I
|
|
2740
|
+
#
|
|
2741
|
+
# Thus for backwards-compatibility, we handle this special
|
|
2742
|
+
# case to return NaN to anyone expecting it.
|
|
2743
|
+
return nan
|
|
2744
|
+
else:
|
|
2745
|
+
raise ValueError(f"complex fast-callable function result {z} " +
|
|
2746
|
+
f"for arguments {args}")
|