%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%% KNOWLEDGEBASE FOR THE FUSION PROTOTYPE %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%% Version of: 1/7/2003 %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%% Author: Rupert Summerton %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%% File name: test.pl %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% Note: %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% All atoms/schema variables in queries that %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% originate DIRECTLY from the Fusion Proto- %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% type must be elements of a LIST. This is %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% because the Java code should be %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% application independent, and so it has %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% no way of knowing if a given goal %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% requires only one atom or variable, or %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% whether it requires many. %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Clauses for rule 1. %Clauses for acceptedsource.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %(Code changed to deal with set variables.) allowedsource('BBC'). allowedsource('BBCi'). allowedsource('BBC London'). allowedsource('ITN'). allowedsource('Sky'). allowedsource('CNN'). allowedsource('The Weather Channel'). allowedsource('The Met Office'). allowedsource('BBC Radio 1'). allowedsource('BBC Radio 2'). allowedsource('BBC Radio 3'). allowedsource('BBC Radio 4'). allowedsource('BBC Radio 5'). allowedsource('Capitol Radio'). allowedsource('The Guardian'). allowedsource('The Times'). allowedsource('The Independent'). allowedsource('The Daily Telegraph'). allowedsource('Sun'). allowedsource('Mirror'). allowedsource('Express'). allowedsource('Mail'). allowedsource('International Herald Tribune'). acceptedsource([H|T]) :- allowedsource(H), acceptedsource(T). acceptedsource([]). %Clauses for samecity.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% city('London'). city('Birmingham'). city('Bristol'). city('Manchester'). city('Liverpool'). city('Leeds'). city('Newcastle'). city('Sheffield'). city('Glasgow'). city('Edinburgh'). city('Aberdeen'). city('Norwich'). city('Southampton'). city('Brighton'). city('Swindon'). city('Cardiff'). city('Oxford'). %samecity(Cities) :- is satisfied if all cities in list Cities are the same. %e.g. ?- samecity(['London','London']). % yes samecity([X]). samecity([X,Y | T]) :- city(X), !, X == Y, samecity([Y | T]). %Clauses for samedate.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %First, we must segment the dates. %segmentdates(DateList,SegDateList) :- takes a list of dates, and %converts them into a list of segmented dates, each segmented date %itself being a list with three members, day, month, and year. For %example, For if DateList is: ['5/12/03','5/May/2003'], then %SegDateList would be [['5','12','03'],['5','May','2003']]. %samedate(DateList) :- satisfied if dates in DateList are all the same. %For example: ?- samedate(['5th Jan 1992', '5/1/92', '5th/January/1992']). % yes samedate(DateList) :- segmentdates(DateList,SegDateList), equivalentdates(SegDateList). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%Code for segmenting date strings into strings for days, years, and months, so %%%that the latter can be compared to see if dates are the same. %segmentdates(UnsegDatesList,SegDatesList) :- SegDatesList is a list of the dates %in UnsegDatesList, but with those dates segmented into day, year, and month. So, %each element in SegDatesList is itself a list. For example, if UnsegDatesList is: %['5/12/03','5/May/2003'], then SegDatesList would be [['5','12','03'],['5','May','2003']]. segmentdates([H1|T1],[H2|T2]) :- partitiondate(H1,H2), segmentdates(T1,T2). segmentdates([],[]). %partitiondate(DateString,PartitonedDate):- converts date string DateString into a list %of its Day, Month and Year components, [Dnum,Mnum,Ynum]. It does this by first creating %a list of ASCII codes for each char in DateString. deleseparators then removes those ASCII %codes corresponding to separators (e.g. "/") and creates a list of lists of the remaining ASCII %codes (each list corresponding to day, month, and year). The parameter 3 is a counter--this %is explained in the comment for deleteseparators. These lists are then converted back %into atoms (e.g. [49,49] would become '11', and those atoms are in turn converted to numbers--11, %in this case. %Example: ?- partitiondate('5/10/2003',X). % X = [5,10,2003] partitiondate(DateString,[D1,M1,Y1]) :- atom_chars(DateString,List1), deleteseparators(List1, [Day,Month,Year],3), atom_chars(D1,Day), atom_chars(M1,Month), atom_chars(Y1,Year). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %deleteseparators(ASCIIcodes,[Day,Month,Year],X) :- Day, Month, and Year are the %LISTS of ASCII codes corresponding to the day, month and year in list ASCIIcodes. %For example, if ASCIIcodes is [49,49,47,49,50,47,48,51] (corresponding to the %chars '11/12/03'), then Day is [49,49], Month is [49,50], and Year is [48,51], %corresponding to the lists of chars ['1','1'], ['1','2'], ['0','3']. %X is a counter. It's set to 3. Each time a separator char is encountered (e.g. '-', %'/', etc.) it's decremented. (Note: this relies on there being only ONE separator %between each segment of the original date string.) %Next 3 clauses construct the Day. %ASCII 48-57 correspond to 0-9. deleteseparators([H|T],[[H|Day],Month,Year],X) :- H > 47, H < 58, X =:= 3, deleteseparators(T,[Day,Month,Year],X). %ASCII 65-90 correspond to A-Z. deleteseparators([H|T],[[H|Day],Month,Year],X) :- H > 64, H < 91, X =:= 3, deleteseparators(T,[Day,Month,Year],X). %ASCII 97-122 correspond to a-z. deleteseparators([H|T],[[H|Day],Month,Year],X) :- H > 96, H < 123, X =:= 3, deleteseparators(T,[Day,Month,Year],X). %Next 3 clauses construct the Month. %ASCII 48-57 correspond to 0-9. deleteseparators([H|T],[Day,[H|Month],Year],X) :- H > 47, H < 58, X =:= 2, deleteseparators(T,[Day,Month,Year],X). %ASCII 65-90 correspond to A-Z. deleteseparators([H|T],[Day,[H|Month],Year],X) :- H > 64, H < 91, X =:= 2, deleteseparators(T,[Day,Month,Year],X). %ASCII 97-122 correspond to a-z. deleteseparators([H|T],[Day,[H|Month],Year],X) :- H > 96, H < 123, X =:= 2, deleteseparators(T,[Day,Month,Year],X). %Next 3 clauses construct the Year. %ASCII 48-57 correspond to 0-9. deleteseparators([H|T],[Day,Month,[H|Year]],X) :- H > 47, H < 58, X =:= 1, deleteseparators(T,[Day,Month,Year],X). %ASCII 65-90 correspond to A-Z. deleteseparators([H|T],[Day,Month,[H|Year]],X) :- H > 64, H < 91, X =:= 1, deleteseparators(T,[Day,Month,Year],X). %ASCII 97-122 correspond to a-z. deleteseparators([H|T],[Day,Month,[H|Year]],X) :- H > 96, H < 123, X =:= 1, deleteseparators(T,[Day,Month,Year],X). %If H is a separator, decrement the counter. %ASCII 32 corresponds to ' '. (whitespace) deleteseparators([H|T],[Day,Month,Year],X) :- H =:= 32, Y is X - 1, deleteseparators(T,[Day,Month,Year],Y). %If H is a separator, decrement the counter. %ASCII 44-47 correspond to ',', '-', '.', '/'. deleteseparators([H|T],[Day,Month,Year],X) :- H > 43, H < 48, Y is X - 1, deleteseparators(T,[Day,Month,Year],Y). %If H is a separator, decrement the counter. %ASCII 92 corresponds to '\'. deleteseparators([H|T],[Day,Month,Year],X) :- H =:= 92, Y is X - 1, deleteseparators(T,[Day,Month,Year],Y). %H is neither numeral nor separator, so do nothing(next 4 clauses). deleteseparators([H|T],[Day,Month,Year],X) :- H < 32, deleteseparators(T,[Day,Month,Year],X). %ASCII 33-43 deleteseparators([H|T],[Day,Month,Year],X) :- H > 32, H < 44, deleteseparators(T,[Day,Month,Year],X). %ASCII 58-64. deleteseparators([H|T],[Day,Month,Year],X) :- H > 57, H < 65, deleteseparators(T,[Day,Month,Year],X). %ASCII 123 and greater. deleteseparators([H|T],[Day,Month,Year],X) :- H > 122, deleteseparators(T,[Day,Month,Year],X). deleteseparators([],[[],[],[]],X). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Now that date strings have been segmented, %we can test if the segmented dates are equivalent. equivalentdates([[D,M,Y]]). equivalentdates([[D1,M1,Y1],[D2,M2,Y2] | Rest]) :- date(D1,M1,Y1), date(D2,M2,Y2), sameday(D1,D2), samemonth(M1,M2), sameyear(Y1,Y2), equivalentdates([[D2,M2,Y2] | Rest]). %(Note: the code for the defintion of predicate segmentdates %makes use of the built-in predicate atom_chars(Atom,Chars), which %returns an atom corresponding to the list of ASCII codes Chars. %Because of this, the code must use only atoms and not numbers. %Hence, the facts here use only atoms, and not, as might be expected, %numbers.) day('1st'). day('2nd'). day('3rd'). day('4th'). day('5th'). day('6th'). day('7th'). day('8th'). day('9th'). day('10th'). day('11th'). day('12th'). day('13th'). day('14th'). day('15th'). day('16th'). day('17th'). day('18th'). day('19th'). day('20th'). day('21st'). day('22nd'). day('23rd'). day('24th'). day('25th'). day('26th'). day('27th'). day('28th'). day('29th'). day('30th'). day('31st'). %Atoms '1' - '31' are also days. day(X) :- number_atom(Num,X), Num < 32, Num > 0, integer(Num). month('Jan'). month('January'). month('Feb'). month('February'). month('Mar'). month('March'). month('April'). month('Apr'). month('May'). month('June'). month('Jun'). month('July'). month('Jul'). month('August'). month('Aug'). month('September'). month('Sept'). month('October'). month('Oct'). month('November'). month('Nov'). month('December'). month('Dec'). %Atoms '1' - '12' are also months. month(X) :- number_atom(Num,X), Num < 13, Num > 0, integer(Num). %Atoms '1' - '2999' are considered years. year(X) :- number_atom(Num,X), Num < 3000, Num > 0, integer(Num). date(D,M,Y) :- day(D), month(M), year(Y). %We can use the clause for equivalent terms %to determine if two days are the same. sameday(X,Y) :- equivalentterms([X,Y]). %We can use the clause for equivalent terms %to determine if two months are the same. samemonth(X,Y) :- equivalentterms([X,Y]). %X,Y are same year if X is 2003, Y is 03, etc. sameyear(X,X) :- year(X). sameyear(X,Y) :- year(X), year(Y), number_atom(Xnum,X), number_atom(Ynum,Y), Xnum > Ynum, Xnum - Ynum =:= 2000. sameyear(X,Y) :- year(X), year(Y), number_atom(Xnum,X), number_atom(Ynum,Y), Ynum > Xnum, Ynum - Xnum =:= 2000. sameyear(X,Y) :- year(X), year(Y), number_atom(Xnum,X), number_atom(Ynum,Y), Xnum > Ynum, Xnum - Ynum =:= 1900. sameyear(X,Y) :- year(X), year(Y), number_atom(Xnum,X), number_atom(Ynum,Y), Ynum > Xnum, Ynum - Xnum =:= 1900. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%% Code for equivalent terms. This predicate %%%%%%%%%%%%%%%%%%% %%%%%%%%%%% is used for merging qualitative text entries, %%%%%%%%%%%%%%%%%%% %%%%%%%%%%% e.g. the elements for today, visibility, %%%%%%%%%%%%%%%%%%% %%%%%%%%%%% pressure/direction of change, and same date. %%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %equivalentterms([List]) :- List is a list of equivalent terms if there's %some list of similar terms such that the head of List is a member of %that list, and the tail of List is a subset of that list of similar terms. equivalentterms(List) :- stripnulls(List,ListWithoutNulls), auxequivalentterms(ListWithoutNulls). auxequivalentterms([X]). auxequivalentterms([X | Y]) :- ismemberofsimilarterms(X, Z), subset(Y, Z). subset([X | Y], Z) :- member(X,Z), subset(Y,Z). subset([], Z). ismemberofsimilarterms(X, Z) :- similarterms(Z), member(X,Z). %This predicate is not the negation of equivalentterms. The latter fails if all terms are not equivalent, %or if all terms are "null". We want a predicate that succeeds if not all terms are equivalent, but that also %fails if all terms are "null". Hence we cannot use negation as failure, but must define our own predicate. notequivalentterms(List) :- stripnulls(List,ListWithoutNulls), length(ListWithoutNulls,Length), Length > 0, not auxequivalentterms(ListWithoutNulls). %terms for today. similarterms([cloudy,cloud,'mostly cloudy',overcast,'mostly cloud','heavy cloud']). similarterms([haze,hazy]). similarterms(['scattered cloud', 'scattered clouds','intermittent clouds','partly cloudy','p/cloudy','p\cloudy','patchy cloud','sunny spells','sunny intervals','sunny periods','patchy sunshine', 'partly sunny', 'some sun', 'some sunshine']). similarterms([storm,storms,stormy,thunder,lightning,'thunder storms','thundery storms','thunder and lightning','severe storms']). similarterms([showers,showery,'scattered showers','patchy rain','intermittant rain',drizzle,'heavy showers','blustery showers','thundery showers']). similarterms([ice,icy,frost,frosty,'ground frost', 'black ice', 'hoar frost']). similarterms([snow, 'snow showers', sleet, 'snow flurries', snowy, snowing, 'heavy snow', 'drifting snow']). similarterms([sun,sunshine,sunny,'mostly sunny',fair]). similarterms([rain,rainy,wet,precipitation,inclement,'mostly wet','mostly rain','heavy rain','prolonged rain',downpour,downpours,'frequent downpours','rain/snow']). %terms for visibility. similarterms([good, fair]). similarterms(['very good', excellent, unlimited]). similarterms(['moderate']). similarterms([poor, minimal]). %terms for sunindex similarterms([low]). similarterms([high]). similarterms([medium]). %terms for pressure/direction of change. similarterms([rising]). similarterms([falling]). similarterms([steady]). %terms for same months. similarterms(['Jan','January','1']). similarterms(['Feb','February','2']). similarterms(['Mar','March','3']). similarterms(['Apr','April','4']). similarterms(['May','5']). similarterms(['Jun','June','6']). similarterms(['Jul','July','7']). similarterms(['Aug','August','8']). similarterms(['Sept','September','9']). similarterms(['Oct','October','10']). similarterms(['Nov','November','11']). similarterms(['Dec','December','12']). %terms for same days. similarterms(['1','1st']). similarterms(['2','2nd']). similarterms(['3','3rd']). similarterms(['4','4th']). similarterms(['5','5th']). similarterms(['6','6th']). similarterms(['7','7th']). similarterms(['8','8th']). similarterms(['9','9th']). similarterms(['10','10th']). similarterms(['11','11th']). similarterms(['12','12th']). similarterms(['13','13th']). similarterms(['14','14th']). similarterms(['15','15th']). similarterms(['16','16th']). similarterms(['17','17th']). similarterms(['18','18th']). similarterms(['19','19th']). similarterms(['20','20th']). similarterms(['21','21st']). similarterms(['22','22nd']). similarterms(['23','23rd']). similarterms(['24','24th']). similarterms(['25','25th']). similarterms(['26','26th']). similarterms(['27','27th']). similarterms(['28','28th']). similarterms(['29','29th']). similarterms(['30','30th']). similarterms(['31','31st']). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Purpose of this predicate is to select the preferred term FROM AMONGST A LIST OF EQUIVALENT TERMS. %preferredterm(List,PreferredTerm) :- PreferredTerm is the term from amongst those terms in List which is preferred. %Given the way this predicate is used in rules, we know that all the term in the list are %equivalent, so we need only look at the first to determine the preferred term. preferredterm(List,PreferredTerm) :- stripnulls(List,ListWithoutNulls), auxpreferredterm(ListWithoutNulls,PreferredTerm). auxpreferredterm([H|T],PreferredTerm) :- equivalentterms([H|T]), prefer(H,PreferredTerm). %prefer(Term,PreferredTerm):- PreferredTerm is preferred to term. %ice is PreferredTerm for: ice,icy,frost,frosty,'ground frost', 'black ice', 'hoar frost'. prefer(ice,ice). prefer(icy,ice). prefer(frost,ice). prefer(frosty,ice). prefer('ground frost',ice). prefer('black ice',ice). prefer('hoar frost',ice). %storms is preferredTerm for: storm,storms,stormy,thunder,lightning,'thunder storms','thundery storms','thunder and lightning','severe storms'. prefer(storms,storms). prefer(storm,storms). prefer(stormy,storms). prefer(thunder,storms). prefer(lightning,storms). prefer('thunder storms',storms). prefer('thundery storms',storms). prefer('thunder and lightning',storms). prefer('severe storms',storms). %showers is PreferredTerm for: showers,showery,'scattered showers','patchy rain','intermittant rain',drizzle,'heavy showers','blustery showers','thundery showers'. prefer(showers,showers). prefer(showery,showers). prefer('scattered showers',showers). prefer('patchy rain',showers). prefer('intermittant rain',showers). prefer(drizzle,showers). prefer('heavy showers',showers). prefer('blustery showers',showers). prefer('thunder showers',showers). %sun is preferred term for: sun, sunshine, sunny, mostly sunny, fair. prefer(sun,sun). prefer('mostly sunny',sun). prefer(sunny,sun). prefer(fair,sun). prefer('sunshine','sun'). %cloud is preferred term for: cloud, cloudy, 'mostly cloud', 'mostly cloudy', 'heavy cloud', 'overcast'. prefer(cloud,cloud). prefer(cloudy,cloud). prefer('mostly cloud',cloud). prefer('mostly cloudy',cloud). prefer('overcast',cloud). prefer('heavy cloud', cloud). %'sun and cloud' is preferred term for: 'scattered cloud', 'scattered clouds','intermittent clouds','partly cloudy','p/cloudy','p\cloudy','patchy cloud','sunny spells','sunny intervals','sunny periods','patchy sunshine', 'partly sunny', 'some sun', 'some sunshine'. prefer('patchy cloud','sun and cloud'). prefer('scattered cloud','sun and cloud'). prefer('scattered clouds','sun and cloud'). prefer('intermittent clouds','sun and cloud'). prefer('partly cloudy','sun and cloud'). prefer('p/cloudy','sun and cloud'). prefer('p\cloudy','sun and cloud'). prefer('partly sunny','sun and cloud'). prefer('some sun','sun and cloud'). prefer('some sunshine','sun and cloud'). prefer('sunny spells','sun and cloud'). prefer('sunny periods','sun and cloud'). prefer('sunny intervals','sun and cloud'). prefer('patchy sunshine','sun and cloud'). %rain is preferred for: rain, rainy,showers, wet, showery, precipitation, 'intermittant showers', 'patchy showers', drizzle, inclement,'mostly wet', 'mostly rain', 'heavy rain', 'prolonged rain', 'rain/snow' prefer(rain,rain). prefer(rainy,rain). prefer(wet,rain). prefer(precipitation,rain). prefer('downpours',rain). prefer('frequent downpours',rain). prefer('inclement',rain). prefer('mostly wet',rain). prefer('mostly rain',rain). prefer('heavy rain',rain). prefer('prolonged rain',rain). prefer('downpour',rain). prefer('rain/snow',rain). %snow is preferred for: snow, snowy, snow flurries, sleet, snow showers, snowing, heavy snow, drifting snow. prefer(snow,snow). prefer(snowy,snow). prefer(sleet,snow). prefer('snow showers', snow). prefer('snow flurries', snow). prefer('snowing', snow). prefer('heavy snow', snow). prefer('drifting snow', snow). %haze is preffered for: haze, hazy. prefer(haze,haze). prefer(hazy,haze). %for pressure: prefer(rising,rising). prefer(falling,falling). prefer(steady,steady). %for visibility: good is preferred for: good, fair prefer('good', 'good'). prefer(fair, good). %for visibility: very good is preferred for: very good, unlimited, excellent prefer('very good', 'very good'). prefer('excellent', 'very good'). prefer('unlimited','very good'). %for visibility: moderate is preferred for: moderate prefer('moderate','moderate'). %for visibility: poor is preferred for: poor, minimal prefer(poor,poor). prefer(minimal,poor). %For sunindex: low is preferred for: low prefer(low,low). prefer(high,high). prefer(medium,medium). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %The purpose of this predicate is to take a list of NONEQUIVALENT TERMS and substitute a preferred term for each term %in that list. This new list of terms is a more satisfactory basis for some subsequent decision procedure (e.g. voting), to %select one or more terms. %preferredterms(Terms,PreferredTerms) :- PreferredTerms is the list of terms Terms, with each term in Terms substituted by %its preferred term. preferredterms(Terms, PreferredTerms) :- stripnulls(Terms,ListWithoutNulls), auxpreferredterms(ListWithoutNulls,PreferredTerms). auxpreferredterms([X|Y],[Z|Ys]) :- prefer(X,Z), auxpreferredterms(Y,Ys). auxpreferredterms([],[]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%% Code for similar magnitudes, e.g. similar temps, similar %%%%%%%%%%%%%%%%% %%%%%%%%%%%%%% windspeeds, etc. For each magnitude the knowledge %%%%%%%%%%%%%%%%% %%%%%%%%%%%%%% engineer can specify an accepted range, within which %%%%%%%%%%%%%%%%% %%%%%%%%%%%%%% magnitudes count as similar. %%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %A top-level goal. similarwindspeeds(List,String) :- String is a string constructed %from the list of windspeeds, List, with the maximum and minimum connected by a hyphen. %For example: similarwindspeeds(['15mph','16mph','29kph'],['15-17mph']) %5 is accepted interval, 67 is "C", [32,116,111,32] is ' to ' similartemps(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,5,[67],[32,116,111,32],String). %10 is accepted interval, [109,112,104] is 'mph', and 45 is '-'. similarwindspeeds(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,10,[109,112,104],[45],String). %20 is the accepted interval, 37 is '%', and 45 is '-'. similarhumidities(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,20,[37],[45],String). %5 miles is the accepted interval for similar distances (used in the rule for visibility.). %[109,105,108,101,115] is "miles". We need to check if only distance is 1 mile--then drop the "s". similardistances(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), ismile(StrippedList), similarvalues(StrippedList,5,[109,105,108,101],[45],String). similardistances(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not ismile(StrippedList), similarvalues(StrippedList,5,[109,105,108,101,115],[45],String). ismile(StrippedList) :- maxlist(StrippedList, X), X =:= 1. %5 mb is the accepted interval for similar pressure absolute values. %[109,66] is "mB", 45 is "-". similarabsolutevalues(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,5,[109,66],[45],String). %Sun indicies do not have units, hence Suffix is "[]". Accepted interval is 2. similarindices(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,2,[],[45],String). %AcceptedInterval for daylighthours is 5 mins. Note, times in list should all be in 12hr format. %Note that, because the minutes in these times have not been converted to decimals, this predicate %fails for times within the accepted interval but that are clustered either side of the hour. %The most obvious way to remedy this is to divide the minutes by 60 to obtain a decimal. However, %if the number of minutes is fairly small, Prolog returns the answer in scientific notation, and that %makes it too complicated to manipulate the ASCII codes corresponding to the numbers. similardaylighthours(List) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), maxlist(StrippedList,Max), minlist(StrippedList,Min), Max - Min =< 0.06. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Code for stripping units, so quantities can be compared. %stripunits(List1,List2) :- List2 is List1 with the units suffixes stripped. %For example, if an element of List1 is "15.5mph", the corresponding element of %List2 would be "15.5". stripunits([H1 | T1], [H2 | T2]) :- deleteunits(H1, H2), stripunits(T1, T2). stripunits([], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Version of deletunits with helper rules that convert the units if required. %deleteunits uses the built-in predicate atom_chars to convert each string to %a list of the ASCII codes of the chars in the string. This allows any unit suffix %to be removed. %deleteunits(NumWithUnit,ConvertedNum) :- ConvertedNum is number corresponding to %the string NumWithUnit with any units suffix removed. deleteunits(NumWithUnit,ConvertedNum) :- atom_chars(NumWithUnit, List1), separatesuffix(List1, List2), convert(List2,ConvertedNum). %separatesuffix separates the unit suffix from the lists of chars, %and creates a list of magnitude-suffix paris. For example %[[15,mph],[16,mph],[27,kph]], except that instead of "15", "mph", etc. %we have the corresponding list of ASCIIcodes. Only ASCII codes we want %in our magnitude are 48-57 (the numerals 0-9), and 45 and 46 ("-" and "."). %separatesuffix(OriginalListOfChars,ListOfMagnitudeSuffixPairs) :- ListOfMagnitudeSuffixPairs % is a list of lists. Each list in the list is a pair consisting of the magnitude and the suffix %in the original list of quantities. %First 3 rules get the magnitude. separatesuffix([H1 | T1], [[H1 | T2],Suffix]) :- H1 =:= 45, separatesuffix(T1, [T2,Suffix]). separatesuffix([H1 | T1], [[H1 | T2],Suffix]) :- H1 =:= 46, separatesuffix(T1, [T2,Suffix]). separatesuffix([H1 | T1], [[H1 | T2],Suffix]) :- H1 > 47, H1 < 58, separatesuffix(T1, [T2,Suffix]). %47 is '/' -- do nothing. separatesuffix([H1 | T1], ListOfMagnitudeSuffixPairs) :- H1 =:= 47, separatesuffix(T1, ListOfMagnitudeSuffixPairs). %32 is whitespace -- do nothing. separatesuffix([H1 | T1], ListOfMagnitudeSuffixPairs) :- H1 =:= 32, separatesuffix(T1, ListOfMagnitudeSuffixPairs). %Now get the suffix. separatesuffix([H1 | T1], [Magnitude,[H1|T2]]) :- H1 < 45, separatesuffix(T1, [Magnitude,T2]). separatesuffix([H1 | T1], [Magnitude,[H1|T2]]) :- H1 > 57, separatesuffix(T1, [Magnitude,T2]). separatesuffix([], [[],[]]). %convert(MagnitudeSuffixPair,ConvertedMagnitude,ConvertedMagWithUnits) :- ConvertedMagnitude is the magnitude component of %MagnitudeSuffixPair converted to the appropriate units. ConvertedMagWithUnits is the reconstituted atom, say '16.778mph'. %The suffix component of the pair is checked and, %if it's either "f","F","kph", or "KPH", then the magnitude is converted to either celsius or mph as required. %For example, if MagnitudeSuffixPair is the two membered list [27,kph] then ConvertedMagnitude would be the %number 16.778. But note that in fact the two members of MagnitudeSuffixPair are both in fact lists of ASCII codes. %So, in this example, it would in fact be the list of lists: [[50,55],[107,112,104]]. %Note: atom_chars ALWAYS RETURNS AN ATOM--in particular, W below will be an atom, that is %something of the form '21' not the number 21. To get a number, something that the procedure tempconvert (etc.) %requires, we must use the built in predicate number_atom(Number,Atom). %67 is "C", no conversion required. convert([X,[67]],Num) :- atom_chars(Atom,X), number_atom(Num,Atom). %99 is "c", no conversion required. convert([X,[99]],Num) :- atom_chars(Atom,X), number_atom(Num,Atom). %70 is "F", conversion required. convert([X,[70]],Num2) :- atom_chars(Atom,X), number_atom(Num,Atom), tempconvert(Num,Num2). %102 is "f", conversion required. convert([X,[102]],Num2) :- atom_chars(Atom,X), number_atom(Num,Atom), tempconvert(Num,Num2). %77,80,72 is "M","P","H", no conversion required. convert([X,[75,80,72]],Num) :- atom_chars(Atom,X), number_atom(Num,Atom). %109,112,104 is "m","p","h", no conversion required. convert([X,[109,112,104]],Num) :- atom_chars(Atom,X), number_atom(Num,Atom). %75,80,72 is "K","P","H", conversion required. convert([X,[75,80,72]],Num2) :- atom_chars(Atom,X), number_atom(Num,Atom), speedconvert(Num,Num2). %107,112,104 is "k","p","h", conversion required. convert([X,[107,112,104]],Num2) :- atom_chars(Atom,X), number_atom(Num,Atom), speedconvert(Num,Num2). %[109,105,108,101] is "mile", no conversion required. convert([X,[109,105,108,101]],Num) :- atom_chars(Atom,X), number_atom(Num,Atom). %[109,105,108,101,115] is "miles", no conversion required. convert([X,[109,105,108,101,115]],Num) :- atom_chars(Atom,X), number_atom(Num,Atom). %107,109 is "km", conversion required. convert([X,[107,109]],Num2) :- atom_chars(Atom,X), number_atom(Num,Atom), speedconvert(Num,Num2). %75,77 is "KM", conversion required. convert([X,[75,77]],Num2) :- atom_chars(Atom,X), number_atom(Num,Atom), speedconvert(Num,Num2). %Any other units (e.g. "%"), no conversion required. %Of course, this may change if more rules with different %top-level goals are added. convert([X,AnyOtherSuffix],Num) :- atom_chars(Atom,X), number_atom(Num,Atom). %%%Conversion of units. mph -> kph, F -> C, etc. %These predicates need to test what the units are for the first parameter, and IF they're Fahrenheit or KPH, then %convert them to Celsius or MPH. %Conversion of Fahrenheit to Celsius. Subtract 32 and multiply by 5/9. %tempconvert(Fahrenheit,Celsius) :- Celsius is the equivalent temp to given Fahrenheit. tempconvert(Fahrenheit,Celsius) :- Celsius is (Fahrenheit - 32) * (5/9). %Conversion of kph to mph-- multiply by .6214. %speedconvert(KPH,MPH). speedconvert(KPH,MPH) :- MPH is KPH * 0.6214. %Strategy is to find the max and min of a list, then see if they %fall within a specified range. %maxlist([List],Max) :- Max is the largest element in list List. maxlist([X|Xs],Max) :- maxlist(Xs,X,Max). maxlist([X|Xs],Y,Max) :- X =< Y, maxlist(Xs,Y,Max). maxlist([X|Xs],Y,Max) :- X > Y, maxlist(Xs,X,Max). maxlist([],Max,Max). %minlist([List],Min) :- Min is smallest member of List. minlist([X|Xs],Min) :- minlist(Xs,X,Min). minlist([X|Xs],Y,Min) :- Y =< X, minlist(Xs,Y,Min). minlist([X|Xs],Y,Min) :- Y > X, minlist(Xs,X,Min). minlist([],Min,Min). %similarvalues(List,AcceptedInterval,Suffix,Connector,String) :- is true if max %of list and min of list is less than or equal to the accepted interval. %The accepted interval will depend on the magnitude being compared. %For example: ?- similarwindspeeds(['10mph','11mph','14mph']). % yes %String is a string concatenated out of max and min value, a connector (typically %a hyphen), and a unit suffix. (e.g. '21-25mph', '-5 to 0C', etc.) %Two cases: if max = min, then string need only contain one (e.g. "1 mile", not "1-1 mile"). similarvalues(List,AcceptedInterval,Suffix,Connector,String) :- maxlist(List,X), minlist(List,Y), Z is X - Y, Z =:= 0, Z =< AcceptedInterval, number_chars(X,CharsMax), append(CharsMax,Suffix,Chars), atom_chars(String,Chars). %Max =\= min, so string includes both values. similarvalues(List,AcceptedInterval,Suffix,Connector,String) :- maxlist(List,X), minlist(List,Y), Z is X - Y, Z =\= 0, Z =< AcceptedInterval, number_chars(X,CharsMax), number_chars(Y,CharsMin), append(CharsMin,Connector,S1), append(S1,CharsMax,S2), append(S2,Suffix,Chars), atom_chars(String,Chars). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%% The occurrence of the string "null" where a textentry is %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%% is expected means MISSING VALUE. That is, two or more %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%% occurrences of "null" in a list of textentries do NOT %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%% count as the same value--each occurrence is treated as %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%% a distinct symbol. %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %stripnulls(List,ListWithoutNulls) :- List is a list of atoms (say terms for today's weather, or terms for temps, etc). %ListWithoutNulls is the same list, except any atoms which consist of the string "null" have been removed. This allows %subsequent predicates to check that the list of atoms are of similar magnitudes, or are similar terms, etc. Using this %predicate as a helper predicate for similartemps (etc.), and equivalentterms means that the predicate groundable is no %longer required, and so the fusion rules are simpler. %terms is null, so drop it. stripnulls([H1|T1],ListWithoutNulls) :- H1 == null, stripnulls(T1,ListWithoutNulls). %terms is not null, so keep it. stripnulls([H1|T1],[H1|T2]) :- not H1 == null, stripnulls(T1,T2). stripnulls([],[]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %groundable(Reports,Branch,TextEntries) :- TextEntries is a list of the text entries which %are the leaves at the end of branch Branch in those reports in list Reports that have Branch. groundable([H|T],Branch,TextEntries) :- segmentbranch(Branch,Nodes), auxgroundable([H|T],Nodes,TextEntries), notempty(TextEntries). auxgroundable([H|T],Nodes,[TextEntry|Rest]) :- isinreport(Nodes,H,TextEntry), auxgroundable(T,Nodes,Rest). auxgroundable([H|T],Nodes,Rest) :- not branchisinreport(Nodes,H), auxgroundable(T,Nodes,Rest). auxgroundable([],Nodes,[]). %If no reports are groundable, then TextEntries is an empty list. %In that case we want groundable to fail. notempty([H]). notempty([H|T]) :- notempty(T). branchisinreport(Nodes,Report) :- hasbranch(Nodes,[Report]). hasbranch([H|T],TreeList) :- nodeisinreport(H,TreeList,SubTreeList), hasbranch(T,SubTreeList). hasbranch([],TreeList). %auxiliary procedure for groundable. %segmentbranch(Branch,Nodes) :- Nodes is a list of the nodes in branch Branch. For example, if Branch were: % weatherreport/daylighthours/sunrise %then Nodes would be the list: % [weatherreport,daylighthours,sunrise] segmentbranch(Branch,Nodes) :- atom_chars(Branch,Chars), dropslashes(Chars,ListOfNodes), converttoatoms(ListOfNodes,Nodes). %converttoatoms(ListOfNodes,Nodes) :- ListOfNodes is a list of lists of chars. Nodes is %a list of atoms corresponding to the list of chars. converttoatoms([H1|T1],[H2|T2]) :- atom_chars(H2,H1), converttoatoms(T1,T2). converttoatoms([],[]). %dropslashes(Chars,ListOfNodes) :- Chars is the list of ASCII codes corresponding to the branch string. ListOfNodes %is a list of lists of chars corresponding to each node in the branch. %If Chars is the list [1,2,3,47,4,5,6,47,7,8,9] then ListOfNodes is the list of lists [[1,2,3],[4,5,6],[7,8,9]] dropslashes(Chars,ListOfNodes) :- dropslashes(Chars,[],ListOfNodes). %47 is ASCII code for forward slash "/". dropslashes([H|T],X,T2) :- H < 47, append(X,[H],Z), dropslashes(T,Z,T2). dropslashes([H|T],X,T2) :- H > 47, append(X,[H],Z), dropslashes(T,Z,T2). dropslashes([H|T],X,[X|T2]) :- H =:= 47, dropslashes(T,[],T2). dropslashes([],X,[X]). %These are the procedures that check if a branch is in a report and, if it is, return the text entry that is the leaf. %isinreport(Nodes,Report,TextEntry) :- TextEntry is the leaf at the end of the branch Nodes in tree Report, if %the branch is a branch in the report. isinreport(Nodes,Report,TextEntry) :- gettextentry(Nodes,[Report],TextEntry). gettextentry([H|T],TreeList,TextEntry) :- nodeisinreport(H,TreeList,SubTreeList), gettextentry(T,SubTreeList,TextEntry). gettextentry([],TextEntry,TextEntry). nodeisinreport(H,[H,H2],H2). nodeisinreport(H,[[H,H2]|T],H2). nodeisinreport(H,[[G,G2]|T],H2) :- H \== G, nodeisinreport(H,T,H2). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %convert12hr(Times, ConvertedTimes) :- ConvertedTimes is the list of all times in list Times in 12hr clock. converttwelvehr(Times, ConvertedTimes) :- stripnulls(Times,ListWithoutNulls), auxconverttwelvehr(ListWithoutNulls,ConvertedTimes). %auxconvert12hr(Times, ConvertedTimes) :- ConvertedTimes is the list of all times in list Times in 12hr clock. auxconverttwelvehr([H1|T1],[H2|T2]) :- number_atom(Num,H1), twelvehrclock(Num,Num2), number_atom(Num2,Atom), trailingzero(Atom,H2), auxconverttwelvehr(T1,T2). auxconverttwelvehr([],[]). %%%24 hr clock: 24hr -> 12 hr (easier than other way round). %twelvehrclock(Time,TwelveCock) :- TwelveClock is the 12hr version of Time. twelvehrclock(Time,Time) :- number(Time), Time =< 12.59, Time >= 0.00. twelvehrclock(Time,TwelveClock) :- number(Time), Time > 12.59, Time < 24.00, TwelveClock is Time - 12.00. %trailingzero(Atom,AtomWithZero) :- if the time is 12.50 or 8.40 then procedure twelvehrclock will return the %number 12.5 or 8.4. AtomWithZero is Atom with an addtional trailing zero, if required. trailingzero(Atom,AtomWithZero) :- atom_chars(Atom,Chars), checkzero(Chars, 0, 0, Counter), addzero(Counter,Chars,CharsWithZero), atom_chars(AtomWithZero,CharsWithZero). %checkzero(Chars,InitCounter,Flag,Counter) :- checks list of chars Chars to see how many chars follow the radix %point (ASCII 46). Flag flags the radix, and Counter is incremented for every char starting from, and including, the radix to the end. %If counter is less than 3, then addzero will append a zero (ASCII 48) to Chars. %H is before the radix. checkzero([H|T],X,Flag,Counter) :- Flag =:= 0, H =\= 46, checkzero(T,X,Flag,Counter). %H is the radix. checkzero([H|T],X,Flag,Counter) :- Flag =:= 0, H =:= 46, Y is X + 1, checkzero(T,Y,1,Counter). %H is after the radix. checkzero([H|T],X,Flag,Counter) :- Flag =:= 1, H =\= 46, Y is X + 1, checkzero(T,Y,1,Counter). checkzero([],Counter,1,Counter). %addzero(Counter,Chars,AtomWithZero) :- if Counter is less than 3, AtomWithZero is chars with zero appended. addzero(Counter,Chars,AtomWithZero) :- Counter < 3, append(Chars,[48],AtomWithZero). addzero(Counter,Chars,Chars) :- Counter >= 3. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %The following code deals with finding, and rejecting, outlying values. This code is used when rules such as %similarwindspeeds fail. For example, if the windspeeds from a set of reports are '15mph','14mph','16mph','45mph', %then these are not similar. In this case, String would be the string "14-16mph". %Accepted interval is 5. [67] is "C", [32,116,111,32] is " to ". notsimilartemps(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), print(StrippedList),nl, not similarvalues(StrippedList,5), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,5,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =\= End, makeintervalstring(End,Start,[67],[32,116,111,32],String). %If all temps within range are the same: notsimilartemps(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,5), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,5,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =:= End, makesingleintervalstring(Start,[67],String). %Accepted interval is 10. [109,112,104] is "mph", [45] is " - ". notsimilarwindspeeds(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,10), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,10,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =\= End, makeintervalstring(End,Start,[109,112,104],[45],String). %If all windspeeds within range are the same: notsimilarwindspeeds(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,10), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,10,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =:= End, makesingleintervalstring(Start,[109,112,104],String). %Accepted interval is 20. [37] is "%", [45] is " - ". notsimilarhumidities(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,20), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,20,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =\= End, makeintervalstring(End,Start,[37],[45],String). %If all humidities within range are the same: notsimilarhumidities(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,20), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,20,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =:= End, makesingleintervalstring(Start,[37],String). %Accepted interval is 5. [109,105,108,101,115] is "miles", [45] is " - ". notsimilardistances(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,5), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,5,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =\= End, makeintervalstring(End,Start,[109,105,108,101,115],[45],String). %If all distances within range are the same: notsimilardistances(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,5), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,5,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =:= End, makesingleintervalstring(Start,[109,105,108,101,115],String). %Accepted interval is 2. [] is no suffix, [45] is " - ". notsimilarindices(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,2), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,2,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =\= End, makeintervalstring(End,Start,[],[45],String). %If all indices within range are the same: notsimilarindices(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,2), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,2,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =:= End, makesingleintervalstring(Start,[],String). %Accepted interval is 5. [109,66] is "mB", [45] is " - ". notsimilarabsolutevalues(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,5), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,5,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =\= End, makeintervalstring(End,Start,[109,66],[45],String). %If all distances within range are the same: notsimilarabsolutevalues(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,5), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,5,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =:= End, makesingleintervalstring(Start,[109,66],String). %Accepted interval is about 5 minutes. [] is no suffix, [45] is " - ". notsimilardaylighthours(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,0.06), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,0.06,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =\= End, addlastzero(Start,End,StartWithTrailingZero,EndWithTrailingZero), maketimestring(EndWithTrailingZero,StartWithTrailingZero,[45],String). %If all times within the range are the same. notsimilardaylighthours(List,String) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,0.06), ordervalues(StrippedList,OrderedList), getlongestinterval(OrderedList,OrderedList,0.06,LeftList,RightList,LeftEndList,RightEndList), findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval), getintervalpair(Interval,Start,End), Start =:= End, addlastzero(Start,End,StartWithTrailingZero,EndWithTrailingZero), atom_chars(String,StartWithTrailingZero). %Adds a trailing zero if required to numbers Start and End (only used for notsimilardaylighthours). addlastzero(Start,End,StartWithTrailingZero,EndWithTrailingZero) :- number_chars(Start,StartChars), number_chars(End,EndChars), addnought(StartChars,StartWithTrailingZero), addnought(EndChars,EndWithTrailingZero). %Number has two or more digits after radix, do nothing. addnought([X1,46,X2,X3|Tail], [X1,46,X2,X3|Tail]). addnought([X1,X2,46,X3,X4|Tail], [X1,X2,46,X3,X4|Tail]). addnought([X1,46,X2,X3], [X1,46,X2,X3]). addnought([X1,X2,46,X3,X4], [X1,X2,46,X3,X4]). %Number has only one digit after the radix, add a zero. addnought([X1,46,X2], [X1,46,X2,48]). addnought([X1,X2,46,X3], [X1,X2,46,X3,48]). %Makes the tring giving the accepted interval of times. maketimestring(EndWithTrailingZero,StartWithTrailingZero,[45],String) :- append(EndWithTrailingZero, [45], S1), append(S1,StartWithTrailingZero,S2), atom_chars(String,S2). %The following procedure for sorting a list is from Sterling and Shapiro, p.55. %ordervalues(List,OrderedList) :- list OrderedList is an ordered permutation of list List. ordervalues([X|Xs],Ys) :- ordervalues(Xs,Zs), insert(X,Zs,Ys). ordervalues([],[]). insert(X,[],[X]). insert(X,[Y|Ys],[Y|Zs]) :- X > Y, insert(X,Ys,Zs). insert(X,[Y|Ys],[X,Y|Ys]) :- X =< Y. %Succeeds if the range of values in List is less than or equals AcceptedInterval. similarvalues(List,AcceptedInterval) :- maxlist(List,X), minlist(List,Y), Z is X - Y, Z =< AcceptedInterval. %getlengthsoflistsofsimilarvalues(OrderedList,OrderedList,AcceptedInterval,LeftList,RightList,LeftEndList,RightEndList) :- for each member of %the ordered list, this procedure searches through that list in both left and right directions counting the number %of elements in the ordered list that are within the accepted interval of that member. These counts are kept in two %lists, LeftList and RightList, together with two further lists containg the end values of those searches. getlongestinterval([H|T],OrderedList,AcceptedInterval,[L|L2],[R|R2],[LeftEnd|T1],[RightEnd|T2]) :- append(LeftList,[H|T],OrderedList), reverse(LeftList,Reverse), left(H,AcceptedInterval,Reverse,L,LeftEnd), right(H,AcceptedInterval,T,R,RightEnd), getlongestinterval(T,OrderedList,AcceptedInterval,L2,R2,T1,T2). getlongestinterval([],OrderedList,AcceptedInterval,[],[],[],[]). %traverses the list in the rightwards direction, checking to see if subsequent members of the list are within the accepted interval, %and, if they are, Count is incremented. RightEnd is the furthest member of the list within AcceptedInterval from H. %right(H,AcceptedInterval,OrderedList,Count,RightEnd) :- Count is a count of all members of OrderedList within AcceptedInterval of H. right(H,AcceptedInterval,OrderedList,Count,RightEnd) :- righttraverse(H,AcceptedInterval,OrderedList,0,Count,RightEnd). right(H,AcceptedInterval,[],0,0). %traverses the list in the leftwards direction, checking to see if subsequent members of the list are within the accepted interval, %and, if they are, Count is incremented. LeftEnd is the furthest member of the list within AcceptedInterval from H. %right(H,AcceptedInterval,OrderedList,Count,LeftEnd) :- Count is a count of all members of OrderedList within AcceptedInterval of H. left(H,AcceptedInterval,OrderedList,Count,LeftEnd) :- lefttraverse(H,AcceptedInterval,OrderedList,0,Count,LeftEnd). left(H,AcceptedInterval,[],0,0). %Does the actual traversal. righttraverse(H,AcceptedInterval,[H1,H2|T],X,Count,RightEnd) :- H1 - H =< AcceptedInterval, H2 - H =< AcceptedInterval, X2 is X + 1, righttraverse(H,AcceptedInterval,[H2|T],X2,Count,RightEnd). righttraverse(H,AcceptedInterval,[H1,H2|T],X,Count,H1) :- H1 - H =< AcceptedInterval, H2 - H > AcceptedInterval, Count is X + 1. righttraverse(H,AcceptedInterval,[H1],X,Count,H1) :- H1 - H =< AcceptedInterval, Count is X + 1. righttraverse(H,AcceptedInterval,[H1|T],0,0,H) :- H1 - H > AcceptedInterval. lefttraverse(H,AcceptedInterval,[H1,H2|T],X,Count,RightEnd) :- H - H1 =< AcceptedInterval, H - H2 =< AcceptedInterval, X2 is X + 1, lefttraverse(H,AcceptedInterval,[H2|T],X2,Count,RightEnd). lefttraverse(H,AcceptedInterval,[H1,H2|T],X,Count,H1) :- H - H1 =< AcceptedInterval, H - H2 > AcceptedInterval, Count is X + 1. lefttraverse(H,AcceptedInterval,[H1],X,Count,H1) :- H - H1 =< AcceptedInterval, Count is X + 1. lefttraverse(H,AcceptedInterval,[H1|T],0,0,H) :- H - H1 > AcceptedInterval. %Combines all the lists into a single list, and finds the triple [DataPoints, Start, End] with the greatest number of data points. findinterval(OrderedList,LeftList,RightList,LeftEndList,RightEndList,Interval) :- makelist(OrderedList,LeftList,RightList,LeftEndList,RightEndList,NewList), getinterval(NewList,Interval). makelist(OrderedList,LeftList,RightList,LeftEndList,RightEndList,NewList) :- constructfromhalf(OrderedList,LeftList,LeftEndList,FirstHalf), constructfromhalf(OrderedList,RightList,RightEndList,SecondHalf), append(FirstHalf,SecondHalf,NewList). constructfromhalf([H1|T1],[H2|T2],[H3|T3],[[H2,H1,H3]|NewList]) :- constructfromhalf(T1,T2,T3,NewList). constructfromhalf([],[],[],[]). %getinterval(NewList,Interval) :- NewList is a list of triples [DataPoints, Start, End]. Interval is the triple [DataPoints, Start, End] %with the greatest number of DataPoints in NewList. getinterval([X|Xs],Max) :- getinterval(Xs,X,Max). getinterval([[Interval1,Start1,End1]|Xs],[Interval2,Start2,End2],Max) :- Interval1 =< Interval2, getinterval(Xs,[Interval2,Start2,End2],Max). getinterval([[Interval1,Start1,End1]|Xs],[Interval2,Start2,End2],Max) :- Interval1 > Interval2, getinterval(Xs,[Interval1,Start1,End1],Max). getinterval([],Max,Max). %getintervalpair(Interval,IntervalPair) :- IntervalPair, are the lowest and highest values within the accepted range. getintervalpair([Interval,Start,End],Start,End). %makeintervalstring(Start,End,Suffix,Connector,String) :- String is the string constructed out of the upper and lower values within the accepted range. makeintervalstring(Start,End,Suffix,Connector,String) :- number_chars(Start,CharMin), number_chars(End,CharMax), append(CharMin,Connector,S1), append(S1,CharMax,S2), append(S2,Suffix,S3), atom_chars(String,S3). %If all data points within the accepted range are the same, then we need only one and no connector. makesingleintervalstring(Start,Suffix,String) :- number_chars(Start,Chars), append(Chars,Suffix,S1), atom_chars(String,S1). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %For rules that resolve conflicts by using preferences over sources we need predicates that show that a %conflict exists, but that do NOT return a binding. The previous predicates all return strings representing the interval of those %text entries that are within the accepted range. But now we want a similar set of predicates %that simply fail if there are outlying values, so that subsequent predicates can select only the text entry for the preferred source. similartemps(List) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,5). similarwindspeeds(List) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,10). similardistances(List) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,5). similarhumidities(List) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,20). similarabsolutevalues(List) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,5). similarindices(List) :- stripnulls(List,ListWithoutNulls), stripunits(ListWithoutNulls,StrippedList), similarvalues(StrippedList,2). %The presence of nulls means that we need another set of predicates (notsimilartemps, etc.) because what we %need is a set of predicates not (quite) the negation of similartemps (etc.). similartemps FAILS if all the members %of List are null, hence not similartemps SUCCEEDS in that case. But if that's true, then, unless we insert a special %condition to test for this circumstance, the RULE in question will also succeed, and then an action will build %the merged report with some combination of nulls for the text entry. notsimilartemps(List) :- stripnulls(List,ListWithoutNulls), length(ListWithoutNulls,Length), Length > 0, stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,5). notsimilarwindspeeds(List) :- stripnulls(List,ListWithoutNulls), length(ListWithoutNulls,Length), Length > 0, stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,10). notsimilardistances(List) :- stripnulls(List,ListWithoutNulls), length(ListWithoutNulls,Length), Length > 0, stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,5). notsimilarhumidities(List) :- stripnulls(List,ListWithoutNulls), length(ListWithoutNulls,Length), Length > 0, stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,20). notsimilarabsolutevalues(List) :- stripnulls(List,ListWithoutNulls), length(ListWithoutNulls,Length), Length > 0, stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,5). notsimilarindices(List) :- stripnulls(List,ListWithoutNulls), length(ListWithoutNulls,Length), Length > 0, stripunits(ListWithoutNulls,StrippedList), not similarvalues(StrippedList,2). notsimilardaylighthours(List) :- stripnulls(List,ListWithoutNulls), length(ListWithoutNulls,Length), Length > 0, stripunits(ListWithoutNulls,StrippedList), maxlist(StrippedList,Max), minlist(StrippedList,Min), Max - Min > 0.06. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Code for voting aggregation functions. For definitions of these functions see the paper "NewsFusion Systems: Logic-based %merging of heterogeneous news reports, definition 4.1. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% Code for first-past-the-post voting. %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %(No majority needed; in case of tie, no winner.) %firstpastthepost(List,Term) :- Term is the term that occurs most in List. e.g. firstpastthepost([rain,snow,sun,rain],rain). %There is a unique winner. firstpastthepost(List,Term) :- stripnulls(List,ListWithoutNulls), getvotes(ListWithoutNulls,VoteList), getwinner(VoteList,WinningPair), checkties(WinningPair,VoteList,TiesForMax), length(TiesForMax,Length), Length =:= 1, getterm(WinningPair,Term). %There's no unique winner, so output the string 'No first past the post' firstpastthepost(List,'No first past the post') :- stripnulls(List,ListWithoutNulls), getvotes(ListWithoutNulls,VoteList), getwinner(VoteList,WinningPair), checkties(WinningPair,VoteList,TiesForMax), length(TiesForMax,Length), Length > 1. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% Code for popular sharing. %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %(No majority needed; in case of tie, form disjunction of winners.) %There's a unique winner. popularsharing(List,Term) :- stripnulls(List,ListWithoutNulls), getvotes(ListWithoutNulls,VoteList), getwinner(VoteList,WinningPair), checkties(WinningPair,VoteList,TiesForMax), length(TiesForMax,Length), Length =:= 1, getterm(WinningPair,Term). %There's no unique winner, so output the disjunction of the winners. %For example: popularsharing([rain,rain,snow,snow],Term). % ?- Term = rain or snow popularsharing(List,Disjunction) :- stripnulls(List,ListWithoutNulls), getvotes(ListWithoutNulls,VoteList), getwinner(VoteList,WinningPair), checkties(WinningPair,VoteList,TiesForMax), length(TiesForMax,Length), Length > 1, constructdisjunction(TiesForMax,Disjunction). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% Code for Majority. %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %There is a term that has a majority. majority(List,Term) :- stripnulls(List,ListWithoutNulls), getvotes(ListWithoutNulls,VoteList), length(ListWithoutNulls,NoOfEntries), getwinner(VoteList,WinningPair), getwinnersvotes(WinningPair,Votes), Votes > NoOfEntries / 2, getterm(WinningPair,Term). %The is no term that has a majority. majority(List,'No Textentry has majority') :- stripnulls(List,ListWithoutNulls), getvotes(ListWithoutNulls,VoteList), length(ListWithoutNulls,NoOfEntries), getwinner(VoteList,WinningPair), getwinnersvotes(WinningPair,Votes), Votes =< NoOfEntries / 2. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% Code for Threshold. %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %threshold(List,Threshold,Term) :- Term is the most frequently occurring %term or terms from List above the user-specified frequency Threshold. %Threshold should be a number such that 0 =< Threshold =< 1. %Some examples: % ?- threshold([rain,rain,rain,snow,snow,snow,sun,showers],0.75,Term). % Term = 'No term has 75% of the votes.' % % ?- threshold([rain,rain,rain,snow,snow,rain,rain,rain],0.75,Term). % Term = rain % % ?- threshold([rain,rain,rain,snow,snow,sun,snow,showers],0.375,Term). % Term = 'rain or snow' %There is no term occurring at or above the threshold frequency. threshold(List,Threshold,ThresholdString) :- stripnulls(List,ListWithoutNulls), getvotes(ListWithoutNulls,VoteList), length(List,NoOfEntries), getwinner(VoteList,WinningPair), getwinnersvotes(WinningPair,Votes), Votes / NoOfEntries < Threshold, atom_chars('No term has ',Chars1), Percentage is Threshold * 100, number_chars(Percentage,Chars2), atom_chars('% of the votes.',Chars3), append(Chars1,Chars2,X), append(X,Chars3,Chars4), atom_chars(ThresholdString,Chars4). %There is a single term occurring at or above the threshold frequency. threshold(List,Threshold,WinningTerm) :- stripnulls(List,ListWithoutNulls), getvotes(ListWithoutNulls,VoteList), length(List,NoOfEntries), getwinner(VoteList,WinningPair), checkties(WinningPair,VoteList,TiesForMax), length(TiesForMax,Length), Length =:= 1, getwinnersvotes(WinningPair,Votes), Votes / NoOfEntries >= Threshold, getterm(WinningPair,WinningTerm). %There is more than one most frequently occurring term at or above the threshold frequency. (For this to be true Threshold must be < 0.5) threshold(List,Threshold,Disjunction) :- stripnulls(List,ListWithoutNulls), getvotes(ListWithoutNulls,VoteList), length(List,NoOfEntries), getwinner(VoteList,WinningPair), checkties(WinningPair,VoteList,TiesForMax), length(TiesForMax,Length), Length > 1, getwinnersvotes(WinningPair,Votes), Votes / NoOfEntries >= Threshold, constructdisjunction(TiesForMax,Disjunction). %There are many options here. So long as the threshold is less than 0.5, there could be %more than one term above the threshold and we might want a list of ALL OF THEM, NOT JUST %THOSE THAT HAD THE MOST VOTES (i.e. regardless of their frequency, so long as it was above %the threshold.). Or, we could stipulate the threshold is greater than 0.5, so that only one %term at most could be over the threshold. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Code for weighted voting. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %The follwoing four aggregation functions are just simple adaptions of the above four voting functions. %A numerical value is assigned each source, and these values are then attached to the textentry from each %report with that source. These values are then used as the number of votes in favour of that textentry. Note %that in the cases of majority and threshold voting care must be taken to determine first what are the total number %of votes in play, so that in turn we can figure out how many votes are required for there to be a majority, or for %a threshold to be met. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% Code for weighted first-past-the-post voting. %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %weightedfirstpastthepost(Sources,Candidates,Winner) :- Winner is the winner from amongst %candidates that has the most votes according to the weighting for the sources in Sources. %For example (given the "facts" about weight used below): % weightedvoting(['BBCi','BBC LDN','CNN','Weather Channel'],[rain,sun,rain,sun],Winner). % ?- Winner = sun %There is a unique winner. weightedfirstpastthepost(Sources,Candidates,Winner) :- stripnulls(Sources,SourcesWithoutNulls), stripnulls(Candidates,CandidatesWithoutNulls), assignweights(SourcesWithoutNulls,Weights), totalvotes(CandidatesWithoutNulls,Weights,Totals), getwinner(Totals,WinningPair), checkties(WinningPair,Totals,TiesForMax), length(TiesForMax,Length), Length =:= 1, getterm(WinningPair,Winner). %There's no unique winner. weightedfirstpastthepost(Sources,Candidates,'No first past the post') :- stripnulls(Sources,SourcesWithoutNulls), stripnulls(Candidates,CandidatesWithoutNulls), assignweights(SourcesWithoutNulls,Weights), totalvotes(CandidatesWithoutNulls,Weights,Totals), getwinner(Totals,WinningPair), checkties(WinningPair,Totals,TiesForMax), length(TiesForMax,Length), Length > 1. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% Code for weighted popular sharing. %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %(No majority needed; in case of tie, form disjunction of winners.) %There's a unique winner. weightedpopularsharing(Sources,Candidates,Winner) :- stripnulls(Sources,SourcesWithoutNulls), stripnulls(Candidates,CandidatesWithoutNulls), assignweights(SourcesWithoutNulls,Weights), totalvotes(CandidatesWithoutNulls,Weights,Totals), getwinner(Totals,WinningPair), checkties(WinningPair,Totals,TiesForMax), length(TiesForMax,Length), Length =:= 1, getterm(WinningPair,Winner). %There's no unique winner, so output the disjunction of the winners. %For example: popularsharing([rain,rain,snow,snow],Term). % ?- Term = rain or snow weightedpopularsharing(Sources,Candidates,Disjunction) :- stripnulls(Sources,SourcesWithoutNulls), stripnulls(Candidates,CandidatesWithoutNulls), assignweights(SourcesWithoutNulls,Weights), totalvotes(CandidatesWithoutNulls,Weights,Totals), getwinner(Totals,WinningPair), checkties(WinningPair,Totals,TiesForMax), length(TiesForMax,Length), Length > 1, constructdisjunction(TiesForMax,Disjunction). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% Code for Weighted Majority. %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %There is a term that has a majority. weightedmajority(Sources,Candidates,Winner) :- stripnulls(Sources,SourcesWithoutNulls), stripnulls(Candidates,CandidatesWithoutNulls), assignweights(SourcesWithoutNulls,Weights), totalvotes(CandidatesWithoutNulls,Weights,Totals), sumlist(Weights,SumOfVotes), getwinner(Totals,WinningPair), getwinnersvotes(WinningPair,Votes), Votes > SumOfVotes / 2, getterm(WinningPair,Winner). %The is no term that has a majority. weightedmajority(Sources,Candidates,'No Textentry has majority') :- stripnulls(Sources,SourcesWithoutNulls), stripnulls(Candidates,CandidatesWithoutNulls), assignweights(SourcesWithoutNulls,Weights), totalvotes(CandidatesWithoutNulls,Weights,Totals), sumlist(Weights,SumOfVotes), getwinner(Totals,WinningPair), getwinnersvotes(WinningPair,Votes), Votes =< SumOfVotes / 2. %We need to summ all the votes in list Weights in order to determine how %many votes are in play, and so t odetermine what consitutes a majority. %sumlist(List,Sum) :- Sum is the sum of the numbers in List. sumlist([],0). sumlist([First|Rest],Sum) :- sumlist(Rest,SumRest), Sum is First + SumRest. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% Code for Weighted Threshold. %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %There is no term occurring at or above the threshold frequency. weightedthreshold(Sources,Candidates,Threshold,ThresholdString) :- stripnulls(Sources,SourcesWithoutNulls), stripnulls(Candidates,CandidatesWithoutNulls), assignweights(SourcesWithoutNulls,Weights), totalvotes(CandidatesWithoutNulls,Weights,Totals), sumlist(Weights,SumOfVotes), getwinner(Totals,WinningPair), getwinnersvotes(WinningPair,Votes), Votes / SumOfVotes < Threshold, atom_chars('No term has ',Chars1), Percentage is Threshold * 100, number_chars(Percentage,Chars2), atom_chars('% of the votes.',Chars3), append(Chars1,Chars2,X), append(X,Chars3,Chars4), atom_chars(ThresholdString,Chars4). %There is a single term occurring at or above the threshold frequency. weightedthreshold(Sources,Candidates,Threshold,WinningTerm) :- stripnulls(Sources,SourcesWithoutNulls), stripnulls(Candidates,CandidatesWithoutNulls), assignweights(SourcesWithoutNulls,Weights), totalvotes(CandidatesWithoutNulls,Weights,Totals), sumlist(Weights,SumOfVotes), getwinner(Totals,WinningPair), checkties(WinningPair,Totals,TiesForMax), length(TiesForMax,Length), Length =:= 1, getwinnersvotes(WinningPair,Votes), Votes / SumOfVotes >= Threshold, getterm(WinningPair,WinningTerm). %There is more than one most frequently occurring term at or above the threshold frequency. (For this to be true Threshold must be < 0.5) weightedthreshold(Sources,Candidates,Threshold,Disjunction) :- stripnulls(Sources,SourcesWithoutNulls), stripnulls(Candidates,CandidatesWithoutNulls), assignweights(SourcesWithoutNulls,Weights), totalvotes(CandidatesWithoutNulls,Weights,Totals), sumlist(Weights,SumOfVotes), getwinner(Totals,WinningPair), checkties(WinningPair,Totals,TiesForMax), length(TiesForMax,Length), Length > 1, getwinnersvotes(WinningPair,Votes), Votes / SumOfVotes >= Threshold, constructdisjunction(TiesForMax,Disjunction). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% Helper predicates for weighted voting %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %assignweights(Sources,Weights) :- Weights is a list of the voting weights for the sources in list Sources. %For example, suppose we assign the following wieghts to the following sources: % BBCi = 5, BBC LDN = 5, CNN = 2, Weather Channel = 3 % assignweights(['BBCi','BBC LDN','CNN','The Weather Channel'],Weights). % ?- Weights = [5,5,2,3] assignweights([Source|T1],[Weight|T2]) :- weight(Source,Weight), assignweights(T1,T2). assignweights([],[]). %combinelists(Weights,Candidates,Pairings) :- Pairing is simple a list of Weight-Candidate pairs. %For example: combinelists([5,5,2,3],[rain,showers,rain,drizzle],Pairs). % ?- Pairs = [[rain,5],[showers,5],[rain,2],[drizzle,3]] combinelists([Weight|T1],[Candidate|T2],[[Candidate,Weight]|T3]) :- combinelists(T1,T2,T3). combinelists([],[],[]). %totalvotes(CandidatesWithoutNulls,Weights,Totals) :- Totals is a list of candidate/total-number-of-votes pairs. %For example: totalvotes([rain,showers,rain,drizzle],[5,5,2,3],Totals). % ?- Totals = [[rain,7],[showers,5],[drizzle,3]] totalvotes([Candidate|T1],[Weight|T2],[Total|T3]) :- countvotes(Candidate,[Candidate|T1],[Weight|T2],Total), removecandidate(Candidate,[Candidate|T1],[Weight|T2],Candidates,Weights), totalvotes(Candidates,Weights,T3). totalvotes([],[],[]). %countvotes(Candidate,Candidates,Weights,Total) :- Total is a pair consisting of candidate and total number of votes, %arrived at by totaling votes for that candidate from list of Entries. %For example: countvotes(rain,[rain,showers,rain,drizzle],[5,5,2,3],Total). % ?- Total = [rain,7] countvotes(Candidate,[],[],[Candidate,0]). %We have a match, so add to the total. countvotes(Candidate,[Candidate|T1],[Num|T2],[Candidate,Sum]) :- countvotes(Candidate,T1,T2,[Candidate,SumRest]), Sum is Num + SumRest,!. %We have no match, so don't add to the total. countvotes(Candidate,[OtherCandidate|T1],[Num|T2],[Candidate,Sum]) :- countvotes(Candidate,T1,T2,[Candidate,Sum]). %removecandidate(Candidate,Candidates,Weights,CandidatesRest,WeightsRest) :- CandidatesRest and WeightsRest are the list %of Candidates and Weights, respectively, with the candidate Candidate removed, and its associated weights also removed. We need this %because after counting the votes for a given candidate we want to remove any further occurrences of that %candidate in the list before moving on to the next candidate in the list to total its votes. (Code is adapted from Sterling & Shapiro, p.53) %For example: removecandidate(rain,[rain,sun,rain,showers],[5,3,2,4],CandidatesRest,WeightsRest). % ?- CandidatesRest = [sun,showers] % Weights = [3,4] %We have a match, so remove candidate and weighting from lists. removecandidate(Candidate,[Candidate|T1],[Weight|T2],Candidates,Weights) :- removecandidate(Candidate,T1,T2,Candidates,Weights). %We don't have a match, so don't remove anything from lists. removecandidate(Candidate,[OtherCandidate|T1],[Weight|T2],[OtherCandidate|Candidates],[Weight|Weights]) :- Candidate \== OtherCandidate, removecandidate(Candidate,T1,T2,Candidates,Weights). removecandidate(Candidate,[],[],[],[]). %weight(Source,Weight) :- Weight is voting weight for Source. weight('BBCi',5). weight('BBC LDN',5). weight('CNN',2). weight('The Weather Channel',3). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% Helper predicates for aggregation functions %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %checkties(WinningPair,VoteList,TiesForMax) :- TiesForMax is a list of Term-Votes pairs, where the terms are tied %for the maximum number of votes. This list can then be used for aggregation functions such as fist-past-the-post, and %popular-sharing. An example: checkties([rain,2],[[rain,2],[snow,2],[showers,1]],TiesForMax). % ?- TiesForMax = [[rain,2],[snow,2]] %We have a tie with the maximum number of votes, so add to list. checkties([Term1,Num1],[[Term2,Num2]|Rest],[[Term2,Num2]|Rest2]) :- Num1 =:= Num2, checkties([Term1,Num1],Rest,Rest2). %We have less votes for this term, so don't add to list. checkties([Term1,Num1],[[Term2,Num2]|Rest],TiesForMax) :- Num1 > Num2, checkties([Term1,Num1],Rest,TiesForMax). %There shouldn't be a term that has more votes than the term in WinningPair--but just in case.. checkties([Term1,Num1],[[Term2,Num2]|Rest],[[Term2,Num2]]) :- Num2 > Num1, checkties([Term2,Num2],Rest,[]). checkties([Term1,Num1],[],[]). %constructdisjunction(TiesForMax,Disjunction) :- Disjunction is a string formed from the disjunction of all the %terms in the list TiesForMax. For example: constructdisjunction([[rain,2],[snow,2]],Disjunction). % ?- Disjunction = rain or snow constructdisjunction(TiesForMax,Disjunction) :- converttochars(TiesForMax,CharsLists), removefirstlist(CharsLists,EditedLists,FirstList), makedisjunction(EditedLists,FirstList,DisjunctionList), atom_chars(Disjunction,DisjunctionList). %converttochars(TiesForMax,CharsLists) :- CharsLists is a list of lists. The lists are lists of chars that %correspond to the Terms in the list TiesForMax, i.e. the list of Terms, Votes for terms pairs. converttochars([[Term,Num]|Rest1],[Chars|Rest2]) :- atom_chars(Term,Chars), converttochars(Rest1,Rest2). converttochars([],[]). %removefirstlist(CharsLists,EditedLists,FirstList) :- EditedLists is the list of lists CharsLists, but %with the first list removed, as FirstList. The purpose of this is simply to set up the data in a way that makes it %easier for predicate makedisjunction to create a string of the disjunction of all the terms with the most votes. removefirstlist([H|T],T,H). %makedisjunction(CharsLists,CurrentList,DisjunctionList) :- DisjunctionList is a list of ASCII codes corresponding %to the string which is the disjunction of all the terms in list CharsLists. For example, if the terms %in CharsLists were the ASCII codes corresponding to "rain","showers","snow", then DisjunctionList would be the list of ASCII codes %corresponding to the string "rain or showers or snow". CurrentList is an accumulator. %[32,111,114,32] are the ASCII codes for " or " makedisjunction([H|T],CurrentList,DisjunctionList) :- append(CurrentList,[32,111,114,32],X), append(X,H,Y), makedisjunction(T,Y,DisjunctionList). makedisjunction([],DisjunctionList,DisjunctionList). %getvotes(List,VoteList) :- VoteList is a list of pairs consisting of each kind of term in List, and %the number of times it occurs in that list. getvotes([H|T],[[H,Num]| VoteList]) :- count(H,[H|T],0,Num), removeall(H,[H|T],Rest), getvotes(Rest,VoteList). getvotes([],[]). %count(Term,List,RT,Num) :- Num is the number of times Term occurs in List. RT keeps a running total. count(Term,List,RT, Num) :- member(Term,List), X is RT + 1, remove(Term,List,Rest),!, count(Term,Rest,X,Num ). count(Term,List,Num,Num) :- not member(Term,List). %getwinner(VoteList,WinningPair):- WinningPair is the Term/Votes pair with the highest number of votes. getwinner([X|Xs],Max) :- getwinner(Xs,X,Max). getwinner([[Term1,Num1]|Xs],[Term2,Num2],Max) :- Num1 =< Num2, getwinner(Xs,[Term2,Num2],Max). getwinner([[Term1,Num1]|Xs],[Term2,Num2],Max) :- Num1 > Num2, getwinner(Xs,[Term1,Num1],Max). getwinner([],Max,Max). %getterm(WinningPair,Term) :- Term is the term component of WinningPair. getterm([Term,Votes],Term). % getwinnersvotes(WinningPair,Votes) :- Votes is the number of votes of the winning term. getwinnersvotes([Term,Votes],Votes). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%% Code for resolving conflicts by preferences over sources %%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %One way to deal with conflicts is not to merge the text entries, or to take a vote, but to %select the textentry from the report with the most preferred source. The trickiest problem %in implementing this idea in Prolog is the fact that we cannot assume that the number of %input reports is the same as the number of reports containing a text entry of the kind in %question. To get around this problem, when the fusion engine grounds a set variable, we have %it insert a null value if a report has no textentry of the required kind. % This gives rise to a further problem: suppose there is a conflict between the textentries %in several reports for a given element, and suppose further that the most preferred report %does not in fact have a textentry for this element. Unless this method is ammended, a null %value will be returned for this element. Clearly, in such cases we need to find those reports %which do have a text entry for this element, then determine the most preferred source from %amongst those reports, and finally select the text entry for the element from this report. %usepreferredsource(Sources,TextEntries,TextEntry) :- TextEntry is the text entry from amongst %TextEntries that comes from the report with the preferred source from amongst sources. (Sources is %a list of sources of all the input reports. TextEntries is a list of the text entries for a %particular report element from all the reports, or a null value if a given report has no such element.) usepreferredsource(Sources,TextEntries,ConvertedTextEntry) :- correctfornulls(Sources,TextEntries,CorrectedSources,CorrectedTextEntries), preferredsource(CorrectedSources,Source), findtextentry(CorrectedSources,CorrectedTextEntries,Source,TextEntry), convertunits(TextEntry,ConvertedTextEntry). %We've found the preferred source and so the textentry. findtextentry([Source|T1],[TextEntry|T2],Source,TextEntry). %Not found the preferred source, so keep looking. findtextentry([H1|T1],[H2|T2],Source,TextEntry) :- findtextentry(T1,T2,Source,TextEntry). %preferredsource(ListOfSources,Source) :- Source is the preferred source from amongst the list of sources ListOfSources. preferredsource([H|T],Source) :- preferredsource(T,H,Source). preferredsource([H|T],S,Source) :- preferred(S,H), preferredsource(T,S,Source). preferredsource([H|T],S,Source) :- preferred(H,S), preferredsource(T,H,Source). preferredsource([],Source,Source). %preferred(PreferredTerm,UnPreferredTerm) :- PreferredTerm is preferred to UnPreferredTerm. preferred(Term1,Term2) :- sourcepreference(Term1,Term2). preferred(Term1,Term2) :- sourcepreference(Term1,Term3), preferred(Term3,Term2). %Define the following preference ordering (most preferred first): The Met Office, The Weather Channel, BBC, BBCi, BBC London, The Guardian, %The Independent,The Times, The Daily Telegraph, The International Herald Tribune, Sky, CNN, Mirror, Express, Sun, Mail. %sourcepreference(PreferredTerm,UnPreferredTerm). sourcepreference('Sun','Mail'). sourcepreference('Express','Sun'). sourcepreference('Mirror','Express'). sourcepreference('CNN','Mirror'). sourcepreference('Sky','CNN'). sourcepreference('The International Herald Tribune','Sky'). sourcepreference('The Daily Telegraph','The International Herald Tribune'). sourcepreference('The Times','The Daily Telegraph'). sourcepreference('The Independent','The Times'). sourcepreference('The Guardian','The Independent'). sourcepreference('BBC London','The Guardian'). sourcepreference('BBCi','BBC London'). sourcepreference('BBC','BBCi'). sourcepreference('The Weather Channel','BBC'). sourcepreference('The Met Office','The Weather Channel'). %correctfornulls(Sources,TextEntries,CorrectedSources,CorrectedTextEntries) :- CorrectedTextEntries is the list %TextEntries with any nulls removed. CorrectedSources is the list Sources with any sources removed if the reports %for which they are the sources have a null textentry. For example: % ?- correctfornulls(['BBCi','CNN','The Weather Channel','BBC London'],[rain,null,sun,null],L1,L2). % L1 = ['BBCi','The Weather Channel'] % L2 = [rain,sun] %the textentry is a null, so remove it and the corresponding source from the lists. correctfornulls([Source|T1],[null|T2],CorrectedSources,CorrectedTextEntries) :- correctfornulls(T1,T2,CorrectedSources,CorrectedTextEntries). %The text entry is not a null, so add it and the corresponding source to the lists. correctfornulls([Source|T1],[TextEntry|T2],[Source|T3],[TextEntry|T4]) :- not TextEntry == null, correctfornulls(T1,T2,T3,T4). correctfornulls([],[],[],[]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Helper predicates for resolving conflicts by using preferences over sources. %Unit conversion is built into predicates such as similartemps, notsimilarwindspeeds, etc. This means %that by themselves, predicates such as usepreferredsource simply select a text entry without doing %any necessary conversion. convertaddunits is dsigned to test if the selected text entry needs any %unit conversion, and if it does, it subsequently appends the new unit suffix. %convertunits(NumWithUnit,ConvertedNum) :- ConvertedNum is the results of ceonverting NumWithUnit. For example, % ?- convertunits('52F',ConvertedNum). % ConvertedNum = 11.111C convertunits(NumWithUnit,ConvertedNum) :- atom_chars(NumWithUnit, List1), separatesuffix(List1, List2), convertaddunits(List2,ConvertedNum). %These predicates are similar to predicate convert, except they append the converted units. %67 is "C", no conversion required. convertaddunits([X,[67]],ConvertedNum) :- append(X,[67],Chars), atom_chars(ConvertedNum,Chars). %99 is "c", no conversion required. convertaddunits([X,[99]],ConvertedNum) :- append(X,[67],Chars), atom_chars(ConvertedNum,Chars). %70 is "F", conversion required. 67 is "C". convertaddunits([X,[70]],ConvertedNum) :- atom_chars(Atom,X), number_atom(Num,Atom), tempconvert(Num,Num2), number_atom(Num2,Atom2), atom_chars(Atom2,Chars2), append(Chars2,[67],Chars3), atom_chars(ConvertedNum,Chars3). %102 is "f", conversion required. convertaddunits([X,[102]],ConvertedNum) :- atom_chars(Atom,X), number_atom(Num,Atom), tempconvert(Num,Num2), number_atom(Num2,Atom2), atom_chars(Atom2,Chars2), append(Chars2,[67],Chars3), atom_chars(ConvertedNum,Chars3). %77,80,72 is "M","P","H", no conversion required. convertaddunits([X,[75,80,72]],ConvertedNum) :- append(X,[75,80,72],Chars), atom_chars(ConvertedNum,Chars). %109,112,104 is "m","p","h", no conversion required. convertaddunits([X,[109,112,104]],ConvertedNum) :- append(X,[75,80,72],Chars), atom_chars(ConvertedNum,Chars). %75,80,72 is "K","P","H", conversion required. convertaddunits([X,[75,80,72]],ConvertedNum) :- atom_chars(Atom,X), number_atom(Num,Atom), speedconvert(Num,Num2), number_atom(Num2,Atom2), atom_chars(Atom2,Chars2), append(Chars2,[75,80,72],Chars3), atom_chars(ConvertedNum,Chars3). %107,112,104 is "k","p","h", conversion required. convertaddunits([X,[107,112,104]],ConvertedNum) :- atom_chars(Atom,X), number_atom(Num,Atom), speedconvert(Num,Num2), number_atom(Num2,Atom2), atom_chars(Atom2,Chars2), append(Chars2,[75,80,72],Chars3), atom_chars(ConvertedNum,Chars3). %[109,105,108,101] is "mile", no conversion required. convertaddunits([X,[109,105,108,101]],ConvertedNum) :- append(X,[109,105,108,101],Chars), atom_chars(ConvertedNum,Chars). %[109,105,108,101,115] is "miles", no conversion required. convertaddunits([X,[109,105,108,101,115]],ConvertedNum) :- append(X,[109,105,108,101,115],Chars), atom_chars(ConvertedNum,Chars). %107,109 is "km", conversion required. convertaddunits([X,[107,109]],ConvertedNum) :- atom_chars(Atom,X), number_atom(Num,Atom), speedconvert(Num,Num2), number_atom(Num2,Atom2), atom_chars(Atom2,Chars2), append(Chars2,[109,105,108,101,115],Chars3), atom_chars(ConvertedNum,Chars3). %75,77 is "KM", conversion required. convertaddunits([X,[75,77]],ConvertedNum) :- atom_chars(Atom,X), number_atom(Num,Atom), speedconvert(Num,Num2), number_atom(Num2,Atom2), atom_chars(Atom2,Chars2), append(Chars2,[109,105,108,101,115],Chars3), atom_chars(ConvertedNum,Chars3). %Any other units (e.g. "%"), no conversion required. %Of course, this may change if more rules with different %top-level goals are added. convertaddunits([X,AnyOtherSuffix],ConvertedNum) :- atom_chars(Atom,AnyOtherSuffix), not Atom == 'C', not Atom == 'c', not Atom == 'F', not Atom == 'f', not Atom == 'MPH', not Atom == 'mph', not Atom == 'KPH', not Atom == 'kph', not Atom == 'KM', not Atom == 'km', not Atom == 'mile', not Atom == 'miles', append(X,AnyOtherSuffix,Chars), atom_chars(ConvertedNum,Chars). %Times are dealt with separately (i.e. rules dealing with times need a distinct condition.) %checktwelvehrclock(Time,CheckedTime) :- CheckedTime is, if needed, a converted version of Time. checktwelvehrclock([Time],CheckedTime) :- auxchecktwelvehrclock(Time,CheckedTime). auxchecktwelvehrclock(Time,CheckedTime) :- number_atom(Num,Time), twelvehrclock(Num,Num2), number_atom(Num2,Atom), trailingzero(Atom,CheckedTime). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%% Alternative code for resolving conflicts by preferences over sources: %%%%%%%%%%%%%% %%%%%%%%% The diference this time is that sources are allowed to have %%%%%%%%%%%%%% %%%%%%%%% equal preferences, so the highest level predicate returns a %%%%%%%%%%%%%% %%%%%%%%% list of text entries from the reports with the preferred %%%%%%%%%%%%%% %%%%%%%%% sources. Other predicates in subsequent conditions can then %%%%%%%%%%%%%% %%%%%%%%% decide how to merge those entries. %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %First the facts about preferences: Rank 1: The Met Office, The Weather Channel; Rank 2: BBCi and BBCLDN, Rank 3: The Guardian, %Rank 4: The Times. %preference(PreferredTerm,UnPreferredTerm). preference('The Weather Channel','BBCi'). preference('The Weather Channel','BBC London'). preference('The Met Office','BBCi'). preference('The Met Office','BBC London'). preference('BBC London','The Guardian'). preference('BBCi','The Guardian'). preference('The Guardian','The Times'). %usepreferredsources(Sources,TextEntries,PreferredTextEntries) :- PreferredTextEntries is a list of the text entries from amongst %TextEntries that comes from the reports with the preferred sources from amongst sources. (Sources is %a list of sources of all the input reports. TextEntries is a list of the text entries for a %particular report element from all the reports, or a null value if a given report has no such element.) %Some examples: usepreferredsources(['The Times','The Guardian','The Weather Channel','The Met Office'],[rain,rain,sun,sunny],List). % List = [sun,sunny] % % usepreferredsources(['The Times','BBC London','The Guardian','BBCi'],[rain,rain,sun,sunny],List). % List = [rain,sunny] % % usepreferredsources(['The Times','The Met Office','The Guardian','BBCi'],[showers,rain,sun,sunny],List). % List = [rain] usepreferredsources(Sources,TextEntries,PreferredTextEntries) :- correctfornulls(Sources,TextEntries,CorrectedSources,CorrectedTextEntries), preferredsources(CorrectedSources,CorrectedSources,PreferredSources), findtextentries(CorrectedSources,CorrectedTextEntries,PreferredSources,PreferredTextEntries). %preferredsources(ListOfSources,ListOfSources,PreferredSources) :- PreferredSources is a list of the %preferred sources from amongst the list of sources ListOfSources. %H is preferred to all in ListofSources. preferredsources([H|T],Sources,[H|T2]) :- auxpreferredsources(H,Sources), preferredsources(T,Sources,T2). %H is not preferred to all in ListofSources. preferredsources([H|T],Sources,T2) :- not auxpreferredsources(H,Sources), preferredsources(T,Sources,T2). preferredsources([],Sources,[]). %auxpreferredsources(Source,Sources) :- succeeds if Source is preferred to all sources in Sources. auxpreferredsources(Source,[H|T]) :- not auxpreferred(H,Source), auxpreferredsources(Source,T). auxpreferredsources(Source,[]). %auxpreferred(PreferredTerm,UnPreferredTerm) :- PreferredTerm is preferred to UnPreferredTerm. auxpreferred(Term1,Term2) :- preference(Term1,Term2). auxpreferred(Term1,Term2) :- preference(Term1,Term3), auxpreferred(Term3,Term2). %findtextentries(Sources,TextEntries,PreferredSources,PreferredTextEntries) :- PreferredTextEntries is a list of text entries %from amongst TextEntries that comes from those preferred source, PreferredSources, from the list of sources, Sources. %e.g.: findtextentries(['Sun','Mail','Guardian','Mirror','BBC'],[rain,rain,sun,snow,sunny],['BBC','Guardian'],X). % X = [sun,sunny] ; %We have a match, source is a preferred source, so add text entry to our list. findtextentries([Source|T1],[TextEntry|T2],PreferredSources,[TextEntry|T3]) :- ispreferredsource(Source,PreferredSources), findtextentries(T1,T2,PreferredSources,T3). %We have no match, source is not a preferred source, so move on to next source. findtextentries([Source|T1],[TextEntry|T2],PreferredSources,PreferredTextEntries) :- not ispreferredsource(Source,PreferredSources), findtextentries(T1,T2,PreferredSources,PreferredTextEntries). findtextentries([],[],PreferredSources,[]). %ispreferredsource(Source,PreferredSources) :- succeeds if Source if a member of list PreferredSources. %Source is a preferred source. ispreferredsource(Source,[Source|T]). %No match, check rest of PreferredSources ispreferredsource(Source,[H|T]) :- ispreferredsource(Source,T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%% A more fine-grained way of resolving preferences %%%%%%%%%%%%%%%%%% %%%%%%%%% over sources. Some sources are more reliable for %%%%%%%%%%%%%%%%%% %%%%%%%%% some subjects than for others. These predicates %%%%%%%%%%%%%%%%%% %%%%%%%%% allow the rule engineer to specify the subject. %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %usepreferredsourceforsubject(Sources,TextEntries,Subject,TextEntry) :- TextEntry is the text entry from amongst %TextEntries that comes from the report with the preferred source from amongst sources, where the preferred source %is the preferred source for subject Subject. (Sources is a list of sources of all the input reports. TextEntries %is a list of the text entries for a particular report element from all the reports, or a null value if a given %report has no such element.) %For example: ?- usepreferredsourceforsubject(['BBCi','CNN','The Weather Channel','BBC London'],['10C','23C','15C','28C'],temp,X). % X = '15C' usepreferredsourceforsubject(Sources,TextEntries,Subject,ConvertedTextEntry) :- correctfornulls(Sources,TextEntries,CorrectedSources,CorrectedTextEntries), preferredsourcebysubject(CorrectedSources,Subject,Source), findtextentry(CorrectedSources,CorrectedTextEntries,Source,TextEntry), convertunits(TextEntry,ConvertedTextEntry). %preferredsourcebysubject(ListOfSources,Source) :- Source is the preferred source from amongst the list of sources ListOfSources for %subject Subject. preferredsourcebysubject([H|T],Subject,Source) :- preferredsourcebysubject(T,H,Subject,Source). preferredsourcebysubject([H|T],S,Subject,Source) :- preferredforsubject(Subject,S,H), preferredsourcebysubject(T,S,Subject,Source). preferredsourcebysubject([H|T],S,Subject,Source) :- preferredforsubject(Subject,H,S), preferredsourcebysubject(T,H,Subject,Source). preferredsourcebysubject([],Source,Subject,Source). %preferredforsubject(Subject,PreferredTerm,UnPreferredTerm) :- PreferredTerm is preferred to UnPreferredTerm for subject Subject. %For example, we may prefer the BBC for daylighthours, but the met office for temps. preferredforsubject(Subject,Term1,Term2) :- subjectpreference(Subject,Term1,Term2). preferredforsubject(Subject,Term1,Term2) :- subjectpreference(Subject,Term1,Term3), preferredforsubject(Subject,Term3,Term2). %Here are some example (and entirely artificial) facts about preferences for sources by subject. %For today's weather, in order of preference, highest first: The Met Office, BBCi, BBC London, The Weather Channel, CNN subjectpreference(today,'The Met Office', 'BBCi'). subjectpreference(today,'BBCi','BBC London'). subjectpreference(today,'BBC London', 'The Weather Channel'). subjectpreference(today,'The Weather Channel', 'CNN'). %For temps we have: The Weather Channel, The Met Office, CNN, BBCi, BBC London. subjectpreference(temp,'The Met Office', 'CNN'). subjectpreference(temp,'BBCi','BBC London'). subjectpreference(temp,'CNN', 'BBCi'). subjectpreference(temp,'The Weather Channel', 'The Met Office'). %For windspeed we have: CNN, BBCi, BBC London, The Met Office, The Weather Channel subjectpreference(windpseed,'BBC London','The Met Office'). subjectpreference(windspeed,'BBCi','BBC London'). subjectpreference(windspeed,'CNN', 'BBCi'). subjectpreference(windspeed,'The Met Office','The Weather Channel'). %For Relative humidity we have: BBCi,BBC London, The Met Office, CNN, The Weather Channel subjectpreference(humidity,'BBC London','The Met Office'). subjectpreference(humidity,'BBCi','BBC London'). subjectpreference(humidity,'CNN', 'The Weather Channel'). subjectpreference(humidity,'The Met Office','CNN'). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%% Code for least upper bound %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %leastupperbound(TextEntries,FirstCommonAncestor) :- TextEntries is a list of text entries that ought to be %part of a tree hierarchy. FirstCommonAncestor is the member of that hierarchy that is the ancestor of all the %text entries in TextEntries that has the greatest depth, or least height, in the tree. %All entries in text entries belong to the same semantic net. leastupperbound(TextEntries,FirstCommonAncestor) :- aremembers(TextEntries), getlineages(TextEntries,Lineages), sameroot(Lineages), getleastupperbound(Lineages,FirstCommonAncestor). %All entries in text entries do not belong to the same semantic net. leastupperbound(TextEntries,'No least upper bound') :- getlineages(TextEntries,Lineages), not sameroot(Lineages). %getlineages(TextEntries,Lineages) :- Lineages is a list of all the lineages of the members of TextEntries. %Lineages is thus a list of lists. %For example: ?- getlineages([andy,jack,fred],X). % X = [[tom,bob],[tom,bob,jim],[tom,bob,jim,jane]] %The lineages are reversed so that the root memeber (in this case tom) comes first. getlineages([H|T1],[ReversedLineage|T2]) :- lineage(H,Lineage), reverse(Lineage,ReversedLineage), getlineages(T1,T2). getlineages([],[]). %lineage(Person,Lineage) :- Lineage is a list of the ancestors of Person. %For example: ?- lineage(jane,X). % X = [jane,jim,bob,tom] %This rule makes sure person herself is included in lineage. If this isn't done, rule %getleastupperbound will not give the parent as answer to a parent child pair, but instead, %will return the parent of the parent. (i.e. without this rule, leastupperbound([fred,jane],X) %would return jim, when it should return jane.) %We also want to check first to make sure that Person is a member of the hierarchy. lineage(Person,[Person|Rest]) :- auxlineage(Person,Rest). auxlineage(Person,[X|Rest]) :- parent(X,Person), auxlineage(X,Rest),!. auxlineage(Person,[]). %aremembers(TextEntries) :- succeeds if all text entries in list TextEntries are members of some or other semantic net. aremembers([H|T]) :- ismember(H), length(T,L), L > 0, aremembers(T). aremembers([H]) :- ismember(H). %ismember(X) :- succeeds if X is member of the semantic net/hierachy--i.e if X is a relata of Parent. ismember(X) :- parent(X,Y) ; parent(Y,X). %parent(Parent,Child). %tree hierarchy for precipitation. parent(precipitation,rain). parent(precipitation,snow). parent(precipitation,hail). parent(precipitation,'ice crystals'). parent(rain,showers). parent(rain,'scattered showers'). parent(rain,'light showers'). parent(rain,'heavy showers'). parent(rain,'intermittant rain'). parent(rain,'blustery showers'). parent(rain,'mostly rain'). parent(rain,drizzle). parent(rain,'downpours'). parent(rain,'heavy downpours'). parent(rain,'heavy rain'). parent(rain,'light rain'). parent(rain,'prolonged rain'). parent(snow,'snow flurries'). parent(snow,'snow showers'). parent(snow,sleet). parent(snow,'heavy snow'). parent(snow,'light snow'). parent(snow,'drifting snow'). %tree hierarchy for sun. parent(sun,sunny). parent(sun,'mostly sunny'). parent(sun,fair). %tree hierarchy for cloud. parent(cloud,cloudy). parent(cloud,'mostly cloudy'). parent(cloud,'heavy cloud'). parent(cloud,overcast). %tree hierarchy for storms. parent(storms,thunder). parent(storms,lightning). parent(storms,'thunder storms'). parent(storms,'thundery storms'). parent(storms,'thunder and lightning'). parent(storms,'severe storms'). %tree hierarchy for sun and cloud. parent('sun and cloud','scattered cloud'). parent('sun and cloud','partly cloudy'). parent('sun and cloud','patchy cloud'). parent('sun and cloud','sunny spells'). parent('sun and cloud','sunny intervals'). parent('sun and cloud','patchy sunshine'). %tree hierarchy for ice. parent(ice,frost). parent(ice,'black ice'). parent(ice,'hoar frost'). parent(ice,'ground frost'). %sameroot(Lineages) :- succeeds if all lineages in list Lineages share a root. If this test fails, we do not want %to return a least upper bound. sameroot(Lineages) :- getheads(Lineages,Heads), testheads(Heads). %getleastupperbound(Lineages,FirstCommonAncestor) :- FirstCommonAncestor is the first or 'most recent' member common %to all lineages in Lineages. For example: % ?- getleastupperbound([[bob,tom],[jim,bob,tom],[jane,jim,bob,tom]],FirstCommonAncestor). % FirstCommonAncestor = bob %The basic strategy used is to start at the ROOT of the lineages and work down (or back) until we no longer %have a member common to all; the previous member is then the FirstCommonAncestor. getleastupperbound(Lineages,FirstCommonAncestor) :- findleastupperbound(Lineages,Lineages,FirstCommonAncestor). %The recursive case: all heads of lineages are the same, and there is more than one member in the lineage. findleastupperbound(Lineages,PrevLineages,FirstCommonAncestor) :- notsingletonlists(Lineages), getheads(Lineages,Heads), testheads(Heads), stripheads(Lineages,Tails), findleastupperbound(Tails,Lineages,FirstCommonAncestor). %One case in which we've found the first common ancestor. At least one lineage is down to a single member, so %given that the heads of the lineages are the same, this is the first common ancestor. %This case occurs when the lineages are NOT of the same length. %For example, if the ORIGINAL lineages are: % [tom,bob] % [tom,bob,jim] % [tom,bob,jim,jane] %then this rule will capture the FirstCommonAncestor. findleastupperbound(Lineages,PrevLineages,FirstCommonAncestor) :- not notsingletonlists(Lineages), getheads(Lineages,Heads), testheads(Heads), gethead(Heads,FirstCommonAncestor). %This is the second kind of case in which we find the FirstCommonAncestor. Here the lineages are equal %in length. For example, if the ORIGINAL lineages are: % [tom,pat,sally,olly] % [tom,bob,jim,jane] % [tom,pat,sally,luke] % [tom,bob,jim,jane] %then this rule will capture the FirstCommonAncestor. findleastupperbound(Lineages,PrevLineages,FirstCommonAncestor) :- getheads(Lineages,Heads), not testheads(Heads), getheads(PrevLineages,PrevHeads), gethead(PrevHeads,FirstCommonAncestor). %getheads(Lineages,Heads) :- Lineages is a list of lineages of the textentries whose least upper bound we are searching for. %Lineages is thus a list of lists. Heads is a list of the heads of each of those lists in Lineages. getheads([[H|T1]|T2],[H|T3]) :- getheads(T2,T3). getheads([],[]). %gethead(Heads,Head) :- Head is simply the head of list Heads. gethead([H|T],H). %testheads(Heads) :- Heads is a list of the heads of all the lists in Lineages. Succeeds if all heads are the same. testheads([H1,H2|T]) :- H1 == H2, testheads([H2|T]). testheads([H|[]]). %stripheads(Lineages,Tails) :- Tails is the list of lists in Lineages, but with the head of each list removed. stripheads([[H|T1]|T2],[T1|T3]) :- stripheads(T2,T3). stripheads([],[]). %notsingletonlists(Lineages) :- succeeds if all the lists in lineages are not singletons, i.e. their length is greater than 1. notsingletonlists([List|T]) :- length(List,Length), Length > 1, notsingletonlists(T). notsingletonlists([]). %reverselineages(Lineages,ReverseLineages) :- ReverseLineages is the list of lineages in Lineages, but with the %members of each LINEAGE LIST reversed. (Code not used at present.) reverselineages([Lineage|T1],[ReversedLineage|T2]) :- reverse(Lineage,ReversedLineage), reverselineages(T1,T2). reverselineages([],[]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %endswith(String,Suffix) :- succeeds if String ends with Suffix. endswith(String,Suffix) :- atom_chars(String,StringChars), atom_chars(Suffix,SuffixChars), append(X,SuffixChars,StringChars). %stripsuffix(String,Suffix,SuffixlessString) :- SuffixlessString is String with Suffix removed. stripsuffix(String,Suffix,SuffixlessString) :- atom_chars(String,StringChars), atom_chars(Suffix,SuffixChars), append(Chars,SuffixChars,StringChars), atom_chars(SuffixlessString,Chars). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %This is a test query for groundable: /* %All 3 reports groundable: groundable([[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London'],[daylighthours,[sunrise,'6.51'],[sunset,'17.51']]]],[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London'],[daylighthours,[sunrise,'6.52'],[sunset,'17.51']]]],[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London'],[daylighthours,[sunrise,'6.53'],[sunset,'17.51']]]]],'weatherreport/daylighthours/sunrise',X). %2 reports groundable, 1 not. groundable([[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London'],[daylighthours,[sunrise,'6.51']]]],[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London']]],[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London'],[daylighthours,[sunrise,'6.53']]]]],'weatherreport/daylighthours/sunrise',X). %1 report groundable, 2 not: groundable([[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London'],[daylighthours,[sunrise,'6.51']]]],[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London']]],[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London']]]],'weatherreport/daylighthours/sunrise',X). %No reports groundable: groundable([[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London']]],[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London']]],[weatherreport,[[source,'BBC'],[date,'10/10/03'],[city,'London']]]],'weatherreport/daylighthours/sunrise',X). %The case study example. (wrong bracketing) groundable([[weatherreport,[[source,'BBCi'],[date,'10/12/2002'],[city,'London'],[today,'cloudy'],[temp,[max,'3C'],[min,'-1C']],[windspeed,'11mph'],[relativehumidity,'63%'],[daylighthours,[sunrise,'7.53'],[sunset,'15.51']],[pressure,[absolutevalue,'1021mB'],[directionofchange,'rising']],[visibility,'good'],[airpollutionindex,'3'],[sunindex,'1']]],[weatherreport,[[source,'BBCi'],[date,'10/12/2002'],[city,'London'],[today,'cloudy'],[temp,[max,'3C'],[min,'-1C']],[windspeed,'11mph'],[relativehumidity,'63%'],[daylighthours,[sunrise,'7.53'],[sunset,'15.51']],[pressure,[absolutevalue,'1021mB'],[directionofchange,'rising']],[visibility,'good'],[airpollutionindex,'3'],[sunindex,'1']]],[weatherreport,[[source,'BBCi'],[date,'10/12/2002'],[city,'London'],[today,'cloudy'],[temp,[max,'3C'],[min,'-1C']],[windspeed,'11mph'],[relativehumidity,'63%'],[daylighthours,[sunrise,'7.53'],[sunset,'15.51']],[pressure,[absolutevalue,'1021mB'],[directionofchange,'rising']],[visibility,'good'],[airpollutionindex,'3'],[sunindex,'1']]]],'weatherreport/daylighthours/sunrise',X). %The case study example. (right bracketing) groundable([[weatherreport,[[source,'BBCi'],[date,'10/12/2002'],[city,'London'],[today,'cloudy'],[temp,[[max,'3C'],[min,'-1C']]],[windspeed,'11mph'],[relativehumidity,'63%'],[daylighthours,[[sunrise,'7.53'],[sunset,'15.51']]],[pressure,[[absolutevalue,'1021mB'],[directionofchange,'rising']]],[visibility,'good'],[airpollutionindex,'3'],[sunindex,'1']]],[weatherreport,[[source,'BBCi'],[date,'10/12/2002'],[city,'London'],[today,'cloudy'],[temp,[[max,'3C'],[min,'-1C']]],[windspeed,'11mph'],[relativehumidity,'63%'],[daylighthours,[[sunrise,'7.53'],[sunset,'15.51']]],[pressure,[[absolutevalue,'1021mB'],[directionofchange,'rising']]],[visibility,'good'],[airpollutionindex,'3'],[sunindex,'1']]],[weatherreport,[[source,'BBCi'],[date,'10/12/2002'],[city,'London'],[today,'cloudy'],[temp,[[max,'3C'],[min,'-1C']]],[windspeed,'11mph'],[relativehumidity,'63%'],[daylighthours,[[sunrise,'7.53'],[sunset,'15.51']]],[pressure,[[absolutevalue,'1021mB'],[directionofchange,'rising']]],[visibility,'good'],[airpollutionindex,'3'],[sunindex,'1']]]],'weatherreport/daylighthours/sunrise',X). */