Skip to content

What's New in Version 1.0.19

April 28, 2026 8:46 AM UTC

Version 1.0.19 of the relationalai Python package is now available!

To upgrade, activate your virtual environment and run the following command:

Terminal window
pip install --upgrade relationalai
  • Graph.is_acyclic() is now much faster on many large graphs, especially large trees and grids. In benchmarks for the change, the runtime for the largest tested grid graph dropped from about 751 seconds to 47 seconds.
  • Removed unsupported std.re APIs. These APIs would raise errors if you tried to use them. All documentation pertaining to std.re has also been removed.

  • Fixed active profile selection so that the RAI_ACTIVE_PROFILE environment variable takes precedence over active_profile in raiconfig.yaml.

  • Fixed prescriptive models that combined filtered aggregates in one expression. Before, PyRel could leak one aggregate’s filter into another, so objectives and constraints could silently drop instead of being sent to the solver. For example:

    from relationalai.semantics import Float, Integer, Model
    from relationalai.semantics.reasoners.prescriptive import Problem
    model = Model("ScopedAggregates")
    X = model.Concept("X", identify_by={"i": Integer})
    X.v = model.Property(f"{X} has {Float:v}")
    model.define(X.new(i=1), X.new(i=2))
    problem = Problem(model, Float)
    problem.solve_for(X.v, name=["v", X.i], lower=0, upper=10)
    v = Float.ref()
    problem.satisfy(model.require(sum(v).where(X.v(v), X.i == 1) == 1.0))
    problem.satisfy(model.require(sum(v).where(X.v(v), X.i == 2) == 4.0))
    # The bug was triggered by combining two filtered aggregates in one expression.
    problem.minimize(sum(X.v).where(X.i == 1) + sum(X.v).where(X.i == 2))
    problem.satisfy(model.require(sum(X.v).where(X.i == 1) <= sum(X.v).where(X.i == 2)))
    problem.solve("highs")
    print(model.select(problem.num_min_objectives().alias("objective_count")).to_df())
    print(model.select(problem.num_constraints().alias("constraint_count")).to_df())
    print("objective_value:", problem.solve_info().objective_value)

    Output before the fix:

    objective_count
    0 0
    constraint_count
    0 2
    objective_value: 0.0

    Output after the fix:

    objective_count
    0 1
    constraint_count
    0 3
    objective_value: 5.0
  • Fixed model.data() calls created on the same source line, such as inside a loop. model.data() creates a temporary data source each time you call it. Before, if two calls happened on the same line, PyRel could mistake them for the same source. That could make later queries fail even when the input data was valid.

    For example:

    import pandas as pd
    from relationalai.semantics import Integer, Model, String
    m = Model("DataLoop")
    Item = m.Concept("Item", identify_by={"id": Integer, "name": String})
    batches = [
    pd.DataFrame([(1, "a"), (2, "b")], columns=["id", "name"]),
    pd.DataFrame([(3, "c"), (4, "d")], columns=["id", "name"]),
    ]
    for batch in batches:
    m.define(Item.new(m.data(batch).to_schema()))
    print(m.select(Item.id, Item.name).to_df())

    Output before the fix:

    RelQueryError: Query error

    Output after the fix:

    id name
    0 1 a
    1 2 b
    2 3 c
    3 4 d

    Now PyRel gives each same-line data source its own deterministic sequence number, so queries compile and run correctly.