Derivación (exponentes y otros)

elkaoD

Las preguntas no son directamente de programación pero sospecho que aquí voy a recibir mejores respuestas y sobre todo, más orientadas a la programación que es lo que me interesa.

Estoy implementando un derivador de funciones en Scala (en el conjunto R.) Lo he implementado usando recursión, pero estando un poco oxidado en mates estoy teniendo problemas con los exponentes (¿lo más chungo de derivar?) He intentado Googlear pero lo que más encuentro son tablas de las funciones de derivación y poca información de verdad.

De momento sólo se pueden derivar exponentes constantes naturales (enteros positivos) pero me he encontrado con varias excepciones extendiéndolo a R, variables/funciones, etc.

  • En cuanto encuentro d(X1), simplemente devuelvo 1.0 como su derivada (el exponente queda 0 al derivar.) Quiero extenderlo a cero/números negativos, pero no estoy seguro de si d(d(X1)) es 0.0 (primera derivada da 1.0, luego 0.0 por ser constante) o simplemente x-1. Si es lo primero, me quedaré con la opción de devolver 1.0, pero si es lo último simplemente debería seguir restando 1 al exponente por cada derivación.

  • ¿Cómo puedo extender esto a R? ¿La derivada de d(X2.1) debería devolver d(X2) + d(X0.1)? ¿Me estoy perdiendo algo y hay alguna forma más fácil de hacerlo?

  • Aunque aún no está implementado, sé como derivar aX, pero... ¿qué hay de XX y Xf(X), etc.? No recuerdo una puta mierda de cómo derivar esto. ¿Es muy complicado? ¿Dónde hay recursos para este tipo de derivadas más complejas?

Y una opción que se me ha ocurrido de equivalencias, pero no logro decidirme:

  • ¿Sería inteligente devolver 1/Xn para exponente -n? ¿Hay alguna razón para hacerlo o no hacerlo?

Fuera del tema de exponentes, el profesor (sí, son deberes) comenta que da puntos extra si se implementa la derivada de funciones no definidas en todo R (sqrt, ln...) o funciones a trozos (|X|)

¿Hay alguna razón para estos puntos extra? Para sqrt y demás no le veo el problema, la derivada es exactamente igual sólo que debería devolver NaN cuando la quieras en los puntos donde no está definida (y de esto se encarga la propia fórmula de derivación.)

Para |X| si no recuerdo mal hay una fórmula de derivación que hace lo mismo (cuando X=0, hay un divisor que vale 0 y devuelve NaN.) (por cierto si alguien se sabe la fórmula lo agradezco xD)

¿De dónde viene la pajada de los puntos extra entonces?


Y por último, composición de funciones. f(g(X)) lo resuelvo simplemente sustituyendo cada valor de X en f por g(X) y posteriormente derivando la composición resuelta. ¿Lo estoy haciendo bien o me estoy pasando la regla de la cadena por el forro?

freskito24

Respondo en base a lo que interpreto. Utilizo la notación ()' o f'(x) para la derivada y entiendo que vas a hacer cálculo simbólico.

  • Para derivar un polinomio / exponencial no deberías tener ningún problema:

(axn)' = a * n * xn-1

No importa que n sea natural o real:
(3x)' = 3 * x0 = 3
(x-2.1)' = -2.1*x-3.1

  • Las funciones (f(x)g(x))' son un poco más complicadas, mira esto:

http://www.wolframalpha.com/input/?i=%28f%28x%29%5Eg%28x%29%29%27

El caso xf(x) y xx es básicamente sustituir en la expresión de arriba:

http://www.wolframalpha.com/input/?i=%28x%5Ef%28x%29%29%27

http://www.wolframalpha.com/input/?i=%28x%5Ex%29%27

-Lo ideal, para simplificar el código o la lógica es tratar siempre el caso general:

Por ejemplo trata a todos los exponentes reales por igual, sin importar el signo, es decir: Si tu entrada es 1/x interpretarlo como x-1 y aplica la fórmula general.

No sé si tiene sentido hablar de equivalencias en derivadas pero si puedes modificar la expresión para que se ajuste a uno de los modelos que ya has codificado. Creo que era a esto a lo que te referías con equivalencias.

  • Sobre los puntos extra:

Supongo que quiere que controles los casos extra para dar respuestas más exactas. Derivar ln(x) es sencillo si tienes en cuenta que x>0 pero si tienes ln(f(x)) tienes que asegurarte de que f(x) sea mayor que cero.

A partir de aquí supongo que en realidad lo que quiere tu profesor es que si le pasas ln(f(x)) el resultado sea una función (la derivada) definida a trozos donde la la expresión evaluar ln(f(x)) tenga sentido (y por lo tanto su derivada exista).

La expresión directa para |x|' = x / sqrt(x2), (me suena que se podía simplificar de otra forma pero ahora mismo no caigo)

  • Te estás inventado la regla de la cadena xD

Te pongo la expresión para 2 y 3 funciones, a partir de ahí el patrón es claro. La forma idónea para hacerlo es utilizar recursividad:

(f(g(x)))' = g'(x) * f'(g(x))

(f(g(h(x))))' = h'(x) * g'(h(x)) * f'(g(h(x))

Básicamente es derivar de dentro hacia fuera. Revisa la expresión que la he sacado de memoria y igual he metido la gamba en alguna parte.

elkaoD
  • Sigo con la duda de si d(d(x1)) = x-1 o 0. Supongo que es 0 pero... EDIT: vale, según Wolfram es 0, entonces lo dejo como está.

  • Coño, no sabía que también funcionaba con reales lo de restar 1 al exponente, imaginé que se complicaría la cosa al involucrarse raíces.

  • ¿El caso de d(f(x)g(x)) es aplicable a xx y xf(x)? Es por ajustarme al caso general y quitarme de mierdas raras, aunque me compliquen la expresión final (pretendo simplificar también.) EDIT: joder si ya lo ponías antes xD perfecto...

¿Es aplicable también al caso de exponente constante? Por la misma razón, por dejar el caso general y olvidarme del caso constante.

  • 1/x lo trato como derivada de la divisón, no como x-1, por eso lo decía. Ya veré qué hago.

  • Lo de las funciones a trozos vale, ya lo pillo, pero sólo se da en el caso del logaritmo (porque su derivada 1/x está definida en todo R menos en 0, mientras que ln sólo en > 0.) En el caso de sqrt por ejemplo el resultado de la propia derivada es el que da NaN en <0.

A ver cómo me lo monto para el logaritmo porque, de momento, lo que hago es primero derivar y luego plantar la aplicación en X. Supongo que tendré que acompañar dominios en las funciones.

  • Lo de la regla de la cadena no es que me lo esté inventando, funciona, pero no veo si es equivalente siempre:

Ejemplo:
f(x) = x/2, g(x) = 2x
f(g(x)) = 2x/2 = x, no? Y su derivada 1.

Por regla de la cadena:
(f(g(x)))' = g' (x) * f' (g(x)) = 2*1/2 = 1

Lo pregunto sobre todo porque de momento PRIMERO compongo y luego derivo, por hacerlo al revés.

1 respuesta
freskito24

#3

Ahora que lo dices si que es mejor considerar el 1/x como cociente, simplifica bastante. Meterte en temas de dominio e images de funciones es complicado porque en ln(f(x)) esa f(x) puede llevar cualquier cosa y comprobar en que intervalos es positiva va a ser bastante difícil.

La idea la derivada es una función a trozos es la derivada de sus trozos. Podrías permitir que la entrada fuera de ese tipo pero yo no me preocuparía demasiado por esto. Aun así si te interesa te dejo un truco para evaluar numéricamente que usaba en mi calculadora (que no permitía funciones a trozos):

f(x) =
{
g(x) si x < 0
h(x) si x >= 0
}

Cuando escribas tu expresión para evaluar pones:

f(x) = g(x) * (x<0) + h(x) * (x>=0)

Suponiendo que la comparación lógica devuelve 1/0, si los interpretas como enteros:

cuando x < 0 f(x) = g(x) + h(x) * 1 = g(x), que es lo que buscas

Y lo contrario cuando x >= 0

Respecto a lo último:
Si lo estás haciendo de forma numérica tu método es perfecto, te da lo mismo el orden de las operaciones.

Si lo estás haciendo de forma simbólica y tienes la posibilidad de identificar las funciones creo será mucho más sencillo (y eficiente) derivar antes porque son funciones elementales -> derivada inmediata.

Imagínate que te dan (o identificas en la entrada) estas dos funciones

f(g(x)):
f(x) = sen(x)
g(x) = x^2

Las derivadas son inmediatas:

f'(x) = cos(x)
g'(x) = 2x

Sustituyes y devuelves:
-> 2x*cos(x2)

Si compones tienes que derivar:
(sen(x2))'

Pero para derivar eso tienes que volver a identificar las funciones x2 y sen(x) y luego derivarlas! Haces el trabajo de la composición y si la expresión no se puede simplificar no te sirve para nada.
Yo recomendaría: Si la función viene "montada" lo mejor que puedes hacer es buscar las funciones y luego derivar. Si viene "desmontada" deriva directamente y da el resultado.


Me está gustando el problema. ¿En que lenguaje estás haciéndolo? Estoy pensando en una función recursiva que sea capaz de evaluar cualquier expresión. Voy a pensar un poco el pseudocódigo y mañana lo pongo.

2 respuestas
elkaoD

#4 luego me leo el post a ver si me ha quedado alguna duda. De momento te contesto a qué estoy usando:

Estoy con Scala y, como todo lo que estoy haciendo en Scala es SORPRENDENTEMENTE FÁCIL y ligero. Un bombazo.

El sistema inicial fueron unas... ¿50 líneas?

Ahora son unas 20 líneas por cada función (suma, resta, etc.) incluyendo derivada, simplificación, aplicaciones de funciones, cálculo del valor, el toString para imprimir bonito... un ejemplo:

case class Suma(val f1 : Funcion, val f2: Funcion) extends Funcion {
  def apply (a : Funcion) = f1(a) + f2(a)
  // esta es la derivada en sí misma
  def unary_! = !f1 + !f2 // !f1 es la derivada de f1 (! por limitaciones del lenguaje)
  def calcular : Double = (f1 calcular) + (f2 calcular)
  def simplificar : Funcion = {
    (f1,f2) match {
      case (Const(0.0), f2:Funcion) => f2 simplificar
      case (f1:Funcion, Const(0.0)) => f1 simplificar
      case (f1:Funcion, f2:Funcion) => (f1 simplificar) + (f2 simplificar)
    }
  }
  override def toString = "(" + f1 + "+" + f2 + ")"
}

Y esas son las funciones complejas. Cosas como el seno:

case class Sin(val f : Funcion) extends Funcion {
  def apply (a : Funcion) = Sin(f(a))
  def unary_! = !f * Cos(f)
  def calcular : Double = math.sin(f calcular)
  def simplificar : Funcion = Sin(f simplificar)
}

Y la sintaxis encima queda elegante. Ejemplo de cómo introduzco las funciones y la respuesta del intérprete de Scala (ojo, puede estar buggy):

val funcion = X + (Sin(X^2)^2) * X
funcion: org.kaod.derivada.Suma = (X+(Sin(X^2)^2*X))

val derivadaFuncion = d(funcion) // esto es un wrapper alrededor de !funcion para hacerlo bonito
derivadaFuncion = (1+((((Sin(X^2)^1*2)*(((X^1*2)*1)*Cos(X^2)))*X)+(1*Sin(X^2)^2)))

val derivadaFuncionEn2 = d(funcion)(2.0)
derivadaFuncionEn2: Double = 9.487615989891362

val simplificada = derivadaFuncion.simplificar
simplificada = (1+((((Sin(X^2)*2)*((X*2)*Cos(X^2)))*X)+Sin(X^2)^2))

EDIT: comprobado en Wolfram, funciona bien!

elkaoD

#4 vale ya he leído el resto.

Meterte en temas de dominio e images de funciones es complicado porque en ln(f(x)) esa f(x) puede llevar cualquier cosa y comprobar en que intervalos es positiva va a ser bastante difícil.

Mi idea es la siguiente: no puedes tener ln(f(x)) porque en cuanto aplicas una función cualquiera sustituye automáticamente la X por la aplicación que hayas hecho (nunca se queda simbólico.) No tengo funciones instanciables que no tengan operaciones: no existe g(x) si g(x) no significa algo, por lo que f(g(x)) simplemente sustituye EL CONTENIDO DE g(x) en cada X de f. Esto simplifica MUCHO las cosas.

Aún así no lo necesito: la idea que se me ha ocurrido es que toda función vaya acompañada de un dominio válido (por defecto todo R.) Así, si derivo ln(x) me devuelve la función 1/x y un dominio asociado a esta (x>0) Los dominios además los puedo guardar programáticamente (las funciones en Scala son de primer nivel.) Entonces, cuando vaya a hacer la aplicación (-2.0 por ejemplo) sobre la función 1/x, en cuanto comprueba que -2.0 no está en el dominio CREEEEEJ me suela el error.

Sin embargo si aplicas -2.0 a 1/x con el dominio por defecto (es decir, la misma función PERO sin haber sido derivada de ln(x), y por tanto sin dominio asociado), no casca.

Como la aplicación de 2.0 se hace recursivamente desde las hojas hasta la raíz (de dentro a afuera) en cuanto el valor devuelto por alguna función no sea válido para el dominio de su función padre, casca. En definitiva: magia potagia.


La idea la derivada es una función a trozos es la derivada de sus trozos.
Es lo que había pensado, pero entonces me tendría que comer aún más la cabeza: ¡a cada dominio habría que asociarle una función! (que es básicamente lo que hace el truqui al que apuntas)

Pero la historia es que no hay ninguna función definida a trozos a mano, si no que la única que comenta que implementemos en |X| y creo que no necesito hacer magia alguna porque (|X|)' da el valor correcto en todo el dominio (y NaN en 0.0, que es donde no está definida...)

Así que creo que aquí sí que no me voy a complicar... aunque a lo mejor lo acabo haciendo con un map que mapee dominios a funciones y mato dos pájaros de un tiro, el problema anterior y este...


Con respecto a lo del final, en efecto, como dije antes, no hago en ningún momento cálculo simbólico y en el momento en el que aplicas una función a otra esta queda aplicada por huevos, así que no me voy a complicar la vida.

Usuarios habituales

  • elkaoD
  • freskito24