SEPARACIÓN Y BÚSQUEDA DE RAÍCES E INTERPOLACIÓN Yensur Camacho Hernández. 2110206 1. Se tiene la función f(x)= ( ) ( ), donde p es un parámetro que varía entre 1.0 y 10.0, en intervalos enteros de uno en uno. a. Separación de raíces. Convirtiendo la función en una ecuación, se tiene la igualdad ( ). En una gráfica de ambas partes de la igualdad como funciones, los puntos en que se intersectan las curvas, son las raíces de la función. La función de la parte izquierda, es un logaritmo elevado al cuadrado, de la cual se sabe que predomina el logaritmo sobre el exponente. Por lo tanto, ésta tiene una concavidad hacia abajo. De la función del lado derecho, se sabe que es un coseno con amplitud variable, donde la función f(x)=x representa el contorno. Dado el comportamiento (concavidad) de la primera función, se sabe que en el intervalo (1,20), siempre es superior la recta. Para un parámetro p=1.0, las curvas son las siguientes: Fig. 1. Funciones para un parámetro p=1. Para un parámetro p=10.0, las funciones son las siguientes: Fig. 2. Funciones para un parámetro p=10. (Se utilizó un software para realizar las gráficas. Sin embargo, su uso no es obligatorio, debido a que se las deducciones sobre las tendencias de las curvas, se realizaron sin disponer de éste. Estos son resultados puramente analíticos). Del estudio de las funciones periódicas, se sabe que el parámetro p representa la frecuencia de la función coseno, y que el producto por la variable x únicamente altera su amplitud (contorno). El coseno se hace cero, cada vez que el ángulo se hace múltiplo impar de π/2. Es decir, si empezamos en π/2, cada vez que sumemos π nos encontraremos con una raíz. Pero dado que el parámetro p altera la frecuencia de la función se tiene que cada π/p se tiene una raíz. Así, a medida que se incremente p, también aumentará el número de raíces en un mismo intervalo (“densidad de raíces”). Por otra parte, si se toman unos cuantos valores de la función logarítmica, y se comparan con los valores de la recta y=x, se observa que la altura de la recta es considerablemente superior a la de la curva (esto se sabe por la dominancia del logaritmo sobre la potencia o por su concavidad). Este resultado es importante, dado que se puede encontrar raíces a una menos distancia que π/p, dependiendo de la altura a la que se encuentren las dos curvas. Un intervalo de π/2p parecería suficiente “distancia” para evitar encontrar dos raíces juntas, pero por seguridad se utilizó π/4p como paso. b. Cálculo de las raíces. El programa escrito en el lenguaje C, está diseñado para calcular las raíces de la función especificada inicialmente, dentro del intervalo (1,20) y para valores del parámetro p entre 1 y 10, aumentando de uno en uno y contemplando una tolerancia de . Asimismo, para cada valor de p, el programa indica la cantidad de raíces encontradas dentro del intervalo. /* Programa que calcula las raíces de una función específica, dentro de un intervalo establecido y en función de un parámetro p que varía de forma controlada y conocida */ #include<stdio.h> #include<math.h> #define pi 3.14159265 //Funciones o subrutinas double fun(double x,double p); double raiz(double a,double b,double p); int main(){ int nroot,salto=0; double a,b,p,paso,i,root=0.00000000; printf("\nPROGRAMA QUE CALCULA LAS RAICES DE UNA FUNCIÓN DADA, DENTRO DEL INTERVALO (1,20), Y PARA DIFERENTES PARÁMETROS P \n",p); for(p=1.00000000;p<=10.00000000;p++){ nroot=0; paso=pi/(4.00000000*p); printf("\n\nLas raices para la función con el parámetro p=%lf son:\n\n",p); for(i=1.00000000;i<20.00000000;i=i+paso){ a=i; b=i+paso; if ((fun(a,p)*fun(b,p))<0.00000000) { root=raiz(a,b,p); nroot=nroot+1; printf("x%d= %.8lf, f(x%d)=%.9lf\t\t",nroot,root,nroot,fun(root,p)); salto++; //Orden en consola if(salto==3){ printf("\n"); salto=0; } } } printf("\n\nPara la función con el parámetro p=%lf, se encontraron %d raíces, en el intervalo (1,20)\n\n\n",p,nroot); printf("________________________________________\n"); } return 0; } // Función que calcula la raíz para un intervalo (a,b) double raiz(double a,double b,double p){ int i=0; double c,e=0.00000001; while(fun(a,p)>e || fun(a,p)<-e){ c=(b+a)/2; if((fun(a,p)*fun(c,p))<0.00000000) b=c; else if ((fun(c,p)*fun(b,p))<0.00000000)a=c; } return a; } // Función que evalúa cada valor double fun(double x,double p){ double f; f=(log(x))*(log(x)) - x*cos(p*x); return f; } c. Variación de la cantidad de raíces con respecto al parámetro p. El programa anterior, también está diseñado para indicar la cantidad de raíces halladas para cada parámetro, donde p varía entre 1 y 10 de uno en uno. A continuación, se muestra un histograma que presenta la cantidad de raíces halladas dentro del intervalo (1,20) para cada parámetro diferente: Cantidad de raices Número de raices para cada parámetro 70 60 50 40 30 20 10 0 Raices 1 7 2 12 3 18 4 24 5 30 6 37 7 43 8 48 Fig .3. Histograma de la cantidad de raíces para cada parámetro. 9 54 10 61 d. Comparación entre el método de bisección y el método de Newton. Los siguientes dos programas, permiten calcular la primera raíz de la función dada en el intervalo (1,20) para los parámetros p=1 y p=10, respectivamente. Asimismo, muestra la cantidad de iteraciones necesarias para llegar a tal resultado usando cada método. Método de bisección. /* PROGRAMA QUE CALCULA LA PRIMERA RAIZ DE UNA FUNCIóN DADA DENTRO DE UN INTERVALO */ #include<stdio.h> #include<math.h> #define pi 3.14159265 //Funciones double fun(double x,double p); //Programa principal int main(){ int iter; double a,b,c,p,paso; printf("\n\nPROGRAMA QUE CALCULA LA PRIMERA RAIZ DE UNA FUNCIÓN DADA DENTRO DE UN INTERVALO USANDO EL MÉTODO DE BISECCIÓN\n\n"); for(p=1.00000000; p<=10.00000000;p=p*10.0){ printf("\n__________________________________\n\nBúsqueda de la primera raiz para la función dada, en el intervalo (1,20), para un parámetro p=%.8lf\n\n",p); paso=pi/(20*p); a=1.00000000; b=a+paso; while((fun(a,p)*fun(b,p))>0.00000000){ a=b; b=b+paso; } iter=0; while((fun(a,p))>0.00000001 || (fun(a,p))<-0.00000001 || (fun(b,p))>0.00000001 || (fun(b,p))<-0.00000001){ c=(b+a)/2; if((fun(a,p)*fun(c,p))<0.00000000) b=c; else if ((fun(c,p)*fun(b,p))<0.00000000)a=c; iter=iter+1; if((fun(a,p))>0.00000001 || (fun(a,p))<-0.00000001) printf("x%d=%.8lf, f(x%d)=%.9lf.\n\n",iter,a,iter,fun(a,p),b,fun(b,p)); else printf("x%d=%.8lf, f(x%d)=%.9lf.\n\n",iter,b,iter,fun(b,p)); } printf("\nNúmero de iteraciones: %d\n\n",iter); } return 0; } //Función para evaluar la "función" double fun(double x,double p){ double f; f=(log(x))*(log(x)) - x*cos(p*x); return f; } Método de Newton. /* PROGRAMA QUE CALCULA LA PRIMERA RAIZ DE UNA FUNCIÓN DADA DENTRO DE UN INTERVALO */ #include<stdio.h> #include<math.h> #define pi 3.14159265 //Funciones double fun(double x,double p); double derivuno(double x,double p); double derivdos(double x,double p); //Programa principal int main(){ int iter; double a,b,p,paso,x; printf("\n\nPROGRAMA QUE CALCULA LA PRIMERA RAIZ DE UNA FUNCIÓN DADA DENTRO DE UN INTERVALO USANDO EL MÉTODO DE NEWTON\n\n"); for(p=1.00000000; p<=10.00000000;p=p*10.0){ printf("\n__________________________________\n\nBúsqueda de la primera raiz para la función dada, en el intervalo (1,20), para un parámetro p=%.8lf\n\n",p); paso=pi/(20*p); a=1.00000000; b=a+paso; while((fun(a,p)*fun(b,p))>0.00000000){ a=b; b=b+paso; } if((fun(a,p)*derivdos(a,p))>0.00000000) x=b; else if((fun(b,p)*derivdos(b,p))>0.00000000) x=a; iter=0; while((fun(x,p))>0.00000001 || (fun(x,p))<-0.00000001){ iter=iter+1; x= x-(fun(x,p)/derivuno(x,p)); printf("x%d=%.8lf, f(x%d)=%.9lf.\n\n",iter,x,iter,fun(x,p)); } printf("\nNúmero de iteraciones: %d\n\n",iter); } return 0; } //Función para evaluar la "función" double fun(double x,double p){ double f; f=(log(x))*(log(x)) - x*cos(p*x); return f; } //Función que evalúa la primera derivada double derivuno(double x,double p){ double g; if(p==1.00000000) g=(x*sin(x))+((2.00000000*log(x))/x)-cos(x); else if(p==10.00000000) g=(2.00000000*log(x)/x)+(10.00000000*x*sin(10.00000000*x))-cos(x); return g; } //Función que evalúa la segunda derivada double derivdos(double x,double p){ double h; if(p==1.00000000) h=(2.00000000*sin(x)) + (x*cos(x)) ((2.00000000*log(x))/(x*x)) + (2.00000000/(x*x)); else if(p==10.00000000) h=(20.00000000*sin(10*x))+(100.00000000*x*cos(10.00000000*x))((2.00000000*log(x))/(x*x))+(2.00000000/(x*x)); return h; } El método de bisección requirió de 24 y 25 iteraciones para calcular la primera raíz de los parámetros p=1 y p=10, respectivamente. Por su parte, el método de Newton necesitó realizar únicamente 4 y 5 ciclos (en el mismo orden). Esto se debe principalmente a que a medida que se está más cerca de la raíz, el método de Newton se acerca mucho más al valor real (porque la pendiente de la recta tangente es más inclinada hacia la raíz), mientras que el método de bisección realiza particiones uniformes siempre. 2. Se tiene la función ( ) ( ) ( ) entre a=1 y b=5. a. Valores de la función. Se calcularon los valores de la función, utilizando un programa que genera números ( )( aleatorios entre 0 y 1, donde se tiene ). Tab. 1. Datos de la función con un error ε= 0.000001. i 1 2 3 4 5 6 7 8 9 10 11 xi 1.0 1.4 1.8 2.2 2.6 3.0 3.4 3.8 4.2 4.6 5.0 yi 0.259590 0.130271 -0.240861 0.796175 -1.405977 -1.902580 -2.122269 -1.946222 -1.332566 -0.332860 0.9106070 Tab. 2. Datos de la función con un error ε=0.1. i 1 2 3 4 5 6 7 8 9 10 11 xi 1.0 1.4 1.8 2.2 2.6 3.0 3.4 3.8 4.2 4.6 5.0 yi 0.270045 0.141019 -0.249052 0.833427 -1.488352 -1.955351 -2.161273 -2.102620 -1.347872 -0.335111 0.961361 b. Cálculo de los valores de la función usando el polinomio de Lagrange. El siguiente programa, permite calcular f(x) para un x que pertenece al intervalo [1,5], por medio del polinomio de LaGrange, a partir de los datos de (Tab. 1-2): #include<stdio.h> #include<math.h> #include<stdlib.h> #define e 0.000001 //En esta línea se cambia el error................................ double fun(double x); double exacta(double x); int main(){ int i,j,h=0; double a=1.000000,b=5.000000,x[11],y[11],P[201],paso,L[11],z; printf("\n\nPROGRAMA QUE REALIZA INTERPOLACIÓN USANDO LA FÓRMULA DE LAGRANGE\n\n"); paso=(b-a)/10.000000; for(z=1.0;z<=5.0;z=z+0.02){ // Lectura de X y Y, e inicialización de L for(i=0;i<11;i++){ x[i]=a+(i*paso); y[i]=fun(x[i]); L[i]=1.0; } //Fórmula de Lagrange for(i=0;i<11;i++){ for(j=0;j<11;j++){ if(i!=j){ L[i]=L[i]*(z-x[j])/(x[i]-x[j]); } } } // Cada vez que pasa, el valor del polinomio se reinicia P[h]=0.000000; for(i=0;i<11;i++){ P[h]=P[h]+ L[i]*y[i]; } printf("X=%lf , P(%d)=%lf , Error=%lf\t\t",z,h,P[h],exacta(z)-P[h]); h++; if(h%3 ==0.0) printf("\n\n"); } return 0; } double fun(double x){ double f,rn; rn=(drand48() * (0.94-0.1)+0.05); f=((log(1.0+x)*log(1.0+x))*cos(x)*(1.0+e*rn)); return f; } double exacta(double x){ double f,rn; f=((log(1.0+x)*log(1.0+x))*cos(x)); return f; } El programa permite realizar el cálculo tomando en cuenta diferentes errores en la función (ε=0.1 y ε=0.000001), solo basta con ajustar el valor de ε en cada caso. Si el programa se ejecuta para un error de ε=0.1 en la función, se puede observar que en algunos casos el error en la imagen es superior a la unidad, siendo demasiado elevado. Sin embargo, al cambiar el parámetro e (error) por ε=0.000001, se puede ver que la diferencia entre el valor exacto y el valor aproximado, calculado por este método, recién aparece a partir de la quinta cifra decimal (10-5). Razón por la cual, se puede deducir que ha sido una aproximación aceptable. c. Interpolación segmentaria línea y cuadrática. Adicionalmente, tomando las mismas doscientas divisiones uniformes del intervalo [1,5] como malla, se realizó el cálculo de cada una de las imágenes utilizando interpolación polinomial segmentaria lineal y parabólica. Asimismo, cada programa registra el error cometido en la aproximación con respecto al valor exacto. //Interpolación lineal #include<stdio.h> #include<math.h> #include<stdlib.h> #define e 0.000001 double fun(double x); double exacta(double x); int main(){ int i,j,h=0; double a=1.0,b=5.0,z,paso,cota,c,d,P; printf("\n\nPROGRAMA QUE INTERPOLA UNA FUNCIÓN USANDO UN POLINOMIO DE PRIMER GRADO\n\n\n"); paso=(b-a)/10.0; //---------------------------------------//Condición fuerte: X debe estar entre a y b for(z=1.0;z<=5.0;z=z+((b-a)/200.0)){ for(cota=1.0;cota<=5.0;cota=cota+paso){ if(z>=cota && z<(cota+paso)){ c=cota; d=cota+paso; } } P=0.000000; P= fun(c) + ((z-c)*(fun(d)-fun(c))/(d-c)); printf("X=%lf , P(%d)=%lf , Error=%lf\t\t",z,h+1,P,exacta(z)-P); h++; if(h%3 ==0.0) printf("\n\n"); } printf("\n"); //----------------------------------------return 0; } double fun(double x){ double f,rn; rn=(drand48() * (0.94-0.1)+0.05); f=((log(1.0+x)*log(1.0+x))*cos(x)*(1.0+e*rn)); return f; } double exacta(double x){ double f,rn; f=((log(1.0+x)*log(1.0+x))*cos(x)); return f; } /* Interpolación parabólica de segundo orden*/ #include<stdio.h> #include<math.h> #include<stdlib.h> #define e 0.000001 double fun(double x); double exacta(double x); int main(){ int i,j,h=0; double a=1.0,b=5.0,z,paso,cota,c,d,P,r; printf("\n\nPROGRAMA QUE INTERPOLA UNA FUNCIÓN USANDO UN POLINOMIO DE SEGUNDO GRADO\n\n\n"); paso=(b-a)/10.0; //---------------------------------------//Condición fuerte: X debe estar entre a y b for(z=1.0;z<=5.0;z=z+((b-a)/200.0)){ for(cota=1.0;cota<=5.0;cota=cota+paso){ if(z>=cota && z<(cota+(2*paso))){ c=cota; d=cota+paso; r=cota+(2*paso); } } P=0.000000; P= fun(c)+((z-c)*(fun(d)-fun(c))/(d-c)) + ((fun(r)(2*fun(d))+fun(c))*(z-c)*(z-d)/(d-c)*(d-c)); printf("X=%lf , P(%d)=%lf , Error=%lf\t\t",z,h+1,P,exacta(z)-P); h++; if(h%3 ==0.0) printf("\n\n"); } printf("\n"); //----------------------------------------return 0; } double fun(double x){ double f,rn; rn=(drand48() * (0.94-0.1)+0.05); f=((log(1.0+x)*log(1.0+x))*cos(x)*(1.0+e*rn)); return f; } double exacta(double x){ double f,rn; f=((log(1.0+x)*log(1.0+x))*cos(x)); return f; } En términos generales, los errores cometidos por interpolación lineal y parabólica, no son demasiado superiores con respecto a la interpolación por el polinomio de LaGrange, cuando el error ε en la función es alto. Pese a esto, se destaca como más preciso el método de LaGrange, seguido del método parabólico y por último el método lineal (esto se puede comprobar ejecutando los programas respectivos que están diseñados para mostrar el error para cada nodo). Sin embargo, cuando el error ε se hace arbitrariamente más pequeño, el polinomio de LaGrange aumenta considerablemente su precisión, disminuyendo su error en varios órdenes de magnitud. Mientras tanto, en los métodos polinomiales no se producen mayores cambios, dado que su precisión se basa en la unión por medio de curvas suaves entre los puntos. Por esta razón, a medida que los valores de x se alejan de los nodos, los métodos lineales pierden precisión. A continuación, se muestran algunos resultados de la ejecución de cada programa para demostrar las afirmaciones realizadas, dado que para sacar dichas conclusiones, los programas deben ser compilados y ejecutados. Se muestran datos únicamente de ε=10-6. Fórmula de LaGrange. Forma polinomial lineal. Forma polinomial parabólicas
© Copyright 2024