Skip to main content

rubi_integrate

⚠️ Experimental — this package is under active development; APIs, rule content and behaviour may change without notice. Version 0.0.2 is a pre-alpha snapshot.

Symbolic integration for SymPy using the Rubi rule set (Rule-Based Integration), matched with OmniMatch.

The public entry point is rubi_integrate.

from sympy import Symbol, sin, exp
from rubi_integrate import rubi_integrate

x = Symbol('x')

rubi_integrate(1/x, x)            # log(x)
rubi_integrate(x**2, x)           # x**3/3
rubi_integrate(sin(x)*x, x)       # -x*cos(x) + sin(x)
rubi_integrate(exp(x**2), x)      # sqrt(pi)*erfi(x)/2

Results are ordinary SymPy expressions. Note that exponentials are returned in E**(...) (i.e. Pow(E, ...)) form rather than exp(...) — the two are equal (E**u == exp(u)); the port carries exponentials as Pow(E, ·) internally so they match Rubi's F^(...) rule patterns.

rubi_integrate(expr, x, ...)

argument default meaning
expr the integrand (a SymPy expression)
x the integration variable
pattern '**' glob selecting which generated rule files to load (see below)
return_matched_rules False also return the list of rules that were accepted
return_trace False also return a full DFS trace of rules accepted and rejected

If the integrand cannot be integrated with the available rules it is returned unevaluated as Int(expr, x), or wrapped in CannotIntegrate(expr, x) when a Rubi rule explicitly gives up.

Which rules ran: return_matched_rules=True

Returns (result, matched_rules). matched_rules is a list of (result, applied) pairs (one per top-level integrand handled), where applied is a flat list of the rules accepted on the winning path, each as (module_name, rule_number):

result, matched = rubi_integrate(exp(x)*sin(x**2 + x), x, return_matched_rules=True)
# matched == [(result, [
#     ('4.7.7 F^(c (a+b x)) trig(d+e x)^n', 44),
#     ('2.3 Miscellaneous exponentials', 43),
#     ('2.3 Miscellaneous exponentials', 42),
#     ('2.3 Miscellaneous exponentials', 11),
#     ... ])]

This shows only the rules that stuck. It does not show rules that matched but were rejected — for that, use return_trace.

Full search trace: return_trace=True

rubi_integrate reduces an integral with a depth-first search: at each sub-integral it tries the matching rules and picks one, backing out of rules that lead to a cycle or that fail their side conditions. return_trace=True returns (result, trace), where trace is a list of one record per rule tried:

{'depth': int,           # reduction depth (how nested the sub-integral is)
 'integrand': Int(...),  # the sub-integral the rule was tried on
 'rule': (module, num),  # the rule, or its label string
 'status': str}          # what happened — see below

Statuses:

status meaning
accepted produced a clean antiderivative (no Int, no CannotIntegrate); taken
accepted (fallback) no clean rule was available, so this non-clean result was taken
rejected (cycle) its result re-entered a sub-integral already on the current path (a loop)
rejected (condition failed) the rule's side condition (/;) failed at apply time
candidate (non-clean) matched but produced an Int/CannotIntegrate; kept only as a fallback

format_trace renders it as indented text (deeper reductions are indented more):

from rubi_integrate import rubi_integrate
from rubi_integrate.base_objects import format_trace

result, trace = rubi_integrate(exp(x)*sin(x**2 + x), x, return_trace=True)
print(format_trace(trace))
        rejected (cycle)           2.3 Miscellaneous exponentials:[42]   Int(E**(-I*x**2 + x*(1 - I) + 1/2))
        candidate (non-clean)      9.3 Miscellaneous integration rules:[67] Int(E**(-I*x**2 + x*(1 - I) + 1/2))
      candidate (non-clean)      2.3 Miscellaneous exponentials:[43]   Int(E**(I*(-2*I*x + 1 - I)**2/4))
      accepted                   2.3 Miscellaneous exponentials:[11]   Int(E**(I*(-2*I*x + 1 - I)**2/4))
    accepted                   2.3 Miscellaneous exponentials:[42]   Int(E**(-I*x**2 + x*(1 - I)))
  accepted                   2.3 Miscellaneous exponentials:[43]   Int(E**(x*(-I*x + 1 - I)))
accepted                   4.7.7 F^(c (a+b x)) trig(d+e x)^n:[44]   Int(E**x*sin(x**2 + x))

Records are appended in reduction order, so a sub-integral's rules appear (indented) before the rule that produced it. rejected/candidate entries are the branches the DFS explored and backed out of — e.g. above, rule [43] was rejected in favour of [11] at the completed square, which is how the result reaches Erf/Erfi instead of CannotIntegrate.

Scoping the rules: the pattern argument

pattern is a glob over rubi_integrate/rules/**. The default '**' loads the whole rule set (~7700 rules; the first call builds the matcher and takes ~a minute, then it is cached). Scope to a subsection for speed when you know which rules you need:

rubi_integrate(exp(x**2), x, pattern='r_2_exponentials/**')
rubi_integrate(1/x, x,      pattern='r_1_algebraic_functions/**')

How it works (short version)

Rubi's .m rules are translated by codegen/generate.py into Python SymPyReplacementPattern objects under rules/ (auto-generated — do not edit). Each rule is a SymPy pattern + constraints + replacement. rubi_integrate converts the integral to a OmniMatch expression, matches rules, and applies replacements, reducing Int(...) nodes depth-first until the integral is solved. Rule match order is not significant — the search prefers a fully-integrated result over a CannotIntegrate/residual-Int one regardless of which rule matches first.

See the repository-root AGENTS.md for the full architecture (eager vs deferred utility functions, active vs inert trig, the exp/Pow(E,·) representation, and the code-generation pipeline).

License

MIT (Copyright (c) 2026 Francesco Bonazzi) for the Python port. The Rubi-derived content (rules / test corpus) originates from Rubi by Albert Rich, whose MIT license is reproduced in full in LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rubi_integrate-0.0.3.tar.gz (673.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rubi_integrate-0.0.3-py3-none-any.whl (863.3 kB view details)

Uploaded Python 3

File details

Details for the file rubi_integrate-0.0.3.tar.gz.

File metadata

  • Download URL: rubi_integrate-0.0.3.tar.gz
  • Upload date:
  • Size: 673.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for rubi_integrate-0.0.3.tar.gz
Algorithm Hash digest
SHA256 0263c7e5352415d0db11a16abdfa81b1532b4b58b23344bd40b8dd3732e3aea2
MD5 22d0dfc0e7f0d10a5f1a2e5289cb1f4e
BLAKE2b-256 e05da63a616bc67d3545cbb73bc19a7093c8754e24043d6009b5b983f06868da

See more details on using hashes here.

File details

Details for the file rubi_integrate-0.0.3-py3-none-any.whl.

File metadata

  • Download URL: rubi_integrate-0.0.3-py3-none-any.whl
  • Upload date:
  • Size: 863.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for rubi_integrate-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 c5ccd9b5a9a127a5c5c3b306ea2c2e3557f18162635397d94927a6f052f0808e
MD5 930c41f060b5413b608c2c00a7edda9d
BLAKE2b-256 6ca72cf2f9562ac5606c22f807302e4a9fc26f553d31ecfd7d589d1860cad7d5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page