Рекурсивная программа для вычисления приближения Тейлора косинуса, не работающего в Прологе - PullRequest
2 голосов
/ 13 февраля 2020

Я все еще довольно новичок в Прологе, и я не уверен, почему этот код не работает. Я полагаю, что это, скорее всего, проблема с базовым случаем или в последних 3 строках рекурсивного случая. Все остальное работает просто отлично.

Эта программа определяет косинус, вычисленный с помощью приближения ряда ,

enter image description here

, для этого необходимо рассчитать факториал 2K, также -1 ^ K, и затем использует эти 2 вычисления в конечном уравнении (это делается в% Рекурсивный регистр).

% Factorial from class
fact(0, 1).
fact(N, F) :- 
    N > 0,
    N1 is N-1,
    fact(N1, F1),
    F is F1 * N.

% Calculate -1 ^ K
signCnt(0,1).
signCnt(K,S) :- 
    K > 0,
    K1 is K - 1,
    signCnt(K1,S1),
    S is S1 * -1.

% Base case
cosN(N,_,_,0).

% Recursive case
cosN(K,N,X,Y) :- K < N,
    signCnt(K,S),
    K2 is 2 * K,
    fact(K2,F),
    Yk is (S * X**K2)/F,
    K1 is K + 1,
    cosN(K1,N,X,Y1),
    Y is Y1 + Yk.

cosN(N,X,Y) :- 
    N>0,
    cosN(0,N,X,Y).

Входные данные должны иметь вид

?- cosN(25,pi,Y).

с ожидаемым выходным значением

Y = -1.0 ;
false.

, однако, он не go проходит через рекурсию должным образом, и результат в конечном итоге выглядит следующим образом: example output

, где 5 и pi могут быть чем угодно, пока pi остается в форме pi (то есть pi / 2, pi / 3), также не должно быть никаких дополнительных строк, как нам было дано ограничение номера строки. Линии должны быть отредактированы / заменены. Все, что укажет мне правильное направление, также будет высоко оценено.

(спасибо Guy Coder за помощь в форматировании)


Редактирование Guy Coder

Некоторые тестовые случаи с использованием SWI-Prolog

:- begin_tests(cosine_approximation).

factorial_test_case_generator(0,1).
factorial_test_case_generator(1,1).
factorial_test_case_generator(2,2).
factorial_test_case_generator(3,6).
factorial_test_case_generator(4,24).
factorial_test_case_generator(5,120).
factorial_test_case_generator(6,720).
factorial_test_case_generator(7,5040).
factorial_test_case_generator(8,40320).
factorial_test_case_generator(20,2432902008176640000).

test('factorial',[nondet,forall(factorial_test_case_generator(N,Factorial))]) :-
    fact(N,Factorial).

signCnt_test_case_generator(0,1).
signCnt_test_case_generator(1,-1).
signCnt_test_case_generator(2,1).
signCnt_test_case_generator(3,-1).
signCnt_test_case_generator(4,1).
signCnt_test_case_generator(5,-1).

test('signCnt',[nondet,forall(signCnt_test_case_generator(N,Sign))]) :-
    signCnt(N,Sign).

:- end_tests(cosine_approximation).

Пример выполнения:

?- make.
% c:/users/eric/documents/projects/prolog/so_question_161 compiled 0.00 sec, 5 clauses
% PL-Unit: cosine_approximation .......... done
% All 10 tests passed
true.

Ответы [ 2 ]

3 голосов
/ 14 февраля 2020

Базовый случай был неверным, должен был быть cosN (N, N, _, 0). поскольку K и N оба должны быть равны N, когда программа завершает рекурсивный процесс.

Контрольные примеры:

:- begin_tests(cosine_approximation).

factorial_test_case_generator(0,1).
factorial_test_case_generator(1,1).
factorial_test_case_generator(2,2).
factorial_test_case_generator(3,6).
factorial_test_case_generator(4,24).
factorial_test_case_generator(5,120).
factorial_test_case_generator(6,720).
factorial_test_case_generator(7,5040).
factorial_test_case_generator(8,40320).
factorial_test_case_generator(20,2432902008176640000).

test('factorial',[nondet,forall(factorial_test_case_generator(N,Factorial))]) :-
    fact(N,Factorial).

signCnt_test_case_generator(0,1).
signCnt_test_case_generator(1,-1).
signCnt_test_case_generator(2,1).
signCnt_test_case_generator(3,-1).
signCnt_test_case_generator(4,1).
signCnt_test_case_generator(5,-1).

test('signCnt',[nondet,forall(signCnt_test_case_generator(N,Sign))]) :-
    signCnt(N,Sign).

cosN_test_case_generator(3,pi/2,0.01996895776487828).
cosN_test_case_generator(5,pi,-0.9760222126236076).
cosN_test_case_generator(25,pi,-1.0).
cosN_test_case_generator(10,pi/2,-3.3306690738754696e-15).

test('cosN',[nondet,forall(cosN_test_case_generator(N,X,Y))]) :-
    cosN(N,X,Y).

:- end_tests(cosine_approximation).

Пример выполнения:

?- make.
% /Users/oliverclarke/prolog/lab5-quiz compiled 0.00 sec, 3 clauses
% PL-Unit: cosine_approximation .................... done
% All 20 tests passed
true.
1 голос
/ 14 февраля 2020

Просто дополнение

Мне пришлось подумать о том, действительно ли программа суммирует маленькие поплавки в последовательно большие пополам, а не маленькие поплавки в большие пополам (что может сделать результат более неточным, чем нужно), но это так.

Хотя нелегко полностью пересчитать факториал для каждого элемента ряда Тейлора и не использовать -1 * (k mod 2) для непосредственного получения (-1)^k, вместо этого проходя рекурсию.

Вот диаграмма вызова для ориентации:

Taylor series call diagram

Приложение 2: Код для более эффективного вычисления

Итак Я потратил некоторое время, чтобы выполнить упражнение по написанию cos приближения, которое просто повторяется само по себе и несет всю вспомогательную информацию для вычисления терминов и и суммы.

% ===
% Entry point!
% Evaluate the Taylor series for cos(z) at "z" (not too far from 0, probably
% less than 1). The terms (sum elements) for index values 0..K are computed
5 and added. (K >= 0)
% ===

taylor_cos(Res,Z,Kmax,Verbose) :- 
   Zf is Z*1.0, % make a float
   float(Zf),
   integer(Kmax),Kmax >= 0,
   Zsq is Zf*Zf,
   at_element_k(Res,0,Kmax,Zsq,_,_,Verbose).

% The value computed is always the first one

even(K) :- integer(K), (K mod 2) =:= 0. % eval left & compare numerically
odd(K)  :- integer(K), (K mod 2) =:= 1. % eval left & compare numerically

% Compute (-1)^k, k an integer >= 0.
% Computed value is on first place in predicate argument list.

minus_one_tothe_k( 1,K) :- even(K),!. % ! to make this deterministic
minus_one_tothe_k(-1,K) :- odd(K).    % actually no need to test odd(K)

% Compute (2*k)!, k an integer >= 0, if (2*(k-1))! is known.
% Computed value is on first place in predicate argument list.
% The base case is conceptually jarring as the "prior value" can be anything.
% This is not unlike a function becoming evaluatable because of lazy evaluation.

two_times_k_factorial(1  ,0,_)        :- !.
two_times_k_factorial(Res,K,ResPrior) :- K>0, Res is ResPrior*K*(4*K-2).

% Compute (z^(2*k)), k an integer >= 0, if (z^(2*(k-1))) is known.
% z² is passed too so that we do not need to recompute it again and again.
% Computed value is on first place in predicate argument list.

z_tothe_2k(1,   0, _   ,_)        :- !.
z_tothe_2k(Res, K, Zsq ,ResPrior) :- K>0, Res is ResPrior * Zsq.

% Compute the Taylor series by summing the elements(k) with k in [0..Kmax)
% (so Kmax >= 1).
% When calling this initially, the values for TTKFprior and ZTT2Kprior
% are of no importance. 
% The procedures calls itself recursively to compute element(i), element(i+1)
% etc. based on prior intermediate values. The base case is attained when
% K > Kmax. The sum accumulates in SumFromKmaxBackwards when the recursion
% comes back up the stack.

at_element_k(0.0,K,Kmax,_,_,_,Verbose) :-
   K > Kmax,!,
   ((Verbose = verbose) -> 
   format("past the end as K=~d > Kmax=~d, returning back up the stack\n",[K,Kmax]) ; true).

at_element_k(SumFromKmaxBackwards,K,Kmax,Zsq,TTKFprior,ZTT2Kprior,Verbose) :- 
   minus_one_tothe_k(M1TTK,K),                 % M1TTK = (-1)^K
   two_times_k_factorial(TTKF,K,TTKFprior),    % TTKF  = f(K,TTKFprior)
   z_tothe_2k(ZTT2K,K,Zsq,ZTT2Kprior),         % ZTT2K = f(K,z²,ZTT2Kprior)
   ElementK is M1TTK * ZTT2K / TTKF,           % element_k = M1TTK * (ZTT2K / TTKF)
   ((Verbose = verbose) -> format("element(~d) = ~e\n",[K,ElementK]) ; true),
   KP1 is K+1,
   at_element_k(SumFromKmaxBackwardsPrior,KP1,Kmax,Zsq,TTKF,ZTT2K,Verbose),
   SumFromKmaxBackwards is SumFromKmaxBackwardsPrior + ElementK,
   ((Verbose = verbose) -> format("taylor-series-sum(~d ... ~d) = ~e (added ~e to prior value ~e)\n",
                                  [K,Kmax,SumFromKmaxBackwards, ElementK, SumFromKmaxBackwardsPrior]) ; true).

Запустите это! Переменная Verbose установлена ​​в verbose, чтобы генерировать больше распечаток во время вычисления ряда Тейлора. Мы вычисляем 11 членов серии (индексы 0 ... 10).

?- taylor_cos(Res,0.01,10,verbose).
element(0) = 1.000000e+00
element(1) = -5.000000e-05
element(2) = 4.166667e-10
element(3) = -1.388889e-15
element(4) = 2.480159e-21
element(5) = -2.755732e-27
element(6) = 2.087676e-33
element(7) = -1.147075e-39
element(8) = 4.779477e-46
element(9) = -1.561921e-52
element(10) = 4.110318e-59
past the end as K=11 > Kmax=10, returning back up the stack
taylor-series-sum(10 ... 10) = 4.110318e-59 (added 4.110318e-59 to prior value 0.000000e+00)
taylor-series-sum(9 ... 10) = -1.561920e-52 (added -1.561921e-52 to prior value 4.110318e-59)
taylor-series-sum(8 ... 10) = 4.779476e-46 (added 4.779477e-46 to prior value -1.561920e-52)
taylor-series-sum(7 ... 10) = -1.147074e-39 (added -1.147075e-39 to prior value 4.779476e-46)
taylor-series-sum(6 ... 10) = 2.087675e-33 (added 2.087676e-33 to prior value -1.147074e-39)
taylor-series-sum(5 ... 10) = -2.755730e-27 (added -2.755732e-27 to prior value 2.087675e-33)
taylor-series-sum(4 ... 10) = 2.480156e-21 (added 2.480159e-21 to prior value -2.755730e-27)
taylor-series-sum(3 ... 10) = -1.388886e-15 (added -1.388889e-15 to prior value 2.480156e-21)
taylor-series-sum(2 ... 10) = 4.166653e-10 (added 4.166667e-10 to prior value -1.388886e-15)
taylor-series-sum(1 ... 10) = -4.999958e-05 (added -5.000000e-05 to prior value 4.166653e-10)
taylor-series-sum(0 ... 10) = 9.999500e-01 (added 1.000000e+00 to prior value -4.999958e-05)
Res = 0.9999500004166653.

Ум 80-колонной *1029* Stackoverflow действует мне на нервы. В настоящее время у нас на экранах миллионы пикселей ширины, и они не используются и остаются белыми, потому что "Muh Visual Design" !! В любом случае ...

Теперь добавьте код для генерации Count тестовых значений, равномерно распределенных между From и To. generator/4 генерирует последовательные значения при возврате. cos_compare/3 сравнивает то, что вычисляет наша cos -аппроксимирующая функция и что система вычисляет (что происходит где-то в глубине в библиотеке ).

generator(X,From,To,1) :- 
   From =< To,
   From_f is From*1.0,
   To_f   is To*1.0,
   X      is (From_f + To_f) / 2.0.

generator(X,From,To,Count) :- 
   integer(Count), 
   Count > 1,
   From =< To,
   From_f  is From*1.0,
   To_f    is To*1.0,
   Delta_f is (To_f - From_f)/(Count * 1.0),
   CountM1 is Count-1, 
   between(0,CountM1,I), 
   X is From_f + Delta_f*I.

cos_compare(Z,Kmax,Verbose) :-
   taylor_cos(Res,Z,Kmax,Verbose),
   Cos is cos(Z),
   Delta is abs(Res-Cos),
   format("For z = ~e, k_max = ~d, difference to real cos = ~e\n", [Z, Kmax, Delta]).

Тогда давайте на самом деле сравним 100 значений между float -4.0 и float +4.0 и, где мы вычисляем 11 членов (индексы 0..11) ряда Тейлора для каждого значения:

run(Verbose) :- forall(generator(Z,-4.0,+4.0,100), cos_compare(Z,10,Verbose)).

?- run(quiet).  
For z = -4.000000e+00, k_max = 10, difference to real cos = 1.520867e-08
For z = -3.920000e+00, k_max = 10, difference to real cos = 9.762336e-09
For z = -3.840000e+00, k_max = 10, difference to real cos = 6.209067e-09
For z = -3.760000e+00, k_max = 10, difference to real cos = 3.911487e-09
For z = -3.680000e+00, k_max = 10, difference to real cos = 2.439615e-09
......
For z = 3.680000e+00, k_max = 10, difference to real cos = 2.439615e-09
For z = 3.760000e+00, k_max = 10, difference to real cos = 3.911487e-09
For z = 3.840000e+00, k_max = 10, difference to real cos = 6.209067e-09
For z = 3.920000e+00, k_max = 10, difference to real cos = 9.762336e-09
true.

Не выглядит так плохо.

Приложение 3: Использование "диктов" SWI-Prolog для связи между предикатами

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

Давайте попробуем это и здесь.

Это ограничено SWI-Prolog, потому что "dicts" - это функция SWI-Prolog . Подобный код делает механизм индексации Пролога бесполезным, поскольку теперь каждый предикат имеет точно такой же аргумент, Dict, поэтому должен быть относительно медленным во время выполнения.

Только измененные предикаты

taylor_cos(Res,Z,Kmax,Verbose) :-
   Zf is Z*1.0, % make a float
   float(Zf),
   integer(Kmax),Kmax >= 0,
   Zsq is Zf*Zf,
   at_element_k(taylor{  sum     : Res  % the result
                        ,k       : 0
                        ,kmax    : Kmax
                        ,zsq     : Zsq
                        ,ttkf_prior  : _
                        ,ztt2k_prior : _
                        ,verbose : Verbose }).


% ---
% Base case, when k > kmax
% ---

% We map the passed "Dict" to a sub-Dict to grab values.
% As this is "unification", not only "pattern matching" the value for
% sum "0.0" is shared into "Dict".

at_element_k(Dict) :-
   taylor{  sum     : 0.0
           ,k       : K
           ,kmax    : Kmax
           ,verbose : Verbose } :< Dict,

   K > Kmax,  % guard
   !,         % commit
   ((Verbose = verbose) ->
      format("past the end as K=~d > Kmax=~d, returning back up the stack\n",[K,Kmax])
      ; true).

% ---
% Default case, when k <= kmax
% ---

% We map the passed "Dict" to a sub-Dict to grab values.
% We use ":<" instead of "=" so that, if the passed Dict has more values
% than expected (which can happen during program extension and fiddling),
% "partial unification" can still proceed, "=" would fail. However, no
% values can be missing!
% This gives us also the funny possibility of completely ignoring Kmax in
% the "input Dict", it doesn't appear anywhere and is still passed down
% through the recursive call. Well, it *does* appear because we print it
% out.

at_element_k(Dict) :-
   taylor{  sum         : SumFromKmaxBackwards  % the output value, to be captured by the caller
           ,k           : K                     % index of the current term/element in the Taylor sum
           ,kmax        : Kmax                  % max index for which a term/element will be computed
           ,zsq         : Zsq                   % z², a constant
           ,ttkf_prior  : TTKFprior             % prior "two times k factorial" i.e. (2*(k-1))!
           ,ztt2k_prior : ZTT2Kprior            % prior "z to the 2*k" i.e. z^(2*(k-1))
           ,verbose     : Verbose } :< Dict,    % emit messages about progress if Verbose = verbose

   minus_one_tothe_k(M1TTK,K),                       % compute (-1)^K
   two_times_k_factorial(TTKF,K,TTKFprior),          % compute (2*k)! based on prior value
   z_tothe_2k(ZTT2K,K,Zsq,ZTT2Kprior),               % compute z^(2*k) based on prior value
   ElementK is M1TTK * ZTT2K / TTKF,                 % compute value for Taylor sum term/element at k

   % (isn't there a better way to print conditionally?)

   ((Verbose = verbose) ->
      format("element(~d) = ~e\n",[K,ElementK])
      ; true),

   % create a NextDict from Dict for recursive call

   KP1 is K+1,
   put_dict( _{ sum        : SumFromKmaxBackwardsPrior
               ,k          : KP1
               ,ttkf_prior : TTKF
               ,ztt2k_prior: ZTT2K }, Dict, NextDict),

   % recursive call 
   % (foundational thought: the procedure is really a **channel-doing-computations between the series of dicts**)

   at_element_k(NextDict),

   % on return, complete summing the Taylor series backwards from highest index to the current index k

   SumFromKmaxBackwards is SumFromKmaxBackwardsPrior + ElementK,

   % (more conditional printing)

   ((Verbose = verbose) ->
      format("taylor-series-sum(~d ... ~d) = ~e (added ~e to prior value ~e)\n",
            [K,Kmax,SumFromKmaxBackwards,ElementK,SumFromKmaxBackwardsPrior])
      ; true).

Это более читабельно? Я так полагаю.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...