Символьное дифференцирование - Шаги дифференцирования

ОГЛАВЛЕНИЕ

Шаги дифференцирования

Разбор выражения для заполнения набора

Разбор выражения – процесс сканирования выражения для отыскания его элементов (чисел, операторов, функций и переменных). Каждый оператор должен иметь два операнда; например, 2*12. Оператор "*" имеет два операнда: 2 и 12. Каждая функция имеет аргумент. Например, для sin(x) функцией является sin, а аргументом - x. Надо особым образом сканировать выражение, чтобы определить каждый оператор и операнды, а также каждый аргумент функции, чтобы упростить расчет или дифференцирование.

Выполнение выражения зависит от приоритета операторов. Если построить такой набор операторов и операндов, чтобы за каждым оператором следовали его два операнда, то 2*12 построит следующий набор: "*, "2", "12". Итак, функция расчета проверяет набор. Если элемент является оператором, то функция берет два его оператора и производит расчет для них по рекурсивной формуле, чтобы вычислить все выражение. Например, выражение

sin(2*12)/7+9^2

должно вычисляться по следующим шагам:
1.    Вычислить sin(2*12)/7
2.    Вычислить 9^2
3.    Затем применить оператор + к двум результатам
4.    Чтобы вычислить пункт 1, надо:
1.    Вычислить sin(2*12)
2.    Вычислить 7
3.    Затем поделить два результата
4.    Чтобы вычислить пункт 4.1, надо:
1.    Вычислить 2*12
2.    Применить функцию sin к результату
3.    Чтобы вычислить пункт 4.4.1, надо:
1.    Применить оператор * к двум операндам 2 и 12

Сканирующая функция принимает входное выражение и массив операндов в зависимости от их приоритета, как в следующем коде:

///////////////////////////////////////////////////////////
// GetOperator: сканирует входную строку,
// чтобы найти входные операторы
///////////////////////////////////////////////////////////
int GetOperator(IN LPCSTR lpcs, IN LPCSTR lpcsOperators[])
{
    for(int nIndex = 0; lpcsOperators[nIndex]; nIndex++)
    {
        int nOpen = 0;
        // сканируется выражение с конца
        LPSTR p = (LPSTR)lpcs+strlen(lpcs)-1;
        // цикл сообщает о достижении начала выражения
        while(p >= lpcs)
        {
            // проверка на закрывающую скобку
            if(*p == ')')
                nOpen++;
            // проверка на открывающую скобку
            else    if(*p == '(')
                nOpen--;
            // проверка на оператор
            else if(nOpen == 0 && strchr(lpcsOperators[nIndex], *p) != NULL)
                // проверка, является ли оператор меткой знака
                if((*p != '-' && *p != '+') ||
                   (p != lpcs && IsRightSign(*(p-1),
                         lpcsOperators, nIndex+1)))
                    // возвращается индекс оператора
                    return (int)(p-lpcs);
            p--;
        }
    }
    // оператор не найден
    return -1;
}

Очевидно, что это рекурсивная операция. Чтобы сделать это, надо сформулировать выражение в виде набора операторов и операндов и определенным образом обработать этот набор, чтобы получить конечный результат. Основные шаги для осуществления этого таковы:

•    Найти операторы
•    Разбить выражение по индексу оператора
•    Добавить два полученных операнда в набор
•    Если операторы не найдены, проверить, начинается ли выражение с имени функции

Применение предыдущих шагов дает следующий набор:

+

/

sin(2*12)

*

2

12

7

 

^

 

9

 

2

 

Для более подробного описания рассматривается код, разбирающий выражение и заполняющий набор:

bool FillStack(LPCSTR lpcsInput, vector<EXPRESSIONITEM*>& vStack)
{
    // массив операторов от верхнего до нижнего
    priority LPCSTR lpcsOperators[] = { "+-", "*/", "^%", NULL };

    // вставить первый входной элемент в набор
    vStack.push_back(new ExpressionItem(lpcsInput));
    // цикл проходит по набору выражения, чтобы проверить,
    // можно ли разбить выражение на два запроса
    for(int nIndex = 0; nIndex < (int)vStack.size(); nIndex++)
        // проверяет, является ли элемент выражения оператором
        if(vStack[nIndex]->m_cOperator == 0)
        {
            // копируется строка выражения
            CString str = vStack[nIndex]->m_strInput;
            // выражение разбирается, чтобы найти операторы
            int nOpIndex = GetOperator(str, lpcsOperators);
            if(nOpIndex != -1)
            {
                // выражение разбивается на
                // два запроса по индексу оператора
                vStack[nIndex]->m_cOperator = str[nOpIndex];
                // добавляется левый операнд оператора
                // как новое выражение
                vStack.insert(vStack.begin()+nIndex+1,
                  new ExpressionItem(str.Left(nOpIndex)));
                // добавляется правый операнд оператора
                // как новое выражение
                vStack.insert(vStack.begin()+nIndex+2,
                  new ExpressionItem(str.Mid(nOpIndex+1)));
            }
            else
            // проверяется, начинается ли строка выражения
            // с функции или круглых скобок
                if((vStack[nIndex]->m_nFunction = GetFunction(str,
                        vStack[nIndex]->m_nSign)) == 0
                        && vStack[nIndex]->m_nSign == 0)
                    // удаляются круглые скобки, и выражение повторно просматривается
                    vStack[nIndex--]->m_strInput =
                             str.Mid(1, str.GetLength()-2);
    }
    return true;
}

Это простейший код разбора математического выражения. Его комментарии собраны по пунктам ниже:

•    Вставить первый входной элемент в набор
•    Цикл проходит по набору выражения, чтобы проверить, можно ли разбить выражение на два запроса
•    Проверить, является ли элемент выражения оператором (+-*/^)
•    Разобрать выражение, чтобы найти операторы
•    Если оператор найден:
    o    Разбить выражение на два запроса по индексу оператора
    o    Добавить левый операнд оператора как новое выражение
    o    Добавить правый операнд оператора как новое выражение
•    Иначе
    o    Проверить, начинается ли строка выражения с функции или круглых скобок
    o    Если функция найдена, установить ее номер в данных выражения

Применение формул дифференцирования к набору

Применение формул дифференцирования –  значит выполнять итерации по набору, брать каждый оператор и применять правило дифференцирования (в зависимости от оператора)  к двум операндам оператора:

Выражение

Производная

u+v

du/dx+dv/dx

u-v

du/dx-dv/dx

u/v

(v*du/dx-u*dv/dx)/v^2

u*v

u*dv/dx+v*du/dx

c*u

c*du/dx

u^n

n*u^(n-1)*du/dx

c^u

c^u*ln(c)*du/dx

e^u

e^u*du/dx

Если любой операнд является оператором, то функция снова вызывается рекурсивно для дифференцирования всего выражения. Следующий код представляет эту функцию:

CString DifferentiateStack(vector<EXPRESSIONITEM*>& vStack, int& nExpression)
{
    ExpressionItem *pQI = vStack[nExpression++];
    if(pQI->m_cOperator)
    {
        // взять левый операнд
        CString u = vStack[nExpression]->GetInput();
        // взять производную от левого операнда
        CString du = DifferentiateStack(vStack, nExpression);
        // взять правый операнд
        CString v = vStack[nExpression]->GetInput();
        // взять производную от правого операнда
        CString dv = DifferentiateStack(vStack, nExpression);

        if(du == '0')    // u - константа
            ...
        else    if(dv == '0')    // v - константа
            ...
        else
            switch(pQI->m_cOperator)
            {
            case '-':    // d(u-v) = du-dv
            case '+':    // d(u+v) = du+dv
                pQI->m_strOutput = '('+du+pQI->m_cOperator+dv+')';
                break;
            case '*':    // d(u*v) = u*dv+du*v
                pQI->m_strOutput = '('+u+'*'+dv+'+'+du+'*'+v+')';
                break;
            case '/':    // d(u/v) = (du*v-u*dv)/v^2
                pQI->m_strOutput = '('+du+'*'+v+'-'+u+'*'+dv+")/("+v+")^2";
                break;
            case '^':    // d(u^v) = v*u^(v-1)*du+u^v*ln(u)*dv
                pQI->m_strOutput = '('+v+'*'+u+"^("+v+"-1)*"+ du+
                                      '+'+u+'^'+v+"*ln("+u+")*"+dv+')';
                break;
            }
    }
    else
        // взять производную выражения
        pQI->GetDifferentiation();
    // вернуть полученную производную
    return pQI->m_strOutput;
}

Оптимизация уравнения

Оптимизация уравнения включает в себя следующие простые шаги:

•    Заменить "--" на "" или "+"
•    Заменить "+-" на "-"
•    Заменить "((....))" на "(....)"
•    Удалить все "1*"
•    Удалить все "*1"
•    Удалить все "^1"
•    Удалить все "1*"
•    Удалить ненужные круглые скобки

Псевдокод дифференцирования

Differentiate(input)
{
    Stack = FillStack(input)
    output = DifferentiateStack(Stack)
    Optimize(output)
    return output
}

FillStack(input)
{
    operators[] { "+-", "*/", "^%" }

    stack.push(input)
    loop( n = 1  to stack.size() )
    {
        if stack[n] is not operator
            if GetOperator(stack[n],
               operators) success
            {
                Split stack[n]
                stack.Insrt(left  operand)
                stack.Insrt(right operand)
            }
            else
                GetFunction(stack[n])
    }
}

DifferentiateStack(stack, index)
{
    if stack[index] is operator
    {
        index++
        u = stack[index]
        du = DifferentiateStack(stack, index)
        v = stack[index]
        dv = DifferentiateStack(stack, index)

        if operator = '-' or '+'
            output = du+operator+dv
        else    if operator = '*'
            output = u*dv+du*v
        else    if operator = '/'
            output = (du*v-u*dv)/v^2
        else    if operator = '^'
            output = v*u^(v-1)*du+u^v*ln(u)*dv
    }
    else
        output = stack[index++].GetDifferentiation()

    return output
}

void Optimize(str)
{
    replace "--" with "" or "+"
    replace "+-" with "-"
    replace "((....))"  with "(....)"
    remove any 1*
    remove any *1
    remove any exponent equal 1
    remove unneeded parentheses

    if str changed then
        Optimize(str)
}

ExpressionItem::GetDifferentiation()
{
    if Function then
    {
        arument = Argument(input);
        if function = SIN
            output = Differentiate(arument)*cos(arument)
        else    if function = COS
            output = Differentiate(arument)*(-sin(arument))
        else
        ...
        }
    }
    else
    {
        if input = "x"
            output = "1"
        else    if input = "-x"
            output = "-1"
        else    if input is numeric
            output = "0"
        else
            output = "d"+input+"/dx"
    }
}