Siguiente Anterior

El método mitad (raíces múltiples )

Descripción

La representación gráfica de la función en una pantalla de alta resolución nos permitirá estimar los intervalos en los que la función cambia de signo, y aplicar en consecuencia el procedimiento mitad a cada intervalo. Si no es posible una representación gráfica o esta no es de la suficiente resolución, será preciso explorar el eje X en busca de intervalos en los que la función cambia de signo. Cuando los encontremos aplicaremos a cada uno de ellos el procedimiento mitad.

Esta exploración no es sencilla, ya que podemos encontrarnos con intervalos en que la función no cambia de signo ya sea por que no tiene raíces, o por que tiene un número par, tal como se ve en la figura. La solución a este problema es hacer más pequeño el intervalo de exploración, esto implica más tiempo de cálculo, y no garantiza que las raíces puedan ser encontradas si algunas de ellas están muy juntas, o la curva es tangente al eje X.

FIG14_04.gif (1938 bytes)

La función explorar necesitará tres datos, el punto de partida, el de llegada y el paso de exploración.

void explorar(double xIni, double xFin, double dx){
//...
}

Verificará si los valores de la función en los extremos del intervalo explorado (x, x+dx) son del mismo o de distinto signo. En el primer caso, continuará con otro intervalo, en el segundo, buscará la raíz que se encuentra en dicho intervalo aplicando el procedimiento mitad.

            if(f(x)*f(x+dx)>0.0){
                continue;
            }
            raiz=puntoMedio(x, x+dx);

Solamente, nos queda emplear una sentencia iterativa para que se exploren todos los intervalos de longitud dx comprendidos entre el punto de partida xIni y el de llegada xFin, y guardar las raíces encontradas en el array raices. La definición de la función explorar quedará como sigue.

    void explorar(double xIni, double xFin, double dx){
        for(double x=xIni; x<xFin; x+=dx){
            if(f(x)*f(x+dx)>0.0){
                continue;
            }
            raices[iRaiz++]=puntoMedio(x, x+dx);
        }
    }

Este código es correcto, pero se puede hacer más eficiente, un asunto de vital importancia en el cálculo intensivo. Nos daremos cuenta, que se calcula dos veces la función f para el mismo valor de x, en el final de un intervalo y en el comienzo del siguiente, ahorraremos el tiempo que tarda al procesador en realizar estas operaciones si guardamos el valor de f(x) calculado al final del intervalo previo en la variable local y2, y lo asignamos a la variable y1, que guarda el valor de la función f(x) en el principio del intervalo siguiente.

Por otra parte, tendremos que tener en cuenta que el extremo de un intervalo puede ser una raíz y por último, que no se podrá superar la dimensión del array raices, donde se guardan las raíces buscadas. El código completo de la función explorar quedará de la siguiente forma.

protected void explorar(double xIni, double xFin, double dx){
        double y1, y2;
	    iRaiz=0;
        y1=f(xIni);
        for(double x=xIni; x<xFin; x+=dx){
            y2=f(x+dx);
//uno de los extremos del intervalo es raíz
            if(Math.abs(y1)<CERO && iRaiz<MAXRAICES){
                raices[iRaiz++]=x;
                y1=y2;
                continue;
            }
//no hay raíz en este intervalo
            if(y1*y2>=0.0){
                y1=y2;
                continue;
            }
//hay una raíz en este intervalo
            if(iRaiz<MAXRAICES){
                try{
                    raices[iRaiz]=puntoMedio(x, x+dx);
                    iRaiz++;
                }catch(RaizExcepcion ex){
                    System.out.println(ex.getMessage());
                }
            }
            y1=y2;
        }
    }

Para hallar una raíz simple en el intervalo (x, x+dx), empleamos la función puntoMedio que estudiamos en la página anterior. Ya que la función puntoMedio puede lanzar una excepción, la llamada a dicha función se debe de efectuar en un bloque try ... catch. De este modo, se notifica al usuario que el procedimiento numérico ha sido incapaz de hallar la raíz de dicha ecuación, mediante el mensaje "No se ha encontrado la raíz" que se extrae del objeto ex de la clase RaizExcepcion mediante la función getMessage.

Por ejemplo, si cambiamos el valor de MAXITER a 11 una de las raíz 0.347 de la ecuación polinómica x4-3x2+x=0 no se puede encontrar, lanzándose una excepción que nos notifica esta circustancia.

Para hallar las raíces de la ecuación dento de un intervalo y con un paso dado, se llama a la función miembro hallarRaices, que llama a la función explorar y devuelve el array en el que se guardan las raíces calculadas con tres decimales de precisión.

  public double[] hallarRaices(double ini, double fin, double paso){
        explorar(ini, fin, paso);
        double[] solucion=new double[iRaiz];
        for(int i=0; i<iRaiz; i++){
            solucion[i]=(double)Math.round(raices[i]*1000)/1000;
        }
        return solucion;
    }
 

El código completo de la clase abstracta Ecuacionque contiene el procedimiento numérico es

public abstract class Ecuacion {
           protected static final double CERO=1e-10;
           protected static final double ERROR=0.0001;
           protected final int MAXITER=100;
           protected final int MAXRAICES=20;
           protected double raices[]=new double[MAXRAICES];
           protected int iRaiz=0;
 protected double puntoMedio(double a, double b)throws RaizExcepcion{
             double m, ym;
             int iter=0;
             do{
                  m=(a+b)/2;
                  ym=f(m);
                  if(Math.abs(ym)<CERO)           break;
                  if(Math.abs((a-b)/m)<ERROR)     break;
if((f(a)*ym)<0) b=m; else a=m; iter++; }while(iter<MAXITER); if(iter==MAXITER){ throw new RaizExcepcion("No se ha encontrado una raíz"); } return m; }
 protected void explorar(double xIni, double xFin, double dx){
             double y1, y2;
             iRaiz=0;
             y1=f(xIni);
             for(double x=xIni; x<xFin; x+=dx){
                   y2=f(x+dx);
       //Uno de los extremos del intervalo es raíz
                  if(Math.abs(y1)<CERO && iRaiz<MAXRAICES){
                       raices[iRaiz++]=x;
                       y1=y2;
                       continue;
                   }
       //no hay raíz en este intervalo
                   if(y1*y2>=0.0){
                        y1=y2;
                        continue;
                   }
       //hay una raíz en este intervalo
                   if(iRaiz<MAXRAICES){
                          try{
                                 raices[iRaiz]=puntoMedio(x, x+dx);
                                 iRaiz++;
                          }catch(RaizExcepcion ex){
                                 System.out.println(ex.getMessage());
                          }
                   }
                   y1=y2;
             }
  }
  public double[] hallarRaices(double ini, double fin, double paso){
            explorar(ini, fin, paso);
            double solucion[]=new double[iRaiz];
            for(int i=0; i<iRaiz; i++){
                 solucion[i]=(double)Math.round(raices[i]*1000)/1000;
} return solucion; }
  abstract public double f(double x);
}
class RaizExcepcion extends Exception {
        public RaizExcepcion(String s) {
            super(s);
        }
}

Para comprobar el procedimiento busquemos las raíces de la ecuación x4-3x2+x=0. Para ello definimos una clase denominada Funcion derivada de la clase base abstracta Ecuacion, y redefinimos la función miembro f(x) que describe la eacuación.

public class Funcion extends Ecuacion{
    public double f(double x){
        return(x*x*x*x-3*x*x+x);
    }
}

Creamos un objeto de la clase Funcion, y llamamos desde dicho objeto a la función miembro hallarRaices, definida en la clase base abstracta, pasándole el intervalo y el paso de exploración.

public class Aplicacion {
           public static void main(String[] args) {
                   double[] raices=new Funcion().hallarRaices(-5, 5, 0.1);
                   System.out.print("Raíces de la ecuación");
                   for(int i=0; i<raices.length; i++){
                         System.out.print("  "+raices[i]);
                   }
                   System.out.println("");
}

Una de las raíces es x1=0, las otras raíces son las de la ecuación cúbica x3-3x+1=0, que son x2=-1.879, x3=0.347, x4=1.532.

Siguiente Anterior