This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
polynomialmani.pl/polymani.pl

1779 lines
51 KiB
Perl
Raw Normal View History

2018-12-09 00:01:22 +00:00
%% -*- Mode: Prolog-*-
2018-11-21 15:23:35 +00:00
%% vim: set softtabstop=4 shiftwidth=4 tabstop=4 expandtab:
2018-11-17 16:14:13 +00:00
2018-11-22 17:49:55 +00:00
/**
*
* polimani.pl
*
* Assignment 1 - Polynomial Manipulator
* Programming in Logic - DCC-FCUP
*
* Diogo Peralta Cordeiro
* up201705417@fc.up.pt
*
* Hugo David Cordeiro Sales
* up201704178@fc.up.pt
*
*********************************************
2018-11-22 17:49:55 +00:00
* Follows 'Coding guidelines for Prolog' *
* https://doi.org/10.1017/S1471068411000391 *
2018-11-23 00:09:07 +00:00
*********************************************/
2018-11-22 17:49:55 +00:00
/*
* Import the Constraint Logic Programming over Finite Domains library
* Essentially, this library improves the way Prolog deals with integers,
* allowing more predicates to be reversible.
* For instance, number(N) is always false, which prevents the
* reversing of a predicate.
*/
2018-11-22 11:51:19 +00:00
:- use_module(library(clpfd)).
/*
* Import Constraint Logic Programming for Reals library, which is somewhat
* similar to clpfd, but for real numbers
*/
:- use_module(library(clpr)).
2018-11-22 11:51:19 +00:00
/*
* The porter_stem library implements the stemming algorithm described by
* Porter in Porter, 1980, ``An algorithm for suffix stripping''.
* The library also includes a functional tokenizer
*/
:- use_module(library(porter_stem)).
2018-12-07 16:11:11 +00:00
/*******************************
* NLP *
*******************************/
2018-12-10 17:37:34 +00:00
%% polyplay() is det
%
% Interactive prompt for the NLP Interface
%
2018-12-07 16:11:11 +00:00
polyplay :-
%% Replace the prompt and save the old one
2018-12-17 16:31:10 +00:00
prompt(OldPrompt, '> '),
%% Read a line as codes, from the 'user_input' stream
read_line_to_codes(user_input, InCodes),
%% Restore old prompt
2018-12-17 16:31:10 +00:00
prompt(_, OldPrompt),
%% Use the porter_stem library to tokenize the input
%% This does more than splitting at the spaces, such as
%% splitting at operators. Aditionally, gives
%% atoms already and handles 'P1' well
tokenize_atom(InCodes, LA),
2018-12-07 16:11:11 +00:00
(
%% If we read a 'bye', terminate
2018-12-17 16:31:10 +00:00
LA == [bye],
write("See ya"),
nl,
2018-12-17 16:31:10 +00:00
!
2018-12-07 16:11:11 +00:00
;
(
2018-12-20 04:03:38 +00:00
%% Parse the input into a tree
parse_input(TIn, LA, NC),
(
%% If the tree is empty, it means nothing was understood
TIn == void,
writeln("I didn't understand what you want."),
writeln(NC)
;
(
%% If there is unconsumed input, it means there was a syntatic error
NC \== [],
write("Syntax error in token: "),
%% Take the head of the list
cons(H, _, NC),
write(H),
nl
;
%% Otherwise, process the parse tree and execute the commands within
process_input(TIn)
)
)
;
2018-12-20 21:52:17 +00:00
%% Parsing failed
writeln("Could not parse input, so I didn't understand what you want.")
2018-12-20 04:03:38 +00:00
),
%% Go back to the beginning
polyplay
),
!.
text2poly(S, P) :-
split_string(S, " ", "\r\t", LS),
parse_polynomial(T, LS, []),
polynomial_tree_to_polynomial(T, P1),
simpoly(P1, P).
2018-12-07 16:11:11 +00:00
2018-12-20 04:03:38 +00:00
%% process_input(+Tree) is det
%
% Execute the commands from the parse tree
%
2018-12-20 04:03:38 +00:00
process_input(command(CL, void)) :-
%% Process only command left
do_process_input(CL).
process_input(command(CL, TCR)) :-
%% Process first command
do_process_input(CL),
%% If there's a tree on the right
TCR \== void,
%% recurse
process_input(TCR).
2018-12-20 04:03:38 +00:00
%% do_process_input(+Tree) is det
%
% Process a single command from the input
% Takes, as the only argument, the tree representing
% the work to be done
%
do_process_input(help_menu) :-
writeln("Please allow me to introduce myself"),
writeln("I'm a man[ual] of wealth and taste"),
writeln("I've been around for a long, long semester"),
writeln("Saved many a man's soul and faith"),
writeln("Pleased to meet you"),
writeln("Hope you guess my name"),
writeln("But what's puzzling you"),
writeln("Is the nature of my game"),
nl,
writeln("I'm a Polynomial Manipulator and the following commands are available:"),
writeln("=> Show - Allows to print a polynomial mathematically"),
writeln("=> Multiply - Allows to make multiplications"),
writeln("=> Simplify - Allows to simplify a given polynomial"),
writeln("=> Add - Allows to make sums"),
writeln("=> bye - Hello darkness, my old friend"),
writeln("Use 'tell me about {command}' to learn more about a specific command"),
nl,
writeln("Furthermore, I'm capable of memorizing polynomials during runtime. To learn more on that, type: tell me about storage").
do_process_input(help(show)) :-
writeln("It's almost an echo of what you said, but a mathy one."),
2018-12-20 23:19:22 +00:00
writeln("Example query:"),
writeln("> show two plus x squared"),
writeln("2+x^2").
do_process_input(help(multiply)) :-
writeln("Multiplies a polynomial represented as an expression by a scalar resulting in a second polynomial. The two first arguments are assumed to be ground. The polynomial resulting from the sum is in simplified form."),
2018-12-20 23:19:22 +00:00
writeln("Example query:"),
writeln("> multiply three by two plus x squared"),
writeln("3*x^2+6").
do_process_input(help(simplify)) :-
writeln("Simplifies a polynomial represented as an expression as another polynomial as an expression."),
2018-12-20 23:19:22 +00:00
writeln("Example query:"),
writeln("> simplify two plus two plus one times y"),
writeln("y+4").
do_process_input(help(add)) :-
writeln("Adds two polynomials as expressions resulting in a third one. The two first arguments are assumed to be ground. The polynomial resulting from the sum is in simplified form."),
writeln("Some example queries:"),
2018-12-20 23:19:22 +00:00
writeln("> add two times x to four times x"),
writeln("6*x").
do_process_input(help(storage)) :-
2018-12-20 00:48:57 +00:00
writeln("Polynomials, pressed between the entries of my storage"),
writeln("Polynomials, simplified through the ages just like predicates"),
writeln("Quiet goal come floating down"),
2018-12-20 00:48:57 +00:00
writeln("And settle softly to the ground"),
writeln("Like golden atom leaves around my root"),
2018-12-20 00:48:57 +00:00
writeln("I touched them and they burst apart with sweet polynomials"),
writeln("Sweet polynomials"),
nl,
writeln("Storage manipulation is better illustrated with examples. Some example queries:"),
writeln("Asking me to memorize something:"),
2018-12-20 23:19:22 +00:00
writeln("> show two plus x squared as P1"),
writeln("P1 = 2+x^2"),
writeln("> multiply three by P1 as P2"),
writeln("P2 = 3*x^2+6"),
nl,
writeln("Asking me to forget something:"),
2018-12-20 23:19:22 +00:00
writeln("> forget P1 and show stored polynomials"),
writeln("P2 = 6+3*x^2").
do_process_input(help(bye)) :-
writeln("There must be some kind of way outta here"),
writeln("Said the joker to the thief"),
writeln("There's too much confusion"),
writeln("I can't get no relief").
do_process_input(show_stored_polynomials) :-
%% If the command is 'show_stored_polynomials'
%% Store in D the list of all results of
%% polynomial_store, which is a dynamic predicate
findall(nm(X,Y), polynomial_store(X,Y), D),
%% Print the list of stored variables
print_all_stored_variables(D).
2018-12-19 23:37:26 +00:00
do_process_input(show(store(P), T)) :-
%% If the command was to store and show
%% Delegate the work of storing
do_process_input(store(P, T)),
%% Show the variable and the polynomial. Delegate
do_process_input(show(P, T)).
do_process_input(show(load(P), void)) :-
%% Make sure there's a variable
P \== void,
(
%% Check if the variable is stored
polynomial_store(P, T),
%% Show the variable and the polynomial. Delegate
do_process_input(show(P, T))
;
writeln("Variable not stored")
).
do_process_input(show(void, T)) :-
%% Make sure there's a tree
T \== void,
%% Convert it to a flat polynomial
polynomial_tree_to_polynomial(T, Pl),
writeln(Pl).
do_process_input(show(P, T)) :-
%% Make sure the arguments are in the right format and not one
%% that should be handled by other clauses
2018-12-17 17:06:25 +00:00
P \== void,
P \== store(_),
P \== load(_),
2018-12-17 17:06:25 +00:00
T \== void,
%% Write out the variable and the normal
%% representation of the polynomial
2018-12-17 17:06:25 +00:00
write(P),
write(" = "),
%% Convert the input tree to a flat polynomial, to print
polynomial_tree_to_polynomial(T, Pl),
writeln(Pl).
2018-12-20 02:22:38 +00:00
do_process_input(store_simplified(V,PTNS)) :-
polynomial_tree_to_polynomial(PTNS, PNS),
simpoly(PNS,P),
assertz(polynomial_store(V, P)),
write(V),
write(" = "),
write(P),
nl.
do_process_input(store(P, T)) :-
(
%% If there is a polynomial stored with the same name
polynomial_store(P, _),
%% Delete all occurences of it
retractall(polynomial_store(P, _))
;
%% Otherwise, move forward and don't do anything special
true
),
!,
%% Dynamically add to the end of the predicate, effectively storing it
assertz(polynomial_store(P, T)).
do_process_input(forget(P)) :-
%% Remove all stored predicates with this variable
retractall(polynomial_store(P, _)).
do_process_input(simplify(PT)) :-
%% Convert the polynomial to it's flat representation
polynomial_tree_to_polynomial(PT, P),
%% Use simpoly from the UI layer to simplify, so it
%% gives nice error messages
simpoly(P, SP),
writeln(SP).
2018-12-20 02:22:38 +00:00
do_process_input(store_multiplication(TN, PT, V)) :-
polynomial_tree_to_polynomial(TN, N),
polynomial_tree_to_polynomial(PT, P),
2018-12-20 21:52:17 +00:00
%% Left is always number. Enforced by parser
scalepoly(P, N, P2),
2018-12-20 02:22:38 +00:00
simpoly(P2, SP),
assertz(polynomial_store(V, SP)),
write(V),
write(" = "),
write(SP),
nl.
do_process_input(multiply(TN, PT)) :-
%% Flatten both
polynomial_tree_to_polynomial(TN, N),
polynomial_tree_to_polynomial(PT, P),
%% Use function from UI layer
2018-12-20 21:52:17 +00:00
%% Left is always the number. Enforced by parser
2018-12-20 23:29:12 +00:00
scalepoly(P, N, P2),
simpoly(P2, SP),
writeln(SP).
2018-12-20 03:26:17 +00:00
do_process_input(op(+, TN, PT)) :-
polynomial_tree_to_polynomial(TN, N),
polynomial_tree_to_polynomial(PT, P),
addpoly(N, P, P2),
simpoly(P2, SP),
writeln(SP).
do_process_input(store_addition(TN, PT, V)) :-
polynomial_tree_to_polynomial(TN, N),
polynomial_tree_to_polynomial(PT, P),
addpoly(N, P, P2),
simpoly(P2, SP),
assertz(polynomial_store(V, SP)),
write(V),
write(" = "),
write(SP),
nl.
%% print_all_stored_variables
%
% Prints the stored variables, given the list
%
print_all_stored_variables([nm(X,T) | TS]) :-
%% Print
write(X),
write(" = "),
polynomial_tree_to_polynomial(T, P),
writeln(P),
%% Recurse on the right
print_all_stored_variables(TS).
print_all_stored_variables([]).
2018-12-17 16:35:41 +00:00
%% polynomial_tree_to_polynomial(+tree, -polynomial) is det
%
% Flatten a polynomail tree into a simple polynomial
%
2018-12-20 23:15:18 +00:00
polynomial_tree_to_polynomial(op(neg, T), P1) :-
%% If it matched on the version with a node, don't go back
!,
%% Delegate
polynomial_tree_to_polynomial(T, P),
atom_concat('-', P, P1).
polynomial_tree_to_polynomial(op(Op, TL, TR), P) :-
%% If it matched on the version with a node, don't go back
!,
%% Recurse on the left and right
polynomial_tree_to_polynomial(TL, PL),
polynomial_tree_to_polynomial(TR, PR),
%% Convert the results of the recursion to atoms
term_to_atom(PL, TermL),
term_to_atom(PR, TermR),
%% Concat the parts
atom_concat(TermL, Op, Temp),
atom_concat(Temp, TermR, PA),
%% Convert back to terms
term_to_atom(P, PA).
polynomial_tree_to_polynomial(load(P), Pl) :-
%% If there's a load tag, don't go back
!,
%% Retrieve the polynomial from the store
polynomial_store(P, TP),
%% Recurse as if there was a normal tree here
polynomial_tree_to_polynomial(TP, Pl).
%% If we get here, it means we didn't match any of the previous
%% clauses, so the tree is already flat
polynomial_tree_to_polynomial(T, T).
%% special_word_number(?W:Atom, ?D:Int) is det
%
% Definition of a Alphabetical and Numerical relation
% The third argument refers to different types of precedence:
% - f means a single unit digit, which doesn't
% combine on the right, but can on the left
% - g means a number which doesn't combine left or right
% - fy means a number which can optionally combine on the right
% - xfy means a number which requires a number on the left and
% an optional one on the right
%
2018-12-10 17:33:35 +00:00
special_word_number(zero, 0, f).
special_word_number(a, 1, f).
special_word_number(one, 1, f).
special_word_number(two, 2, f).
special_word_number(three, 3, f).
special_word_number(four, 4, f).
special_word_number(five, 5, f).
special_word_number(six, 6, f).
special_word_number(seven, 7, f).
special_word_number(eight, 8, f).
special_word_number(nine, 9, f).
special_word_number(ten, 10, g).
special_word_number(eleven, 11, g).
special_word_number(twelve, 12, g).
special_word_number(thirteen, 13, g).
special_word_number(fourteen, 14, g).
special_word_number(fifteen, 15, g).
special_word_number(sixteen, 16, g).
special_word_number(seventeen, 17, g).
special_word_number(eighteen, 18, g).
special_word_number(nineteen, 19, g).
2018-12-10 17:33:35 +00:00
special_word_number(twenty, 20, fy).
special_word_number(thirty, 30, fy).
special_word_number(forty, 40, fy).
special_word_number(fifty, 50, fy).
special_word_number(sixty, 60, fy).
special_word_number(seventy, 70, fy).
special_word_number(eighty, 80, fy).
special_word_number(ninety, 90, fy).
special_word_number(hundred, 100, xfy).
special_word_number(thousand, 1000, xfy).
special_word_number(million, 1000000, xfy).
%% special_word_number(half, 0.5, xf).
%% special_word_number(third, 0.33333, xf).
%% special_word_number(quarter, 0.25, xf).
%% parse_number_explicit(+precedence, +tree_left, -tree,
%% +stream, -not_consumed) is det
%
% Use the precedence of the last number and the tree
% on the left to create the tree on the right, parsing
% the input stream.
%
% You probably want to call this with void in the first
% two arguments. They are for internal use
%
2018-12-17 16:31:10 +00:00
parse_number_explicit(void, void, T, [WN | In], NC) :-
%% Entry point
%% Convert a word to a number with precedence f, g, or fy
special_word_number(WN, N, P),
member(P, [f, g, fy]),
!,
%% Recurse with the previous precedence
%% and tree as arguments. The same in the other clauses
2018-12-17 16:31:10 +00:00
parse_number_explicit(P, N, T, In, NC).
parse_number_explicit(fy, NL, T, [WN | In], NC) :-
special_word_number(WN, N, f),
!,
%% Add them on the left tree and recurse
2018-12-17 16:31:10 +00:00
parse_number_explicit(f, op(+, NL, N), T, In, NC).
parse_number_explicit(xfy, TL, T, [WN | In], NC) :-
TL \= void,
special_word_number(WN, N, P),
member(P, [f, g, fy]),
!,
%% Add the parts
2018-12-17 16:31:10 +00:00
parse_number_explicit(P, op(+, TL, N), T, In, NC).
parse_number_explicit(_, TL, T, [WN | In], NC) :-
%% Whatever the precedence on the left
special_word_number(WN, N, xfy),
%% Enforce there was a left argument
TL \= void,
!,
%% Multiply this and the left
2018-12-17 16:31:10 +00:00
parse_number_explicit(xfy, op(*, TL, N), T, In, NC).
parse_number_explicit(P, TL, T, [and, WN | In], NC) :-
%% And is part of a valid number if there's a valid
%% number on the left and right
2018-12-10 17:33:35 +00:00
special_word_number(WN, _, _),
%% Passthrough
2018-12-17 16:31:10 +00:00
parse_number_explicit(P, TL, T, [WN | In], NC),
!.
2018-12-17 16:31:10 +00:00
parse_number_explicit(_, T, T, [WN | In], [WN | In]) :-
%% If there's a tree
2018-12-10 17:33:35 +00:00
T \= void,
%% And can't consume more words as
%% numbers (fail on first), then we're done
2018-12-10 17:33:35 +00:00
not(special_word_number(WN, _, _)),
!.
parse_number_explicit(_, T, T, [], []) :-
%% If consumed all innput and there's a
%% tree, we're done
2018-12-10 17:33:35 +00:00
T \= void,
!.
%% parse_floating_number(-tree, +stream, -not_consumed) is det
%
% Parse a floating point number
%
parse_floating_number(N) -->
[N],
2018-12-20 21:38:46 +00:00
{ number(N) }.
parse_floating_number(op('.', TL, TR)) -->
%% A float is a node with a dot as the operator
%% If there's a number on the left
2018-12-20 21:38:46 +00:00
parse_integer_number(TL),
%% Followed by either point or dot
2018-12-20 21:38:46 +00:00
[Sep],
{
2018-12-20 21:38:46 +00:00
member(Sep, [point, dot]),
!
},
%% Followed by another number
2018-12-20 21:38:46 +00:00
parse_positive_integer_number(TR).
parse_floating_number(TN) -->
%% Or any integer
parse_integer_number(TN).
%% Tests:
%% ?- parse_floating_number(TN, [two], _).
%@ TN = 2.
%% ?- parse_floating_number(TN, [two, dot, 4], _).
%@ TN = op('.', 2, 4).
%% ?- parse_floating_number(TN, [negative, two, dot, 4], _).
%@ TN = op('.', op(neg, 2), 4).
2018-12-20 23:15:18 +00:00
%@ TN = op('.', op(neg, 2), 4).
2018-12-20 21:38:46 +00:00
%% parse_positive_integer_number(-tree, +stream, -not_consumed) is det
%
% Parse an positive integer number
%
2018-12-20 21:38:46 +00:00
parse_positive_integer_number(N) -->
[N],
{
2018-12-20 21:38:46 +00:00
integer(N),
%% CLPFD, a number greater than 0
N #>= 1, !
}.
2018-12-20 21:38:46 +00:00
parse_positive_integer_number(T) -->
parse_number_explicit(void, void, T).
2018-12-20 21:38:46 +00:00
%% parse_integer_number(-tree, +stream, -not_consumed) is det
%
% Parse an integer number
%
2018-12-20 21:38:46 +00:00
parse_integer_number(N) -->
[N],
2018-12-20 21:38:46 +00:00
{ integer(N), ! }.
parse_integer_number(op(neg, T)) --> % TODO
%% A number can start with "negative", to negate it
[negative],
2018-12-20 23:15:18 +00:00
parse_integer_number(T).
2018-12-20 21:38:46 +00:00
parse_integer_number(T) -->
parse_number_explicit(void, void, T).
2018-12-09 00:01:22 +00:00
%% Tests:
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [two], _).
2018-12-09 00:01:22 +00:00
%@ T = 2.
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [43], _).
%@ T = 43.
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [nineteen, two], _).
%@ false.
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [twenty], _).
2018-12-09 00:01:22 +00:00
%@ T = 20.
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [twenty, point, two], NC).
%@ T = op('.', 20, 2),
%@ NC = [].
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [twenty, twenty], _).
%@ false.
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [twenty, one], _).
2018-12-09 00:01:22 +00:00
%@ T = op(+, 20, 1).
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [negative, one, hundred], _).
%@ T = op(neg, op(*, 1, 100)) ;
%@ false.
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [three, hundred], _).
2018-12-09 00:01:22 +00:00
%@ T = op(*, 3, 100).
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [twenty, hundred], _).
2018-12-09 00:01:22 +00:00
%@ T = op(*, 20, 100).
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [twenty, one, hundred], _).
2018-12-09 00:01:22 +00:00
%@ T = op(*, op(+, 20, 1), 100).
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [two, hundred, and, one], _).
2018-12-09 00:01:22 +00:00
%@ T = op(+, op(*, 2, 100), 1).
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [twenty, one, hundred, and, twenty, one], _).
%@ T = op(+, op(+, op(*, op(+, 20, 1), 100), 20), 1).
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [twenty, one, hundred, and, twenty, one, foo, bar, blah], NC).
2018-12-10 17:33:35 +00:00
%@ T = op(+, op(+, op(*, op(+, 20, 1), 100), 20), 1),
%@ NC = [foo, bar, blah].
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [twenty, one, hundred, and, bleg, twenty, quux, one, foo, bar], NC).
2018-12-10 17:33:35 +00:00
%@ T = op(*, op(+, 20, 1), 100),
%@ NC = [and, bleg, twenty, quux, one, foo, bar].
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [two, hundred, thousand], _).
%@ T = op(*, op(*, 2, 100), 1000).
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [twenty, one, hundred, thousand], _).
2018-12-10 17:33:35 +00:00
%@ T = op(*, op(*, op(+, 20, 1), 100), 1000).
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [thirty, five, million, five, hundred, thirty, four], _).
%@ T = op(+, op(+, op(*, op(+, op(*, op(+, 30, 5), 1000000), 5), 100), 30), 4).
2018-12-20 21:38:46 +00:00
%% ?- parse_integer_number(T, [foo, five, million], NC).
2018-12-10 17:33:35 +00:00
%@ false.
2018-12-17 16:31:10 +00:00
%% nlp_parse_power(?List, ?List) is det
%
2018-12-17 16:31:10 +00:00
% Parse powers
%
2018-12-17 16:31:10 +00:00
parse_power(op(^, TB, 2)) -->
parse_polynomial_variable(TB),
[squared].
parse_power(op(^, TB, 3)) -->
parse_polynomial_variable(TB),
[cubed].
parse_power(op(^, TB, TN)) -->
parse_polynomial_variable(TB),
[raised, to],
2018-12-20 21:38:46 +00:00
parse_positive_integer_number(TN).
2018-12-17 16:31:10 +00:00
parse_power(TB) -->
parse_polynomial_variable(TB).
%% parse_operation(+Op, +stream, -not_consumed) is det
%
% Associate an operator with a word
%
parse_operation(-) --> [minus] | [-].
parse_operation(+) --> [plus] | [+].
parse_operation(*) --> [times] | [*].
2018-12-10 17:33:35 +00:00
%% parse_polynomial_operand(-tree) is det
%
% Parse a valid operand for either side of an
% operation in a polynomial
%
parse_polynomial_operand(T) -->
parse_floating_number(T).
parse_polynomial_operand(T) -->
parse_power(T).
parse_polynomial_operand(load(T)) -->
%% Tag the variable, to be loaded later
parse_stored_variable(T),
{ ! }.
2018-12-20 03:03:52 +00:00
%% Tests:
%% ?- parse_polynomial_operand(T, [two], NC).
%@ T = 2,
%@ NC = [] ;
%@ false.
%% Declare polynomial_store as a dynamic predicate with two arguments
%% Used to store and retrieve polynomials associated with a variable
%% First being the variable name and the second its content
2018-12-17 15:41:21 +00:00
:- dynamic polynomial_store/2.
%% parse_stored_variable(-var) is det
%
% Parse a valid variable name, for storage
% A valid varaible is one that would be valid in Prolog
%
parse_stored_variable(P) -->
[P],
{ valid_store_variable(P) }.
%% Tests:
%% ?- parse_stored_variable(P, ['P1'], _).
%@ P = 'P1'.
%% ?- parse_stored_variable(P, ['x1'], _).
%@ false.
%% parse_stored_variable(+var) is det
%
% Check if var is a valid variable name, for storage
% A valid varaible is one that would be valid in Prolog
%
valid_store_variable(P) :-
%% Get the list of codes of the atom
atom_codes(P, L),
%% Extract the first element
cons(F, R, L),
%% Partially builtin facilities:
%% Check if it's a valid start to a Prolog variable
code_type(F, prolog_var_start),
%% Check that each of the following is a valid
%% continuation to a variable name
maplist(code_type_swap(prolog_identifier_continue), R).
%% code_type_swap - swap arguments of code_type, so
%% it can be used with maplist. No lambdas...
code_type_swap(X, Y) :- code_type(Y, X).
%% parse_polynomial_variable(-base, +stream, -not_consumed) is det
%
% Parse a polynomial variable
%
parse_polynomial_variable(B) -->
[B],
{ polynomial_variable(B) }.
%% parse_polynomial(-tree, +stream, -not_consumed) is det
%
% Parse a polynomial. Delegates to explicit variant
%
parse_polynomial(T) -->
%% Ignore "polynomial", if followed by a valid polynomial
[polynomial],
{ ! },
%% Delegate
2018-12-20 22:05:37 +00:00
parse_polynomial(T).
2018-12-17 16:31:10 +00:00
parse_polynomial(T) -->
%% Delegate
parse_polynomial_explicit(_-_, T),
!.
%% Tests:
%% ?- parse_polynomial(T, [], _).
%@ false.
%% ?- parse_polynomial(T, [two], _).
2018-12-20 03:03:52 +00:00
%@ T = 2,
%@ NC = [].
%% ?- parse_polynomial(T, [two, times, three], _).
2018-12-20 03:03:52 +00:00
%@ T = op(*, 2, 3),
%@ NC = [].
%% ?- parse_polynomial(T, [two, times, three, plus, four], _).
%@ T = op(+, op(*, 2, 3), 4).
%% ?- parse_polynomial(T, [two, plus, three, times, four], _).
%@ T = op(+, 2, op(*, 3, 4)).
%% ?- parse_polynomial(T, [two, plus, three, times, four, plus, six, times, five], _).
%@ T = op(+, 2, op(+, op(*, 3, 4), op(*, 6, 5))).
%% ?- parse_polynomial(T, [two, times, times, two], NC).
%@ T = void,
%@ NC = [two, times, times, two].
%% ?- parse_polynomial(T, [two, plus, x, times, four], _).
%@ T = op(+, 2, op(*, x, 4)).
%% ?- parse_polynomial(T, [two, plus, x, times, four, plus, y, raised, to, five], _).
%@ T = op(+, 2, op(+, op(*, x, 4), op(^, y, 5))).
%% ?- parse_polynomial(T, [two, plus, two, plus, one, times, y], _).
%@ T = op(+, op(+, 2, 2), op(*, 1, y)).
2018-12-20 22:05:37 +00:00
%% ?- parse_polynomial(T, [polynomial, 2, plus, 3, plus, 4, y], _).
%@ T = op(+, op(+, 2, 3), op(*, 4, y)).
%% ?- parse_polynomial(T, [2, x], _).
%@ true.
%% parse_polynomial_explicit(+tree-at, -tree) is det
%
% Explicitly parse a polynomial. You probably don't want
% to call this directly. The first argument is a difference
% structure of the tree on the left; the second of the pair
% represents where to put the rest of the tree, in
% subsequent passes
%
% Call with _-_ on the first argument, outputs the
% tree on the right
%
2018-12-17 16:31:10 +00:00
parse_polynomial_explicit(void-_, T) -->
%% Entry point. If the three on the left is void
%% Parse an operand and an op
parse_polynomial_operand(TL),
parse_operation(Op),
%% If consumed an op, double down and don't go back
!,
%% Recurse with the tree on the left populated with
%% what we found and a reference to the place to
%% place the next tree
2018-12-17 16:31:10 +00:00
parse_polynomial_explicit(op(Op, TL, TRP)-TRP, T).
2018-12-20 22:05:37 +00:00
parse_polynomial_explicit(void-_, T) -->
%% Entry point. If the three on the left is void
parse_polynomial_operand(TL),
%% There was no op, assume a times
%% Recurse with the tree on the left populated with
%% what we found and a reference to the place to
%% place the next tree
parse_polynomial_explicit(op(*, TL, TRP)-TRP, T).
2018-12-17 16:31:10 +00:00
parse_polynomial_explicit(TLP-TL, T) -->
%% Parse an operand on the left and place it in the tree
%% on the left, through the difference structure,
parse_polynomial_operand(TL),
%% parse either a minus or a plus
2018-12-20 03:03:52 +00:00
{ member(COp, [-, +]) },
parse_operation(COp),
!,
%% Recurse on the right with add; position the sub tree on the right
parse_polynomial_explicit(op(COp, TLP, TRP)-TRP, T).
2018-12-17 16:31:10 +00:00
parse_polynomial_explicit(TLP-T, TLP) -->
%% Parse an operand on the left, placing the result of the
%% recusion on the TLP, through the difference structure and return that
parse_polynomial_operand(TL),
%% If we find a times,
parse_operation(*),
!,
%% Place the operand on the left, the following on the
%% right and get a T to place in the tree on the left from above
2018-12-17 16:31:10 +00:00
parse_polynomial_explicit(op(*, TL, TRP)-TRP, T).
parse_polynomial_explicit(TLP-T, TLP) -->
%% Same as above, but without an operator,
parse_polynomial_operand(TL),
%% Assume times
parse_polynomial_explicit(op(*, TL, TRP)-TRP, T),
!.
2018-12-17 16:31:10 +00:00
parse_polynomial_explicit(TLP-TL, TLP) -->
{ TLP \= void },
%% Base case. Parse a last operand
parse_polynomial_operand(TL),
!,
{ TL \= void }.
2018-12-17 16:31:10 +00:00
parse_polynomial_explicit(void-_, T) -->
%% Base case. Parse a single operand
2018-12-17 15:41:21 +00:00
parse_polynomial_operand(T),
!,
{ T \= void }.
%% parse_command(-tree, +stream, -not_consumed) is semidet
%
% Parse each individual command
%
parse_command(help_menu) -->
[help].
parse_command(help(C)) -->
[tell, me, about, C].
parse_command(show_stored_polynomials) -->
[show, stored, polynomials].
2018-12-19 23:37:26 +00:00
parse_command(forget(P)) -->
[forget],
parse_stored_variable(P).
parse_command(show(store(P), T)) -->
[show],
2018-12-17 16:34:02 +00:00
parse_polynomial(T),
[as],
parse_stored_variable(P).
parse_command(show(P, void)) -->
[show],
parse_stored_variable(P).
2018-12-19 23:33:49 +00:00
parse_command(show(void, T)) -->
2018-12-17 17:06:25 +00:00
[show],
parse_polynomial(T),
{ nonvar(T) }.
2018-12-17 15:41:21 +00:00
parse_command(store(P, T)) -->
[let],
parse_stored_variable(P),
[be],
2018-12-17 16:34:02 +00:00
parse_polynomial(T).
2018-12-17 16:31:10 +00:00
parse_command(store(P, T)) -->
[store],
2018-12-17 16:34:02 +00:00
parse_polynomial(T),
2018-12-17 16:31:10 +00:00
[as],
parse_stored_variable(P).
2018-12-20 02:22:38 +00:00
parse_command(store_simplified(V, P)) -->
[simplify],
parse_polynomial(P),
[as],
[V].
2018-12-17 15:41:21 +00:00
parse_command(simplify(T)) -->
[simplify],
2018-12-17 16:34:02 +00:00
parse_polynomial(T).
2018-12-20 02:22:38 +00:00
parse_command(store_multiplication(TN, TP, V)) -->
[multiply],
2018-12-20 21:52:17 +00:00
parse_floating_number(TN),
2018-12-20 02:22:38 +00:00
[by],
parse_polynomial(TP),
[as],
[V].
2018-12-20 21:52:17 +00:00
parse_command(store_multiplication(TN, TP, V)) -->
[multiply],
parse_polynomial(TP),
[by],
parse_floating_number(TN),
[as],
[V].
parse_command(multiply(TN, TP)) -->
[multiply],
2018-12-20 21:52:17 +00:00
parse_floating_number(TN),
[by],
2018-12-17 16:35:41 +00:00
parse_polynomial(TP).
2018-12-20 21:52:17 +00:00
parse_command(multiply(TN, TP)) -->
[multiply],
parse_polynomial(TP),
[by],
parse_floating_number(TN).
2018-12-20 01:10:35 +00:00
parse_command(op(-, TN, TP)) -->
[sub],
parse_polynomial(TN),
[X],
2018-12-20 03:03:52 +00:00
{ member(X, [to, from, with]) },
2018-12-20 01:10:35 +00:00
parse_polynomial(TP).
2018-12-20 03:26:17 +00:00
parse_command(store_addition(TN, TP, V)) -->
[add],
parse_polynomial(TN),
[X],
{ member(X, [to, with]) },
parse_polynomial(TP),
[as],
[V].
parse_command(op(+, TN, TP)) -->
[add],
parse_polynomial(TN),
[X],
{ member(X, [to, with]) },
parse_polynomial(TP).
%% Tests:
%% ?- parse_command(T, [show, 3], NC).
%@ T = show(void, 3),
%@ NC = [] .
%% ?- parse_command(T, [add, 3, plus, x, to, 4, plus, x], NC).
%@ false.
%@ T = op(+, op(+, 3, x), op(+, 4, x)),
%@ NC = [] ;
%@ false.
%% parse_input(-tree) is det
%
% Parse each command and string into a list
%
2018-12-17 16:31:10 +00:00
parse_input(command(TCL, TCR)) -->
%% Result is a struct with a command on the left
%% and a struct of commands on the right
%% parse a single command
2018-12-17 16:31:10 +00:00
parse_command(TCL),
%% separator
2018-12-17 16:31:10 +00:00
[and],
!,
%% Recurse
2018-12-17 16:31:10 +00:00
parse_input(TCR).
parse_input(command(TC, void)) -->
%% If there's no separator, we're done recursing
%% parse a single command
2018-12-17 16:31:10 +00:00
parse_command(TC).
%% ?- parse_input(CT, [show, 3], _).
%@ CT = command(show(void, 3), void).
2018-11-22 18:04:45 +00:00
/*******************************
2018-12-17 16:31:10 +00:00
* UI *
2018-11-22 18:04:45 +00:00
*******************************/
2018-11-23 00:09:07 +00:00
2018-11-22 18:04:45 +00:00
/*
poly2list/2 transforms a list representing a polynomial (second
2018-11-23 00:09:07 +00:00
argument) into a polynomial represented as an expression (first
argument) and vice-versa.
2018-11-22 18:04:45 +00:00
*/
poly2list(P, L) :-
is_polynomial_valid_in_predicate(P, "poly2list"),
2018-11-23 15:32:15 +00:00
polynomial_to_list(P, L),
!.
2018-11-22 18:04:45 +00:00
/*
simpolylist/2 simplifies a polynomial represented as a list into
another polynomial as a list.
*/
simpoly_list(L, S) :-
is_polynomial_as_list_valid_in_predicate(L, "simpoly_list"),
2018-11-23 15:32:15 +00:00
simplify_polynomial_as_list(L, S),
!.
2018-11-22 18:04:45 +00:00
/*
2018-11-22 18:34:23 +00:00
simpoly/2 simplifies a polynomial represented as an expression
2018-11-22 18:04:45 +00:00
as another polynomial as an expression.
*/
simpoly(P, S) :-
is_polynomial_valid_in_predicate(P, "simpoly"),
2018-11-23 15:32:15 +00:00
simplify_polynomial(P, S),
!.
%% Tests:
%% ?- simpoly(2+2+1*y, S).
%@ S = y+4.
2018-11-22 18:04:45 +00:00
/*
scalepoly/3 multiplies a polynomial represented as an expression by a scalar
2018-11-22 18:04:45 +00:00
resulting in a second polynomial. The two first arguments are assumed to
be ground. The polynomial resulting from the sum is in simplified form.
*/
2018-11-25 18:38:09 +00:00
scalepoly(P1, C, S) :-
is_polynomial_valid_in_predicate(P1, "scalepoly"),
2018-12-17 16:31:10 +00:00
is_number_valid_in_predicate(C, "scalepoly"),
2018-11-25 18:38:09 +00:00
scale_polynomial(P1, C, S),
2018-11-23 15:32:15 +00:00
!.
2018-11-25 18:38:09 +00:00
%% Tests:
%% ?- scalepoly(3*x*z+2*z, 4, S).
%@ S = 12*x*z+8*z.
%% ?- scalepoly(3*x*z+2*z, 2, S).
%@ S = 6*x*z+4*z.
2018-11-22 18:04:45 +00:00
/*
2018-11-22 18:34:23 +00:00
addpoly/3 adds two polynomials as expressions resulting in a
2018-11-22 18:04:45 +00:00
third one. The two first arguments are assumed to be ground.
The polynomial resulting from the sum is in simplified form.
*/
addpoly(P1, P2, S) :-
is_polynomial_valid_in_predicate(P1, "addpoly"),
is_polynomial_valid_in_predicate(P2, "addpoly"),
2018-11-23 15:32:15 +00:00
add_polynomial(P1, P2, S),
!.
%% Tests:
%% ?- addpoly(3 + x, 3 - x, S).
2018-11-25 17:01:38 +00:00
%@ S = 6.
%% is_polynomial_valid_in_predicate(+T, +F) is det
%
% Returns true if valid polynomial, fails with UI message otherwise.
% The failure message reports which polynomial is invalid and in which
% predicate the problem ocurred.
%
is_polynomial_valid_in_predicate(P, _) :-
%% If P is a valid polynomial, return true
polynomial(P),
!.
is_polynomial_valid_in_predicate(P, F) :-
2018-11-25 18:38:09 +00:00
%% Otherwise, write the polynomial and fails
write("Invalid polynomial in "),
write(F),
write(": "),
2018-12-20 23:31:33 +00:00
writeln(P),
fail.
%% Tests:
%% ?- is_polynomial_valid_in_predicate(1-x, "Test").
%@ true.
%% ?- is_polynomial_valid_in_predicate(a*4-0*x, "Test").
%@ Invalid polynomial in Test: a*4-0*x
%@ false.
%% is_polynomial_as_list_valid_in_predicate(+L, +F) is det
%
% Returns true if the polynomial represented as list is valid,
% fails with UI message otherwise.
% The failure message reports which polynomial is invalid and
% in which predicate the problem ocurred.
%
is_polynomial_as_list_valid_in_predicate(L, F) :-
%% If L is a valid polynomial, return true
list_to_polynomial(L, P),
is_polynomial_valid_in_predicate(P, F).
%% Tests:
%% ?- is_polynomial_as_list_valid_in_predicate([1], "Test").
%@ true.
%% ?- is_polynomial_as_list_valid_in_predicate([0*x, a*4], "Test").
%@ Invalid polynomial in Test: a*4+0*x
%@ false.
2018-11-22 18:04:45 +00:00
2018-12-17 16:31:10 +00:00
%% is_number_valid_in_predicate(+C:number, +F:string) is det
2018-11-25 18:38:09 +00:00
%
% Validates that C is a number or prints F and it then it
%
2018-12-17 16:31:10 +00:00
is_number_valid_in_predicate(C, _) :-
2018-11-25 18:38:09 +00:00
number(C),
!.
2018-12-17 16:31:10 +00:00
is_number_valid_in_predicate(C, F) :-
2018-11-25 18:38:09 +00:00
%% Writes the argument and fails
write("Invalid number in "),
write(F),
write(": "),
2018-12-20 23:31:33 +00:00
writeln(C),
2018-11-25 18:38:09 +00:00
fail.
2018-12-09 00:01:22 +00:00
2018-11-22 18:04:45 +00:00
/*******************************
* BACKEND *
*******************************/
%% polynomial_variable_list(-List) is det
2018-11-18 16:33:09 +00:00
%
% List of possible polynomial variables
%
2018-11-19 16:59:53 +00:00
polynomial_variable_list([x, y, z]).
2018-11-17 16:14:13 +00:00
%% polynomial_variable(?X:atom) is semidet
2018-11-18 16:33:09 +00:00
%
% Returns true if X is a polynomial variable, false otherwise.
%
polynomial_variable(X) :-
2018-11-19 16:59:53 +00:00
polynomial_variable_list(V),
2018-11-19 15:56:48 +00:00
member(X, V).
2018-11-19 16:59:53 +00:00
%% Tests:
2018-11-20 16:14:53 +00:00
%% ?- polynomial_variable(x).
%@ true .
%% ?- polynomial_variable(a).
2018-11-22 11:51:19 +00:00
%@ false.
2018-11-17 16:14:13 +00:00
2018-11-22 11:51:19 +00:00
%% power(+X:atom) is semidet
2018-11-18 16:33:09 +00:00
%
2018-11-19 16:59:53 +00:00
% Returns true if X is a power term, false otherwise.
2018-11-18 16:33:09 +00:00
%
2018-11-22 12:34:42 +00:00
power(P^N) :-
2018-11-25 21:22:21 +00:00
%% CLPFD comparison. Reversible
N #>= 1,
polynomial_variable(P).
2018-11-18 16:33:09 +00:00
power(X) :-
2018-11-19 16:59:53 +00:00
polynomial_variable(X).
%% Tests:
2018-11-22 11:51:19 +00:00
%% ?- power(x).
%@ true .
2018-11-22 13:57:46 +00:00
%% ?- power(a).
%@ false.
2018-11-19 16:59:53 +00:00
%% ?- power(x^1).
2018-11-19 16:07:13 +00:00
%@ true .
2018-11-22 11:51:19 +00:00
%% ?- power(x^3).
%@ true .
2018-11-20 16:14:53 +00:00
%% ?- power(x^(-3)).
2018-11-22 13:57:46 +00:00
%@ false.
%% ?- power(-x).
%@ false.
2018-11-22 11:51:19 +00:00
%% ?- power(X).
%@ X = x^_462546,
%@ _462546 in 1..sup ;
%@ X = y^_462546,
%@ _462546 in 1..sup ;
%@ X = z^_462546,
%@ _462546 in 1..sup ;
2018-11-22 11:51:19 +00:00
%@ X = x ;
%@ X = y ;
%@ X = z.
2018-11-18 16:33:09 +00:00
%% term(+N:atom) is semidet
2018-11-18 16:33:09 +00:00
%
2018-11-19 16:59:53 +00:00
% Returns true if N is a term, false otherwise.
2018-11-18 16:33:09 +00:00
%
term(N) :-
% If N is not a free variable
nonvar(N),
% Assert it as a number
number(N).
term(N) :-
% If N is a free variable and not compound
not(compound(N)),
var(N),
% Assert it must be between negative and positive infinity
% This uses the CLPR library, which makes this reversible,
% whereas `number(N)` is always false, since it only succeeds
% if the argument is bound (to a integer or float)
{N >= 0; N < 0}.
2018-11-18 16:33:09 +00:00
term(X) :-
power(X).
term(-X) :-
power(X).
2018-12-17 16:31:10 +00:00
term(L * In) :-
2018-11-18 16:33:09 +00:00
term(L),
2018-12-17 16:31:10 +00:00
term(In).
2018-11-19 16:59:53 +00:00
%% Tests:
2018-11-20 16:14:53 +00:00
%% ?- term(2*x^3).
%@ true .
%% ?- term(x^(-3)).
2018-11-22 11:51:19 +00:00
%@ false.
2018-11-22 13:57:46 +00:00
%% ?- term(a).
%@ false.
%% ?- term(-1*x).
%@ true .
%% ?- term(-x).
%@ true .
2018-11-22 11:51:19 +00:00
%% ?- term((-3)*x^2).
2018-11-20 16:14:53 +00:00
%@ true .
2018-11-22 13:57:46 +00:00
%% ?- term(3.2*x).
%@ true .
%% ?- term(-x*(-z)).
%@ true .
2018-11-22 13:57:46 +00:00
%% ?- term(X).
%@ {X>=0.0} ;
%@ {X<0.0} ;
%@ X = x^_111514,
%@ _111514 in 1..sup ;
%@ X = y^_111514,
%@ _111514 in 1..sup ;
%@ X = z^_111514,
%@ _111514 in 1..sup ;
2018-11-23 14:56:01 +00:00
%@ X = x ;
%@ X = y ;
%@ X = z ;
%@ X = -x^_111522,
%@ _111522 in 1..sup ;
%@ X = -y^_111522,
%@ _111522 in 1..sup ;
%@ X = -z^_111522,
%@ _111522 in 1..sup ;
%@ X = -x ;
%@ X = -y ;
%@ X = -z ;
%% polynomial(+M:atom) is semidet
2018-11-18 16:33:09 +00:00
%
% Returns true if polynomial, false otherwise.
%
polynomial(M) :-
%% A polynomial is either a term
2018-11-18 16:33:09 +00:00
term(M).
2018-12-17 16:31:10 +00:00
polynomial(L + In) :-
%% Or a sum of terms
polynomial(L),
2018-12-17 16:31:10 +00:00
term(In).
polynomial(L - In) :-
%% Or a subtraction of terms
2018-11-18 16:33:09 +00:00
polynomial(L),
2018-12-17 16:31:10 +00:00
term(In).
2018-11-19 16:59:53 +00:00
%% Tests:
2018-11-22 11:51:19 +00:00
%% ?- polynomial(x).
%@ true .
%% ?- polynomial(x^3).
%@ true .
%% ?- polynomial(3*x^7).
%@ true .
%% ?- polynomial(2+3*x+4*x*y^3).
2018-11-22 11:51:19 +00:00
%@ true .
%% ?- polynomial(2 - 3*x + 4*x*y^3).
%@ true .
2018-11-22 13:57:46 +00:00
%% ?- polynomial(a).
%@ false.
%% ?- polynomial(x^(-3)).
%@ false.
%% ?- polynomial(-x + 3).
%@ true .
%% ?- polynomial(-x - -z).
%@ true .
2018-11-18 16:33:09 +00:00
%% power_to_canon(+T:atom, -T^N:atom) is semidet
2018-11-18 16:33:09 +00:00
%
% Returns a canon power term.
%
2018-11-19 16:59:53 +00:00
power_to_canon(T^N, T^N) :-
2018-11-22 11:51:19 +00:00
polynomial_variable(T),
% CLP(FD) operator to ensure N is different from 1,
% in a reversible way
2018-11-22 13:57:46 +00:00
N #\= 1.
2018-11-19 16:59:53 +00:00
power_to_canon(T, T^1) :-
polynomial_variable(T).
%% Tests:
%% ?- power_to_canon(x, X).
2018-11-22 11:51:19 +00:00
%@ X = x^1 .
%% ?- power_to_canon(-x, X).
%@ false.
%@ X = -1*x^1 .
2018-11-22 11:51:19 +00:00
%% ?- power_to_canon(X, x^1).
2018-11-19 16:59:53 +00:00
%@ X = x .
2018-11-22 11:51:19 +00:00
%% ?- power_to_canon(X, x^4).
%@ X = x^4 .
2018-11-22 13:57:46 +00:00
%% ?- power_to_canon(X, a^1).
%@ false.
%% ?- power_to_canon(X, x^(-3)).
%@ X = x^ -3 .
%% ?- power_to_canon(X, -1*x^1).
%@ X = -x .
2018-11-19 16:59:53 +00:00
%% term_to_list(?T, ?List) is semidet
2018-11-18 16:33:09 +00:00
%
2018-11-25 22:00:25 +00:00
% Converts a term to a list of its monomials and vice versa.
% Can verify if term and monomials list are compatible.
2018-11-18 16:33:09 +00:00
%
term_to_list(L * N, [N | TS]) :-
number(N),
2018-11-19 16:59:53 +00:00
term_to_list(L, TS).
term_to_list(L * P, [P2 | TS]) :-
power(P),
power_to_canon(P, P2),
term_to_list(L, TS).
term_to_list(L * -P, [-P2 | TS]) :-
power(P),
power_to_canon(P, P2),
term_to_list(L, TS).
2018-11-19 16:59:53 +00:00
term_to_list(N, [N]) :-
number(N).
term_to_list(P, [P2]) :-
power(P),
power_to_canon(P, P2).
term_to_list(-P, [-P2]) :-
power(P),
power_to_canon(P, P2).
2018-11-19 16:59:53 +00:00
%% Tests:
2018-11-22 13:57:46 +00:00
%% ?- term_to_list(1, X).
%@ X = [1] .
%% ?- term_to_list(-1, X).
%@ X = [-1] .
%% ?- term_to_list(x, X).
%@ X = [x^1] .
%% ?- term_to_list(-x, X).
%@ X = [-x^1] .
2018-11-23 15:32:15 +00:00
%% ?- term_to_list(2 * 3, X).
%@ X = [3, 2] .
2018-11-20 16:14:53 +00:00
%% ?- term_to_list(1*2*y*z*23*x*y*x^3*x, X).
%@ X = [x^1, x^3, y^1, x^1, 23, z^1, y^1, 2, 1] .
%% ?- term_to_list(1*2*y*z*23*x*y*(-1), X).
%@ X = [-1, y^1, x^1, 23, z^1, y^1, 2, 1] .
%% ?- term_to_list(X, [-1]).
%@ X = -1 .
%% ?- term_to_list(X, [x^1, -1]).
%@ X = -1*x .
%% ?- term_to_list(X, [-x^1]).
%@ X = -x .
2018-11-19 16:59:53 +00:00
%% ?- term_to_list(X, [y^1, x^1]).
%@ X = x*y .
%% ?- term_to_list(X, [x^4]).
2018-11-22 11:51:19 +00:00
%@ X = x^4 .
2018-11-19 16:59:53 +00:00
%% ?- term_to_list(X, [y^6, z^2, x^4]).
%@ X = x^4*z^2*y^6 .
%% ?- term_to_list(X, [y^6, z^2, x^4, -2]).
%@ X = -2*x^4*z^2*y^6 .
%% ?- term_to_list(X, [x^1, 0]).
%@ X = 0*x .
%% ?- term_to_list(X, [y^1, -2]).
%@ X = -2*y .
2018-11-20 16:14:53 +00:00
%% simplify_term(+Term_In:term, ?Term_Out:term) is det
2018-11-18 16:33:09 +00:00
%
% Simplifies a given term.
% This function can also be be used to verify if
% a term is simplified.
2018-11-18 16:33:09 +00:00
%
2018-11-20 16:14:53 +00:00
simplify_term(Term_In, Term_Out) :-
term_to_list(Term_In, L),
%% Sort the list of numbers and power to group them,
%% simplifying the job of `join_similar_parts_of_term`
2018-11-17 23:53:49 +00:00
sort(0, @=<, L, L2),
2018-11-23 00:09:07 +00:00
(
2018-11-24 15:06:39 +00:00
%% If there's a 0 in the list, then the whole term is 0
2018-11-23 00:09:07 +00:00
member(0, L2),
Term_Out = 0
;
%% Otherwise
2018-11-23 00:09:07 +00:00
(
2018-11-24 15:06:39 +00:00
%% If there's only one element, then the term was already simplified
%% This is done so that the `exclude` following doesn't remove all ones
2018-11-23 00:09:07 +00:00
length(L2, 1),
Term_Out = Term_In
;
2018-11-24 15:06:39 +00:00
%% Remove all remaining ones
2018-11-23 00:09:07 +00:00
exclude(==(1), L2, L3),
join_similar_parts_of_term(L3, L4),
%% Reverse the list, since the following call gives the result in the
2018-11-24 15:06:39 +00:00
%% reverse order otherwise
reverse(L4, L5),
2018-11-23 00:09:07 +00:00
term_to_list(Term_Out, L5)
)
),
2018-11-20 16:14:53 +00:00
% First result is always the most simplified form.
!.
2018-11-19 16:59:53 +00:00
%% Tests:
2018-11-22 13:57:46 +00:00
%% ?- simplify_term(1, X).
%@ X = 1.
%% ?- simplify_term(x, X).
%@ X = x.
%% ?- simplify_term(2*y*z*x^3*x, X).
2018-11-20 16:14:53 +00:00
%@ X = 2*x^4*y*z.
%% ?- simplify_term(1*y*z*x^3*x, X).
%@ X = x^4*y*z.
%% ?- simplify_term(0*y*z*x^3*x, X).
%@ X = 0.
%% ?- simplify_term(6*y*z*7*x*y*x^3*x, X).
%@ X = 42*x^5*y^2*z.
%% ?- simplify_term(-x, X).
%@ X = -x.
%% ?- simplify_term(-x*y*(-z)*3, X).
%@ X = 3* -x* -z*y.
2018-11-22 13:57:46 +00:00
%% ?- simplify_term(a, X).
%@ false.
%% ?- simplify_term(x^(-3), X).
%@ false.
2018-11-17 23:53:49 +00:00
%% join_similar_parts_of_term(+List, -List) is det
2018-11-19 16:59:53 +00:00
%
% Combine powers of the same variable in the given list.
2018-11-25 22:18:24 +00:00
% Requires that the list be sorted.
2018-11-19 16:59:53 +00:00
%
join_similar_parts_of_term([P1, P2 | L], L2) :-
%% If both symbols are powers
2018-11-19 16:59:53 +00:00
power(P1),
power(P2),
%% Decompose them into their parts
2018-11-19 15:56:48 +00:00
B^N1 = P1,
B^N2 = P2,
%% Sum the exponent
N is N1 + N2,
join_similar_parts_of_term([B^N | L], L2),
% First result is always the most simplified form.
!.
join_similar_parts_of_term([N1, N2 | L], L2) :-
%% If they are both numbers
2018-11-17 23:53:49 +00:00
number(N1),
number(N2),
%% Multiply them
N is N1 * N2,
join_similar_parts_of_term([N | L], L2),
% First result is always the most simplified form.
!.
join_similar_parts_of_term([X | L], [X | L2]) :-
%% Otherwise consume one element and recurse
join_similar_parts_of_term(L, L2),
% First result is always the most simplified form.
!.
join_similar_parts_of_term([], []).
2018-11-19 16:59:53 +00:00
%% Tests:
%% ?- join_similar_parts_of_term([3], T).
%@ T = [3].
%% ?- join_similar_parts_of_term([x^2], T).
%@ T = [x^2].
%% ?- join_similar_parts_of_term([x^1, x^1, x^1, x^1], T).
%@ T = [x^4].
%% ?- join_similar_parts_of_term([2, 3, x^1, x^2], T).
%@ T = [6, x^3].
%% ?- join_similar_parts_of_term([2, 3, x^1, x^2, y^1, y^6], T).
%@ T = [6, x^3, y^7].
%% ?- join_similar_parts_of_term([2, 3, -x^1, -x^2], T).
%@ T = [6, -x^1, -x^2].
2018-11-19 16:59:53 +00:00
%% simplify_polynomial(+P:atom, -P2:atom) is det
2018-11-18 16:33:09 +00:00
%
2018-11-19 16:59:53 +00:00
% Simplifies a polynomial.
2018-11-18 16:33:09 +00:00
%
simplify_polynomial(0, 0) :-
% 0 is already fully simplified. This is an
% exception to the following algorithm
2018-11-19 16:59:53 +00:00
!.
simplify_polynomial(P, P2) :-
polynomial_to_list(P, L),
2018-11-23 14:56:01 +00:00
simplify_polynomial_as_list(L, L2),
list_to_polynomial(L2, P2),
%% The first result is the most simplified one
2018-11-19 16:59:53 +00:00
!.
%% Tests:
2018-11-22 13:57:46 +00:00
%% ?- simplify_polynomial(1, X).
%@ X = 1.
%% ?- simplify_polynomial(0, X).
%@ X = 0.
%% ?- simplify_polynomial(x, X).
%@ X = x.
%% ?- simplify_polynomial(x*x, X).
%@ X = x^2.
%% ?- simplify_polynomial(2 + 2, X).
%@ X = 2*2.
%% ?- simplify_polynomial(x + x, X).
%@ X = 2*x.
%% ?- simplify_polynomial(0 + x*x, X).
%@ X = x^2.
%% ?- simplify_polynomial(x^2*x + 3*x^3, X).
%@ X = 4*x^3.
%% ?- simplify_polynomial(x^2*x + 3*x^3 + x^3 + x*x*x, X).
%@ X = 6*x^3.
%% ?- simplify_polynomial(x^2*x + 3*x^3 + x^3 + x*x*4 + z, X).
%@ X = 5*x^3+4*x^2+z.
%% ?- simplify_polynomial(x^2*x + 3*x^3 - x^3 - x*x*4 + z, X).
%@ X = 3*x^3-4*x^2+z.
%% ?- simplify_polynomial(x + 1 + x, X).
%@ X = 2*x+1.
%% ?- simplify_polynomial(x + 1 + x + 1 + x + 1 + x, X).
%@ X = 4*x+3.
%% simplify_polynomial_as_list(+L1:List,-L3:List) is det
%
% Simplifies a polynomial represented as a list.
%
simplify_polynomial_as_list(L, L14) :-
%% Convert each term to a list
maplist(term_to_list, L, L2),
2018-11-25 22:23:38 +00:00
%% Sort each sublist so that the next
%% sort gives the correct results
maplist(sort(0, @>=), L2, L3),
%% Sort the outer list
sort(0, @>=, L3, L4),
%% For each of the parts of the terms, join them
maplist(join_similar_parts_of_term, L4, L5),
%% Sort each of the sublists
%% Done so the next call simplifies has less work
maplist(sort(0, @=<), L5, L6),
join_similar_terms(L6, L7),
%% Exclude any sublist that includes a 0 (such as the
%% equivalent to the term 0*x)
exclude(member(0), L7, L8),
%% Reverse each sublist, because the next call
%% reverses the result
maplist(reverse, L8, L9),
maplist(term_to_list, L10, L9),
%% Delete any 0 from the list
delete(L10, 0, L11),
%% Sort list converting back gives the result in the correct order
sort(0, @=<, L11, L12),
(
%% If the list is empty, the result is a list with 0
L12 = [], L13 = [0]
;
%% Otherwise, this is the result
L13 = L12
),
%% Further make sure all terms are simplified
maplist(simplify_term, L13, L14).
%% Tests:
%% ?- simplify_polynomial_as_list([x, 1, x^2, x*y, 3*x^2, 4*x], L).
%@ L = [1, 4*x^2, 5*x, x*y] .
%% ?- simplify_polynomial_as_list([1, x^2, x*y, 3*x^2, -4, -1*x], L).
%@ L = [-3, -1*x, 4*x^2, x*y] .
%% ?- simplify_polynomial_as_list([1, 1*x], L).
%@ L = [1, x] .
%% ?- simplify_polynomial_as_list([0*x, 0], L).
%@ L = [0] .
2018-11-25 22:31:46 +00:00
%% join_similar_terms(+P:List, -P2:List) is det
%
% Joins similar sublists representing terms by using
% `add_terms` to check if they can be merged and perform
% the addition. Requires the list of list be sorted with
% `maplist(sort(0, @>=), L, L2),
% sort(0, @>=, L2, L3)`
% and that the sublists to be sorted with
% `sort(0, @=<)` since that is inherited from `add_terms`.
%
join_similar_terms([TL, TR | L], L2) :-
%% Check if terms can be added and add them
add_terms(TL, TR, T2),
%% Recurse, accumulation on the first element
join_similar_terms([T2 | L], L2),
%% Give only first result. Red cut
!.
join_similar_terms([X | L], [X | L2]) :-
%% If a pair of elements can't be added, skip one
%% and recurse
join_similar_terms(L, L2),
%% Give only first result. Red cut
!.
join_similar_terms([], []).
%% Tests:
%% ?- join_similar_terms([[2, x^3], [3, x^3], [x^3]], L).
%@ L = [[6, x^3]].
2018-11-20 16:14:53 +00:00
%% term_to_canon(+T:List, -T2:List) is det
%
% Adds the coefficient of the term as the first element of the list
%
%% Special cases to make this predicate reversible
term_to_canon([1], [1]) :-
!.
term_to_canon(L2, [1 | L]) :-
nonvar(L),
L2 = L,
!.
term_to_canon([-1], [-1]) :-
!.
term_to_canon([-P | L2], [-1, P | L]) :-
nonvar(L),
L2 = L,
!.
term_to_canon([N2 | L], [N | L]) :-
number(N),
N2 = N,
!.
%% Normal case
term_to_canon(L, [N | L2]) :-
term_to_canon_with_coefficient(N, L, L2),
!.
%% Tests:
%% ?- term_to_canon([2], T).
%@ T = [2].
%% ?- term_to_canon([-x], T).
%@ T = [-1, x].
%% ?- term_to_canon([-x^3], T).
%@ T = [-1, x^3].
%% ?- term_to_canon([x^1], T).
%@ T = [1, x^1].
%% ?- term_to_canon([x^3], T).
%@ T = [1, x^3].
%% ?- term_to_canon([x^3, z], T).
%@ T = [1, x^3, z].
%% ?- term_to_canon([2, x^3], T).
%@ T = [2, x^3].
%% ?- term_to_canon([2, -x^3], T).
%@ T = [-2, x^3].
%% ?- term_to_canon([2, -x^3, -z], T).
%@ T = [2, x^3, z].
%% ?- term_to_canon(L, [-1]).
%@ L = [-1].
%% ?- term_to_canon(L, [1]).
%@ L = [1].
%% ?- term_to_canon(L, [-2]).
%@ L = [-2].
%% ?- term_to_canon(L, [-2, x]).
%@ L = [-2, x].
%% ?- term_to_canon(L, [1, x]).
%@ L = [x].
%% ?- term_to_canon(L, [-1, x]).
%@ L = [-x].
%% ?- term_to_canon(L, [1, x, z, y]).
%@ L = [x, z, y].
%% ?- term_to_canon(L, [-1, x, z, y]).
%@ L = [-x, z, y].
%% term_to_canon_with_coefficient(-N:number, +L:List, -L2:List) is semidet
%
% Calculates the coefficient of the term and removes negations of powers,
% accumulating the results in N
%
term_to_canon_with_coefficient(N, [N2 | TS], TS2) :-
number(N2),
term_to_canon_with_coefficient(N3, TS, TS2),
N is N2 * N3,
!.
term_to_canon_with_coefficient(N, [P | TS], [P2 | TS2]) :-
sign_of_power(P, N2 * P2),
term_to_canon_with_coefficient(N3, TS, TS2),
N is N2 * N3,
!.
term_to_canon_with_coefficient(N, [], []) :-
nonvar(N);
N = 1.
%% Tests:
%% ?- term_to_canon_with_coefficient(N, [x], L).
%@ N = 1,
%@ L = [x].
%% ?- term_to_canon_with_coefficient(N, [x, x^2, 2], L).
%@ N = 2,
%@ L = [x^1, x^2].
%% ?- term_to_canon_with_coefficient(N, [x, x^2, 2, 4, z], L).
%@ N = 8,
%@ L = [x, x^2, z].
%% ?- term_to_canon_with_coefficient(N, [x, x^2, 2, 4, -z], L).
%@ N = -8,
%@ L = [x, x^2, z].
%% ?- term_to_canon_with_coefficient(N, [x, -x^2, 2, 4, -z], L).
%@ N = 8,
%@ L = [x, x^2, z].
%% ?- term_to_canon_with_coefficient(N, L, [x]).
%@ N = 1,
%@ L = [x].
%% ?- term_to_canon_with_coefficient(N, L, [1]).
%@ N = 1,
%@ L = [1].
%% ?- term_to_canon_with_coefficient(N, L, [2]).
%@ N = 1,
%@ L = [2].
%% sign_of_power(P:power, P:term) is det
%
% If there isn't a leading minus, multiplies the power by 1,
% otherwise by a -1. This way it prefers the positive version.
% Not idempotent
%
sign_of_power(P, 1*P) :-
%% If P can't unify with a minus followed by an unnamed variable
P \= -_,
!.
sign_of_power(-P, -1*P).
%% Tests:
%% ?- sign_of_power(x, X).
%@ X = 1*x.
%% ?- sign_of_power(-x, X).
%@ X = -1*x.
%% ?- sign_of_power(X, 1*x).
%@ X = x.
%% ?- sign_of_power(X, -1*x).
%@ X = -x.
2018-11-20 16:14:53 +00:00
2018-12-17 16:31:10 +00:00
%% add_terms(+L:List, +In:List, -Result:List) is det
%
% Adds two terms represented as list by adding
% the coeficients if the power is the same.
% Returns false if they can't be added
% Requires the list of terms to be simplified.
%
add_terms([NL | TL], [NR | TR], [N2 | TL2]) :-
%% Convert each term to a canon form. This ensures they
%% have a number in front, so it can be added
term_to_canon([NL | TL], [NL2 | TL2]),
term_to_canon([NR | TR], [NR2 | TR2]),
2018-11-25 17:50:49 +00:00
%% If the rest of the term is the same
TL2 == TR2,
%% Add the coeficients
N2 is NL2 + NR2.
%% Tests
2018-12-17 16:31:10 +00:00
%% ?- add_terms([1], [1], In).
%@ In = [2].
%% ?- add_terms([x], [x], In).
%@ In = [2, x].
%% ?- add_terms([2, x^3], [x^3], In).
%@ In = [3, x^3].
%% ?- add_terms([2, x^3], [3, x^3], In).
%@ In = [5, x^3].
%% ?- add_terms([2, x^3], [3, x^2], In).
%@ false.
2018-11-23 15:32:15 +00:00
%% polynomial_to_list(+P:polynomial, -L:List) is det
%
% Converts a polynomial in a list.
%
polynomial_to_list(L - T, [T2 | LS]) :-
term(T),
negate_term(T, T2),
2018-11-23 15:32:15 +00:00
polynomial_to_list(L, LS),
!.
polynomial_to_list(L + T, [T | LS]) :-
term(T),
2018-11-23 15:32:15 +00:00
polynomial_to_list(L, LS),
!.
polynomial_to_list(T, [T]) :-
2018-11-23 15:32:15 +00:00
term(T),
!.
%% Tests:
%% ?- polynomial_to_list(2, S).
2018-11-23 15:32:15 +00:00
%@ S = [2].
%% ?- polynomial_to_list(x^2, S).
2018-11-23 15:32:15 +00:00
%@ S = [x^2].
%% ?- polynomial_to_list(x^2 + x^2, S).
2018-11-23 15:32:15 +00:00
%@ S = [x^2, x^2].
%% ?- polynomial_to_list(2*x^2+5+y*2, S).
2018-11-23 15:32:15 +00:00
%@ S = [y*2, 5, 2*x^2].
%% ?- polynomial_to_list(2*x^2+5-y*2, S).
2018-11-23 15:32:15 +00:00
%@ S = [-2*y, 5, 2*x^2].
%% ?- polynomial_to_list(2*x^2-5-y*2, S).
2018-11-23 15:32:15 +00:00
%@ S = [-2*y, -5, 2*x^2].
%% list_to_polynomial(+L:List, -P:Polynomial) is det
%
% Converts a list in a polynomial.
% An empty list will return false.
%
list_to_polynomial([T1|T2], P) :-
% Start recursive calls until we are in the
% end of the list. We know that the `-` will
% always be at the left of a term.
list_to_polynomial(T2, L1),
(
% If this is a negative term
term_string(T1, S1),
string_chars(S1, [First|_]),
First = -,
% Concat them
term_string(L1, S2),
string_concat(S2,S1,S3),
term_string(P, S3)
;
% Otherwise sum them
P = L1+T1
),
% The others computations are semantically meaningless
!.
list_to_polynomial([T], T).
%% Tests:
%% ?- list_to_polynomial([1, x, x^2], P).
%@ P = x^2+x+1.
%% ?- list_to_polynomial([-1, -x, -x^2], P).
%@ P = -x^2-x-1.
%% ?- list_to_polynomial([1, -x, x^2], P).
%@ P = x^2-x+1.
%% ?- list_to_polynomial([x^2, x, 1], P).
%@ P = 1+x+x^2.
%% ?- list_to_polynomial([a,-e], P).
%@ P = -e+a.
%% ?- list_to_polynomial([], P).
%@ false.
%% ?- list_to_polynomial([a], P).
%@ P = a.
%% negate_term(T, T2) is det
%
% Negate the coeficient of a term and return the negated term.
%
negate_term(T, T2) :-
term_to_list(T, L),
%% Ensure there is a coeficient
term_to_canon(L, L2),
2018-12-17 16:31:10 +00:00
[N | In] = L2,
%% (-)/1 is an operator, needs to be evaluated, otherwise
%% it gives a symbolic result, which messes with further processing
N2 is -N,
%% Convert the term back from canonic form
2018-12-17 16:31:10 +00:00
term_to_canon(L3, [N2 | In]),
%% Reverse the order of the list, because converting
%% implicitly reverses it
reverse(L3, L4),
term_to_list(T2, L4),
!.
%% Tests:
2018-12-17 16:31:10 +00:00
%% ?- negate_term(1, In).
%@ In = -1.
%% ?- negate_term(x, In).
%@ In = -x.
%% ?- negate_term(-x, In).
%@ In = x.
%% ?- negate_term(x^2, In).
%@ In = -x^2.
%% ?- negate_term(3*x*y^2, In).
%@ In = -3*y^2*x.
%% scale_polynomial(+P:Polynomial,+C:Constant,-S:Polynomial) is det
%
% Multiplies a polynomial by a scalar.
%
scale_polynomial(P, C, S) :-
polynomial_to_list(P, L),
%% Convert each term to a list
2018-11-23 14:56:01 +00:00
maplist(term_to_list, L, L2),
2018-11-25 17:50:49 +00:00
%% Canonize terms
maplist(term_to_canon, L2, L3),
%% Append C to the start of each sublist
2018-11-25 17:50:49 +00:00
maplist(cons(C), L3, L4),
%% Convert to a list of terms
maplist(term_to_list, L5, L4),
%% Simplify the resulting polynomial
2018-11-25 17:50:49 +00:00
simplify_polynomial_as_list(L5, L6),
%% Return as a simplified polynomial
list_to_polynomial(L6, S),
!.
2018-11-21 15:23:35 +00:00
%% Tests:
%% ?- scale_polynomial(3*x^2, 2, S).
2018-11-23 15:32:15 +00:00
%@ S = 6*x^2.
%% cons(+C:atom, +L:List, -L2:List) is det
%
% Add an atom C to the head of a list L.
%
2018-11-23 15:32:15 +00:00
cons(C, L, [C | L]).
%% Tests:
%% ?- cons(C, L, L2).
%@ L2 = [C|L].
2018-11-22 15:41:11 +00:00
%% add_polynomial(+P1:polynomial,+P2:polynomial,-S:polynomial) is det
%
% S = P1 + P2.
2018-11-22 15:41:11 +00:00
%
add_polynomial(P1, P2, S) :-
%% Convert both polynomials to lists
2018-11-22 15:41:11 +00:00
polynomial_to_list(P1, L1),
polynomial_to_list(P2, L2),
%% Join them
append(L1, L2, L3),
%% Simplify the resulting polynomial
simplify_polynomial_as_list(L3, L4),
%% Convert back
list_to_polynomial(L4, S),
!.
2018-11-22 15:41:11 +00:00
%% Tests:
%% ?- add_polynomial(2, 2, S).
%@ S = 4.
%% ?- add_polynomial(x, x, S).
%@ S = 2*x.
%% ?- add_polynomial(2*x+5*z, 2*z+6*x, S).
%@ S = 8*x+7*z.