Skip to contentSkip to frontmatterSkip to Backmatter

Notebook to Generate Tables

# Utility code

import sympy as sym
from sympy import latex, LambertW, CRootOf, N


# Function to check if an expression leads to a transcendental equation
def try_to_solve_internal(expression, variable):
    
    if isinstance(expression, list):
        expression = expression[0]
        timeout = True
    else:
        timeout = False


    equation = sym.Eq(expression, 0)

    if timeout:
        return (equation, None, "No, SymPy Limitation (Timeout)")

    # Try to solve the equation
    try:
        solutions = sym.solve(equation, variable)
        if solutions:
            if any(sol.has(LambertW) for sol in solutions):
                return (equation, solutions, "Yes, with LambertW")
            elif any(sol.has(CRootOf) for sol in solutions):
                return (equation, None, "No, SymPy uses CRootOf")
            else:
                return (equation, solutions, "Yes, Elementary")
        else:
            return (equation, solutions, "No, SymPy returns 0 Solutions")
    except Exception as e:
        return (equation, None, f"No, SymPy Limitation ({e.__class__.__name__})")


def try_to_solve(expression, variable):
    equation, solutions, category = try_to_solve_internal(
        expression, variable
    )
    # if solutions is None, try numeric solving
    if solutions is None:
        try:
            numeric = sym.nsolve(equation, 1)
        except Exception as _:
            try:
                numeric = sym.nsolve(equation, 0)
            except Exception as e:
                numeric = e.__class__.__name__
    elif len(solutions) == 0:
        numeric = sym.nsolve(equation, 0)
    else:
        numeric = N(solutions[0])

    return equation, solutions, category, numeric


def generate_markdown_table_row(equation, solutions, category, numeric):
    # Convert equation and solution to LaTeX format
    equation_md = f"${latex(equation)}$"
    if solutions is None:
        solution_md = ""
    elif len(solutions) == 0:
        solution_md = ""
    elif len(solutions) == 1:
        solution = solutions[0]
        solution_md = f"${latex(solution)}$"
    elif len(solutions) == 2:
        # Show both solutions if there are exactly 2
        solutions_latex = ", ".join([latex(sol) for sol in solutions])
        solution_md = f"$\\{{ {solutions_latex} \\}}$"  # Format as a set
    else:
        op_count = solutions[0].count_ops() + 1
        solution_md = r"\{{ " + latex(solutions[0]) + ", "
        for sol in solutions[1:]:
            op_count += sol.count_ops() + 1
            if op_count >= 30:
                break
            solution_md += f"{latex(sol)}, "
        solution_md += r"\dots \}}"
        solution_md = f"${solution_md}$"

    if numeric is None:
        numeric_md = "*n/a*"
    elif isinstance(numeric, str):
        numeric_md = numeric
    elif numeric.is_real:
        numeric_md = f"{numeric:.4f}"  # Format regular numbers
    else:
        real_part, imag_part = numeric.as_real_imag()
        numeric_md = f"{real_part:.4f} + {imag_part:.4f}i"  # Format complex numbers

    # Create the markdown table row
    markdown_row = f"| {equation_md} | {category} | {solution_md} | {numeric_md} |\n"

    return markdown_row


def generate_markdown_table(tuple_list):
    full_table = "| Equation | SymPy Closed-Form (CF) Solution? | CF Solution(s) | A Numeric |\n|----------|----------|----------|-------|\n"
    for equation, solution, category, numeric in tuple_list:
        table_row = generate_markdown_table_row(equation, solution, category, numeric)
        full_table += table_row
    return full_table


def split_on_predicate(lst, predicate):
    result = []
    current_group = []

    for item in lst:
        if predicate(item):
            # If we already have a group, add it to the result
            if current_group:
                result.append(current_group)
            # Start a new group with the item that matched the predicate
            current_group = [item]
        else:
            # If the item does not match the predicate, add it to the current group
            current_group.append(item)

    # Append the last group at the end
    if current_group:
        result.append(current_group)

    return result
from sympy import sin, cos, exp, log, sqrt, pi, Rational
from IPython.display import Markdown, display

# Define the symbolic variable x and numerator 1
x = sym.symbols("x")
one = sym.Integer(1)

# Transformed combinations into single expressions (add 1 to all multiply cases)
expressions = [
    "Kepler's Equation",
    x - 0.967 * sin(x) - Rational(20,76) * 2 * pi,
    "Polynomials",
    x**2 + (-x - 1),
    2 * x**3 + x + 1,
    x**2 * x**3 + 1,
    x**5 + (-x - 1),
    "Exp, Log and *x*",
    x * exp(x),
    x * exp(x) + Rational(1,10),
    x + exp(x),
    x * log(x) + 1,
    x + log(x),
    x**2 + log(x),
    "Exp and Log",
    log(x) * exp(x) + 1,
    log(x) + exp(x),
    "Trig and Trig Same Frequency",
    cos(x) + sin(x),
    cos(x) * sin(x) + 1,
    sin(x) + sin(x + 1),
    sin(x) * sin(x + 1) + 1,
    "Trig and Trig, Commensurate Frequencies",
    sin(3 * x) + sin(x),
    sin(3 * x) + (sin(x) + 1),
    sin(3 * x) * sin(x) + 1,
    [sin(3 * x) + sin(x + 1)],
    [sin(3 * x) * sin(x + 1) + 1],
    "Trig and Trig, Non-Commensurate Frequencies",
    sin(sqrt(3) * x) + sin(x),
    sin(sqrt(3) * x) + (sin(x) + 1),
    sin(sqrt(3) * x) + sin(x + 1),
    sin(sqrt(3) * x) * sin(x) + 1,
    sin(sqrt(3) * x) * sin(x + 1) + 1,
    "Trigonometric and *x*, Exp, Log",
    x + sin(x),
    x * sin(x) + 1,
    sin(x) + log(x),
    exp(x) + sin(x),
    x**3 + cos(x),
]


groups = split_on_predicate(expressions, lambda x: isinstance(x, str))

for group in groups:
    group_title = f"### {group[0]}"
    display(Markdown(group_title))
    results = [try_to_solve(expression, variable=x) for expression in group[1:]]
    table = generate_markdown_table(results)
    display(Markdown(table))