'''
Created on 19-Apr-2024

@author: bthakur
'''
import random
import inspect
import traceback
import re

from sympy import (symbols, Symbol, limit, latex, Function,
                   Limit, oo, Derivative, simplify,
                   Eq,
                   Derivative, Integral,
                   exp, log, expand_log,
                   Mul, Add, Pow, UnevaluatedExpr,
                   Sum, binomial, factorial, Rational, 
                   UnevaluatedExpr as ueval
                   )

from sympy import (sin, cos, tan, cot, sec, csc,
                   asin, acos, atan, acot, asec, acsc,
                   sinh, cosh, tanh, coth, sech, csch,
                   asinh, acosh, atanh, acoth, asech, acsch,
                   )

import sympy as sp        

from xv.km.util import latex_fraction
from xv.util import UtilClass

# _std_integration_by_parts_helper
# StdIntegrationByPartsHelper

from ._std_integration_base_helper import StdIntegrationBaseHelper

class StdIntegrationByPartsHelper(StdIntegrationBaseHelper):
    '''
    integration of standard functions by reversing the result of differntiation
    '''    

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        
        pass

    
    def get_standrad_integration_by_parts(self, index = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if index is None:
            random_choices = list(range(1, 12))
    
            identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}.random_choices"
            random_choice = self.get_no_repeat_random_choice(identifier, random_choices)
        else:
            random_choice = index
            
        
        
        x = self.x
        
        if random_choice == 1:
            return self._get_log_by_parts(x = x)
        
        if random_choice == 2:
            return self._get_x_cube_log_x_by_parts(x = x)
        
        if random_choice == 3:
            return self._get_sin_x_log_cos_x_by_parts(x = x)
        
        if random_choice == 4:
            return self._get_asin_x_by_parts(x = x)
        
        if random_choice == 5:
            return self._get_x3_4_minus_x2_by_parts(x = x)
        
        if random_choice == 6:
            return self._get_x_cos_x_by_parts(x = x)
        
        if random_choice == 7:
            return self._get_x2_sin_x_by_parts(x = x)
        
        if random_choice == 8:
            return self._get_exp_x_cos_x_by_parts(x = x)
        
        if random_choice == 9:
            return self._get_1_by_x_sqr_log_x_by_parts(x = x)
        
        if random_choice == 10:
            return self._get_4_minus_x_sqr_x_sqr_by_parts(x = x)
        
        if random_choice == 11:
            return self._get_x_sin_x_cos_x_by_parts(x = x)
        
        pass

    
    def get_standrad_integration_by_parts_in_chain(self, index = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
         
        if index is None:
            random_choices = list(range(1, 7))
    
            identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}.random_choices"
            random_choice = self.get_no_repeat_random_choice(identifier, random_choices)
        else:
            random_choice = index
            
        x = self.x
        
        
        if random_choice == 1:
            return self._get_x_sqr_sin_x_by_parts_in_chain(x = x)
        
        if random_choice == 2:
            return self._get_exp_x_cos_x_by_parts_in_chain(x = x)
        
        if random_choice == 3:
            return self._get_1_by_x_log_x_pow_minus_3_get_by_parts_in_chain(x = x)
        
        if random_choice == 4:
            return self._get_sin_2_x_cos_x_x_by_parts_in_chain(x = x)
        
        if random_choice == 5:
            return self._get_exp_2_x_sin_x_by_parts_in_chain(x = x)

        if random_choice == 6:
            return self._get_log_x_sqr_by_parts_in_chain(x = x)
        
        
        pass

    
    def _get_std_solution_by_parts(self, f1, f2, x):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        
        I = Symbol('I')
        
        C = self.C
        f = f1 * f2

        Df1 = Derivative(f1, x)
        Df1_doit = Df1.doit()

        Df2 = Derivative(f2, x)
        Df2_doit = Df2.doit()
        

        Intgl_f1 = Integral(f1, x)
        Intgl_f1_doit = Intgl_f1.doit()

        Intgl_f2 = Integral(f2, x)
        Intgl_f2_doit = Intgl_f2.doit()
        
        Intgl_f = Integral(f, x)
        Intgl_f_doit = Intgl_f.doit()
        
        u = Symbol('u')
        v = Symbol('v')
        
        Du = Derivative(u, x) 
        Dv = Derivative(v, x) 
        Duv = Derivative(u * v, x) 
        
        Intgl_v_Du = Integral(Symbol(f"{latex(v)} \\cdot {latex(Du)}"), x)
        Intgl_u_Dv = Integral(Symbol(f"{latex(u)} \\cdot {latex(Dv)}"), x)
        
        Intgl_f2_Df1 = Integral(Intgl_f2_doit * Df1_doit, x)
        Intgl_f1_Df2 = Integral(Intgl_f1_doit * Df2_doit, x)
        
        Intgl_f2_f1_expanded = Integral(
            Symbol(f"\\left( {latex(f2)} \\right) \\cdot \\left( {latex(f1)} \\right)"), 
            x)
        Intgl_f2_f1 = Integral(f2 * f1, x)
        
        Intgl_f1_Df2_doit = Intgl_f1_Df2.doit()
        
        
        prob_s = f"""
        Find integral of $\\displaystyle {latex(f)}
        $ with respect to $\\displaystyle {latex(x)}$.
        """
        
        soln_stmts = []

        soln_stmt = f"""
        We have to compute 
        
        $\\displaystyle {latex(I)} = {latex(Intgl_f)} 
        \\qquad \\cdots \\text{{ (i) }}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        integration_by_parts_rule = f"""
        {latex(Intgl_v_Du)} 
        = {latex(u * v)}
        - {latex(Intgl_u_Dv)}
        """
        
        integration_by_parts_stmt = f"""
        We know:
        
        $\\displaystyle
        \\begin{{aligned}}
        
        & {latex(Duv)} 
        = {latex(v)} \\cdot {latex(Du)}  
        + {latex(v)} \\cdot {latex(Dv)}  
        \\\\ \\\\
        
        & \\text {{Integrate both sides with respect to }} {latex(x)}:
        \\\\ \\\\
        
        & \\implies  
        {latex(u * v)}
        = {latex(Intgl_v_Du)} 
        + {latex(Intgl_u_Dv)}
        \\\\ \\\\

        & \\implies 
        {integration_by_parts_rule}
        \\qquad \\cdots \\text{{ (ii) }}

        \\end{{aligned}}
        $
        """
        soln_stmts.append(integration_by_parts_stmt.strip())


        uv_to_find_stmt = f"""
        Let us compare (i) and LHS of (ii). We have to find $u$ and  $v$ functions such that 
        
        $\\displaystyle 
        {latex(Intgl_v_Du)}  = {latex(Intgl_f)} 
        $ 
        
        and it is easy to calculate 
        
        $\\displaystyle 
        {latex(Intgl_u_Dv)}$.
        """
        soln_stmts.append(uv_to_find_stmt.strip())
        


        option1_stmt = f"""
        <h4>Option 1:</h4>
        
        $\\displaystyle
        \\begin{{aligned}}
        
        \\text {{Let }} 
        & 
            \\left \\{{
            \\begin{{array}} {{ll}}
            & {latex(v)} = {latex(f1)} \\\\
            & {latex(Du)} = {latex(f2)}
            \\end{{array}}
            \\right .
            \\qquad \\cdots \\text{{ (1A) }}
        \\\\ \\\\
        
        \\implies \\;
        & 
            \\left \\{{
            \\begin{{array}} {{ll}}
            & {latex(Dv)} = {latex(Df1_doit)} \\\\
            & {latex(u)} = {latex(Intgl_f2_doit)}
            \\end{{array}}
            \\right .
            \\qquad \\cdots \\text{{ (1B) }}
        \\\\ \\\\
        
        \\implies \\;
        & {latex(u)} {latex(Dv)} 
        = \\left( {latex(Intgl_f2_doit)} \\right) \\cdot \\left( {latex(Df1_doit)} \\right)
        \\\\ \\\\ 
        
        \\implies \\;
        & {latex(Intgl_u_Dv)} 
        = {latex(Intgl_f2_Df1)} 
        \\qquad \\cdots \\text{{ (1C) }}
        
        \\end{{aligned}}
        $
        
        """
        soln_stmts.append(option1_stmt.strip())
        

        option2_stmt = f"""
        <h4>Option 2:</h4>
        
        $\\displaystyle
        \\begin{{aligned}}
        
        \\text {{Let }} 
        & 
            \\left \\{{
            \\begin{{array}} {{ll}}
            & {latex(v)} = {latex(f2)} \\\\
            & {latex(Du)} = {latex(f1)}
            \\end{{array}}
            \\right .
            \\qquad \\cdots \\text{{ (2A) }}
        \\\\ \\\\
        
        \\implies \\;
        & 
            \\left \\{{
            \\begin{{array}} {{ll}}
            & {latex(Dv)} = {latex(Df2_doit)} \\\\
            & {latex(u)} = {latex(Intgl_f1_doit)}
            \\end{{array}}
            \\right .
            \\qquad \\cdots \\text{{ (2B) }}
        \\\\ \\\\
        
        \\implies \\;
        & {latex(u)} {latex(Dv)} 
        = \\left( {latex(Intgl_f1_doit)} \\right) \\cdot \\left( {latex(Df2_doit)} \\right)
        \\\\ \\\\ 
        
        \\implies \\;
        & {latex(Intgl_u_Dv)} 
        = {latex(Intgl_f1_Df2)} 
        \\qquad \\cdots \\text{{ (2C) }}
        
        \\end{{aligned}}
        $
        
        """
        soln_stmts.append(option2_stmt.strip())

        
        option_choice_basis_stmt = f"""
        As we note, it is easier to compute (2C) than (1C).
        So, we will use option 2 for our computation further.
        """
        soln_stmts.append(option_choice_basis_stmt.strip())

        option_choice_stmt = f"""
        $\\displaystyle
        \\begin{{aligned}}
        
        & {latex(u)} = {latex(Intgl_f1_doit)} 
        \\\\ \\\\

        & {latex(v)} = {latex(f2)} 
        \\\\ \\\\

        & {latex(Intgl_v_Du)} 
        = {latex(Intgl_f2_f1_expanded)} 
        = {latex(Intgl_f2_f1)} 
        \\\\ \\\\
    

        & {latex(Intgl_u_Dv)} 
        = {latex(Intgl_f1_Df2)} 
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(option_choice_stmt.strip())


                

        option_params_stmt = f"""
        Using (ii), 
        
        $\\displaystyle
        \\begin{{aligned}}
        
        & {latex(Intgl_v_Du)} 
        = {latex(u * v)}
        - {latex(Intgl_u_Dv)}
        \\\\ \\\\
        
        & \\implies  
        {latex(Intgl_f2_f1)} 
        = \\left( {latex(Intgl_f1_doit)} \\right) \\cdot \\left( {latex(f2)} \\right) 
        - {latex(Intgl_f1_Df2)} 
        \\\\ \\\\

        & \\implies  
        {latex(Intgl_f2_f1)} 
        = \\left( {latex(Intgl_f1_doit * f2)} \\right) 
        - \\left( {latex(Intgl_f1_Df2_doit)} \\right) 

        \\end{{aligned}}
        $
        """
        soln_stmts.append(option_params_stmt.strip())


        answer_stmt = f"""
        $\\displaystyle
        \\begin{{aligned}}
        
        \\implies  
        {latex(Intgl_f)} 
        & = {latex(Intgl_f_doit)}
        \\\\ \\\\
        
        & = {latex(Intgl_f_doit)} + {latex(C)}
        \\qquad \\text{{ added integration constant }}
        
        \\end{{aligned}}
        $
        
        """
        soln_stmts.append(answer_stmt.strip())
        

        answer_s = f"""
        $\\displaystyle {latex(Intgl_f_doit)} + {latex(C)}$
        """

        soln_s = "\n\n".join(soln_stmts)
        

        result = {
            'Intgl_f': Intgl_f,
            'Intgl_f_doit': Intgl_f_doit,
            
            'u_val': Intgl_f1_doit,
            'v_val': f2,
            'Intgl_vdu_val': Intgl_f2_f1,
            'Intgl_udv_val': Intgl_f1_Df2,


            'integration_by_parts_rule': integration_by_parts_rule.strip(),
            'integration_by_parts_stmt': integration_by_parts_stmt.strip(),
            'uv_to_find_stmt': uv_to_find_stmt.strip(),
            'option1_stmt': option1_stmt.strip(),
            'option2_stmt': option2_stmt.strip(),
            'option_choice_stmt': option_choice_stmt.strip(),
            'option_params_stmt': option_params_stmt.strip(),
            'answer_stmt': answer_stmt.strip(),
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
        
        

    def _get_log_by_parts(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1 = sp.simplify(1)
        f2 = log(x)
        
        return self._get_std_solution_by_parts(f1, f2, x)
        pass
    
    def _get_x_cube_log_x_by_parts(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1 = x**3 
        f2 = log(x)
        
        return self._get_std_solution_by_parts(f1, f2, x)
        pass

    def _get_sin_x_log_cos_x_by_parts(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1 = sin(x)
        f2 = log(cos(x))
        
        return self._get_std_solution_by_parts(f1, f2, x)
        pass

    def _get_asin_x_by_parts(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1 = 1
        f2 = asin(x)
        
        return self._get_std_solution_by_parts(f1, f2, x)
        pass

    def _get_x3_4_minus_x2_by_parts(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1 = x ** 3
        f2 = 4 - x ** 2
        
        return self._get_std_solution_by_parts(f1, f2, x)
        pass
        
    def _get_x_cos_x_by_parts(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1 = cos(x)
        f2 = x
        
        return self._get_std_solution_by_parts(f1, f2, x)
        pass
    
    def _get_3_x_exp_minus_x_by_parts(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1 = sp.exp(-x)
        f2 = 3 * x
        
        return self._get_std_solution_by_parts(f1, f2, x)
        pass
    
    def _get_1_by_x_sqr_log_x_by_parts(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1 = 1 / x ** 2
        f2 = log(x)
        
        return self._get_std_solution_by_parts(f1, f2, x)
        pass
    
    
    def _get_4_minus_x_sqr_x_sqr_by_parts(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1 = sp.sqrt(4 - x ** 2)
        f2 = x ** 2
        
        return self._get_std_solution_by_parts(f1, f2, x)
        pass
    
    
    
    def _get_x_sin_x_cos_x_by_parts(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1_org = sin(x) * cos(x)
        f2_org = x

        f1 = sin(2 * x)
        f2 = x / 2

        soln_stmts = []

        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        {latex(f1_org * f2_org)} 
        & = \\frac {{ {latex(f2_org)} \\cdot 2 {latex(f1_org)} }} {{ 2 }} 
        \\\\ \\\\
        
        & = {latex(f1 * f2)} 
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())


        
        result =  self._get_std_solution_by_parts(f1, f2, x)
        
        soln_stmt = f"""
        Therefore, 
        {result['soln_s']}
        """
        soln_stmts.append(soln_stmt.strip())

        
        soln_s = "\n\n".join(soln_stmts)
        
        result['soln_s'] = soln_s.strip()
        
        return result
        pass
    
    


    def _get_x_sqr_sin_x_by_parts_in_chain(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x
            
        C = self.C

        u = Symbol('u')
        v = Symbol('v')

        f1 = sin(x)
        f2 = x ** 2
        
        I = Symbol('I')
        I1 = Symbol('I_1')
            
        result_step1 = self._get_std_solution_by_parts(f1, f2, x)
        
        soln_stmts = []

        soln_stmt = f"""
        We have to compute 

        $\\displaystyle 
        {latex(I)} = {latex(result_step1['Intgl_f'])} 
        \\qquad \\cdots \\text{{ (i) }}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        u_val_step1 = result_step1['u_val']
        v_val_step1 = result_step1['v_val']

        I_val = result_step1['Intgl_f_doit']

        soln_stmt = f"""
        Let:
        
        $\\displaystyle 
        {latex(u)} = {latex(u_val_step1)}
        $
        
        $\\displaystyle 
        {latex(v)} = {latex(v_val_step1)}
        $
        
        Now, 
        $\\displaystyle
        {result_step1['integration_by_parts_rule']}
        $
        
        $\\displaystyle
        \\implies
        {latex(result_step1['Intgl_vdu_val'])}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(result_step1['Intgl_udv_val'])}
        $
        
        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(I1)}
        \\qquad \\text {{ assumed }}
        {latex(I1)} = {latex(result_step1['Intgl_udv_val'])}  
        \\qquad \\cdots \\text{{ (ii) }}
        $
        """
        soln_stmts.append(soln_stmt.strip())


        f1_step2 = u_val_step1
        f2_step2 = Derivative(v_val_step1, x).doit()



        result_step2 = self._get_std_solution_by_parts(f1_step2, f2_step2, x)

        u_val_step2 = result_step2['u_val']
        v_val_step2 = result_step2['v_val']

        I1_val = result_step2['Intgl_f_doit']

        soln_stmt = f"""
        To solve ${latex(I1)}$, let:
        
        $\\displaystyle 
        {latex(u)} = {latex(u_val_step2)}
        $
        
        $\\displaystyle 
        {latex(v)} = {latex(v_val_step2)}
        $
        
        Now, 
        $\\displaystyle
        {result_step2['integration_by_parts_rule']}
        $
        
        $\\displaystyle
        \\implies
        {latex(result_step2['Intgl_vdu_val'])}
        = {latex(u_val_step2 * v_val_step2)}
        - {latex(result_step2['Intgl_udv_val'])}
        $
        
        $\\displaystyle
        \\implies
        {latex(I1)}
        = {latex(I1_val)}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        
        
        soln_stmt = f"""
        From (ii):
        
        $\\displaystyle
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(I1)}
        $

        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - \\left(
        {latex(I1_val)}
        \\right)
        $

        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(I_val)} + {latex(C)}
        \\qquad \\text{{ added integration constant }}
        $

        """
        soln_stmts.append(soln_stmt.strip())

        soln_s = "\n\n".join(soln_stmts)
        
        
        result = {
        'prob_s': result_step1['prob_s'],
        'soln_s': soln_s.strip(),
        'answer_s': result_step1['answer_s'],
        }
        
        return result
        pass
    
    
    def _get_exp_x_cos_x_by_parts_in_chain(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x
            
        C = self.C

        u = Symbol('u')
        v = Symbol('v')

        f1 = sp.exp(x)
        f2 = cos(x)
        
        I = Symbol('I')
        I1 = Symbol('I_1')
            
        result_step1 = self._get_std_solution_by_parts(f1, f2, x)
        
        soln_stmts = []

        soln_stmt = f"""
        We have to compute 

        $\\displaystyle 
        {latex(I)} = {latex(result_step1['Intgl_f'])} 
        \\qquad \\cdots \\text{{ (i) }}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        u_val_step1 = result_step1['u_val']
        v_val_step1 = result_step1['v_val']

        I_val = result_step1['Intgl_f_doit']

        
        soln_stmt = f"""
        Let:
        
        $\\displaystyle 
        {latex(u)} = {latex(u_val_step1)}
        $
        
        $\\displaystyle 
        {latex(v)} = {latex(v_val_step1)}
        $
        
        Now, 
        $\\displaystyle
        {result_step1['integration_by_parts_rule']}
        $
        
        $\\displaystyle
        \\implies
        {latex(result_step1['Intgl_vdu_val'])}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(result_step1['Intgl_udv_val'])}
        $
        
        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(I1)}
        \\qquad \\text {{ assumed }}
        {latex(I1)} = {latex(result_step1['Intgl_udv_val'])}  
        \\qquad \\cdots \\text{{ (ii) }}
        $
        """
        soln_stmts.append(soln_stmt.strip())


        f1_step2 = u_val_step1
        f2_step2 = Derivative(v_val_step1, x).doit()



        result_step2 = self._get_std_solution_by_parts(f1_step2, f2_step2, x)

        u_val_step2 = result_step2['u_val']
        v_val_step2 = result_step2['v_val']

        soln_stmt = f"""
        To solve ${latex(I1)}$, let:
        
        $\\displaystyle 
        {latex(u)} = {latex(u_val_step2)}
        $
        
        $\\displaystyle 
        {latex(v)} = {latex(v_val_step2)}
        $
        
        Now, 
        $\\displaystyle
        {result_step2['integration_by_parts_rule']}
        $
        
        $\\displaystyle
        \\implies
        {latex(result_step2['Intgl_vdu_val'])}
        = {latex(u_val_step2 * v_val_step2)}
        - {latex(result_step2['Intgl_udv_val'])}
        $
        
        $\\displaystyle
        \\implies
        {latex(I1)}
        = {latex(u_val_step2 * v_val_step2)}
        - {latex(I)}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        
        I_val_times_2 = u_val_step1 * v_val_step1 - u_val_step2 * v_val_step2

        soln_stmt = f"""
        From (ii):
        
        $\\displaystyle
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(I1)}
        $

        $\\displaystyle
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - \\left(
        {latex(u_val_step2 * v_val_step2)}
        - {latex(I)}
        \\right)
        $

        $\\displaystyle
        \\implies
        {latex(2 * I)}
        = {latex(I_val_times_2)}
        $

        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(I_val)} + {latex(C)}
        \\qquad \\text{{ added integration constant }}
        $

        """
        soln_stmts.append(soln_stmt.strip())

        
        soln_s = "\n\n".join(soln_stmts)
        
        
        result = {
        'prob_s': result_step1['prob_s'],
        'soln_s': soln_s.strip(),
        'answer_s': result_step1['answer_s'],
        }
        
        return result
        pass
    
    def _get_1_by_x_log_x_pow_minus_3_get_by_parts_in_chain(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x
            
        C = self.C

        u = Symbol('u')
        v = Symbol('v')

        f1 = 1 / x
        f2 = (log(x)) ** -3
        
        I = Symbol('I')
            
        result_step1 = self._get_std_solution_by_parts(f1, f2, x)
        
        soln_stmts = []

        soln_stmt = f"""
        We have to compute 

        $\\displaystyle 
        {latex(I)} = {latex(result_step1['Intgl_f'])} 
        \\qquad \\cdots \\text{{ (i) }}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        u_val_step1 = result_step1['u_val']
        v_val_step1 = result_step1['v_val']

        I_val = result_step1['Intgl_f_doit']

        soln_stmt = f"""
        Let:
        
        $\\displaystyle 
        {latex(u)} = {latex(u_val_step1)}
        $
        
        $\\displaystyle 
        {latex(v)} = {latex(v_val_step1)}
        $
        
        Now, 
        $\\displaystyle
        {result_step1['integration_by_parts_rule']}
        $
        
        $\\displaystyle
        \\implies
        {latex(result_step1['Intgl_vdu_val'])}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(result_step1['Intgl_udv_val'])}
        $
        
        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(- 3 * I)}
        $
        """
        soln_stmts.append(soln_stmt.strip())


        soln_stmt = f"""
        $\\displaystyle
        \\implies
        {latex(- 2 * I)}
        = {latex(u_val_step1 * v_val_step1)}
        $

        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1 / (-2) )}
        $

        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(I_val)} + {latex(C)}
        \\qquad \\text{{ added integration constant }}
        $

        """
        soln_stmts.append(soln_stmt.strip())

        
        soln_s = "\n\n".join(soln_stmts)
        
        
        result = {
        'prob_s': result_step1['prob_s'],
        'soln_s': soln_s.strip(),
        'answer_s': result_step1['answer_s'],
        }
        
        return result
        pass
    
    def _get_sin_2_x_cos_x_x_by_parts_in_chain(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x

        f1_org = sin(2 * x) * cos(x)
        f2_org = x
        
        A = 2 * x
        B = x
        
        A_plus_B = Symbol(f"{latex(A)} + {latex(B)}")
        A_minus_B = Symbol(f"{latex(A)} - {latex(B)}")
        
        #f1_mod = 2 * sin(A) * cos(B) 
        f1_mod = Symbol(f"{latex(sin(A_plus_B))} - {latex(sin(A_minus_B))}")

        f1 = sin(A + B) - sin(A - B)
        f2 = x / 2

        soln_stmts = []

        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        {latex(f1_org * f2_org)} 
        & = \\frac {{ {latex(f2_org)} \\cdot 2 {latex(f1_org)} }} {{ 2 }} 
        \\\\ \\\\
        
        & = {latex(f2)}  \\cdot \\left( {latex(f1_mod)} \\right)
        \\\\ \\\\
        
        & = {latex(f2)}  \\cdot \\left( {latex(f1)} \\right)
        \\\\ \\\\
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())


        
        result =  self._get_std_solution_by_parts(f1, f2, x)
        
        soln_stmt = f"""
        Therefore, 
        {result['soln_s']}
        """
        soln_stmts.append(soln_stmt.strip())

        
        soln_s = "\n\n".join(soln_stmts)
        
        result['soln_s'] = soln_s.strip()
        
        return result
        pass
    
    def _get_log_x_sqr_by_parts_in_chain(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x
            
        C = self.C

        u = Symbol('u')
        v = Symbol('v')

        f1 = sp.sympify(1)
        f2 = (log(x)) ** 2
        
        I = Symbol('I')
        I1 = Symbol('I_1')
            
        result_step1 = self._get_std_solution_by_parts(f1, f2, x)
        
        soln_stmts = []

        soln_stmt = f"""
        We have to compute 

        $\\displaystyle 
        {latex(I)} = {latex(result_step1['Intgl_f'])} 
        \\qquad \\cdots \\text{{ (i) }}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        u_val_step1 = result_step1['u_val']
        v_val_step1 = result_step1['v_val']

        I_val = result_step1['Intgl_f_doit']

        soln_stmt = f"""
        Let:
        
        $\\displaystyle 
        {latex(u)} = {latex(u_val_step1)}
        $
        
        $\\displaystyle 
        {latex(v)} = {latex(v_val_step1)}
        $
        
        Now, 
        $\\displaystyle
        {result_step1['integration_by_parts_rule']}
        $
        
        $\\displaystyle
        \\implies
        {latex(result_step1['Intgl_vdu_val'])}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(result_step1['Intgl_udv_val'])}
        $
        
        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(I1)}
        \\qquad \\text {{ assumed }}
        {latex(I1)} = {latex(result_step1['Intgl_udv_val'])}  
        \\qquad \\cdots \\text{{ (ii) }}
        $
        """
        soln_stmts.append(soln_stmt.strip())


        #f1_step2 = u_val_step1
        #f2_step2 = Derivative(v_val_step1, x).doit()

        f1_step2 = sp.simplify(2)
        f2_step2 = log(x)

        result_step2 = self._get_std_solution_by_parts(f1_step2, f2_step2, x)

        u_val_step2 = result_step2['u_val']
        v_val_step2 = result_step2['v_val']

        I1_val = result_step2['Intgl_f_doit']

        soln_stmt = f"""
        To solve ${latex(I1)}$, let:
        
        $\\displaystyle 
        {latex(u)} = {latex(u_val_step2)}
        $
        
        $\\displaystyle 
        {latex(v)} = {latex(v_val_step2)}
        $
        
        Now, 
        $\\displaystyle
        {result_step2['integration_by_parts_rule']}
        $
        
        $\\displaystyle
        \\implies
        {latex(result_step2['Intgl_vdu_val'])}
        = {latex(u_val_step2 * v_val_step2)}
        - {latex(result_step2['Intgl_udv_val'])}
        $
        
        $\\displaystyle
        \\implies
        {latex(I1)}
        = {latex(I1_val)}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        
        
        soln_stmt = f"""
        From (ii):
        
        $\\displaystyle
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(I1)}
        $

        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - \\left(
        {latex(I1_val)}
        \\right)
        $

        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(I_val)} + {latex(C)}
        \\qquad \\text{{ added integration constant }}
        $

        """
        soln_stmts.append(soln_stmt.strip())

        soln_s = "\n\n".join(soln_stmts)
        
        
        result = {
        'prob_s': result_step1['prob_s'],
        'soln_s': soln_s.strip(),
        'answer_s': result_step1['answer_s'],
        }
        
        return result
        pass
    
  
    
    def _get_exp_2_x_sin_x_by_parts_in_chain(self, x = None):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())
        

        if x is None:
            x = self.x
            
        C = self.C

        u = Symbol('u')
        v = Symbol('v')

        f1 = sin(x)
        f2 = sp.exp(2 * x)
        
        I = Symbol('I')
        I1 = Symbol('I_1')
            
        result_step1 = self._get_std_solution_by_parts(f1, f2, x)
        
        
        
        soln_stmts = []

        soln_stmt = f"""
        We have to compute 

        $\\displaystyle 
        {latex(I)} = {latex(result_step1['Intgl_f'])} 
        \\qquad \\cdots \\text{{ (i) }}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        u_val_step1 = result_step1['u_val']
        v_val_step1 = result_step1['v_val']
        
        I_val = result_step1['Intgl_f_doit']
        
        soln_stmt = f"""
        Let:
        
        $\\displaystyle 
        {latex(u)} = {latex(u_val_step1)}
        $
        
        $\\displaystyle 
        {latex(v)} = {latex(v_val_step1)}
        $
        
        Now, 
        $\\displaystyle
        {result_step1['integration_by_parts_rule']}
        $
        
        $\\displaystyle
        \\implies
        {latex(result_step1['Intgl_vdu_val'])}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(result_step1['Intgl_udv_val'])}
        $
        
        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(I1)}
        \\qquad \\text {{ assumed }}
        {latex(I1)} = {latex(result_step1['Intgl_udv_val'])}  
        \\qquad \\cdots \\text{{ (ii) }}
        $
        """
        soln_stmts.append(soln_stmt.strip())


        f1_step2 = u_val_step1
        f2_step2 = Derivative(v_val_step1, x).doit()



        result_step2 = self._get_std_solution_by_parts(f1_step2, f2_step2, x)

        u_val_step2 = result_step2['u_val']
        v_val_step2 = result_step2['v_val']

        soln_stmt = f"""
        To solve ${latex(I1)}$, let:
        
        $\\displaystyle 
        {latex(u)} = {latex(u_val_step2)}
        $
        
        $\\displaystyle 
        {latex(v)} = {latex(v_val_step2)}
        $
        
        Now, 
        $\\displaystyle
        {result_step2['integration_by_parts_rule']}
        $
        
        $\\displaystyle
        \\implies
        {latex(result_step2['Intgl_vdu_val'])}
        = {latex(u_val_step2 * v_val_step2)}
        - {latex(result_step2['Intgl_udv_val'])}
        $
        
        $\\displaystyle
        \\implies
        {latex(I1)}
        = {latex(u_val_step2 * v_val_step2)}
        + {latex(4 * I)}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        
        soln_stmt = f"""
        From (ii):
        
        $\\displaystyle
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - {latex(I1)}
        $

        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(u_val_step1 * v_val_step1)}
        - \\left(
        {latex(u_val_step2 * v_val_step2)}
        + {latex(4 * I)}
        \\right)
        $

        $\\displaystyle
        \\implies
        {latex(5 * I)}
        = {latex(u_val_step1 * v_val_step1)}
        - \\left(
        {latex(u_val_step2 * v_val_step2)}
        \\right)
        $

        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex((u_val_step1 * v_val_step1 - u_val_step2 * v_val_step2) / 5)}
        $

        $\\displaystyle
        \\implies
        {latex(I)}
        = {latex(I_val)} + {latex(C)}
        \\qquad \\text{{ added integration constant }}
        $

        """
        soln_stmts.append(soln_stmt.strip())


        
        soln_s = "\n\n".join(soln_stmts)
        
        
        result = {
        'prob_s': result_step1['prob_s'],
        'soln_s': soln_s.strip(),
        'answer_s': result_step1['answer_s'],
        }
        
        return result
        pass
    

    pass
#class StdIntegrationByPartsHelper:
