Метод Ньютона в Perl

Итак, для моего математического класса нас попросили написать программу, которая выполняет и печатает метод Ньютона до тех пор, пока значения не сойдутся и у нас не будет корня для функции. Сначала я думал, что это будет легко. Это было до тех пор, пока я просто не смог получить значения, полученные в первый раз, для использования во второй раз. Мои знания языка базовые. Действительно простой, так что то, что вы собираетесь увидеть, может быть некрасивым.

#!usr/bin/perl

use PDL;

print "First guess? (this is x0)\n";
$xorig = <>;

do {
  &fx;
} until ($fex == 0);

sub fx {

  if ($xn == 0) {
    $x = $xorig;
  }
  else {
    $x = $xn;
  }

  print "What is the coefficient (for each factor) of your function?\n";
  $fcx = <STDIN>;
  push @coefficient_of_x, $fcx;

  print "... times x to the (enter exponent, if no exponent, enter 1. if no x, enter 0)?\n";
  $fex = <STDIN>;
  push @exponent_x, $fex;

  chomp ($fcx, $fex, $x, $xorig);

  $factor = $fcx * ($x ** $fex);
  push @fx, $factor;
}

my $fx = 0;
foreach my $variable (@fx) {
  $fx = $variable + $fx #THIS PROVIDES A VALUE FOR THE GIVEN F(X) WITH A GIVEN X VALUE
}
print "f($x)=$fx\n";

do {
  &fprimex;
} until ($fprimeex == 0);

sub fprimex {

  if ($xn == 0) {
    $x = $xorig;
  }
  else {
    $x = $xn;
  }

  print "What is the coefficient (for each factor) of your derivative function?\n";
  $fprimecx = <STDIN>;
  push @coefficient_of_fpx, $fprimecx; 

  print "... times x to the (enter exponent, if no exponent, enter 1. if no x, enter 0)?\n";
  $fprimeex = <STDIN>;
  push @exponent_fpx, $fprimeex;

  chomp ($fprimecx, $fprimeex, $x, $xorig);

  $factorprime = $fprimecx * ($x ** $fprimeex);
  push @fprimex, $factorprime;
}

$fprimex = 0;
foreach my $variableprime (@fprimex) {
  $fprimex = $variableprime + $fprimex #THIS PROVIDES A VALUE FOR THE GIVEN F'(X) WITH THAT SAME X VALUE
}
print "f'($x)=$fprimex\n";

sub x0 {
  $xn = $xorig - $fx / $fprimex; #THIS IS NEWTON'S METHOD EQUATION FOR THE FIRST TIME
  push @newxn, $xn;
  print "xn ia $xn\n";
}

&x0;

foreach $value (@exponent_x) {
  $exponent_x = $xn ** $value;
  push @part1, $exponent_x;
  $part1 = @part1;
}

foreach $value2 (@coefficient_of_x) {
  $part2 = $value2 * @part1;
  push @final1, $part2;
}

print "@part1\n";
print "@final1\n";

По сути, что это такое, я сначала прошу первое предположение. Я использую это значение для определения коэффициентов и показателей степени f(x), чтобы получить значение f(x) в терминах заданного x. Я делаю это снова для f'(x). Затем я выполняю метод Ньютона в первый раз и получаю новое значение xn. Но мне трудно получить значения для f(xn) и f'(xn), а это означает, что я не могу получить x(n+1) и не могу продолжить метод Ньютона. Мне нужна помощь.


person Lucas    schedule 09.11.2014    source источник


Ответы (2)


Добро пожаловать в Перл.

Я настоятельно рекомендую следующие изменения в вашем коде:

  1. Всегда указывайте use strict; и use warnings; в КАЖДОМ Perl-скрипте.
  2. Всегда chomp ваш ввод из STDIN, как вы его принимаете:

    chomp( my $input = <STDIN> );
    
  3. Не создавайте без необходимости подпрограммы, особенно для таких одноразовых сценариев, как этот.

  4. Вместо использования модификатора инструкции формы do, я бы рекомендовал использовать бесконечный while с операторами управления циклом для выхода:

    while (1) {
    
        last if COND;
    }
    
  5. Наконец, поскольку все коэффициенты вашего полинома связаны с показателем степени для X, я бы рекомендовал использовать %hash для удобного сохранения этих значений.

Как показано:

#!usr/bin/env perl
use strict;
use warnings;

print "Build your Polynomial:\n";

my %coefficients;

# Request each Coefficient and Exponent of the Polynomial
while (1) {
    print "What is the coefficient (for each factor) of your function? (use a bare return when done)\n";
    chomp( my $coef = <STDIN> );

    last if $coef eq '';

    print "... times x to the (enter exponent, if no exponent, enter 1. if no x, enter 0)?\n";
    chomp( my $exp = <STDIN> );

    $coefficients{$exp} = $coef;
}

print "\nFirst guess? (this is x0)\n";
chomp( my $x = <> );

# Newton's Method Iteration
while (1) {
    my $fx  = 0;
    my $fpx = 0;

    while ( my ( $exp, $coef ) = each %coefficients ) {
        $fx += $coef * $x**$exp;
        $fpx += $coef * $exp * $x**( $exp - 1 ) if $exp != 0;
    }

    print "   f(x) = $fx\n";
    print "   f'(x) = $fpx\n";

    die "Slope of 0 found at $x\n" if $fpx == 0;

    my $new_x = $x - $fx / $fpx;

    print "Newton's Method gives new value for x at $new_x\n";

    if ( abs($x - $new_x) < .0001 ) {
        print "Accuracy reached\n";
        last;
    }

    $x = $new_x;
}
person Miller    schedule 09.11.2014
comment
Спасибо. Много. Это имеет гораздо больше смысла, чем то, что я делал раньше. Двойной запрос пользователя на ввод действительно раздражает, и меня раздражало простое тестирование снова и снова. Ваши предложения очень полезны, и они помогут мне, когда я попытаюсь узнать больше. Как вы, наверное, понимаете, мои знания и опыт минимальны. Я только что понял, что есть еще одна проблема. Одна из проблем, с которой я должен использовать это, включает триггерные функции. Есть ли простой способ, которым я мог бы обработать это в этом? - person Lucas; 10.11.2014

У меня возникли проблемы с определением того, что вы намеревались использовать в своем коде. Основная проблема, по-видимому, заключается в том, что у вас в голове не ясно, что делает каждая из ваших подпрограмм, поскольку fx и fprimex запрашивают данные, а также оценивают функцию (за исключением суммирования условий, которое, как ни странно, выполняется вне подпрограмма). Это совсем не то, что вам нужно, так как показатели степени и коэффициенты остаются постоянными на протяжении всей программы, которая должна много раз оценивать эти функции, и вы действительно не хотите каждый раз запрашивать значения снова.

Вот несколько советов, как заставить Perl-код работать в целом.

  • Пишите программу небольшими порциями — по одной-две строки за раз — и проверяйте после каждого добавления, что программа компилируется, запускается и дает ожидаемые результаты. Написание всей программы еще до того, как вы попытаетесь ее запустить, — прямой путь к катастрофе.

  • Всегда use strict и use warnings, и объявляйте каждую переменную с my как можно ближе к точке, где она впервые используется. У вас есть много необъявленных переменных, которые, следовательно, являются глобальными, и передача информации между разделами кода с использованием глобальных переменных — хороший способ потеряться в собственном коде. Для подпрограммы рекомендуется использовать только переданные ей параметры или объявленные в ней переменные.

  • chomp переменные, как только они будут прочитаны либо из файла, либо из терминала. Полезная идиома для обрезки входных строк в источнике:

    chomp(my $input = <>)
    

    что гарантирует, что в ваших данных нет случайных новых строк

По крайней мере, это должно заставить вас начать.

Я сомневаюсь, стоит ли это показывать. Я надеюсь, что это поможет вам, но я действительно не хочу втягивать вас в те части Perl, с которыми вы не знакомы.

Это программа, которая использует метод Ньютона–Рафсона для нахождения корня многочлена. На данный момент я пропустил ввод терминала и жестко запрограммировал данные. В нынешнем виде он находит квадратный корень из 3000, находя положительный корень из x2 - 3000.

Обратите внимание, что @f и @f_prime содержат коэффициенты функции и ее производной назад от обычного порядка, поэтому @f равно (-3000, 0, 1). Программа также вычисляет коэффициенты производной функции, так как это просто и гораздо предпочтительнее, чем запрашивать у пользователя другой набор значений.

Есть только одна подпрограмма polynomial, которая принимает значение для x и список коэффициентов. Это используется для вычисления значения как функции, так и ее производной для заданного значения x.

Шаг алгоритма находится в строке

my $new_x = $x - polynomial($x, @f) / polynomial($x, @f_prime);

который вычисляет x - f(x) / f'(x) и присваивает его $new_x. Каждая последующая оценка выводится в STDOUT до выхода из цикла.

Сравнение значений с плавающей запятой на равенство — плохая идея. Точность компьютерных значений с плавающей запятой, очевидно, ограничена, и последовательность, вероятно, никогда не сойдется к точке, в которой два последних значения последовательности равны. Лучшее, что можно сделать, это проверить, находится ли абсолютное значение разницы между двумя последними значениями ниже разумной дельты. Точность 10E12 приемлема для 32-битных чисел с плавающей запятой. Я обнаружил, что последовательность сходится в пределах 10E14 довольно надежно, но если вы обнаружите, что ваша программа зависает в бесконечном цикле, вам следует увеличить запас.

use strict;
use warnings;

my @f       = reverse (1, 0, -3000);
my @f_prime = map { $f[$_] * $_ } 1 .. $#f;

my $x = 0.5;
print $x, "\n";

while () {
  my $new_x = $x - polynomial($x, @f) / polynomial($x, @f_prime);
  last if abs($new_x - $x) < $x / 1e14;
  $x = $new_x;
  print $x, "\n";
}

sub polynomial {
  my ($x, @coeffs) = @_;

  my $total = 0;
  my $x_pow = 1;

  for my $coeff (@coeffs) {
    $total += $x_pow * $coeff;
    $x_pow *= $x;
  }

  $total;
}

вывод

0.5
3000.25
1500.62495833681
751.312062703027
377.652538627869
192.798174296885
104.179243809523
66.4878834504349
55.8044433107163
54.7818016853582
54.7722565822241
54.7722557505166
person Borodin    schedule 09.11.2014
comment
Это тоже здорово. Единственное, что я не уверен, что смогу сделать, это легко работать с пользовательским вводом, не запрашивая тонну информации. В какой-то момент я был настолько потерян, что просто создавал подпрограммы, к которым мог обратиться, если бы у меня появилась другая идея, которая, как я думал, могла бы помочь. - person Lucas; 10.11.2014
comment
@Lucas: Обработка пользовательского ввода должна быть довольно простой. Взяв мой код в качестве примера, нужно просто написать код, который позволяет вручную вводить значения в @f вместо назначения буквального списка. Вы можете или не хотите сохранять линию, которая различает коэффициенты. После этого, IMO, лучше писать подпрограммы только в том случае, если есть большой раздел вашего алгоритма, который можно разделить как do this, или если есть несколько вхождений похожих процессов, которые можно параметризовать. Думаю, для этого нужен опыт, а также выбор стиля. - person Borodin; 10.11.2014
comment
@ Лукас: Может быть, тебе стоит написать все это без подпрограмм? Это было бы вполне приемлемым решением, и я уверен, что вы это заметите, если устанете повторять один и тот же код! Не верьте своему наставнику, что все должно быть в подпрограмме или что вы должны много комментировать свой код. Хорошая программа должна выражать свое значение с помощью правильного выбора идентификаторов и разумного использования пробелов, разделяющих функциональность на фрагменты. Лишь изредка вам придется добавлять минимальные комментарии, поясняющие, почему необходима та или иная строчка кода. - person Borodin; 10.11.2014
comment
Таким образом, в вашем коде, если бы я запросил пользовательский ввод и использовал push для ввода этих переменных в @f, я теоретически мог бы использовать введенные пользователем данные? Я только что попробовал это, и это не сработало, и я думаю, что могу объяснить это своим недостатком знаний. - person Lucas; 10.11.2014
comment
@Lucas: Пользовательский ввод (и вывод) — безусловно, самые неудобные части программирования. По сравнению с этим промежуточные манипуляции с данными тривиальны. Есть много способов сделать это. Забудем пока о коэффициентах для производной, все, что вам нужно, это ряд (для квадратного) трех чисел с клавиатуры. Вы можете принять все три числа в одной строке или по одному числу в строке. Вы должны спросить себя, что вы хотели бы ввести для ввода. Вы хотите ввести, скажем, 1 0 -3000? Это было бы самым простым. Или вы хотите заниматься ерундой типа 1 xx PI() - person Borodin; 10.11.2014
comment
@Lucas: Если все, что вы хотите, это ввести данные в программу, забыв обо всех тонкостях, тогда вы можете написать просто my @f = reverse split ' ', <>. Затем, если вы запустите свою программу и наберете 1 0 -3000, массив @f будет таким же, как и при буквальном назначении в моем решении. Вы захотите добавить подсказку и т. д., но я надеюсь, вы поняли идею? - person Borodin; 10.11.2014
comment
Я старался. Но когда я ввел значения, он просто работал бесконечно и не останавливался. Вот что я ввел my @f = reverse split ' ', <>; my @f_prime = map { $f[$_] * $_ } 1 .. $#f; chomp (my $x = <>); print $x, "\n"; - person Lucas; 10.11.2014
comment
Извини, что я такая назойливая. Это, наверное, как иметь дело с 3-летним ребенком. - person Lucas; 10.11.2014
comment
Вам не нужен второй <>. Первая строка считывает все значения в @f. Помните, я говорил о тестировании небольшими порциями? Создайте программу, в которой есть только my @f = reverse split ' ', <> и print "@f\n". Затем вы можете увидеть, что делает эта строка, и вставить ее обратно в вашу основную программу. - person Borodin; 10.11.2014
comment
Вы знаете, как использовать Data::Dumper? Это стоит больше, чем любое количество моих слов, чтобы помочь вам исправить ваш код. Data::Dump снова намного лучше, но это не основной модуль, и вам может понадобиться установить его. - person Borodin; 10.11.2014