Hola
Entonces, en la
primera parte, ya hicimos un buen trabajo haciendo derivación, simplificación y compilación. Entonces, ¿qué otra cosa debería hacer nuestra calculadora simple? Bueno, al menos ecuaciones de la forma
(x−b)( tan( sin(x))2−3 tan( sin(x))+c)=0
Definitivamente tengo que decidir. También es hermoso dibujar este caso en latech, ¡y estará bien! Vamos!

Descargo de responsabilidadAunque el código se proporciona aquí en C #, es tan pequeño aquí que no conocer el idioma u odiarlo no debería complicar en gran medida la lectura.
Aceleración de compilación
En la última parte, compilamos la función para poder compilarla una vez y luego ejecutarla muchas veces en el futuro.
Entonces, presentamos la función
sin(x2)+ cos(x2)+x2+ sin(x2)
Al final de la primera parte, la velocidad de esta función era la siguiente:
Que?Sustituir: una forma clásica de calcular el valor de una expresión, es decir, por ejemplo
var x = MathS.Var("x"); var expr = x + 3 * x; Console.WriteLine(expr.Substitute(x, 5).Eval()); >>> 20
Nuestra función compilada es cuando hacemos lo mismo, pero después de compilarlo
var x = MathS.Var("x"); var expr = x + 3 * x; var func = expr.Compile(x); Console.WriteLine(func.Substitute(5));
Una función escrita directamente en el código es cuando hacemos
static Complex MyFunc(Complex x) => x + 3 * x;
Como podemos ver, hay partes repetidas en esta función, por ejemplo,
x2 , y sería bueno almacenarlos en caché.
Para hacer esto, presentamos dos instrucciones más PULLCACHE y TOCACHE. El primero empujará el número en el caché en la dirección que le pasamos a la pila. El segundo
copiará (
stack.Peek()
) el último número de la pila a la caché, también en una dirección específica.
Queda por hacer una tabla en la que durante la compilación escribiremos funciones para el almacenamiento en caché. ¿Qué no vamos a almacenar? Bueno, en primer lugar, lo que pasa una vez. La instrucción adicional para acceder al caché no es buena. En segundo lugar, las operaciones que son demasiado simples tampoco tienen sentido almacenar en caché. Bueno, por ejemplo, acceder a una variable o número.
Al interpretar la lista de instrucciones, tendremos una matriz pre-creada para el almacenamiento en caché. Ahora las instrucciones para esta función se ven como
PUSHCONST (2, 0) PUSHVAR 0 CALL powf TOCACHE 0 # , , - . CALL sinf TOCACHE 1 # PULLCACHE 0 # , . PULLCACHE 0 CALL cosf PULLCACHE 1 CALL sumf CALL sumf CALL sumf
Después de eso obtenemos un resultado claramente mejor:
En los nabos,
aquí se implementan tanto la compilación como la interpretación de las instrucciones.
LaTeX
Este es un formato bien conocido para grabar fórmulas matemáticas (¡aunque no solo ellas!), Que se representa en bellas imágenes. También está en el centro, y todas las fórmulas que escribo están escritas en este formato.
Tener un árbol de expresión hace que la representación en látex sea muy simple. ¿Cómo hacer esto en términos de lógica? Entonces, tenemos la copa del árbol. Si es un número o una variable, entonces todo es simple. Si este vértice, por ejemplo, el operador de división, nos gustaría más
fracab que
a/b , entonces para la división escribimos algo como
public static class Div { internal static string Latex(List<Entity> args) => @"\frac{" + args[0].Latexise() + "}{" + args[1].Latexise() + "}"; }
Todo es muy simple, como vemos. El único problema que encontré durante la implementación es que no está claro cómo colocar los corchetes. Si los empujamos a cada operador, obtendremos tales tonterías:
left( left( left( left(x+3 right)+ left(a+b right) right) right)+c right)
Sin embargo, una persona atenta notará de inmediato (y no como yo) que si se eliminan por completo, al construir una expresión del formulario
c∗(a+b) , imprimiremos
c∗a+b
Se resuelve simplemente, ingresamos las prioridades de los operadores a la
args[0].Latexise(args[0].Priority < Const.PRIOR_MUL) + "*" + args[1].Latexise(args[1].Priority < Const.PRIOR_MUL);
Y voila, eres hermosa.
Latexirización hecha
aquí . Y la palabra latexise no existe, la inventé yo mismo, no me patees por eso.
Solución de ecuaciones
En realidad, desde el punto de vista de las matemáticas, no puedes escribir un algoritmo que encuentre todas las soluciones de alguna ecuación. Por lo tanto, queremos encontrar tantas raíces diferentes como sea posible, dándonos cuenta de la imposibilidad de alcanzar el objetivo final. Hay dos componentes: una solución numérica (todo es lo más simple posible) y analítica (pero esto es historia).
Solución numérica. Método de Newton
Es extremadamente simple, conoce la función.
f(x) buscaremos la raíz usando una fórmula iterativa
xn+1=xn− fracf(x)f(x)′
Como todos resolvemos en un plano complejo, básicamente podemos escribir un ciclo bidimensional que buscará soluciones y luego devolverá soluciones únicas. Además, ahora podemos encontrar la derivada de la función analíticamente, y luego ambas funciones
f(x) y
f(x)′ compilar
Newton está sentado
aquí .
Solución analítica
Los primeros pensamientos son bastante obvios. Entonces, la expresión, las raíces de la ecuación
a(x)∗b(x) igual totalidad de raíces
a(x) y
b(x) , de manera similar para la división:
internal static void Solve(Entity expr, VariableEntity x, EntitySet dst) { if (expr is OperatorEntity) { switch (expr.Name) { case "mul": Solve(expr.Children[0], x, dst); Solve(expr.Children[1], x, dst); return; case "div": Solve(expr.Children[0], x, dst); return; } } ...
Para seno, esto se verá un poco diferente:
case "sinf": Solve(expr.Children[0] + "n" * MathS.pi, x, dst); return;
Después de todo, queremos encontrar todas las raíces, y no solo aquellas que son 0.
Después de asegurarnos de que la expresión actual no sea un producto, y no otros operadores y funciones fácilmente simplificados, debemos tratar de encontrar una plantilla para resolver la ecuación.
La primera idea es usar los patrones que hicimos para simplificar la expresión. Y, de hecho, necesitaremos aproximadamente esto, pero primero tenemos que hacer un reemplazo variable. Y de hecho, a la ecuación
sin(x)2+ sin(x)−0.4=0
no hay plantilla, pero para el par
begincasest2+t−0.4=0t= sin(x) endcases
incluso tal como existe.
Por lo tanto, hacemos una función del tipo GetMinimumSubtree, que devolvería el reemplazo variable más eficiente. ¿Qué es un reemplazo efectivo? Este es un reemplazo en el que
- Maximiza el uso de este reemplazo
- Maximiza la profundidad del árbol (de modo que en la ecuación sin( sin(x))2+3 tuvimos un reemplazo sin( sin(x)) )
- Nos aseguramos de que con este reemplazo hemos reemplazado todas las referencias a una variable, por ejemplo, si está en la ecuación sin(x)2+ sin(x)+x haremos un reemplazo t= sin(x) entonces todo se volverá malo. Por lo tanto, en esta ecuación, el mejor reemplazo para x es x (es decir, no hay un buen reemplazo), pero por ejemplo en sin(x)2+ sin(x)+c podemos hacer un reemplazo con seguridad t= sin(x) .
Después de reemplazar la ecuación parece mucho más simple.
Polinomio
Entonces, lo primero que hacemos, hacemos
expr.Expand()
- abre todos los corchetes para deshacernos de la basura del formulario
x(x+2) convirtiéndose en
x2+2x . Ahora, después de la divulgación, obtenemos algo como
c+x3+3x2−a∗x3+x
¿No es canónico? Luego, primero recopilamos información sobre cada monomio, aplicando “hijos lineales” (en el
último artículo) y ampliando todos los términos.
¿Qué tenemos sobre el monomio? Un monomio es un producto de factores, uno de los cuales es una variable, o un operador del grado de una variable y un número entero. Por lo tanto, introduciremos dos variables, una tendrá un grado y la otra un coeficiente. A continuación, solo repase los factores, y cada vez nos aseguramos de que
x hasta cierto punto, o sin un título en absoluto. Si nos encontramos con byaku, volvemos con nulo.
byakaSi de repente nos encontramos con alguno x3.2 , log(x,2) y otras cosas que mencionan x , pero no se ajusta al patrón monomial; esto es malo.
Bien, hemos compilado un diccionario en el que la clave es un grado (entero) y el valor es un coeficiente monomial. Esto es lo que parece para el ejemplo anterior:
0 => c 1 => 1 2 => 3 3 => 1 - a
Aquí, por ejemplo, la implementación de la solución de la ecuación cuadrática
if (powers[powers.Count - 1] == 2) { var a = GetMonomialByPower(2); var b = GetMonomialByPower(1); var c = GetMonomialByPower(0); var D = MathS.Sqr(b) - 4 * a * c; res.Add((-b - MathS.Sqrt(D)) / (2 * a)); res.Add((-b + MathS.Sqrt(D)) / (2 * a)); return res; }
Este punto aún no se ha completado (por ejemplo, debe hacerlo correctamente para polinomios cúbicos), pero en general sucede
aquí .
Barrido inverso
Entonces, aquí hemos hecho algún tipo de reemplazo
t= arcsin(x3+a2) , y ahora queremos obtener x desde allí. Aquí solo tenemos una implementación paso a paso de funciones y operadores, como
t= arcsin(x3+a2)
sin(t)=x3+a2
sin(t)−a2=x3
( s i n ( t ) - a 2 ) f r a c 1 3 = x
El fragmento de código se ve así:
switch (func.Name) { case "sinf":
El código para estas funciones está
aquí .
Todo, la solución final de las ecuaciones (¡adiós!) Se parece a esto
- Si conocemos la función u operador cero, envíe Resolver allí (por ejemplo, si a ∗ b = 0 luego ejecute Solve (a) y Solve (b))
- Encuentra un reemplazo
- Resolver como un polinomio si es posible
- Para todas las soluciones, implemente un reemplazo para obtener la solución final
- Si, como polinomio, no se resuelve y solo tenemos una variable, la resolvemos por el método de Newton
Entonces, hoy estamos:
- Acelerar la función compilada debido a la memoria caché
- Rendido en LaTeX
- Resolvimos una pequeña parte de los casos de ecuaciones.
Probablemente no voy a hablar sobre el cálculo numérico de cierta integral, ya que es
muy simple . No hablé de análisis, ya que recibí críticas al respecto en un artículo anterior. La idea era escribir la tuya. Sin embargo, ¿es necesario hablar sobre cómo analizamos una expresión de una cadena?
El proyecto ya está
aquí .
En la siguiente parte ...Si alguien más está interesado, intentaremos complicar la solución de las ecuaciones agregando grados cúbicos y cuarto, así como un sistema de plegado más inteligente. Tal vez seleccionaremos patrones, porque cualquier estudiante decidirá
1− sin(x)2+ cos(x)+0.5
Pero no nuestro algoritmo. Además, puede haber una integral indefinida (comienzo). Expandir el número a cuaterniones o matrices aún no tiene sentido, pero de alguna manera será posible. Si tiene una idea específica para la parte III, escriba mensajes o comentarios personales.
Sobre el proyectoAquellos que miraron el código pudieron notar lo crudo y no refactorizado que era. Y, por supuesto, voy a refactorizar, por lo que la idea principal de este artículo es transmitir información teóricamente, y solo luego espiar el código. Y las solicitudes de extracción son bienvenidas, aunque probablemente aún no podamos acceder a wolfram.alpha. El proyecto está abierto.
Y aquí hay un par de ejemplos de lo que hicimos hoy.
var x = MathS.Var("x"); var y = MathS.Var("y"); var expr = x.Pow(y) + MathS.Sqrt(x + y / 4) * (6 / x); Console.WriteLine(expr.Latexise()); >>> {x}^{y}+\sqrt{x+\frac{y}{4}}*\frac{6}{x}
x y +sqrt x + f r a c y 4 ∗frac 6 x var x = MathS.Var("x"); var expr = MathS.Sin(x) + MathS.Sqrt(x) / (MathS.Sqrt(x) + MathS.Cos(x)) + MathS.Pow(x, 3); var func = expr.Compile(x); Console.WriteLine(func.Substitute(3)); >>> 29.4752368584034
Entity expr = "(sin(x)2 - sin(x) + a)(b - x)((-3) * x + 2 + 3 * x ^ 2 + (x + (-3)) * x ^ 3)"; foreach (var root in expr.Solve("x")) Console.WriteLine(root); >>> arcsin((1 - sqrt(1 + (-4) * a)) / 2) >>> arcsin((1 + sqrt(1 + (-4) * a)) / 2) >>> b >>> 1 >>> 2 >>> i >>> -i
Gracias por su atencion!