'''
Created on 16-Apr-2021

@author: bthakur
'''


import random
import inspect
import traceback


from xv.km.util import latex_fraction
import sympy as sp


from ...helper import BaseHelper
from xv.km.managers import BaseManager
from xv.km.kelements import KProblemTemplate

from xv.km.util import KmTemplateUtil

# _differential_equation_manager
# DifferentialEquationManager


class DifferentialEquationManager(BaseManager):
    '''

    Example:
        ke = DifferentialEquationManager()
        ke

        ke.printProblemTypes()

        ke.getRandomProblem()
        ke.getRandomProblem(problem_type = 0)
        ke.getRandomProblem(problem_type = 1)
        ...

        ke.printProblem()
        ke.printAnswer()
        ke.printSolution()


    '''


    def __new__(cls, *args, **kwargs):
        instance = super(DifferentialEquationManager, cls).__new__(cls, *args, **kwargs)
        for base_class in DifferentialEquationManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance

    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        pass


    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([

            ("_problem_ordinary_differential_equations", "Ordinary Differential Equations (ODEs) - Definitions & Notation"),
            ("_problem_partial_differential_equations", "Partial Differential Equations (PDEs) - Definitions & Notation"),

            # --- 1. Introduction & Basics ---
            #("_problem_differential_equation_overview_manager", "Differential Equation Overview"),
            ("_problem_types_of_differential_equations", "Types of Differential Equations + Examples"),
            ("_problem_solve_ode_yprime_equals_fx", "Solve ODE: y' = f(x)"),

            # --- 2. First-Order ODEs: Standard Analytical Methods ---
            ("_problem_solve_ode_first_order_separable", "Solve ODE: First-Order Separable"),
            ("_problem_solve_ode_first_order_linear_nonhomogeneous", "Solve ODE: First-Order Linear (Integrating Factor)"),
            ("_problem_solve_ode_first_order_exact", "Solve ODE: First-Order Exact"),
            ("_problem_solve_ode_first_order_exact_integrating_factor", "Solve ODE: Exact with Integrating Factor (Special Cases)"),

            # --- 3. First-Order ODEs: Substitution & Special Forms ---
            ("_problem_solve_ode_first_order_homogeneous_yprime_f_y_over_x", "Solve ODE: First-Order Homogeneous (y' = F(y/x))"),
            ("_problem_solve_ode_first_order_bernoulli", "Solve ODE: Bernoulli"),
            ("_problem_solve_ode_first_order_riccati", "Solve ODE: Riccati"),
            ("_problem_solve_ode_first_order_lagrange", "Solve ODE: Lagrange"),
            ("_problem_solve_ode_first_order_clairaut", "Solve ODE: Clairaut"),

            # --- 4. Second-Order Linear ODEs: Constant Coefficients ---
            ("_problem_solve_ode_second_order_linear_homogeneous_const_coeff", "Solve ODE: 2nd-Order Linear Homogeneous (Const Coeff)"),
            ("_problem_solve_ode_second_order_linear_nonhomogeneous_const_coeff", "Solve ODE: 2nd-Order Linear Nonhomogeneous (Const Coeff)"),

            # --- 5. Second-Order Linear ODEs: Variable Coefficients & General Methods ---
            ("_problem_solve_ode_second_order_cauchy_euler_homogeneous", "Solve ODE: Cauchy–Euler (Equidimensional) Homogeneous"),
            ("_problem_solve_ode_second_order_reduction_of_order", "Solve ODE: Reduction of Order"),
            ("_problem_solve_ode_second_order_variation_of_parameters", "Solve ODE: Variation of Parameters"),

            # --- 6. Higher-Order ODEs & Systems ---
            ("_problem_solve_ode_higher_order_constant_coeff", "Solve ODE: Higher-Order Linear (Const Coeff)"),
            ("_problem_solve_ode_linear_system_2x2_constant_coeff", "Solve ODE System: 2×2 Linear (Const Coeff)"),

            # --- 7. Advanced Analytical Tools (Laplace Transforms) ---
            ("_problem_solve_ode_laplace_transform_ivp", "Solve ODE: Laplace Transform (IVP)"),

            # --- 8. Qualitative & Physical Approaches ---
            ("_problem_solve_ode_autonomous_scalar_phase_line", "Solve ODE: Autonomous Scalar (Phase Line)"),
            ("_problem_solve_ode_conservative_newtonian_mechanics_form", "Solve ODE: Conservative / Newtonian Mechanics Form"),

            # --- 9. Numerical Methods ---
            ("_problem_solve_ode_numerical_rk4_ivp", "Numerical ODE: RK4 (IVP)"),

            # --- 10. Introduction to PDEs ---
            ("_problem_solve_first_order_nonlinear_pde", "Solve PDE: First-Order Nonlinear (Characteristics)"),
            ("_problem_solve_pde_separation_of_variables", "Solve PDE: Separation of Variables"),


            # These cover formal definitions and recognition tasks not handled by specific solvers:
            "_problem_solve_autonomous_differential_equations",

            # These cover PDE classification (B^2 - 4AC), which is currently missing entirely:
            ("_problem_linear_pde", "Linear vs. Nonlinear PDEs (Concepts)"),
            ("_problem_elliptic_pde", "Elliptic PDEs (Laplace/Poisson Concepts)"),
            ("_problem_parabolic_pde", "Parabolic PDEs (Heat Equation Concepts)"),
            ("_problem_hyperbolic_pde", "Hyperbolic PDEs (Wave Equation Concepts)"),


            "_problem_series_methods_for_odes",

            "_problem_special_function_odes",

            "_problem_ode_boundary_value_problems",

            "_problem_nonlinear_planar_systems",

            "_problem_linear_systems_nxn_matrix_exponential",

            "_problem_transform_methods",

            "_problem_second_order_pde_canonical_reduction",


            "_problem_fourier_series_pde_solutions",

            "_problem_pde_nonhomogeneous_boundary_conditions",

            "_problem_poisson_equation_inhomogeneous_elliptic",

            "_problem_greens_function_methods",

            "_problem_numerical_pde_finite_difference_methods",

            "_problem_numerical_ode_methods_advanced",

            "_problem_nonlinear_equation_solve_newton",

            "_problem_numerical_stability_and_stiffness_analysis",

            "_problem_nonclassical_differential_equations",

            "_problem_integral_equations",


            "_problem_integro_differential_equations",

            "_problem_differential_algebraic_equations_dae",



            ])
        pass


    def _problem_ordinary_differential_equations(self, *args, **kwargs):
        """
        Covers ODE Definitions & Notations.
        """
        from ...de_solvers import solve_ode_definitions

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # No arguments needed for this specific concept solver
        result = solve_ode_definitions()

        problemTemplate.add_problem(result['problem_s'].strip(), 'text/html')
        problemTemplate.add_solution(result['soln_s'].strip(), 'text/html')
        problemTemplate.add_answer(result['answer_s'].strip(), 'text/html')

        return problemTemplate

    def _problem_partial_differential_equations(self, *args, **kwargs):
        """
        Covers PDE Definitions & Notation.
        """
        from ...de_solvers import solve_pde_definitions

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # No arguments needed for this specific concept solver
        result = solve_pde_definitions()

        problemTemplate.add_problem(result['problem_s'].strip(), 'text/html')
        problemTemplate.add_solution(result['soln_s'].strip(), 'text/html')
        problemTemplate.add_answer(result['answer_s'].strip(), 'text/html')

        return problemTemplate

    # --- 1. Introduction & Basics ---

    def _problem_differential_equation_overview_manager(self, *args, **kwargs):
        """
        @generate_notebook: Default True

        doc_style: xv_doc
        """

        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}:
        creating notebook and getting problem from DifferentialEquationOverviewManager
        """
        self._log(sInfo.strip())


        #create notebook
        generate_notebook = kwargs.get('generate_notebook', True)

        if generate_notebook:
            saveToFolder = f"differential_equation"
            KmTemplateUtil.CREATE_MANAGER_NOTEBOOK("xv.math.calculus",
                                                   "DifferentialEquationOverviewManager",
                                                   saveToFolder = saveToFolder,
                                                   overwrite = False,
                                                   verbose = self.verbose)

        #get a problem
        from xv.math.calculus import DifferentialEquationOverviewManager
        kwargs['manager_class'] = DifferentialEquationOverviewManager
        kwargs['problem_type'] = None
        kwargs['generate_notebook'] = False
        problemTemplate = self._get_problem_from_a_manager(*args, **kwargs)
        return problemTemplate

        pass


    def _problem_types_of_differential_equations(self, *args, **kwargs):

        de_helper = self.base_helper.get_calculus_helper().get_differential_equation_helper()

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        de_examples_html = de_helper.get_diff_eq_type_examples_as_html()

        problem_s = f"""
        Give examples of different types of differential equations.
        """

        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        See the answer.
        """

        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        {de_examples_html}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')

        return problemTemplate

        pass


    def _problem_solve_ode_yprime_equals_fx(self, *args, **kwargs):

        from ...de_solvers import solve_ode_yprime_equals_fx

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        fxs = [
            # Polynomials
            ("x**2", {}),
            ("3*x**4 - 2*x + 7", {}),
            ("x**3 - 6*x + 1", {"x0": 0, "y0": 5}),
            ("(x-1)**5", {"x0": 1, "y0": 2}),

            # Exponentials
            ("exp(x)", {}),
            ("5*exp(2*x)", {}),
            ("exp(-x)", {"x0": 0, "y0": 3}),
            ("2*exp(x) + 1", {"x0": 0, "y0": 0}),

            # Trig
            ("sin(x)", {}),
            ("cos(x)", {}),
            ("sin(2*x) + cos(x)", {"x0": 0, "y0": 1}),
            ("sec(x)**2", {"x0": 0, "y0": 0}),  # derivative of tan(x)

            # Rational functions (including log forms)
            ("1/x", {"x0": 1, "y0": 0}),

            ("2/(1 + x**2)", {}),
            ("x/(1 + x**2)", {"x0": 1, "y0": 0}),
            ("(2*x)/(x**2 + 1)", {}),

            # Roots / powers
            ("sqrt(x)", {"x0": 0, "y0": 1}),
            ("1/sqrt(1 - x**2)", {"x0": 0, "y0": 0}),  # arcsin(x)
            ("x**(-1/2)", {"x0": 1, "y0": 2}),
            ("(x+1)**(3/2)", {"x0": 0, "y0": 0}),

            # Logs / mixed expressions
            ("log(x)", {"x0": 1, "y0": 0}),
            ("log(x) + x", {"x0": 1, "y0": 2}),
            ("exp(x) * sin(x)", {"x0": 0, "y0": 0}),
            ("x*exp(x)", {}),

            # Special-function-ish (may yield Si/Ci/Integral depending on SymPy)
            ("sin(x)/x", {"x0": 1, "y0": 0}),
        ]

        fx = random.choice(fxs)
        result = solve_ode_yprime_equals_fx(fx[0], **fx[1])

        problem_s = f"""
        {result['problem_s']}
        """

        problemTemplate.add_problem(problem_s.strip(), 'text/html')


        soln_s = f"""
        {result['soln_s']}
        """

        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')

        return problemTemplate

        pass


    # --- 2. First-Order ODEs: Standard Analytical Methods ---

    def _problem_solve_ode_first_order_separable(self, *args, **kwargs):

        from ...de_solvers import solve_ode_first_order_separable

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (g(x), h(y), {optional IC kwargs})
        # IC kwargs: x0, y0
        gh_list = [
            # Polynomial x, polynomial y
            ("x", "y", {}),
            ("x**2 + 1", "y**2", {}),
            ("2*x", "y*(1-y)", {}),
            ("x", "y**2 - 1", {"x0": 0, "y0": 2}),

            # Exponential / log mix
            ("exp(x)", "y", {}),
            ("1/(1+x**2)", "1+y**2", {}),
            ("1/x", "y", {"x0": 1, "y0": 2}),  # gives ln|y| = ln|x| + C

            # Trig
            ("cos(x)", "sin(y)", {}),
            ("sin(x)", "1/(1+y**2)", {}),

            # Rational y factors (still separable)
            ("x", "(y-1)/(y+1)", {"x0": 0, "y0": 2}),
            ("1", "y/(1+y**2)", {}),

            # More variety
            ("x*(1+x)", "y*(y-2)*(y+1)", {}),
            ("1 + x", "exp(-y)", {}),
        ]

        g_str, h_str, ic_kwargs = random.choice(gh_list)

        result = solve_ode_first_order_separable(g_str, h_str, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    def _problem_solve_ode_first_order_linear_nonhomogeneous(self, *args, **kwargs):

        import random
        from ...de_solvers import solve_ode_first_order_linear_nonhomogeneous

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        # Each item: ((P(x), Q(x)), {optional IC kwargs})
        # IC kwargs (when used): x0, y0
        pqs = [
            # -----------------------------
            # Simple constant P
            # -----------------------------
            (("1", "exp(x)"), {}),                              # y' + y = e^x
            (("2", "sin(x)"), {}),                              # y' + 2y = sin x
            (("-1", "cos(x)"), {}),                             # y' - y = cos x
            (("3", "1"), {}),                                   # y' + 3y = 1

            (("1", "exp(x)"), {"x0": 0, "y0": 2}),
            (("2", "sin(x)"), {"x0": 0, "y0": 0}),
            (("-1", "cos(x)"), {"x0": 0, "y0": 1}),
            (("3", "1"), {"x0": 0, "y0": 0}),

            # -----------------------------
            # P(x) = x, polynomials
            # -----------------------------
            (("x", "x**2"), {}),                                # y' + x y = x^2
            (("x", "1"), {}),                                   # y' + x y = 1
            (("2*x", "x"), {}),                                 # y' + 2x y = x
            (("x+1", "x"), {}),                                 # y' + (x+1)y = x

            (("x", "x**2"), {"x0": 0, "y0": 1}),
            (("x", "1"), {"x0": 1, "y0": 0}),
            (("2*x", "x"), {"x0": 0, "y0": 2}),
            (("x+1", "x"), {"x0": 0, "y0": 0}),

            # -----------------------------
            # Rational P(x)
            # -----------------------------
            (("1/x", "1"), {}),                                 # y' + (1/x)y = 1
            (("2/x", "x"), {}),                                 # y' + (2/x)y = x
            (("1/(1+x)", "1"), {}),                             # y' + y/(1+x) = 1
            (("1/(1+x**2)", "x"), {}),                          # y' + y/(1+x^2) = x

            (("1/x", "1"), {"x0": 1, "y0": 0}),
            (("2/x", "x"), {"x0": 1, "y0": 2}),
            (("1/(1+x)", "1"), {"x0": 0, "y0": 1}),
            (("1/(1+x**2)", "x"), {"x0": 0, "y0": 0}),

            # -----------------------------
            # Trig / exponential mixes
            # -----------------------------
            (("sin(x)", "cos(x)"), {}),                          # y' + sin(x) y = cos(x)
            (("cos(x)", "sin(x)"), {}),                          # y' + cos(x) y = sin(x)
            (("exp(x)", "x"), {}),                               # y' + e^x y = x
            (("x", "exp(-x)"), {}),                              # y' + x y = e^{-x}

            (("sin(x)", "cos(x)"), {"x0": 0, "y0": 1}),
            (("cos(x)", "sin(x)"), {"x0": 0, "y0": 0}),
            (("exp(x)", "x"), {"x0": 0, "y0": 2}),
            (("x", "exp(-x)"), {"x0": 0, "y0": 0}),

            # -----------------------------
            # Slightly harder but still common
            # -----------------------------
            (("2", "x*exp(x)"), {}),                             # y' + 2y = x e^x
            (("x**2", "x"), {}),                                 # y' + x^2 y = x
            (("1+x", "exp(x)"), {}),                             # y' + (1+x)y = e^x
            (("1/(x**2)", "1/x"), {}),                           # y' + y/x^2 = 1/x

            (("2", "x*exp(x)"), {"x0": 0, "y0": 1}),
            (("x**2", "x"), {"x0": 0, "y0": 0}),
            (("1+x", "exp(x)"), {"x0": 0, "y0": 2}),
            (("1/(x**2)", "1/x"), {"x0": 1, "y0": 0}),
        ]

        pq, ic_kwargs = random.choice(pqs)
        P, Q = pq

        result = solve_ode_first_order_linear_nonhomogeneous(P, Q, **ic_kwargs)

        problem_s = f"""
        {result['problem_s']}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        soln_s = f"""
        {result['soln_s']}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')

        return problemTemplate


    def _problem_solve_ode_first_order_exact(self, *args, **kwargs):

        from ...de_solvers import solve_ode_first_order_exact

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        # Each item: ((M(x,y), N(x,y)), {optional IC kwargs})
        # IC kwargs (when used): x0, y0  (means y(x0)=y0)
        mns = [
            # from ψ = x*y + x**2*y**3
            (("y + 2*x*y**3", "x + 3*x**2*y**2"), {}),
            (("y + 2*x*y**3", "x + 3*x**2*y**2"), {"x0": 0, "y0": 2}),

            # from ψ = x**2 + y**2
            (("2*x", "2*y"), {}),
            (("2*x", "2*y"), {"x0": 1, "y0": 3}),

            # from ψ = x*sin(y) + y
            (("sin(y)", "x*cos(y) + 1"), {}),
            (("sin(y)", "x*cos(y) + 1"), {"x0": 0, "y0": 0}),

            # from ψ = exp(x)*y**2
            (("exp(x)*y**2", "2*exp(x)*y"), {}),
            (("exp(x)*y**2", "2*exp(x)*y"), {"x0": 0, "y0": 1}),

            # from ψ = log(x**2 + y**2)
            (("2*x/(x**2+y**2)", "2*y/(x**2+y**2)"), {}),
            (("2*x/(x**2+y**2)", "2*y/(x**2+y**2)"), {"x0": 1, "y0": 1}),

            # from ψ = x**3/3 + x*y + y**3
            (("x**2 + y", "x + 3*y**2"), {}),
            (("x**2 + y", "x + 3*y**2"), {"x0": 0, "y0": 1}),

            # from ψ = x**2*y + sin(y)
            (("2*x*y", "x**2 + cos(y)"), {}),
            (("2*x*y", "x**2 + cos(y)"), {"x0": 1, "y0": 0}),

            # from ψ = x*y**2 + x**2*y
            (("y**2 + 2*x*y", "2*x*y + x**2"), {}),
            (("y**2 + 2*x*y", "2*x*y + x**2"), {"x0": 0, "y0": 2}),

            # from ψ = exp(x+y)
            (("exp(x+y)", "exp(x+y)"), {}),
            (("exp(x+y)", "exp(x+y)"), {"x0": 0, "y0": 0}),

            # from ψ = x**2*y**2
            (("2*x*y**2", "2*y*x**2"), {}),
            (("2*x*y**2", "2*y*x**2"), {"x0": 1, "y0": 1}),

            # from ψ = x/(1+y) + log(1+x)
            (("1/(1+y) + 1/(1+x)", "-x/(1+y)**2"), {}),
            (("1/(1+y) + 1/(1+x)", "-x/(1+y)**2"), {"x0": 0, "y0": 1}),
        ]

        (M, N), ic_kwargs = random.choice(mns)

        result = solve_ode_first_order_exact(M, N, **ic_kwargs)

        problem_s = f"""{result['problem_s']}"""
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        soln_s = f"""{result['soln_s']}"""
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""{result['answer_s']}"""
        problemTemplate.add_answer(answer_s.strip(), 'text/html')

        return problemTemplate


    def _problem_solve_ode_first_order_exact_integrating_factor(self, *args, **kwargs):

        from ...de_solvers import solve_ode_first_order_exact_integrating_factor

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (M(x,y), N(x,y), {optional IC kwargs})
        # IC kwargs: x0, y0
        mns = [
            # mu(y) example:
            # M=y, N=x*y  => g(y)=(Nx-My)/M=(y-1)/y -> mu(y)=exp(∫(y-1)/y dy)=e^y/y
            ("y", "x*y", {}),

            # mu(x) example:
            # M=(1+x)*y, N=x  => (My-Nx)/N = (1+x-1)/x = 1 -> mu(x)=e^x
            ("(1+x)*y", "x", {}),

            # mu(x) example:
            # M=x, N=x*y  => (My-Nx)/N = (0-y)/(x*y) = -1/x -> mu(x)=1/x
            ("x", "x*y", {"x0": 1, "y0": 2}),

            # mu(y) example:
            # M=y, N=x*y**2 => (Nx-My)/M=(y**2-1)/y -> mu(y)=exp(∫(y-1/y)dy)=exp(y^2/2)/y
            ("y", "x*y**2", {"x0": 0, "y0": 1}),
        ]

        M_str, N_str, ic_kwargs = random.choice(mns)

        result = solve_ode_first_order_exact_integrating_factor(M_str, N_str, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    # --- 3. First-Order ODEs: Substitution & Special Forms ---

    def _problem_solve_ode_first_order_homogeneous_yprime_f_y_over_x(self, *args, **kwargs):

        from ...de_solvers import solve_ode_first_order_homogeneous_yprime_f_y_over_x

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (F(v), {optional IC kwargs})
        # IC kwargs: x0, y0  (y(x0)=y0), must keep x0 != 0
        fvs = [
            ("v + 1", {}),
            ("v**2", {}),
            ("v + sin(v)", {}),
            ("v + exp(v)", {}),
            ("1 + v**2", {}),

            ("v + 1", {"x0": 1, "y0": 2}),
            ("v**2", {"x0": 2, "y0": 2}),
            ("1 + v**2", {"x0": 1, "y0": 0}),

            ("(v+1)/(v-1)", {}),
            ("(v-1)/(v+1)", {"x0": 1, "y0": 2}),
            ("(v+2)/(v+1)", {}),

            ("sin(v)", {}),
            ("cos(v) + v", {"x0": 1, "y0": 1}),
            ("tan(v) + v", {}),

            ("2*v", {}),                      # solvable: F(v)-v = v
            ("3*v", {"x0": 1, "y0": 3}),
            ("v/2", {}),

            ("v + 1/(1+v**2)", {}),
            ("v + 1/(1+v)", {"x0": 1, "y0": 1}),
            ("v + v/(1+v)", {}),

            # Allow input written as y/x directly (auto-detected)
            ("y/x + 1", {"x0": 1, "y0": 2}),
            ("(y/x)**2", {}),
            ("sin(y/x) + y/x", {"x0": 2, "y0": 1}),

            ("v + 2", {}),
            ("v**3", {"x0": 1, "y0": 1}),
        ]

        fv, ic_kwargs = random.choice(fvs)

        result = solve_ode_first_order_homogeneous_yprime_f_y_over_x(fv, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    def _problem_solve_ode_first_order_bernoulli(self, *args, **kwargs):
        from ...de_solvers import solve_ode_first_order_bernoulli

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: ((P(x), Q(x), n), {optional IC kwargs})
        # IC kwargs (when used): x0, y0
        p_q_n = [
            # --- n = 2 (common) ---
            (("1", "exp(x)", "2"), {}),
            (("2", "1", "2"), {}),
            (("x", "1", "2"), {}),
            (("1/x", "1", "2"), {"x0": 1, "y0": 1}),
            (("sin(x)", "1", "2"), {"x0": 0, "y0": 1}),

            # --- n = 3 ---
            (("1", "1", "3"), {}),
            (("2", "x", "3"), {}),
            (("x", "exp(x)", "3"), {"x0": 0, "y0": 1}),
            (("1/(1+x)", "1", "3"), {"x0": 0, "y0": 1}),
            (("cos(x)", "sin(x)", "3"), {"x0": 0, "y0": 1}),

            # --- n = -1 (also common) ---
            (("1", "1", "-1"), {}),
            (("2", "x", "-1"), {}),
            (("x", "1", "-1"), {"x0": 0, "y0": 1}),
            (("1/x", "1", "-1"), {"x0": 1, "y0": 2}),
            (("exp(x)", "1", "-1"), {"x0": 0, "y0": 1}),

            # --- n = 1/2 ---
            (("1", "1", "1/2"), {}),
            (("2", "x", "1/2"), {}),
            (("x", "1", "1/2"), {"x0": 0, "y0": 1}),
            (("1/(1+x)", "exp(x)", "1/2"), {"x0": 0, "y0": 1}),
            (("sin(x)", "cos(x)", "1/2"), {"x0": 0, "y0": 1}),

            # --- mixed “nice” ones ---
            (("3", "sin(x)", "2"), {"x0": 0, "y0": 1}),
            (("x+1", "x", "2"), {"x0": 0, "y0": 1}),
            (("2*x", "1", "3"), {"x0": 0, "y0": 1}),
            (("1/(x**2+1)", "x", "-1"), {"x0": 0, "y0": 1}),
            (("1", "x*exp(x)", "2"), {"x0": 0, "y0": 1}),
        ]

        (P, Q, n), ic_kwargs = random.choice(p_q_n)
        result = solve_ode_first_order_bernoulli(P, Q, n, **ic_kwargs)

        problem_s = f"""{result["problem_s"]}"""
        problemTemplate.add_problem(problem_s.strip(), "text/html")

        soln_s = f"""{result["soln_s"]}"""
        problemTemplate.add_solution(soln_s.strip(), "text/html")

        answer_s = f"""{result["answer_s"]}"""
        problemTemplate.add_answer(answer_s.strip(), "text/html")

        return problemTemplate


    def _problem_solve_ode_first_order_riccati(self, *args, **kwargs):

        from ...de_solvers import solve_ode_first_order_riccati

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (q0(x), q1(x), q2(x), y_particular(x), {optional IC kwargs})
        # IC kwargs: x0, y0
        items = [
            # y' = y^2  (q0=0,q1=0,q2=1), y_p=0
            ("0", "0", "1", "0", {}),

            # y' = -y + y^2, y_p=0
            ("0", "-1", "1", "0", {"x0": 0, "y0": 1}),

            # y' = y^2 + 2y - 3, y_p=1 (since -3 +2*1 +1^2 =0)
            ("-3", "2", "1", "1", {}),

            # y' = y - y^2 (logistic form but still Riccati), y_p=0
            ("0", "1", "-1", "0", {"x0": 0, "y0": sp.Rational(1, 2)}),

            # y' = (2/x) y + y^2, y_p=0  (keeps integrals/logs manageable)
            ("0", "2/x", "1", "0", {"x0": 1, "y0": 1}),

            # y' = x*y^2 + 2y - 3, pick y_p=1 and set q0(x)=-(2*1 + x*1)=-(2+x)
            ("-(2+x)", "2", "x", "1", {}),

        ]

        q0, q1, q2, yp, ic_kwargs = random.choice(items)

        result = solve_ode_first_order_riccati(q0, q1, q2, y_particular=yp, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    def _problem_solve_ode_first_order_lagrange(self, *args, **kwargs):

        from ...de_solvers import solve_ode_first_order_lagrange

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (phi(p), psi(p), {optional IC kwargs})
        # IC kwargs: x0, y0
        items = [
            # Simple polynomial choices
            ("p+1", "p**2", {}),                    # y = x(p+1) + p^2
            ("2*p", "p**2", {}),                    # y = x(2p) + p^2
            ("p+2", "p**3", {}),

            # Trig / exp (still symbolic OK)
            ("p+1", "sin(p)", {}),
            ("p+1", "exp(p)", {}),

            # With IC (best-effort): solve y0 = x0*phi(p0)+psi(p0) for p0
            ("p+1", "p**2", {"x0": 0, "y0": 1}),    # at x0=0 => p0^2=1
            ("2*p", "p**2", {"x0": 1, "y0": 3}),    # 3 = 2p0 + p0^2 => quadratic
        ]

        phi_str, psi_str, ic_kwargs = random.choice(items)

        result = solve_ode_first_order_lagrange(phi_str, psi_str, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    def _problem_solve_ode_first_order_clairaut(self, *args, **kwargs):

        from ...de_solvers import solve_ode_first_order_clairaut

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (f(p), {optional IC kwargs})
        # IC kwargs: x0, y0
        fps = [
            ("p**2", {}),
            ("p**2", {"x0": 0, "y0": 1}),          # y = Cx + C^2 -> at x0=0 gives C^2=1
            ("p**3", {}),
            ("sin(p)", {}),
            ("exp(p)", {}),
            ("log(1+p**2)", {}),
            ("p + p**2", {"x0": 1, "y0": 2}),
        ]

        f_str, ic_kwargs = random.choice(fps)

        result = solve_ode_first_order_clairaut(f_str, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    # --- 4. Second-Order Linear ODEs: Constant Coefficients ---

    def _problem_solve_ode_second_order_linear_homogeneous_const_coeff(self, *args, **kwargs):

        import random
        from ...de_solvers import solve_ode_second_order_linear_homogeneous_const_coeff

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        # Each item: ((a, b, c), {optional IC kwargs})
        # IC kwargs (when used): x0, y0, dy0
        abcs = [
            # -----------------------------
            # Distinct real roots (D > 0)
            # -----------------------------
            (("1", "-3", "2"), {}),                         # r=1,2
            (("2", "-5", "2"), {}),                         # r=2,1/2
            (("1", "0", "-4"), {}),                         # r=±2
            (("3", "-6", "0"), {}),                         # r=0,2
            (("1", "1", "-6"), {}),                         # r=2,-3

            (("1", "-3", "2"), {"x0": 0, "y0": 1, "dy0": 0}),
            (("1", "0", "-4"), {"x0": 0, "y0": 2, "dy0": 1}),
            (("2", "-5", "2"), {"x0": 1, "y0": 0, "dy0": 3}),

            # -----------------------------
            # Repeated root (D = 0)
            # -----------------------------
            (("1", "-2", "1"), {}),                         # (r-1)^2
            (("1", "4", "4"), {}),                          # (r+2)^2
            (("2", "4", "2"), {}),                          # 2(r+1)^2
            (("3", "-6", "3"), {}),                         # 3(r-1)^2

            (("1", "-2", "1"), {"x0": 0, "y0": 1, "dy0": 2}),
            (("1", "4", "4"), {"x0": 0, "y0": -1, "dy0": 0}),
            (("2", "4", "2"), {"x0": 1, "y0": 3, "dy0": -2}),

            # -----------------------------
            # Complex roots (D < 0)
            # -----------------------------
            (("1", "0", "1"), {}),                          # r=±i
            (("1", "2", "5"), {}),                          # r=-1±2i
            (("2", "0", "8"), {}),                          # r=±2i
            (("1", "-4", "13"), {}),                        # r=2±3i
            (("3", "6", "15"), {}),                         # r=-1±2i (scaled)

            (("1", "0", "1"), {"x0": 0, "y0": 2, "dy0": 0}),
            (("1", "2", "5"), {"x0": 0, "y0": 0, "dy0": 1}),
            (("2", "0", "8"), {"x0": 0, "y0": 1, "dy0": 2}),
            (("1", "-4", "13"), {"x0": 1, "y0": 3, "dy0": -1}),
            (("3", "6", "15"), {"x0": 0, "y0": -2, "dy0": 4}),

            # -----------------------------
            # Mixed / slightly harder coefficients (still constants)
            # -----------------------------
            (("1", "-1", "-2"), {}),                        # r=2,-1
            (("4", "4", "1"), {}),                          # D=0? b^2-4ac=16-16=0 repeated r=-1/2
            (("1", "6", "10"), {}),                         # D=36-40=-4 complex
            (("5", "-1", "-6"), {}),                        # D=1+120=121 distinct
        ]

        abc, ic_kwargs = random.choice(abcs)

        a, b, c = abc
        result = solve_ode_second_order_linear_homogeneous_const_coeff(a, b, c, **ic_kwargs)

        problem_s = f"""
        {result['problem_s']}
        """
        problemTemplate.add_problem(problem_s.strip(), 'text/html')

        soln_s = f"""
        {result['soln_s']}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')

        answer_s = f"""
        {result['answer_s']}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')

        return problemTemplate


    def _problem_solve_ode_second_order_linear_nonhomogeneous_const_coeff(self, *args, **kwargs):

        from ...de_solvers import solve_ode_second_order_linear_nonhomogeneous_const_coeff

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        # Each item: ((a,b,c,g(x)), {optional IC kwargs})
        # IC kwargs: x0, y0, dy0
        # Keep g(x) in forms that UC can usually handle nicely.
        abcg = [
            # Polynomial forcing
            (("1", "0", "1", "x"), {}),
            (("1", "0", "1", "x**2"), {"x0": 0, "y0": 0, "dy0": 1}),
            (("1", "2", "1", "3*x + 2"), {}),
            (("2", "-3", "1", "x**3"), {"x0": 0, "y0": 1, "dy0": 0}),

            # Exponential forcing
            (("1", "-3", "2", "exp(x)"), {}),
            (("1", "2", "5", "exp(2*x)"), {"x0": 0, "y0": 1, "dy0": 0}),
            (("2", "0", "8", "5*exp(x)"), {}),
            (("1", "0", "-4", "exp(-x)"), {"x0": 0, "y0": 0, "dy0": 2}),

            # Sin/Cos forcing
            (("1", "0", "1", "sin(x)"), {}),
            (("1", "0", "1", "cos(x)"), {"x0": 0, "y0": 1, "dy0": 0}),
            (("1", "2", "5", "sin(3*x)"), {}),
            (("1", "-4", "13", "cos(2*x)"), {"x0": 0, "y0": 0, "dy0": 1}),

            # Mixed: exp * poly
            (("1", "-2", "1", "x*exp(x)"), {}),
            (("1", "0", "1", "x**2*exp(2*x)"), {"x0": 0, "y0": 0, "dy0": 0}),

            # Mixed: exp * trig
            (("1", "0", "1", "exp(x)*sin(x)"), {}),
            (("1", "2", "5", "exp(-x)*cos(2*x)"), {"x0": 0, "y0": 1, "dy0": 1}),

            # Constant forcing
            (("1", "0", "1", "5"), {}),
            (("1", "4", "4", "3"), {"x0": 0, "y0": 0, "dy0": 0}),
            (("2", "4", "2", "7"), {}),

            # More variety
            (("1", "1", "-6", "x + exp(x)"), {}),
            (("3", "6", "15", "sin(x) + 2*cos(x)"), {}),
            (("1", "6", "10", "x**2 + 1"), {"x0": 0, "y0": 2, "dy0": 0}),
            (("5", "-1", "-6", "exp(2*x)"), {}),
        ]

        (a, b, c, g), ic_kwargs = random.choice(abcg)

        result = solve_ode_second_order_linear_nonhomogeneous_const_coeff(a, b, c, g, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), 'text/html')
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), 'text/html')
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), 'text/html')

        return problemTemplate


    # --- 5. Second-Order Linear ODEs: Variable Coefficients & General Methods ---

    def _problem_solve_ode_second_order_cauchy_euler_homogeneous(self, *args, **kwargs):

        from ...de_solvers import solve_ode_second_order_cauchy_euler_homogeneous

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        # Each item: ((A,B), {optional IC kwargs})
        # IC kwargs: x0, y0, dy0   (take x0>0 typically)
        abs_list = [
            # Distinct real m
            (("0", "-1"), {}),                   # m^2 - m -1 =0
            (("2", "-3"), {}),                   # m^2 + m -3 =0
            (("1", "-4"), {"x0": 1, "y0": 2, "dy0": 0}),
            (("3", "2"), {"x0": 1, "y0": 1, "dy0": 1}),

            # Repeated root (disc = 0): (A-1)^2 = 4B
            (("3", "1"), {}),                    # (A-1)^2=4 -> B=1
            (("5", "4"), {"x0": 1, "y0": 0, "dy0": 1}),  # (A-1)^2=16 -> B=4

            # Complex roots (disc < 0)
            (("1", "1"), {}),                    # m^2 + 1 = 0
            (("0", "1"), {"x0": 1, "y0": 1, "dy0": 0}),
            (("2", "5"), {}),                    # m^2 + (1)m + 5 => disc<0

            # More variety
            (("4", "10"), {"x0": 2, "y0": 1, "dy0": -1}),
            (("-1", "2"), {}),
            (("6", "-5"), {}),
        ]

        (A, B), ic_kwargs = random.choice(abs_list)

        result = solve_ode_second_order_cauchy_euler_homogeneous(A, B, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), 'text/html')
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), 'text/html')
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), 'text/html')

        return problemTemplate


    def _problem_solve_ode_second_order_reduction_of_order(self, *args, **kwargs):

        from ...de_solvers import solve_ode_second_order_reduction_of_order

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (P(x), Q(x), y1(x))
        items = [
            # y'' - y = 0, y1 = e^x
            ("0", "-1", "exp(x)"),

            # y'' + y = 0, y1 = cos(x)
            ("0", "1", "cos(x)"),

            # Cauchy-Euler in standard form: y'' - (1/x) y' + (1/x^2) y = 0, y1 = x
            ("-1/x", "1/x**2", "x"),

            # y'' + (2/x) y' = 0, y1 = 1
            ("2/x", "0", "1"),
        ]

        P_str, Q_str, y1_str = random.choice(items)

        result = solve_ode_second_order_reduction_of_order(P_str, Q_str, y1_str)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    def _problem_solve_ode_second_order_variation_of_parameters(self, *args, **kwargs):

        from ...de_solvers import solve_ode_second_order_variation_of_parameters

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (P(x), Q(x), R(x), y1(x), y2(x))
        items = [
            # y'' + y = sin(x), y1=cos, y2=sin
            ("0", "1", "sin(x)", "cos(x)", "sin(x)"),

            # y'' - y = exp(x), y1=exp(x), y2=exp(-x)
            ("0", "-1", "exp(x)", "exp(x)", "exp(-x)"),

            # y'' + 2y' + y = exp(-x), y1=exp(-x), y2=x*exp(-x)
            ("2", "1", "exp(-x)", "exp(-x)", "x*exp(-x)"),

            # y'' + (2/x) y' = 1/x, (x>0), y1=1, y2=1/x
            ("2/x", "0", "1/x", "1", "1/x"),
        ]

        P_str, Q_str, R_str, y1_str, y2_str = random.choice(items)

        result = solve_ode_second_order_variation_of_parameters(P_str, Q_str, R_str, y1_str, y2_str)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    # --- 6. Higher-Order ODEs & Systems ---

    def _problem_solve_ode_higher_order_constant_coeff(self, *args, **kwargs):

        from ...de_solvers import solve_ode_higher_order_constant_coeff

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: coeffs [a_n, ..., a_0] for a_n y^(n) + ... + a_0 y = 0
        items = [
            # y''' - y = 0  -> r^3 - 1 = 0 (one real + complex pair)
            ([1, 0, 0, -1], {}),

            # y'''' - 5y'' + 4y = 0 -> r^4 - 5r^2 + 4 = (r^2-1)(r^2-4)
            ([1, 0, -5, 0, 4], {}),

            # y'''' + 2y'' + y = 0 -> (r^2+1)^2  (complex roots ±i with multiplicity 2)
            ([1, 0, 2, 0, 1], {}),

            # y''' - 3y'' + 3y' - y = 0 -> (r-1)^3 (triple root)
            ([1, -3, 3, -1], {}),
        ]

        coeffs, _ = random.choice(items)

        result = solve_ode_higher_order_constant_coeff(coeffs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    def _problem_solve_ode_linear_system_2x2_constant_coeff(self, *args, **kwargs):

        from ...de_solvers import solve_ode_linear_system_2x2_constant_coeff

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (A, {optional IC kwargs})
        # IC kwargs: x0, u0, v0
        items = [
            # Pure rotation: eigenvalues ±i
            ([[0, -1],
              [1,  0]], {"x0": 0, "u0": 1, "v0": 0}),

            # Saddle / distinct real eigenvalues
            ([[2, 1],
              [0, 1]], {"x0": 0, "u0": 1, "v0": 1}),

            # Repeated eigenvalue (Jordan block)
            ([[1, 1],
              [0, 1]], {"x0": 0, "u0": 0, "v0": 1}),

            # Spiral (complex with positive real part): eigenvalues 1 ± 2i
            ([[1, -2],
              [2,  1]], {"x0": 0, "u0": 1, "v0": -1}),
        ]

        A, ic_kwargs = random.choice(items)

        result = solve_ode_linear_system_2x2_constant_coeff(A, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    # --- 7. Advanced Analytical Tools (Laplace Transforms) ---

    def _problem_solve_ode_laplace_transform_ivp(self, *args, **kwargs):

        from ...de_solvers import solve_ode_laplace_transform_ivp

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (coeffs [a_n..a_0], rhs g(x), ics dict, {})
        items = [
            # y' + 3y = e^{2x}, y(0)=1
            ([1, 3], "exp(2*x)", {0: 1}, {}),

            # y'' + y = sin(x), y(0)=0, y'(0)=1
            ([1, 0, 1], "sin(x)", {0: 0, 1: 1}, {}),

            # y'' + 2y' + y = 1, y(0)=0, y'(0)=0
            ([1, 2, 1], "1", {0: 0, 1: 0}, {}),

            # y'' - y = Heaviside(x-1), y(0)=0, y'(0)=0
            ([1, 0, -1], "Heaviside(x-1)", {0: 0, 1: 0}, {}),
        ]

        coeffs, rhs, ics, _ = random.choice(items)

        result = solve_ode_laplace_transform_ivp(coeffs, rhs, ics)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    # --- 8. Qualitative & Physical Approaches ---

    def _problem_solve_ode_autonomous_scalar_phase_line(self, *args, **kwargs):

        from ...de_solvers import solve_ode_autonomous_scalar_phase_line

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (f(y), {optional IC kwargs})
        # IC kwargs: t0, y0
        fys = [
            # Logistic-type / polynomial
            ("y*(1-y)", {}),
            ("y*(1-y)", {"t0": 0, "y0": 1/sp.Integer(2)}),
            ("y**2 - 1", {}),
            ("y**2 - 1", {"t0": 0, "y0": 2}),
            ("y - y**3", {}),
            ("y - y**3", {"t0": 0, "y0": sp.Rational(1, 2)}),

            # Trig
            ("sin(y)", {}),
            ("sin(y)", {"t0": 0, "y0": sp.pi/2}),
            ("sin(y) - y/10", {}),

            # Exponential / mixed
            ("1 - exp(-y)", {}),
            ("exp(y) - 1", {"t0": 0, "y0": 0}),
            ("y*exp(-y)", {}),
            ("y - exp(-y)", {}),

            # Rational
            ("y/(1+y**2)", {}),
            ("(y-1)/(y+1)", {"t0": 0, "y0": 2}),
            ("1/(1+y**2)", {}),

            # More variety
            ("y*(y-2)*(y+1)", {}),
            ("y*(y-2)*(y+1)", {"t0": 0, "y0": 3}),
            ("y**3 - y", {}),
            ("y**3 - y", {"t0": 0, "y0": 1}),
            ("(y-1)**2*(y+2)", {}),
        ]

        fy, ic_kwargs = random.choice(fys)

        result = solve_ode_autonomous_scalar_phase_line(fy, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    def _problem_solve_ode_conservative_newtonian_mechanics_form(self, *args, **kwargs):

        from ...de_solvers import solve_ode_conservative_newtonian_mechanics_form

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (f(y), {optional IC kwargs})
        # IC kwargs: x0, y0, dy0
        fys = [
            # canonical conservative examples
            ("-y", {}),
            ("-y**3", {}),
            ("-sin(y)", {}),

            # with ICs
            ("-y", {"x0": 0, "y0": 1, "dy0": 0}),
            ("-y**3", {"x0": 0, "y0": 1, "dy0": 1}),
            ("-sin(y)", {"x0": 0, "y0": 0, "dy0": 2}),

            # other smooth forces
            ("-exp(y)", {}),
            ("-2*y", {}),
            ("-3*y + y**3", {}),          # Duffing-type
            ("-y + sin(y)", {}),

            # rational / polynomial forces
            ("-y*(1+y)", {}),
            ("-(y**2+1)", {}),
            ("-(y**2-1)", {"x0": 0, "y0": 0, "dy0": 1}),
            ("-y/(1+y**2)", {}),

            # “nice” potentials (force = -V'(y))
            ("-(2*y)", {"x0": 0, "y0": 2, "dy0": 0}),     # V=y^2
            ("-(4*y**3)", {}),                            # V=y^4
            ("-cos(y)", {"x0": 0, "y0": 0, "dy0": 1}),    # V=sin(y)

            # more variety
            ("-(y**5)", {}),
            ("-(y**3 + y)", {"x0": 0, "y0": 1, "dy0": 0}),
            ("-(y + y**2 + y**3)", {}),
            ("-(sin(y) + y)", {}),
            ("-(sin(y) + y**3)", {"x0": 0, "y0": 1, "dy0": 0}),
            ("-(y/(1+y))", {}),
            ("-(y/(1+y**2))", {"x0": 0, "y0": 1, "dy0": 0}),
            ("-(1/(1+y**2))", {}),
            ("-(y*exp(y))", {}),
        ]

        fy, ic_kwargs = random.choice(fys)

        result = solve_ode_conservative_newtonian_mechanics_form(fy, **ic_kwargs)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    # --- 9. Numerical Methods ---

    def _problem_solve_ode_numerical_rk4_ivp(self, *args, **kwargs):

        from ...de_solvers import solve_ode_numerical_rk4_ivp

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item: (f(x,y), {params})
        items = [
            ("x + y", {"x0": 0, "y0": 1, "h": sp.Rational(1, 10), "n_steps": 5}),
            ("y - x**2 + 1", {"x0": 0, "y0": sp.Integer(1), "h": sp.Rational(1, 5), "n_steps": 5}),
            ("x*y", {"x0": 0, "y0": 1, "h": sp.Rational(1, 10), "n_steps": 4}),
            ("sin(x) + y", {"x0": 0, "y0": 0, "h": sp.Rational(1, 10), "n_steps": 5}),
            ("y*(1-y)", {"x0": 0, "y0": sp.Rational(1, 2), "h": sp.Rational(1, 10), "n_steps": 5}),
        ]

        f_xy, cfg = random.choice(items)

        result = solve_ode_numerical_rk4_ivp(f_xy, **cfg)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    # --- 10. Introduction to PDEs ---

    def _problem_solve_first_order_nonlinear_pde(self, *args, **kwargs):

        from ...de_solvers import solve_first_order_nonlinear_pde

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        # Each item: (m, c, alpha)
        # Keep them simple so the final u(x,t) stays clean.
        items = [
            (sp.Integer(1), sp.Integer(0), sp.Integer(0)),     # Burgers with u(x,0)=x
            (sp.Integer(2), sp.Integer(1), sp.Integer(0)),     # Burgers with u(x,0)=2x+1
            (sp.Integer(3), sp.Integer(-2), sp.Integer(0)),    # Burgers with u(x,0)=3x-2

            (sp.Integer(1), sp.Integer(2), sp.Integer(1)),     # shifted speed: u+1
            (sp.Integer(2), sp.Integer(-1), sp.Integer(2)),    # shifted speed: u+2
            (sp.Integer(3), sp.Integer(1), sp.Integer(-1)),    # shifted speed: u-1
        ]

        m, c, alpha = random.choice(items)

        result = solve_first_order_nonlinear_pde(
            m=m,
            c=c,
            alpha=alpha,
        )

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), 'text/html')
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), 'text/html')
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), 'text/html')

        return problemTemplate


    def _problem_solve_pde_separation_of_variables(self, *args, **kwargs):

        from ...de_solvers import solve_pde_separation_of_variables_1d_dirichlet

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')

        # We ONLY generate single-eigenmode ICs:
        #   u(x,0)=A sin(nπx/L)   (heat)
        #   u(x,0)=A sin(nπx/L),  u_t(x,0)=B sin(nπx/L)   (wave)
        #
        # This keeps output clean (no Fourier coefficient integrals).

        heat_items = [
            # (L, k, n, A)
            (sp.Integer(1), sp.Integer(1), 1, sp.Integer(2)),
            (sp.Integer(2), sp.Integer(1), 1, sp.Integer(3)),
            (sp.Integer(2), sp.Integer(3), 3, sp.Integer(5)),  # matches your screenshot style
            (sp.Integer(3), sp.Integer(2), 2, sp.Integer(4)),
            (sp.pi,         sp.Integer(1), 2, sp.Integer(1)),
            (sp.pi,         sp.Integer(2), 3, sp.Integer(2)),
        ]

        wave_items = [
            # (L, c, n, A, B)
            (sp.Integer(1), sp.Integer(1), 1, sp.Integer(1), sp.Integer(0)),
            (sp.Integer(2), sp.Integer(2), 1, sp.Integer(2), sp.Integer(0)),
            (sp.Integer(2), sp.Integer(3), 2, sp.Integer(2), sp.Integer(6)),
            (sp.Integer(3), sp.Integer(2), 2, sp.Integer(1), sp.Integer(3)),
            (sp.pi,         sp.Integer(2), 3, sp.Integer(1), sp.Integer(4)),
            (sp.pi,         sp.Integer(1), 2, sp.Integer(3), sp.Integer(0)),
        ]

        # Prefer heat slightly (common intro PDE)
        pde_kind = random.choice(["heat", "heat", "wave"])

        if pde_kind == "heat":
            L_value, k_value, mode_n, A_value = random.choice(heat_items)

            result = solve_pde_separation_of_variables_1d_dirichlet(
                pde_kind="heat",
                L_value=L_value,
                k_value=k_value,
                mode_n=mode_n,
                A_value=A_value,
            )

        else:
            L_value, c_value, mode_n, A_value, B_value = random.choice(wave_items)

            result = solve_pde_separation_of_variables_1d_dirichlet(
                pde_kind="wave",
                L_value=L_value,
                c_value=c_value,
                mode_n=mode_n,
                A_value=A_value,
                B_value=B_value,
            )

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    def _problem_solve_autonomous_differential_equations(self, *args, **kwargs):

        from ...de_solvers import solve_ode_autonomous_differential_equations

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item:
        #   (order, rhs_str, ic_kwargs)
        # For order=1: dy/dt = f(y)
        #   IC kwargs: t0, y0
        # For order=2: y'' = F(y)
        #   IC kwargs: t0, y0, v0 (where v0 = y'(t0))
        items = [
            # --- first-order autonomous (separable) ---
            (1, "y*(1-y)", {"t0": 0, "y0": sp.Rational(1, 2)}),  # logistic
            (1, "y**2 - 1", {"t0": 0, "y0": 2}),
            (1, "sin(y)", {"t0": 0, "y0": sp.pi/6}),
            (1, "y - y**3", {}),

            # --- second-order autonomous (order reduction / energy integral) ---
            (2, "-y", {"t0": 0, "y0": 1, "v0": 0}),             # SHO: y''=-y
            (2, "-y**3", {"t0": 0, "y0": 1, "v0": 0}),          # Duffing-like conservative
            (2, "-sin(y)", {"t0": 0, "y0": sp.pi/3, "v0": 0}),  # pendulum equation
        ]

        order, rhs_str, ic_kwargs = random.choice(items)

        result = solve_ode_autonomous_differential_equations(
            rhs_str,
            order=order,
            **ic_kwargs,
        )

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_linear_pde(self, *args, **kwargs):

        from ...de_solvers import solve_pde_linear

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Pick from a small set of classic linear PDEs (with known general/canonical solutions)
        items = [
            # First-order linear PDE: a u_x + b u_y = 0
            ("first_order_const_coeff", {"a": 1, "b": 1}),
            ("first_order_const_coeff", {"a": 2, "b": -3}),
            ("first_order_const_coeff", {"a": 0, "b": 5}),

            # Wave equation: u_tt - c^2 u_xx = 0
            ("wave_1d", {"c": 2}),
            ("wave_1d", {"c": 3}),

            # Heat equation (single sine mode IBVP): u_t = k u_xx
            ("heat_1d_sine_mode", {"k": 2, "n": 1, "L_": sp.pi}),
            ("heat_1d_sine_mode", {"k": 1, "n": 2, "L_": sp.pi}),
            ("heat_1d_sine_mode", {"k": 3, "n": 1, "L_": 2 * sp.pi}),
        ]

        kind, params = random.choice(items)

        result = solve_pde_linear(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_elliptic_pde(self, *args, **kwargs):

        from ...de_solvers import solve_pde_elliptic

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Elliptic PDEs (Laplace) — app-friendly canonical forms
        items = [
            # Laplace in rectangle: 3 sides zero, top is single sine mode
            ("laplace_rectangle_single_sine_top", {"L_": sp.pi, "H_": 1, "n": 1}),
            ("laplace_rectangle_single_sine_top", {"L_": sp.pi, "H_": 2, "n": 2}),
            ("laplace_rectangle_single_sine_top", {"L_": 2 * sp.pi, "H_": 1, "n": 1}),

            # Radial Laplace (polar symmetry)
            ("laplace_polar_radial", {}),
        ]

        kind, params = random.choice(items)

        result = solve_pde_elliptic(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_parabolic_pde(self, *args, **kwargs):

        from ...de_solvers import solve_pde_parabolic

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            # Single sine mode (closed form)
            ("heat_1d_sine_mode", {"k": 1, "n": 1, "L_": sp.pi}),
            ("heat_1d_sine_mode", {"k": 2, "n": 2, "L_": sp.pi}),
            ("heat_1d_sine_mode", {"k": 3, "n": 1, "L_": 2 * sp.pi}),

            # Nonzero fixed ends (steady + transient series)
            ("heat_1d_fixed_ends_steady_plus_series", {"k": 1, "L_": sp.pi, "U0": 1}),
            ("heat_1d_fixed_ends_steady_plus_series", {"k": 2, "L_": sp.pi, "U0": 2}),
        ]

        kind, params = random.choice(items)

        result = solve_pde_parabolic(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    def _problem_hyperbolic_pde(self, *args, **kwargs):

        from ...de_solvers import solve_pde_hyperbolic

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            # Wave equation (d'Alembert general form)
            ("wave_1d_dalembert", {"c": 2}),
            ("wave_1d_dalembert", {"c": 3}),

            # Transport / advection equation
            ("transport_1d", {"a": 1}),
            ("transport_1d", {"a": -2}),
        ]

        kind, params = random.choice(items)

        result = solve_pde_hyperbolic(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate




    def _problem_series_methods_for_odes(self, *args, **kwargs):

        from ...de_solvers import solve_ode_series_methods_power_frobenius

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Each item:
        #   (method, params)
        items = [
            # -------------------------
            # Power series (ordinary point)
            # y'' + P(x) y' + Q(x) y = R(x)
            # -------------------------
            ("power_series", {
                "x0": 0,
                "N": 8,
                "P_of_x": "0",
                "Q_of_x": "1",
                "R_of_x": "0",          # y'' + y = 0  -> sin/cos series
                "y0": 1,
                "yp0": 0,
            }),
            ("power_series", {
                "x0": 0,
                "N": 8,
                "P_of_x": "0",
                "Q_of_x": "x",
                "R_of_x": "0",          # y'' + x y = 0  (Airy-type series)
                "y0": 1,
                "yp0": 0,
            }),
            ("power_series", {
                "x0": 0,
                "N": 8,
                "P_of_x": "1",
                "Q_of_x": "x",
                "R_of_x": "0",          # y'' + y' + x y = 0
                "y0": 0,
                "yp0": 1,
            }),

            # -------------------------
            # Frobenius (regular singular point)
            # (x-x0)^2 y'' + (x-x0) P(x) y' + Q(x) y = 0
            # -------------------------
            ("frobenius", {
                "x0": 0,
                "N": 6,
                "P_reg_of_x": "1",
                "Q_reg_of_x": "-1",     # x^2 y'' + x y' - y = 0  -> r=±1
                "pick_root_index": 0,
                "a0_value": 1,
            }),
            ("frobenius", {
                "x0": 0,
                "N": 6,
                "P_reg_of_x": "1",
                "Q_reg_of_x": "-1",
                "pick_root_index": 1,
                "a0_value": 1,
            }),
            ("frobenius", {
                "x0": 0,
                "N": 6,
                "P_reg_of_x": "1",
                "Q_reg_of_x": "x**2",   # x^2 y'' + x y' + x^2 y = 0 (Bessel-like with ν=0 structure)
                "pick_root_index": 0,
                "a0_value": 1,
            }),
        ]

        method, params = random.choice(items)

        result = solve_ode_series_methods_power_frobenius(method=method, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate






    def _problem_special_function_odes(self, *args, **kwargs):

        from ...de_solvers import solve_ode_special_function_families

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Canonical family instances (parameters chosen to keep expressions readable)
        items = [
            # Bessel family
            ("bessel", {"nu": 0, "k": 1, "regular_at_0": True}),
            ("bessel", {"nu": 1, "k": 2, "regular_at_0": False}),
            ("modified_bessel", {"nu": 0, "k": 1, "regular_at_0": True}),
            ("modified_bessel", {"nu": 1, "k": 2, "regular_at_0": False}),

            # Legendre
            ("legendre", {"ell": 2}),
            ("legendre", {"ell": 3}),

            # Hermite
            ("hermite", {"n": 0}),
            ("hermite", {"n": 2}),

            # Laguerre
            ("laguerre", {"n": 2}),
            ("assoc_laguerre", {"n": 2, "alpha": 1}),

            # Airy
            ("airy", {}),
        ]

        kind, params = random.choice(items)

        result = solve_ode_special_function_families(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_ode_boundary_value_problems(self, *args, **kwargs):

        from ...de_solvers import solve_ode_boundary_value_problems

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Pick common BVP modes (kept simple; closed-form eigenpairs)
        items = [
            ("dd", {"L_": sp.pi, "n": 1}),
            ("dd", {"L_": sp.pi, "n": 2}),
            ("dd", {"L_": 2 * sp.pi, "n": 1}),

            ("nn", {"L_": sp.pi, "n": 0}),
            ("nn", {"L_": sp.pi, "n": 1}),
            ("nn", {"L_": 2 * sp.pi, "n": 2}),

            ("dn", {"L_": sp.pi, "n": 0}),
            ("dn", {"L_": sp.pi, "n": 1}),

            ("nd", {"L_": sp.pi, "n": 0}),
            ("nd", {"L_": sp.pi, "n": 2}),
        ]

        kind, params = random.choice(items)

        result = solve_ode_boundary_value_problems(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_nonlinear_planar_systems(self, *args, **kwargs):

        from ...de_solvers import solve_nonlinear_planar_systems_phase_plane

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # A few classic nonlinear planar systems for teaching:
        # 1) Predator–Prey (Lotka–Volterra): x' = ax - bxy, y' = -cy + dxy
        # 2) Van der Pol oscillator (as first-order system): x'=y, y' = mu(1-x^2)y - x
        # 3) Simple nonlinear saddle-node-ish: x'=mu - x^2, y'=-y  (has bifurcation in x dynamics)
        # 4) Normal form Hopf (polar-like in Cartesian): x' = mu x - y - x(x^2+y^2), y' = x + mu y - y(x^2+y^2)

        items = [
            # Lotka–Volterra (choose simple params)
            ("x - x*y", "-y + x*y", {}),

            # Van der Pol (mu parameter kept symbolic or numeric)
            ("y", "mu*(1-x**2)*y - x", {"locals_map": {"mu": 1}}),
            ("y", "mu*(1-x**2)*y - x", {"locals_map": {"mu": 2}}),

            # Hopf normal form (mu=1 or mu=-1)
            ("mu*x - y - x*(x**2+y**2)", "x + mu*y - y*(x**2+y**2)", {"locals_map": {"mu": 1}}),
            ("mu*x - y - x*(x**2+y**2)", "x + mu*y - y*(x**2+y**2)", {"locals_map": {"mu": -1}}),

            # Simple saddle-node in x with stable y
            ("mu - x**2", "-y", {"locals_map": {"mu": 1}}),
            ("mu - x**2", "-y", {"locals_map": {"mu": -1}}),
        ]

        f_str, g_str, extra = random.choice(items)

        result = solve_nonlinear_planar_systems_phase_plane(
            f_str,
            g_str,
            **extra,
        )

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate






    def _problem_linear_systems_nxn_matrix_exponential(self, *args, **kwargs):

        from ...de_solvers import solve_linear_systems_nxn_matrix_exponential

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Keep examples small (n=3) for readability, but the solver supports any n×n.
        items = [
            # -------------------------
            # Homogeneous, diagonal-ish
            # -------------------------
            (
                [[-1, 0, 0],
                 [0, -2, 0],
                 [0, 0, -3]],
                None,
                {"t0": 0, "x0": [1, 1, 1]},
            ),

            # -------------------------
            # Homogeneous, rotation+decay block (complex eigenpairs)
            # -------------------------
            (
                [[-1, -2, 0],
                 [ 2, -1, 0],
                 [ 0,  0, -3]],
                None,
                {"t0": 0, "x0": [1, 0, 1]},
            ),

            # -------------------------
            # Defective/Jordan-like 3×3 (upper triangular with repeated eigenvalue)
            # -------------------------
            (
                [[2, 1, 0],
                 [0, 2, 1],
                 [0, 0, 2]],
                None,
                {"t0": 0, "x0": [1, 0, 0]},
            ),

            # -------------------------
            # Nonhomogeneous with constant forcing
            # -------------------------
            (
                [[-1, 0, 0],
                 [0, -2, 0],
                 [0, 0, -3]],
                [1, 0, 0],
                {"t0": 0, "x0": [0, 0, 0]},
            ),

            # -------------------------
            # Nonhomogeneous with time forcing (often returned as integral form)
            # -------------------------
            (
                [[0, 1, 0],
                 [-1, 0, 0],
                 [0, 0, -1]],
                ["cos(t)", "sin(t)", "exp(-t)"],
                {"t0": 0, "x0": [0, 0, 0]},
            ),
        ]

        A_in, b_in, extra = random.choice(items)

        result = solve_linear_systems_nxn_matrix_exponential(
            A_in,
            b_in=b_in,
            **extra,
        )

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate




    def _problem_transform_methods(self, *args, **kwargs):

        from ...de_solvers import solve_transform_methods

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            # ODE as convolution engine
            ("ode_lti_convolution", {"a": 2, "b": 5}),
            ("ode_lti_impulse_shift", {"a": 1, "b": 4, "shift_c": 2}),
            ("ode_lti_step_shift", {"a": 3, "b": 2, "shift_c": 1}),

            # Whole-line PDE kernels
            ("heat_whole_line_kernel", {"k": 2}),
            ("wave_whole_line_dalembert", {"c": 3}),

            # Half-line heat (sine/cosine)
            ("heat_half_line_dirichlet", {"k": 1}),
            ("heat_half_line_neumann", {"k": 1}),
        ]

        kind, params = random.choice(items)

        result = solve_transform_methods(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_second_order_pde_canonical_reduction(self, *args, **kwargs):

        from ...de_solvers import solve_second_order_pde_canonical_reduction

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Examples include cross-term cases and classic types.
        # Each item: (A, B, C)
        items = [
            # Example from your prompt: u_xx + 4 u_xy + 3 u_yy = 0
            (1, 4, 3),

            # Elliptic with cross term: u_xx + 2 u_xy + 5 u_yy
            (1, 2, 5),

            # Parabolic: u_xx + 2 u_xy + u_yy (Δ=0)
            (1, 2, 1),

            # Already no cross term: u_xx - u_yy (hyperbolic)
            (1, 0, -1),

            # Pure elliptic: u_xx + u_yy
            (1, 0, 1),
        ]

        A, B, C = random.choice(items)

        result = solve_second_order_pde_canonical_reduction(A, B, C)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_fourier_series_pde_solutions(self, *args, **kwargs):

        from ...de_solvers import solve_fourier_series_pde_solutions_multi_mode

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # Provide a few "general" f(x) that produce nontrivial multi-mode expansions.
        # Keep them integrable by SymPy often; if not, integral form is still valid.
        items = [
            # Heat DD
            ("heat_dd", {"f_of_x": "x*(pi-x)", "L_": sp.pi, "k_": 1, "N_terms": 4}),
            ("heat_dd", {"f_of_x": "sin(x) + 2*sin(3*x)", "L_": sp.pi, "k_": 2, "N_terms": 4}),
            ("heat_dd", {"f_of_x": "Piecewise((x, x<pi/2),(pi-x, True))", "L_": sp.pi, "k_": 1, "N_terms": 4}),

            # Wave DD
            ("wave_dd", {"f_of_x": "x*(pi-x)", "g_of_x": "0", "L_": sp.pi, "c_": 1, "N_terms": 4}),
            ("wave_dd", {"f_of_x": "0", "g_of_x": "sin(2*x)", "L_": sp.pi, "c_": 2, "N_terms": 4}),
            ("wave_dd", {"f_of_x": "sin(x)", "g_of_x": "sin(3*x)", "L_": sp.pi, "c_": 1, "N_terms": 4}),

            # Laplace strip decay DD
            ("laplace_strip_decay_dd", {"f_of_x": "x*(pi-x)", "L_": sp.pi, "N_terms": 4}),
            ("laplace_strip_decay_dd", {"f_of_x": "sin(2*x)+sin(5*x)", "L_": sp.pi, "N_terms": 4}),
        ]

        kind, params = random.choice(items)

        result = solve_fourier_series_pde_solutions_multi_mode(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate


    def _problem_pde_nonhomogeneous_boundary_conditions(self, *args, **kwargs):

        from ...de_solvers import solve_pde_nonhomogeneous_boundary_conditions

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            # Heat with constant nonzero Dirichlet BCs
            ("heat_dirichlet_constant", {
                "L_": sp.pi,
                "k_": 1,
                "A_bc": 1,
                "B_bc": 2,
                "f_of_x": "sin(x) + x",
                "N_terms": 4,
            }),

            # Wave with constant nonzero Dirichlet BCs
            ("wave_dirichlet_constant", {
                "L_": sp.pi,
                "c_": 2,
                "A_bc": 0,
                "B_bc": 1,
                "f_of_x": "x*(pi-x)",
                "g_of_x": "sin(2*x)",
                "N_terms": 4,
            }),

            # Heat with time-dependent BCs (forced transient)
            ("heat_dirichlet_time_dependent", {
                "L_": sp.pi,
                "k_": 1,
                "A_bc": "sin(t)",
                "B_bc": "2*sin(t)",
                "f_of_x": "0",
                "N_terms": 4,
            }),
        ]

        kind, params = random.choice(items)

        result = solve_pde_nonhomogeneous_boundary_conditions(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate





    def _problem_poisson_equation_inhomogeneous_elliptic(self, *args, **kwargs):

        from ...de_solvers import solve_pde_poisson_inhomogeneous_elliptic

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            # Full double-sine Poisson (homogeneous Dirichlet)
            ("poisson_rectangle_dirichlet_homogeneous", {
                "L_": sp.pi,
                "H_": sp.pi,
                "f_of_xy": "sin(x)*sin(y) + x*(pi-x)*y*(pi-y)",
                "M_terms": 2,
                "N_terms": 2,
            }),

            # Boundary lifting template (nonzero boundary data)
            ("poisson_rectangle_with_boundary_lifting_template", {
                "L_": sp.pi,
                "H_": sp.pi,
                "f_of_xy": "sin(x)*sin(y)",
            }),

            # Neumann solvability rule
            ("poisson_neumann_solvability_rule", {}),

            # Green's function template
            ("poisson_greens_function_template", {}),
        ]

        kind, params = random.choice(items)

        result = solve_pde_poisson_inhomogeneous_elliptic(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_greens_function_methods(self, *args, **kwargs):

        from ...de_solvers import solve_greens_function_methods

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            # ODE BVP general template (user can later pass y1,y2)
            ("ode_bvp_dirichlet_template", {
                "a_": 0,
                "b_": 1,
                "p_of_x": 1,
                "q_of_x": 0,
                "f_of_x": "f(x)",
                # Optional explicit homogeneous solutions (uncomment if desired):
                # "y1_of_x": "x",
                # "y2_of_x": "1-x",
            }),

            # Classic kernel (fully explicit)
            ("ode_bvp_ypp_classic_kernel", {
                "L_": sp.pi,
                "f_of_x": "sin(x)",
            }),

            # Poisson fundamental solution template
            ("pde_poisson_fundamental_solution_template", {}),

            # Heat kernel template
            ("pde_heat_kernel_template", {
                "k_": 2,
            }),

            # Wave kernel template
            ("pde_wave_retarded_template", {
                "c_": 3,
            }),
        ]

        kind, params = random.choice(items)

        result = solve_greens_function_methods(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate




    def _problem_numerical_pde_finite_difference_methods(self, *args, **kwargs):

        from ...de_solvers import solve_numerical_pde_finite_difference_methods

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            # Heat FTCS with stability ratio shown
            ("heat_ftcs", {
                "L_": 1,
                "Nx_": 20,
                "dt_": sp.Rational(1, 1000),
                "k_": 1,
            }),

            # Heat BTCS (implicit)
            ("heat_btcs", {
                "L_": 1,
                "Nx_": 20,
                "dt_": sp.Rational(1, 200),
                "k_": 1,
            }),

            # Heat Crank–Nicolson
            ("heat_crank_nicolson", {
                "L_": 1,
                "Nx_": 20,
                "dt_": sp.Rational(1, 200),
                "k_": 1,
            }),

            # Wave centered + CFL
            ("wave_centered", {
                "L_": 1,
                "Nx_": 50,
                "dt_": sp.Rational(1, 100),
                "c_": 1,
            }),

            # Laplace 2D stencil
            ("laplace_2d_stencil_jacobi", {
                "L_": 1,
                "H_": 1,
                "Nx_": 20,
                "Ny_": 20,
            }),

            # Poisson 2D stencil with a source
            ("poisson_2d_stencil_jacobi", {
                "L_": 1,
                "H_": 1,
                "Nx_": 20,
                "Ny_": 20,
                "f_of_xy": "sin(pi*x)*sin(pi*y)",
            }),
        ]

        kind, params = random.choice(items)

        result = solve_numerical_pde_finite_difference_methods(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_numerical_ode_methods_advanced(self, *args, **kwargs):

        from ...de_solvers import solve_numerical_ode_methods_advanced

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            ("euler_forward", {}),
            ("heun_improved_euler", {}),
            ("midpoint_rk2", {}),
            ("rk4_reference", {}),
            ("rk45_adaptive_template", {"tol_": sp.Rational(1, 6), "p_control_": 4}),
            ("adams_bashforth_ab2", {}),
            ("predictor_corrector_ab2_am2", {}),
            ("backward_euler_implicit", {}),
            ("implicit_trapezoid", {}),
            ("bdf2_template", {}),
            ("stiffness_stability_test_lambda", {"lam_": -50}),
        ]

        kind, params = random.choice(items)

        result = solve_numerical_ode_methods_advanced(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate




    def _problem_nonlinear_equation_solve_newton(self, *args, **kwargs):

        from ...de_solvers import solve_implicit_nonlinear_solve_newton_engine

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            ("implicit_ode_backward_euler_scalar", {}),
            ("implicit_ode_backward_euler_vector", {}),
            ("implicit_ode_trapezoid_scalar", {}),
            ("implicit_pde_step_template", {}),
            ("newton_scalar_core", {}),
            ("newton_vector_core", {}),
            ("finite_difference_jacobian_template", {}),
            ("broyden_quasi_newton_template", {}),
            ("damped_newton_line_search_template", {}),
            ("stopping_criteria_template", {}),
        ]

        kind, params = random.choice(items)

        result = solve_implicit_nonlinear_solve_newton_engine(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate




    def _problem_numerical_stability_and_stiffness_analysis(self, *args, **kwargs):

        from ...de_solvers import solve_numerical_stability_and_stiffness_analysis

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            ("linear_test_equation_overview", {}),
            ("absolute_stability_definition", {}),
            ("forward_euler_stability_region", {"lam_": "-10", "h_": "0.05"}),
            ("backward_euler_a_stability", {}),
            ("trapezoidal_a_stability_not_l_stable", {}),
            ("rk4_stability_function", {}),
            ("stiffness_definition_and_ratio", {"kappa_": "1000"}),
            ("stiffness_detection_heuristics", {}),
            ("pde_cfl_rules", {}),
        ]

        kind, params = random.choice(items)

        result = solve_numerical_stability_and_stiffness_analysis(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate





    def _problem_nonclassical_differential_equations(self, *args, **kwargs):

        from ...de_solvers import solve_nonclassical_differential_equations

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            ("dde_method_of_steps", {"tau_": "1"}),
            ("dde_method_of_steps", {"tau_": "2"}),

            ("sde_euler_maruyama", {"dt_": "0.01"}),
            ("sde_euler_maruyama", {"dt_": "0.1"}),

            ("sde_milstein", {"dt_": "0.01"}),

            ("fractional_caputo_l1_scheme", {"alpha_": "0.5", "dt_": "0.01"}),
            ("fractional_caputo_l1_scheme", {"alpha_": "0.8", "dt_": "0.01"}),
        ]

        kind, params = random.choice(items)

        result = solve_nonclassical_differential_equations(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_integral_equations(self, *args, **kwargs):

        from ...de_solvers import solve_integral_equations

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        items = [
            ("volterra_second_kind_neumann_iteration", {"a_": 0, "b_": 1, "lam_": "lambda", "n_iter": 3}),
            ("fredholm_second_kind_neumann_iteration", {"a_": 0, "b_": 1, "lam_": "lambda", "n_iter": 3}),

            # Degenerate kernel example (N=2)
            ("fredholm_second_kind_degenerate_kernel", {
                "a_": 0, "b_": 1, "lam_": "lambda",
                "degenerate_a_list": ["x", "x**2"],
                "degenerate_b_list": ["t", "1+t"],
                "f_str": "f(x)",
                "kernel_str": "K(x,t)"
            }),

            ("volterra_convolution_laplace_template", {"conv_k_str": "k(t)"}),

            ("fredholm_quadrature_discretization_template", {"N_grid": 6}),
        ]

        kind, params = random.choice(items)

        result = solve_integral_equations(kind=kind, **params)

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate



    def _problem_integro_differential_equations(self, *args, **kwargs):

        import random
        from ...de_solvers import solve_integro_differential_equations

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        # We keep this generator "template-based" (like integral equations),
        # so we avoid hard symbolic solving and instead return canonical reduction steps.
        cases = [
            {
                "kind": "volterra_convert_to_integral_iteration",
                "params": {
                    "t0_": 0,
                    "y0_": 1,
                    "f_str": "f(t,y(t))",
                    "kernel_str": "K(t,s)",
                    "n_iter": 3,
                },
            },
            {
                "kind": "convolution_laplace_linear",
                "params": {
                    "t0_": 0,
                    "y0_": 0,
                    "a_param": 2,
                    "g_str": "g(t)",
                    "conv_k_str": "k(t)",
                },
            },
            {
                "kind": "degenerate_kernel_augmented_ode_system",
                "params": {
                    "t0_": 0,
                    "y0_": 0,
                    "a_param": 1,
                    "g_str": "g(t)",
                    "degenerate_a_list": ["a1(t)", "a2(t)"],
                    "degenerate_b_list": ["b1(s)", "b2(s)"],
                },
            },
            {
                "kind": "numerical_memory_stepping_template",
                "params": {
                    "t0_": 0,
                    "kernel_str": "K(t,s)",
                    "f_str": "f(t,y(t))",
                    "N_steps": 6,
                    "dt_symbol": r"\Delta t",
                },
            },
            {
                "kind": "fredholm_integro_differential_template",
                "params": {
                    "a_": 0,
                    "b_": 1,
                    "lam_": 2,  # avoid passing the raw string "lambda"
                    "kernel_str": "K(x,s)",
                    "f_str": "f(x)",
                },
            },
        ]

        pick = random.choice(cases)
        result = solve_integro_differential_equations(kind=pick["kind"], **pick["params"])

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate





    def _problem_differential_algebraic_equations_dae(self, *args, **kwargs):

        import random
        from ...de_solvers import solve_dae_differential_algebraic_equations

        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get("problem_type")

        cases = [
            {
                "kind": "semi_explicit_index1_backward_euler_newton_template",
                "params": {
                    "f_str": r"f(t,y,z)",
                    "g_str": r"g(t,y,z)",
                    "method_name": "Backward Euler (BDF1)",
                },
            },
            {
                "kind": "fully_implicit_bdf_template",
                "params": {
                    "F_str": r"F(t,y,\dot y)",
                    "bdf_order": 1,
                },
            },
            {
                "kind": "consistent_initial_conditions_template",
                "params": {},
            },
            {
                "kind": "index_discussion_and_flagging_template",
                "params": {},
            },
        ]

        pick = random.choice(cases)
        result = solve_dae_differential_algebraic_equations(kind=pick["kind"], **pick["params"])

        problemTemplate.add_problem(f"""{result['problem_s']}""".strip(), "text/html")
        problemTemplate.add_solution(f"""{result['soln_s']}""".strip(), "text/html")
        problemTemplate.add_answer(f"""{result['answer_s']}""".strip(), "text/html")

        return problemTemplate

