[ << ] [ < ] [ Acima ] [ > ] [ >> ]         [Topo] [Conteúdo] [Índice] [ ? ]

35 Encontrando raízes multidimencionais

Esse capítulo descreve funções para procura de raízes multidimensionais (resolvendo sistemas não lineares com n equações em n incógnitas). A biblioteca fornece componentes de baixo nível para uma variedade de resolvedores iterativos e testes de convergência. Esses resolvedores e testes podem ser combinados pelo usuário para encontrar a solução desejada, com acesso completo aos passos intermediários da iteração. Cada classe de métodos usa a mesma estrutura, de forma que você pode alternar entre resolvedores durante a execução sem precisar recompilar seu programa. Cada instância de um resolvedor mantém registros de seu próprio estado, permitindo aos resolvedores serem usados em programas com suporte a multiplas linhas de execução. Os resolvedores são baseados na bilioteca fortran original MINPACK.

O arquivo de cabeçalho ‘gsl_multiroots.h’ contém protótipos para funções de procura de raízes multidimensionais e declarações relacionadas.


[ << ] [ < ] [ Acima ] [ > ] [ >> ]         [Topo] [Conteúdo] [Índice] [ ? ]

35.1 Visão Geral

O problema de procura de raízes multidimensionais requer a solução simultânea de n equações, f_i, em n variáveis, x_i,

fi (x1, ..., xn) = 0       for i = 1 ... n.
Em geral não existem métodos de delimitação disponíveis para sistemas n dimensionais, e não meio de saber se qualquer das soluções existe. Todos os algoritmos procedem de uma suposição inicial usando uma variante da iteração de newton,
x → x′ = x − J−1 f(x)
onde x, f são quantidades de vetores e J é a matriz Jacobiana (68)matrix J_{ij} = d f_i / d x_j. Estratégia adicionais podem ser usadas para ampliar a região de convergência. Essas estratégias adicionais incluem exigir um decrescimo do módulo |f| sobre cada passo proposto pelo método de Newton, ou a tomada de passos extremamente íngremes e descendentes na direção do coeficiente angular negativo de |f|.

Muitos algoritmos de procura por raízes estão disponíveis dentro de uma estrutura simples. O usuário fornece um norteador de alto nível para os algoritmos, e a biblioteca fornece as funções individuais necessárias para cada um dos passos. Existem três principais fases da iteração. Os passos são,

A avaliação da matriz de Jacobi pode ser problemática, ou pelo fato de a programação das derivadas é intratável ou pelo fato de a computação dos n^2 termos da matriz torna-se muito dispendiosa. Por essas razões os algoritmos fornecidos pela biblioteca são divididos em duas classes conforme se as derivadas estão disponíveis ou não.

O estado para resolvedores com uma matriz analítica de Jacobi é mantida em uma estrutura gsl_multiroot_fdfsolver. O procedimento de atualização requer que ambas a função e suas derivadas sejam fornecidas pelo usuário.

O estado de resolvedores que não usam uma matriz analítica de Jacobi é mantido em uma estrutura gsl_multiroot_fsolver. O procedimento de atualização usa somente avaliações de função (não derivadas). Os algoritmos fazem estimativa da matriz J ou J^{-1} por métodos aproximados.


[ << ] [ < ] [ Acima ] [ > ] [ >> ]         [Topo] [Conteúdo] [Índice] [ ? ]

35.2 Inicializando o Resolvedor

As seguintes funções inicializam um resolvedor multidimensional, ou com ou sem derivadas. O resolvedor propriamente dito depende somente da dimensão do problema e do algoritmo e pode ser reusado para diferentes problemas.

Function: gsl_multiroot_fsolver * gsl_multiroot_fsolver_alloc (const gsl_multiroot_fsolver_type * T, size_t n)

Essa função retorna um apontador para uma instância recentemente alocada de um resolvedor do tipo T para um sistema de n dimensões. Por exemplo, o seguinte código cria uma instância de um resolvedor híbrido, para resolver um sistema tridimensional de equações.

const gsl_multiroot_fsolver_type * T 
    = gsl_multiroot_fsolver_hybrid;
gsl_multiroot_fsolver * s 
    = gsl_multiroot_fsolver_alloc (T, 3);

Se não houver memória suficiente para criar o resolvedor então a função retorna um apontador nulo e o controlador de erro é chamado com um código de erro de GSL_ENOMEM.

Function: gsl_multiroot_fdfsolver * gsl_multiroot_fdfsolver_alloc (const gsl_multiroot_fdfsolver_type * T, size_t n)

Essa função retorna um apontador para uma instância recentemente alocada de um resolvedor derivativo do tipo T para um sistema de n dimensões. Por exemplo, o seguinte código cria uma instância de resolvedor Newton-Raphson, para um sistema bidimensional de equações.

const gsl_multiroot_fdfsolver_type * T 
    = gsl_multiroot_fdfsolver_newton;
gsl_multiroot_fdfsolver * s = 
    gsl_multiroot_fdfsolver_alloc (T, 2);

Se não houver memória insuficiente para criar o resolvedor então a função retorna um apontador nulo e o controlador de erro é chamado com um código de erro GSL_ENOMEM.

Function: int gsl_multiroot_fsolver_set (gsl_multiroot_fsolver * s, gsl_multiroot_function * f, const gsl_vector * x)
Function: int gsl_multiroot_fdfsolver_set (gsl_multiroot_fdfsolver * s, gsl_multiroot_function_fdf * fdf, const gsl_vector * x)

Essas funções ajustam, ou zeram, um resolvedor existente s para usar a função f ou função e derivada fdf (69), e a suposição inicial x. Note que a posição inicial é copiada a partir de x, esse argumento não é modificado por subsequentes iterações.

Function: void gsl_multiroot_fsolver_free (gsl_multiroot_fsolver * s)
Function: void gsl_multiroot_fdfsolver_free (gsl_multiroot_fdfsolver * s)

Essas funções liberam toda a memória associada ao resolvedor s.

Function: const char * gsl_multiroot_fsolver_name (const gsl_multiroot_fsolver * s)
Function: const char * gsl_multiroot_fdfsolver_name (const gsl_multiroot_fdfsolver * s)

Essas funções retornam um apontador para o nome do resolvedor. Por exemplo,

printf ("s é um resolvedor '%s'\n", 
        gsl_multiroot_fdfsolver_name (s));

pode imprimir algo como s é um resolvedor 'newton'.


[ << ] [ < ] [ Acima ] [ > ] [ >> ]         [Topo] [Conteúdo] [Índice] [ ? ]

35.3 Fornecendo a função a ser resolvida

Vcê deve fornecer n funções de n variáveis para a rotina de procura de raízes trabalhar sobre essas funções e essas variáveis. Com o objetivo de permitir parâmetros em geral as funções são definidas pelos seguintes tipos de dados:

Data Type: gsl_multiroot_function

Esse tipo de dados define um sistema geral de funções com parâmetros.

int (* f) (const gsl_vector * x, void * params, gsl_vector * f)

essa função deve armazenar o vetor resultado f(x,params) em f para o argumento x e parâmetros params, retornando um código de erro apropriado se a função não puder ser calculada.

size_t n

a dimensão do sistema, i.e. o número de componentes dos vetores x e f.

void * params

um apontador para os parâmetros da função.

Aqui está um exemplo usando a função do teste de Powell,

f1(x) = A x0 x1 − 1,f2(x) = exp(−x0) + exp(−x1) − (1 + 1/A)
com A = 10^4. O seguinte código define um sistema gsl_multiroot_function F que você deve informar para um resolvedor:

struct powell_params { double A; };

int
powell (gsl_vector * x, void * p, gsl_vector * f) {
   struct powell_params * params 
     = (struct powell_params *)p;
   const double A = (params->A);
   const double x0 = gsl_vector_get(x,0);
   const double x1 = gsl_vector_get(x,1);

   gsl_vector_set (f, 0, A * x0 * x1 - 1);
   gsl_vector_set (f, 1, (exp(-x0) + exp(-x1) 
                          - (1.0 + 1.0/A)));
   return GSL_SUCCESS
}

gsl_multiroot_function F;
struct powell_params params = { 10000.0 };

F.f = &powell;
F.n = 2;
F.params = &params;
Data Type: gsl_multiroot_function_fdf

Esse tipo de dados define um sistema geral de funções com parâmetros e a correspondente matriz de Jacobi de derivadas,

int (* f) (const gsl_vector * x, void * params, gsl_vector * f)

essa função deve armazenar o vetor resultado f(x,params) em f para argumento x e parâmetros params, retornando um código de erro apropriado se a função não puder ser calculada.

int (* df) (const gsl_vector * x, void * params, gsl_matrix * J)

essa função deve armazenar a matriz resultado n-por-n J_ij = d f_i(x,params) / d x_j em J para argumento x e parâmetros params, retornando um código de erro apropriado se a função não puder ser calculada.

int (* fdf) (const gsl_vector * x, void * params, gsl_vector * f, gsl_matrix * J)

Essa função deve ajustar os valores de f e J como acima, para argumentos x e parâmetros params. Essa função fornece uma otomização de funções separadas para f(x) e J(x)—essa otimização separada é sempre mais rápido de calcular a função e sua derivada ao mesmo tempo.

size_t n

a dimensão do sistema, i.e. o número de componentes dos vetores x e f.

void * params

um apontador para os parâmetros da função.

O exemplo da função de teste de Powell definida acima pode ser extendida para incluir derivadas analíticas usando o seguinte código,

int
powell_df (gsl_vector * x, void * p, gsl_matrix * J) 
{
   struct powell_params * params 
     = (struct powell_params *)p;
   const double A = (params->A);
   const double x0 = gsl_vector_get(x,0);
   const double x1 = gsl_vector_get(x,1);
   gsl_matrix_set (J, 0, 0, A * x1);
   gsl_matrix_set (J, 0, 1, A * x0);
   gsl_matrix_set (J, 1, 0, -exp(-x0));
   gsl_matrix_set (J, 1, 1, -exp(-x1));
   return GSL_SUCCESS
}

int
powell_fdf (gsl_vector * x, void * p, 
            gsl_matrix * f, gsl_matrix * J) {
   struct powell_params * params 
     = (struct powell_params *)p;
   const double A = (params->A);
   const double x0 = gsl_vector_get(x,0);
   const double x1 = gsl_vector_get(x,1);

   const double u0 = exp(-x0);
   const double u1 = exp(-x1);

   gsl_vector_set (f, 0, A * x0 * x1 - 1);
   gsl_vector_set (f, 1, u0 + u1 - (1 + 1/A));

   gsl_matrix_set (J, 0, 0, A * x1);
   gsl_matrix_set (J, 0, 1, A * x0);
   gsl_matrix_set (J, 1, 0, -u0);
   gsl_matrix_set (J, 1, 1, -u1);
   return GSL_SUCCESS
}

gsl_multiroot_function_fdf FDF;

FDF.f = &powell_f;
FDF.df = &powell_df;
FDF.fdf = &powell_fdf;
FDF.n = 2;
FDF.params = 0;

Note que a função powell_fdf é capaz de reutilizar termos existentes da função ao fazer o cálculo da matriz de Jacobi, dessa forma economizando tempo.


[ << ] [ < ] [ Acima ] [ > ] [ >> ]         [Topo] [Conteúdo] [Índice] [ ? ]

35.4 Iteração

As seguintes funções norteiam a iteração de cada algoritmo. Cada função executa uma iteração para atualizar o estado de qualquer resolvedor de tipo correspondente. A mesma função trabalha para todos os resolvedores de forma que diferentes métodos podem ser substituídos durante a execução sem modificações no código.

Function: int gsl_multiroot_fsolver_iterate (gsl_multiroot_fsolver * s)
Function: int gsl_multiroot_fdfsolver_iterate (gsl_multiroot_fdfsolver * s)

Essas funções executam uma simples iteração do resolvedor s. Se a iteração encontrar um problema inesperado então um código de erro irá ser retornado,

GSL_EBADFUNC

a iteração encontrou um ponto singular onde a função ou sua derivada avaliou para Inf ou para NaN.

GSL_ENOPROG

a iteração não está fazendo qualquer progresso, evitando que o algoritmo continue trabalhando indefinidamente.

O resolvedor mantém uma atual melhor estimativa da raíz s->x e seu valor de função s->f em todas as vezes. Essa informação pode ser acessada com as seguintes funções auxiliares,

Function: gsl_vector * gsl_multiroot_fsolver_root (const gsl_multiroot_fsolver * s)
Function: gsl_vector * gsl_multiroot_fdfsolver_root (const gsl_multiroot_fdfsolver * s)

Essas funções retornam a atual estimativa de raíz para o resolvedor s, fornecida por s->x.

Function: gsl_vector * gsl_multiroot_fsolver_f (const gsl_multiroot_fsolver * s)
Function: gsl_vector * gsl_multiroot_fdfsolver_f (const gsl_multiroot_fdfsolver * s)

Essas funções retornam o valor de função f(x) na atual estimativa de raíz para o resolvedor s, fornecida por s->f.

Function: gsl_vector * gsl_multiroot_fsolver_dx (const gsl_multiroot_fsolver * s)
Function: gsl_vector * gsl_multiroot_fdfsolver_dx (const gsl_multiroot_fdfsolver * s)

Essas funções retornam o último passo dx tomado pelo resolvedor s, fornecido por s->dx.


[ << ] [ < ] [ Acima ] [ > ] [ >> ]         [Topo] [Conteúdo] [Índice] [ ? ]

35.5 Busca por Parâmetros de Parada

Um procedimento de procura de raíz deve parar quando uma das seguintes condições for verdadeira:

A manipulação dessas condições está sob o controle do usuário. As funções abaixo permitem ao usuário testar a precisão do atual resultado de muitas maneiras padronizadas.

Function: int gsl_multiroot_test_delta (const gsl_vector * dx, const gsl_vector * x, double epsabs, double epsrel)

Essa função testa a convergência da sequência comparando o último passo dx com o erro absoluto epsabs e com o erro relativo epsrel na posição atual x. O teste retorna GSL_SUCCESS se a seguinte condição for alcançada,

|dxi| < epsabs + epsrel  |xi|
para cada componente de x e retorna GSL_CONTINUE de outra forma.

Function: int gsl_multiroot_test_residual (const gsl_vector * f, double epsabs)

essa função testa o valor do resíduo de f contra o limite de erro absoluto epsabs. O teste retorna GSL_SUCCESS se a seguinte condição for encontrada,



i 
|fi| < epsabs
e retorna GSL_CONTINUE de outra forma. Esse critério é adequado para situações onde a precisa localização da raíz, x, é secundáriamente fornecida um valor pode ser encontrado onde o resíduo seja pequeno o suficiente.


[ << ] [ < ] [ Acima ] [ > ] [ >> ]         [Topo] [Conteúdo] [Índice] [ ? ]

35.6 Algoritmos Usando Derivadas

Os algoritmos de procura de raízes descritos nessa seção fazem uso de ambas a função e sua derivada. Eles requerem uma suposição inicial para a localização da raíz, mas não existe garantia absoluta de convergência—a função deve ser adequada a essa técnica e a suposição inicial deve estar suficientemente próxima da raíz para o método funcionar. Quando as condições forem satisfeitas então a convergência é quadrática.

Derivative Solver: gsl_multiroot_fdfsolver_hybridsj

Essa é uma versão modificada do método Híbrido de Powell da forma que foi implementado no algoritmo HYBRJ no pacote MINPACK. O Minpack foi escrito por Jorge J. Moré, Burton S. Garbow e Kenneth E. Hillstrom. O algoritmo Híbrido reté a convergência rápida do método de Newton mas irá também reduzir o resíduo quando o método de Newton for inseguro.

O algoritmo usa uma região de confiabilidade para manter cada passo sob controle. Com o objetivo de ser aceita uma nova posição proposta x' deve satisfazer a condição |D (x' - x)| < \delta, onde D é uma matriz diagonal escalonada e \delta é o tamanho da região de confiabilidade. As componentes de D são calculadas internamente, usando os módulos de coluna do determinante de Jacobi para estimar a sensibilidade do resíduo para cada componente de x. Isso melhora o comportamento do algoritmos para funções péssimamente escalonadas.

A cada iteração o algoritmo primeiramente determina o passo padrão de Newton resolvendo o sistema J dx = - f. Se esse passo localizar-se dentro da região de confiabilidade é usado como um passo de teste no estágio seguinte. Se não, o algoritmo usa a combinação linear de Newton e direções de coeficiente angular que é previsto para minimizar a norma da função enquanto aguardando dentro da região de confiabilidade,

dx = − αJ−1 f(x) − β∇|f(x)|2.
Essa combinação de Newton e direções de coeficiente angular é referenciada com um passo dogleg.

O passo proposto é agora testado por meio de uma avaliação de função no ponto resultante, x'. Se o passo reduzir a norma da função suficientemente então é aceita e o tamanho da região de confiabilidade é aumentado. Se o passo proposto falha em melhorar a solução então o tamanho da região de confiabilidade é diinuído e outro passo de teste é calculado.

A velocidade do algoritmo é aumentada calculando as mudanças para o determinante de Jacobi aproximadamente, usando uma atualização rank-1 (70). Se duas sucessivas tentativas em reduzir o resíduo o determinante de Jacobi em sua totalidade é recalculada. O algoritmo também monitora o progresso da solução e retorna um erro se muitos passos falharem em fazer qualquer melhoria,

GSL_ENOPROG

a iteração não está fazendo qualquer progresso, evita que o algoritmo continue.

GSL_ENOPROGJ

reavaliações do determinante de Jacobi indicam que a iteração não está fazendo qualquer progresso, evitando que o algoritmo continue.

Derivative Solver: gsl_multiroot_fdfsolver_hybridj

Esse algoritmo é uma versão não escalonada de hybridsj. Os passos são controlados por uma região de confiabilidade esférica |x' - x| < \delta, ao invés de por uma região generalizada. Pode ser útil se a região generalizada estimada por hybridsj seja inapropriada.

Derivative Solver: gsl_multiroot_fdfsolver_newton

O método de Newton é o algoritmo padrão para melhoria de raízes. O algoritmo inicia-se com uma suposição inicial para a localização da solução. A cada iteração uma aproximação linear para a função F é usada para estimar o passo que irá zerar todos os componentes do resíduo. A iteração é definida pela seguinte sequência,

x → x′ = x − J−1 f(x)
onde a matriz de Jacobi J é calculada a partir de funções de derivadas fornecidas por f. O passo dx é obtido resolvendo o sistema linear,
J  dx = − f(x)
usando decomposição LU. Se a matriz de Jacobi for singular, um código de erro de GSL_EDOM é retornado.

Derivative Solver: gsl_multiroot_fdfsolver_gnewton

Essa é uma versão modificada do método de Newton que tenta melhorar a convergência global requerendo a cada passo para reduzir o módulo Euclidiano do resíduo, |f(x)|. Se o passo de Newton levar a um incremento do módulo Euclideano então um passo reduzido de tamanho relativo,

t = (

 

1 + 6 r
 
− 1) / (3 r)
é proposto, com r sendo a razão dos módulos |f(x')|^2/|f(x)|^2. Esse procedimento é repetido até que um passo de tamanho adequado seja encontrado.


[ << ] [ < ] [ Acima ] [ > ] [ >> ]         [Topo] [Conteúdo] [Índice] [ ? ]

35.7 Algoritmos sem Derivadas

Os algoritmos descritos nessa seção não requerem que quaisquer informações de derivadas sejam fornecidas pelo usuário. Quaisquer derivadas necessárias são aproximadas por meio de diferenças finitas. Note que se o tamanho do passo da diferenciação finita mudado por essas rotinas for inapropriado, uma derivada numérica explícita fornecida pelo usuário pode sempre ser usada com os algoritmos descritos na seção anterior.

Solver: gsl_multiroot_fsolver_hybrids

Essa é uma versão do algoritmo Híbrido que substitui chamadas à função de cálculo do determinante de Jacobi por suas aproximações de diferenças finitas. A aproximação por diferenças finitas é calculado usando gsl_multiroots_fdjac com um tamanho de passo relativo de GSL_SQRT_DBL_EPSILON. Note que esse tamanho de passo não irá ser adequado para todos os problemas.

Solver: gsl_multiroot_fsolver_hybrid

Essa é uma versão por diferenças finitas do algoritmo Híbrido sem escalonamento interno.

Solver: gsl_multiroot_fsolver_dnewton

O algoritmo de Newton discreto é o mais simples método de resolver um sistema multidimensional. Usa uma iteração de Newton

x → x − J−1 f(x)
onde a matriz de Jacobi J é aproximadamente tomando diferenças finitas da função f. O esquema de aproximação usado por essa implementação é,
Jij = (fi(x + δj) − fi(x)) / δj
onde \delta_j é um passo de tamanho \sqrt\epsilon |x_j| com \epsilon sendo a precisão da máquina (\epsilon \approx 2.22 \times 10^-16). A ordem de convergência do algoritmo de Newton é quadrática, mas as diferenças finitas requerem n^2 avaliações de funções a cada iteração. O algoritmo pode tornar-se instável se as diferenças finitas não fornecerem uma boa aproximação para as verdadeiras derivadas.

Solver: gsl_multiroot_fsolver_broyden

O algoritmos de Broyden é uma versão do algoritmo discreto de Newton que tenta evitar atualização dispendiosa da matriz de Jacobi a cada iteração. As modificações no determinante de Jacobi são também aproximadas, usando uma atualização posto-1,

J−1 → J−1 − (J−1 df − dx) dxT J−1 / dxT J−1 df
onde os vetores dx e df são as modificações em x e f. Sobre a primeira iteração o inverso do determinante de Jacobi é estimado usando diferenças finitas, como no algoritmo de Newton discreto.

Essa aproximação fornece uma atualização rápida mas sem confiabilidade se as modificações não forem pequenas, e a estimativa do inverso do determinante de Jacobi torna-se pior com o decorrer do tempo. O algoritmo tem uma tendência a tornar-se instável a menos que comece perto da raíz. O determinante de Jacobi é atualizado se essa instabilidade for detectada (consulte o código fonte para detalhes).

Esse algoritmo é incluído somente com propósitos demonstrativos, e é recomendado seu uso somente para em testes superficiais e eventuais.


[ << ] [ < ] [ Acima ] [ > ] [ >> ]         [Topo] [Conteúdo] [Índice] [ ? ]

35.8 Exemplos

Os resolvedores multidimensionais são suados de forma similar aos algoritmos unidimensionais de procura de raízes. O primeiro exemplo demonstra algoritmo híbrido escalonado, hybrids, que não requer derivadas. O rograma resolve o sistema de equaçẽos de Rosenbrock,

f1 (x, y) = a (1 − x), f2 (x, y) = b (y − x2)
com a = 1, b = 10. a solução desse sistema localiza-se em (x,y) = (1,1) em um vale estreito.

O primeiro estágio do programa é definir o sistema de equações,

#include <stdlib.h>
#include <stdio.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_multiroots.h>

struct rparams
  {
    double a;
    double b;
  };

int
rosenbrock_f (const gsl_vector * x, void *params, 
              gsl_vector * f)
{
  double a = ((struct rparams *) params)->a;
  double b = ((struct rparams *) params)->b;

  const double x0 = gsl_vector_get (x, 0);
  const double x1 = gsl_vector_get (x, 1);

  const double y0 = a * (1 - x0);
  const double y1 = b * (x1 - x0 * x0);

  gsl_vector_set (f, 0, y0);
  gsl_vector_set (f, 1, y1);

  return GSL_SUCCESS;
}

O programa principal inicia criando o objeto de função f, com os argumentos (x,y) e parâmetros (a,b). O resolvedor s é inicializado para usar essa função, com método hybrids.

int
main (void)
{
  const gsl_multiroot_fsolver_type *T;
  gsl_multiroot_fsolver *s;

  int status;
  size_t i, iter = 0;

  const size_t n = 2;
  struct rparams p = {1.0, 10.0};
  gsl_multiroot_function f = {&rosenbrock_f, n, &p};

  double x_init[2] = {-10.0, -5.0};
  gsl_vector *x = gsl_vector_alloc (n);

  gsl_vector_set (x, 0, x_init[0]);
  gsl_vector_set (x, 1, x_init[1]);

  T = gsl_multiroot_fsolver_hybrids;
  s = gsl_multiroot_fsolver_alloc (T, 2);
  gsl_multiroot_fsolver_set (s, &f, x);

  print_state (iter, s);

  do
    {
      iter++;
      status = gsl_multiroot_fsolver_iterate (s);

      print_state (iter, s);

      if (status)   /* check if solver is stuck */
        break;

      status = 
        gsl_multiroot_test_residual (s->f, 1e-7);
    }
  while (status == GSL_CONTINUE && iter < 1000);

  printf ("status = %s\n", gsl_strerror (status));

  gsl_multiroot_fsolver_free (s);
  gsl_vector_free (x);
  return 0;
}

Note que é importante verificar a situação atual de retorno de cada passo de resolvedor, no caso do algoritmo travar. Se uma condição de erro for detectada, indicando que o algoritmo não pode continuar, então o erro pode ser informado ao usuário, um novo ponto de início é escolhido ou um diferente algoritmo usado.

O estado intermediário da solução é mostrado pela seguinte função. O estado do resolvedor contém o vetor s->x que é a posição atual, e o vetor s->f com os correspondentes valores de função.

int
print_state (size_t iter, gsl_multiroot_fsolver * s)
{
  printf ("iter = %3u x = % .3f % .3f "
          "f(x) = % .3e % .3e\n",
          iter,
          gsl_vector_get (s->x, 0), 
          gsl_vector_get (s->x, 1),
          gsl_vector_get (s->f, 0), 
          gsl_vector_get (s->f, 1));
}

Aqui está os resultados de execução do programa. O algoritmo é iniciado em (-10,-5) longe da solução. Uma vez que a solução está escondida em um vale estreito os primeiros passos seguem o coeficiente angular da função morro abaixo, em uma tentativa de reduzir o grande valor do resíduo. Uma vez que a raíz tenha sido aproximadamente localiada, na iteração 8, o comportamento de Newton assume e a convergência é muito rápida.

iter =  0 x = -10.000  -5.000  f(x) = 1.100e+01 -1.050e+03
iter =  1 x = -10.000  -5.000  f(x) = 1.100e+01 -1.050e+03
iter =  2 x =  -3.976  24.827  f(x) = 4.976e+00  9.020e+01
iter =  3 x =  -3.976  24.827  f(x) = 4.976e+00  9.020e+01
iter =  4 x =  -3.976  24.827  f(x) = 4.976e+00  9.020e+01
iter =  5 x =  -1.274  -5.680  f(x) = 2.274e+00 -7.302e+01
iter =  6 x =  -1.274  -5.680  f(x) = 2.274e+00 -7.302e+01
iter =  7 x =   0.249   0.298  f(x) = 7.511e-01  2.359e+00
iter =  8 x =   0.249   0.298  f(x) = 7.511e-01  2.359e+00
iter =  9 x =   1.000   0.878  f(x) = 1.268e-10 -1.218e+00
iter = 10 x =   1.000   0.989  f(x) = 1.124e-11 -1.080e-01
iter = 11 x =   1.000   1.000  f(x) = 0.000e+00  0.000e+00
status = success

Note que o algoritmo não atualiza a localização a cada iteração. Algumas iterações são usadas para ajustar o parâmetro de região de confiabilidade, após tentar um passo encontrado que era divergente, ou recalcular a matriz de Jacobi, quando comportamento pobre de convergência é detectado.

O programa exemplo seguinte adiciona informações de derivada, com o objetivo de acelerar a solução. Existem duas funções de derivadas rosenbrock_df e rosenbrock_fdf. O último calcula ambas a função e sua derivada simultâneamente. Isso permite a otimização de quaisquer termos comuns. Por simplicidade substituimos chamadas separadas às funções f e df nesse ponto no código abaixo.

int
rosenbrock_df (const gsl_vector * x, void *params, 
               gsl_matrix * J)
{
  const double a = ((struct rparams *) params)->a;
  const double b = ((struct rparams *) params)->b;

  const double x0 = gsl_vector_get (x, 0);

  const double df00 = -a;
  const double df01 = 0;
  const double df10 = -2 * b  * x0;
  const double df11 = b;

  gsl_matrix_set (J, 0, 0, df00);
  gsl_matrix_set (J, 0, 1, df01);
  gsl_matrix_set (J, 1, 0, df10);
  gsl_matrix_set (J, 1, 1, df11);

  return GSL_SUCCESS;
}

int
rosenbrock_fdf (const gsl_vector * x, void *params,
                gsl_vector * f, gsl_matrix * J)
{
  rosenbrock_f (x, params, f);
  rosenbrock_df (x, params, J);

  return GSL_SUCCESS;
}

O programa principal faz chamadas para as correspondentes versões fdfsolver das funções,

int
main (void)
{
  const gsl_multiroot_fdfsolver_type *T;
  gsl_multiroot_fdfsolver *s;

  int status;
  size_t i, iter = 0;

  const size_t n = 2;
  struct rparams p = {1.0, 10.0};
  gsl_multiroot_function_fdf f = {&rosenbrock_f, 
                                  &rosenbrock_df, 
                                  &rosenbrock_fdf, 
                                  n, &p};

  double x_init[2] = {-10.0, -5.0};
  gsl_vector *x = gsl_vector_alloc (n);

  gsl_vector_set (x, 0, x_init[0]);
  gsl_vector_set (x, 1, x_init[1]);

  T = gsl_multiroot_fdfsolver_gnewton;
  s = gsl_multiroot_fdfsolver_alloc (T, n);
  gsl_multiroot_fdfsolver_set (s, &f, x);

  print_state (iter, s);

  do
    {
      iter++;

      status = gsl_multiroot_fdfsolver_iterate (s);

      print_state (iter, s);

      if (status)
        break;

      status = gsl_multiroot_test_residual (s->f, 1e-7);
    }
  while (status == GSL_CONTINUE && iter < 1000);

  printf ("status = %s\n", gsl_strerror (status));

  gsl_multiroot_fdfsolver_free (s);
  gsl_vector_free (x);
  return 0;
}

A adição de informações de derivadas para o resolvedor hybrids não faz qualquer diferença significativa em seu comportamento, uma vez que o programa está apto a aproximar o determinante de Jacobi numericamente com suficiente precisão. Para ilustrar o comportamento de um resolvedor derivativo diferente mudamos para gnewton. É um tradicional resolvedor do tipo Newton com a restrição que escalona de volta seu passo se o passo completo conduzir “morro acima”. Aqui está a saída para o algoritmo gnewton,

iter = 0 x = -10.000  -5.000 f(x) =  1.100e+01 -1.050e+03
iter = 1 x =  -4.231 -65.317 f(x) =  5.231e+00 -8.321e+02
iter = 2 x =   1.000 -26.358 f(x) = -8.882e-16 -2.736e+02
iter = 3 x =   1.000   1.000 f(x) = -2.220e-16 -4.441e-15
status = success

A convergência é muito mais rápida, mas faz uma grande excursão para fora no ponto (-4.23,-65.3). Isso pode fazer com que o algoritmo extravie em uma aplicação realista. O algoritmo híbrido segue o caminho morro abaixo para a solução mais confiável.


[ << ] [ < ] [ Acima ] [ > ] [ >> ]         [Topo] [Conteúdo] [Índice] [ ? ]

35.9 Referências e Leituras Adicionais

A versão original do método Híbrido é descrito no seguintes artigos de autoria de Powell,

Os seguintes de artigos são relevantes para os algoritmos descritos nessa seção,


[ << ] [ >> ]           [Topo] [Conteúdo] [Índice] [ ? ]

Esse documento foi gerado em 23 de Julho de 2013 usando texi2html 5.0.