I'm still pretty new to Prolog, and i'm not sure why this code isn't working. I believe it is most likely a problem with the base case or in the last 3 lines of the recursive case. Everything else works just fine.
This program determines cosine calculated with series approximation,
to do so it needs to calculate the factorial of 2K, also -1 ^ K, and then uses these 2 calculations in the final equation (this is done in % Recursive Case).
% 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).
Inputs should be in the form
?- cosN(25,pi,Y).
with an expected output of
Y = -1.0 ;
false.
however, it doesn't go through the recursion properly and the output ends up looking like this:
where 5
and pi
could be anything so long as pi remains in form pi (i.e. pi/2, pi/3), also there shouldn't be any additional lines added, as we were given a line number restriction. Lines should be edited/replaced. Anything to point me in the right direction would be greatly appreciated too.
(Thank you to Guy Coder for help formatting)
Edit by Guy Coder
Some test cases using 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).
Example run:
?- 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.
Just an addendum
I had to think about whether the program indeed sums small floats together into successively larger floats and not small floats onto larger floats (which might render the result more imprecise than it needs to), but it does.
Although it's inelegant to recompute the factorial fully at each element of the Taylor series and to not use
-1 * (k mod 2)
to obtain(-1)^k
directly, instead going through recursion.Here is the call diagram for orientation:
Addendum 2: Code for more efficient computation
So I availed myself to some time to perform the exercise of writing a
cos
approximation that just recurses on itself and carries all the ancillary information for computing the terms and and the sum.Run this! The
Verbose
variable is set toverbose
to generate more printout during the Taylor series computation. We compute 11 terms of the series (indexes 0...10).The 80-column mind of Stackoverflow is getting a bit on my nerves. We have a gazillion pixels of width on screens nowadays, and they are unused and left white because "Muh Visual Design"!! Anyway...
Now add some code to generate
Count
test floats evenly distributed betweenFrom
andTo
. Thegenerator/4
generates successive values on backtracking. Thecos_compare/3
compares what ourcos
-approximating function computes and what the system computes (which comes somewhere deep down in a library).Then let's actually compare 100 values between float
-4.0
and float+4.0
and, where we compute 11 terms (indexes 0..11) of the Taylor series at each value:Not looking so bad.
Addendum 3: Using SWI-Prolog "dicts" to communicate between predicates
I have found that when writing Perl functions, it is often advantageous to short-circuit position-based argument passing and pass a single bunch of name-value pairs, aka 'hashes' instead. This adds a lot of flexibility (named parameters, easy to add parameters, easy to debug, easy to pass parameters to subfunctions etc.)
Let's try this here too.
This is restricted to SWI-Prolog because "dicts" are an SWI-Prolog feature. Code like this renders Prolog's indexing mechanism useless, as now every predicate has exactly the same argument,
Dict
, so should be relatively slow at runtime.Just the modified predicates are
Is it more readable? I suppose so.
Base case was wrong, should've been cosN(N,N,_,0). as K and N must both be equal to N when the program is finished the recursive process.
Test cases:
Example run: