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

prev.gif (997 bytes)chapter.gif (1105 bytes)home.gif (1232 bytes)next.gif (1211 bytes)

Raíces de una ecuación

Raíces múltiples

Niveles de energía de un pozo de potencial

Código fuente


Raíces múltiples

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 raices 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íces 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;
    } 

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

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

Creamos un objeto de la clase Funcion1, 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.

        double[] raices=new Funcion1().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]);
        }

 

Niveles de energía de un pozo de potencial

Vamos a resolver un problema clásico de la Física consistente en calcular los niveles de energía de un pozo de potencial de anchura 2a y profundidad H. Los niveles de energía de simetría par se hallan mediante la ecuación trascendente de la energía E.

y los niveles de simetría impar se hallan mediante la ecuación

donde

Nuestra estrategia para resolver el problema va a consistir en crear una jerarquía de clases derivadas de la clase base Ecuacion, en las que se redefinan las funciones f(x). En la figura podemos ver la relación jerárquica entre las clases que resuelven este problema.

figura14_05.gif (2050 bytes)

La clase Pozo se deriva de Ecuacion y define las características geométricas de un pozo de potencial, su semianchura a, y de profundidad H. Ya que deriva de una clase abstracta y no redefine la función f(x), es también una clase abstracta. El constructor de la clase derivada Pozo inicializa sus miembros dato a y h, que son la semianchura del pozo de potencial y su profundidad. Ya que los niveles de energía están comprendidos entre 0 y h, definimos la función hallarNiveles para ahorrarnos el pasar los argumentos 0 y h en la llamada a la función miembro hallarRaices de la clase base Ecuacion.

public abstract class Pozo extends Ecuacion {
    protected double a;   //semianchura
    protected double h;   //profundidad
    public Pozo(double a, double h) {
        this.a=a;
        this.h=h;
    }
    public double[] hallarNiveles(double paso){
        hallarRaices(paso, h-paso, paso);
    }
}

Las clases derivadas PozoPar y PozoImpar, de la clase base Pozo, llaman al constructor de la clase base para inicializar los miembros dato comunes a ambas clases: semianchura a del pozo y profundidad h del mismo, y redefinen la función miembro f(x) declarada abstract en la clase base.

public class PozoPar extends Pozo{
    public PozoPar(double a, double h) {
        super(a, h);
    }
    public double f(double x){
        double q=Math.sqrt(x);
        double k=Math.sqrt(h-x);
        double y=k*Math.cos(q*a)-q*Math.sin(q*a);
        return y;
    }
}
public class PozoImpar extends Pozo {
    public PozoImpar(double a, double h) {
        super(a, h);
    }
    public double f(double x){
        double q=Math.sqrt(x);
        double k=Math.sqrt(h-x);
        double y=q*Math.cos(q*a)+k*Math.sin(q*a);
        return y;
    }
}

Para hallar los niveles de energía de un pozo de potencial podemos utilizar varias alternativas. Crear objetos de la clases PozoPar y PozoImpar y llamar desde ellos a la función miembro hallarNiveles de la clase base Pozo.

	PozoPar par=new PozoPar(1, 6);
	double[] nivelesPar=par.hallarNiveles(0.5);
	double[] nivelesImpar=new PozoImpar(1, 6).hallarNiveles(0.5);

Como vimos al estudiar la herencia y el polimorfismo podemos guardar en una variable de la clase base Pozo el valor devuelto por new al crear un objeto de la clase derivada.

	Pozo pozo=new PozoPar(1, 6);
	double[] nivelesPar=pozo.hallarNiveles(0.5);
	pozo=new PozoImpar(1, 6);
	double[] nivelesImpar=pozo.hallarNiveles(0.5);

El polimorfismo, se manifiesta en que la función hallarNiveles llama a la función hallarRaices y ésta a explorar y puntoMedio, todas ellas miembros de la clase base Ecuacion, en estas dos últimas se llama a la función f(x). ¿A cual de las dos funciones f(x) llama?. Justamente a la función f(x) definida en la clase a la que pertenece el objeto que llama a la función hallarNiveles.

 

Código fuente

disco.gif (1035 bytes) Ecuacion.java, Funcion1.java, EcuacionApp3.java

disco.gif (1035 bytes) Ecuacion.java, Pozo.java, PozoPar.java, PozoImpar.java, EcuacionApp4.java