/* $Header$ @author Nikolai van Kempen - My personal evaluation predicates. I have some doubts whether they are of much use for anyone else. Thaty why I put them here. */ reloadMA :- ['MemoryAllocation/ma.pl'], ['MemoryAllocation/madata.pl'], ['MemoryAllocation/ma_improvedcosts.pl'], ['MemoryAllocation/test.pl'], ['MemoryAllocation/progressconstants.pl'], ['MemoryAllocation/eval.pl']. % The nrw3 database is created by the nrwtest/testnrw3.sh script. % The difference is that the counter are kept to produce specific join % results. % case 1 evalQuery(2001, [database(nrw3)], select * from [points as p, railways as r] where[p:no=r:no]). % case 2 evalQuery(2000, [database(nrw3)], select * from [buildings as b, roads as r] where[b:no=r:no]). % case 3 evalQuery(2002, [database(nrw3)], select * from [buildings as b, roads as r, points as p] where[b:no=r:no,r:no=p:no]). % 4374 cost edge paths for 6 pog paths: % 2003 evalQuery(2003, [database(nrw3)], select * from [buildings as b, roads as r, points as p, natural as n] where[b:no=r:no,r:no=p:no,p:no=n:no]). evalQuery(8002, [database(nrw3)], select * from [points as r1, buildings300k as r2, roads300k as r3, roads300k as r4, buildings200k as r5] where[r1:no=r2:no,r2:no=r3:no,r3:no=r4:no,r4:no=r5:no]). evalQuery(8003, [database(nrw3)], select * from [points as r1, buildings300k as r2, roads300k as r3, roads400k as r4, buildings400k as r5] where[r1:no=r2:no,r2:no=r3:no,r3:no=r4:no,r4:no=r5:no]). evalQuery(8013, [database(nrw3), maEval([staticEqual(16), static(16),static(256),staticMaxDivOpCount,staticMaxDivPOG, modifiedDijkstra])], select * from [roads500k as r1, roads400k as r2, buildings400k as r3, buildings500k as r4] where[r1:no=r2:no,r2:no=r3:no,r3:no=r4:no,r1:no>0,r2:no>0,r3:no>0]). evalQuery(8004, [database(nrw3)], select * from [roads300k as r1, roads400k as r2, roads500k as r3, roads600k as r4, roads700k as r5] where[r1:no=r2:no,r2:no=r3:no,r3:no=r4:no,r4:no=r5:no]). % 7085880 cost edge paths for 120 pog paths: % standard costs path count: 29160 evalQuery(8005, [database(nrw3)], select * from [roads300k as r1, roads400k as r2, roads500k as r3, buildings300k as r4, buildings400k as r5, buildings500k as r6] where[r1:no=r2:no,r2:no=r3:no,r3:no=r4:no,r4:no=r5:no, r5:no=r6:no]). evalQuery(8001, [database(nrw3)], select * from [points as r1, buildings200k as r2, roads200k as r3, roads200k as r4, roads300k as r5] where [r1:no=r2:no, r2:no=r3:no, r3:no=r4:no, r4:no=r5:no]). optEval :- maCreateTestSels, retractall(maUseNewTranslationRules(_)), asserta(maUseNewTranslationRules(true)), retractall(useCostEstimation(_)), asserta(useCostEstimation(true)), optEvalRun, with_output_to(atom(A1), findall(_, ( sum(A, B, C), write_list([A, ' & \\numtwo{', B, '} & \\numtwo{', C, '} \\\\\n']) ), _)), retractall(maUseNewTranslationRules(_)), asserta(maUseNewTranslationRules(false)), retractall(useCostEstimation(_)), asserta(useCostEstimation(true)), optEvalRun, with_output_to(atom(A2), findall(_, ( sum(A, B, C), write_list([A, ' & \\numtwo{', B, '} & \\numtwo{', C, '} \\\\\n']) ), _)), retractall(useCostEstimation(_)), asserta(useCostEstimation(mixed)), optEvalRun, with_output_to(atom(A3), findall(_, ( sum(A, B, C), write_list([A, ' & \\numtwo{', B, '} & \\numtwo{', C, '} \\\\\n']) ), _)), write_list(['\nNew Operators + CostEstimation\n', A1]), write_list(['\nCostEstimation\n', A2]), write_list(['\nProlog Cost predicates\n', A3]), true. optEvalRun :- !, retractall(sum(_, _, _)), Runs=4, testMAByID(2001, Runs, 4, 2), % Case 1 testMAByID(2000, Runs, 4, 2), % Case 2 testMAByID(2002, Runs, 4, 2), % Case 3 testMAByID(2003, Runs, 4, 2), % Case 4 testMAByID(8001, Runs, 4, 2), % Case 5 testMAByID(8002, Runs, 4, 2), % Case 6 testMAByID(8005, Runs, 4, 2), % Case 7 %testMAByID(8003, 4, 4, 2), %testMAByID(8004, 4, 4, 2), %testMAByID(8006, 1, 1, 2), %testMAByID(8013, 1, 1, 2), true. createTestFiles :- testMAByID(2001, 4, 4, 2), % Case 1 testMAByID(2000, 4, 4, 2), % Case 2 testMAByID(2002, 4, 4, 2), % Case 3 testMAByID(2003, 4, 4, 2), % Case 4 testMAByID(8001, 4, 4, 2), % Case 5 testMAByID(8002, 4, 4, 2), % Case 6 testMAByID(8005, 4, 4, 2), % Case 7 true. /* Creates the exact selectivities for the test queries. */ maCreateTestSels :- maCreateTestSels2. maCreateTestSels :- !. maCreateTestSels2 :- getAllNonSampleRels(Rels), databaseName(DB), !, member(R1, Rels), member(R2, Rels), showRowCounts(R1, 'count', C1), showRowCounts(R2, 'count', C2), downcase_atom(R1, R1DC), downcase_atom(R2, R2DC), Min is min(C1, C2), % for the first Min rows, ~no~ exists once in % both relations. Because of this special case, % selectivities can be easiliy computed. Sel is Min / (C1*C2), retractall(storedSel(DB,R1DC:no=R2DC:no, _)), assertz(storedSel(DB,R1DC:no=R2DC:no, Sel)), fail. testMAByID(No) :- testMAByID(No, 50, 10, 30). testMAByID(No, CO, CQ, Sleep) :- evalQuery(No, Properties, Query), processProperties(Properties), runMATest(Properties, No, tmode(opt(CO), query(CQ), sleep(Sleep)), Query). runMATest(Properties, No, TMode, Query) :- %maCreateTestSels, !, (member(maEval(Strats), Properties) -> % Test only selected strategies... setMAStrategies(Strats) ; % Test all strategies... ( %createExtendedEvalList(TList), %setMAStrategies(TList), % currently i want to check only these strategies TList=[staticEqual(16),static(16),static(256), staticMaxDivPOG,modifiedDijkstra] ) ), testStrategies(No, TMode, Query, TList, L), delOption(memoryAllocation), setOption(improvedcosts), %delOption(noHashjoin), sqltest(No, impcosts, TMode, Query, Atom, Costs, OptTime, ExecTime, OTimes1, QTimes1, MInfo1), S=[improvedcosts, OptTime, ExecTime, Costs, Atom, OTimes1, QTimes1, MInfo1], delOption(improvedcosts), sqltest(No, stdcosts, TMode, Query, AtomSC, CostsSC, OptTimeSC, ExecTimeSC, OTimes2, QTimes2, MInfo2), SC=[standardcosts, OptTimeSC, ExecTimeSC, CostsSC, AtomSC, OTimes2, QTimes2, MInfo2], setOption(memoryAllocation), %setOption(noHashjoin), append(L, [S, SC], LNew), with_output_to(atom(A), processResults3(Query, LNew)), write(A), % Store results get_time(TS), my_convert_time(TS, Year, Month, Day, Hour, Minute, Sec, _MilliSec), atomic_list_concat([Year, '-', Month, '-', Day, ' ', Hour, ':', Minute, ':', Sec], DateTime), atomic_list_concat(['MemoryAllocation/test/matest',No,'.log'], FileName), open(FileName, write, FD), write(FD, DateTime), write(FD, '\n'), write(FD, Query), write(FD, '\n'), write(FD, A), close(FD), atomic_list_concat(['MemoryAllocation/test/matest',No,'.term'], FileNameT), open(FileNameT, write, FDT), writeq(FDT, LNew), write(FDT, '.'), close(FDT). deleteTestFile(File) :- atomic_list_concat(['MemoryAllocation/test/', File], FileName), (exists_file(FileName) -> delete_file(FileName) ; true ). ensureDirExists(No) :- atomic_list_concat(['MemoryAllocation/test/', No], FileName), (exists_directory(FileName) -> true ; make_directory(FileName) ). appendToTestFile(File, Data) :- atomic_list_concat(['MemoryAllocation/test/', File], FileName), open(FileName, append, FD), write(FD, Data), close(FD). processResults(Query, LNew) :- % just store if later needed (manually) assertz(matestTestResultList(Query, LNew)), write_list(['\nQuery: ', Query]), nl, FH='~w~30+ &~` t~w~9+ &~` t~w~11+ &~` t~w~12+ &~w &~w &~w\n \\\\', format(FH, ['Type', 'Costs', 'OptTime ms', 'ExecTime ms', 'Query', 'Query times', 'Memory']), findall(X, ( member(X, LNew), X=[T, OT, ET, C, A, _OTimes, QTimes, MInfo|_], F='~w~30+ &~` t~d~9+ &~` t~d~10+ &~` t~d~12+ &~w &~w &~w\\\\~n', ICosts is round(C), format(F, [T, ICosts, OT, ET, A, QTimes, MInfo]) ), _), nl. reprocessResults3(ID) :- atomic_list_concat(['MemoryAllocation/test/matest',ID,'.term'], FileName), open(FileName, read, FD), read(FD, Term), close(FD), processResults3(_, Term). reprocessResults4(ID) :- atomic_list_concat(['MemoryAllocation/test/',ID,'.term'], FileName), open(FileName, read, FD), read(FD, Term), close(FD), processResults3(_, Term). :- dynamic sum/3. addSum(Label, C, O) :- \+ sum(Label, _, _), assertz(sum(Label, C, O)). addSum(Label, C, O) :- retract(sum(Label, C1, O1)), C2 is C1+C, O2 is O1+O, assertz(sum(Label, C2, O2)). labelToStrat(T, Strat) :- Strat=staticMaxDivOpCount, sub_atom(T, 0, 19, _POS, Strat), !. labelToStrat(T, Strat) :- Strat=staticMaxDivPOG, sub_atom(T, 0, 15, _POS, Strat), !. labelToStrat(T, T) :- !. processResults3(Query, LNew) :- write_list(['\nQuery: ', Query]), nl, FH='~w~30+ &~` t~w~9+ &~` t~w~11+ &~` t~w~12+ &~w \\\\ ~n', format(FH, ['Type', 'Costs', 'OptTime ms', 'ExecTime ms', 'Memory']), %, 'Query', 'Query times', 'Memory']), findall(X, ( member(X, LNew), X=[T, OT, ET, C, A, _OTimes, _QTimes, MInfo|_], %F='~w~30+ &~` t~d~9+ &~` t~d~10+ &~` t~d~12+ &~w \\\\~n', ICosts is round(C), (MInfo = [] -> MI='' ; MInfo=[MI|_] ), labelToStrat(T, Strat), addSum(Strat, C, OT), %DATA=[T, ICosts, OT, ET, MI], %format(F, DATA), F='~w~30+ &~` t~w~9+ &~` t~w~10+ &~` t~d~12+ &~w &~w \\\\~n', atomic_list_concat(['\\num{', ICosts, '}'], X2), atomic_list_concat(['\\num{', OT, '}'], X3), %format(F, [T, ICosts, OT, ET, A, MInfo]) format(F, [T, X2, X3, ET, A, MInfo]) ), _), nl, findall(X, ( member(X, LNew), X=[T, _OT, _ET, _C, A|_], write_list(['# ', T, ':\n']), write_list([A, '\n']) ), _), nl. latexListing(A, AL) :- atom_chars(A, CharList), latexTranslate(CharList, CharListL), atom_chars(AL, CharListL). latexTranslate([], []) :- !. latexTranslate([A|Rest], Out) :- latexTranslateA(A, ACharList), latexTranslate(Rest, RestCharList), append(ACharList, RestCharList, Out). latexTranslateA('{', ['\\', '{']) :- !. latexTranslateA('}', ['\\', '}']) :- !. latexTranslateA(A, [A]) :- !. testStrategies(_No, _TMode, _Query, [], []) :- !. testStrategies(No, TMode, Query, [T|Rest], [S|SRest]) :- setMAStrategies([T]), sqltest(No, T, TMode, Query, Atom, Costs, OptTime, ExecTime, OTimes, QTimes, MInfo), maResult(Label, _, _, _, _), S=[Label, OptTime, ExecTime, Costs, Atom, OTimes, QTimes, MInfo], ensure(count(maResult(_, _, _, _, _), 1)), % ensure that no more % than one strategies was tested. testStrategies(No, TMode, Query, Rest, SRest). sqltest(No, Name, TMode, Query, Atom, Costs, OptTime, AvgExecTime, OTimes, QTimes, MInfo) :- TMode=tmode(opt(OCount), query(QCount), sleep(Sleep)), defaultExceptionHandler(( % init warm state (onlyWriteTestFiles -> true ; (QCount > 1 -> ( write_list(['Init warm state...\n']), mOptimize(Query, AtomW, _), silentquery(Name, AtomW, _ETime), write_list(['done.\n']) ) ; true )), % Test run xcallstat(getTime(mOptimize(Query, Atom, Costs)), 0, OCount, [OptTime, _OMin, _OMax], OTimes), (optimizerOption(memoryAllocation) -> ( formulaList(F), maMemoryInfo(MInfo1), MInfo=[MInfo1, F] ) ; MInfo=[] ), (onlyWriteTestFiles -> ( writeTestFile(No, Name, Atom, QCount), AvgExecTime=0, QTimes=[0] ) ; xcallstat(silentquery(Name, Atom), Sleep, QCount, [AvgExecTime, _QMin, _QMax], QTimes) ) )). % Allowes to test the queries with the SecondoBDB command. % currently it is not possible to call rmlogs during during query % execution (and it would falsify the results). onlyWriteTestFiles. % If not interessent in any output. writeTestFile(No, Name, Query, QCount) :- onlyWriteTestFiles, %atom_concat('query ', Query, QueryText), atomic_list_concat(['query ', Query, ' feed head[0] consume'], QueryText), write_list(['Exec: ', QueryText, '...\n']), term_to_atom(Name, NameA), ensureDirExists(No), atomic_list_concat([No, '/', testrun_, NameA, '.sec'], F), QCountP1 is QCount+1, deleteTestFile(F), databaseName(DB), atomic_list_concat(['open database ', DB, '\n\n'], ODB), appendToTestFile(F, ODB), xcall(( appendToTestFile(F, QueryText), appendToTestFile(F, '\n\n') ), 0, QCountP1, _), % The result is not streamed, hence it can be thrown away without % falsifing the result. atomic_list_concat(['query SEC2COMMANDS feed sortby[CmdNr desc] head[', QCount, '] project[ElapsedTime]consume \n\n'], TQ), appendToTestFile(F, TQ), atomic_list_concat(['query SEC2COMMANDS feed sortby[CmdNr desc] head[', QCountP1, '] tail[', QCount, ']project[ElapsedTime]avg[ElapsedTime]\n\n'], TQ2), appendToTestFile(F, TQ2), !. silentquery(_Name, Query, ETimeMS) :- %atom_concat('query ', Query, QueryText), atomic_list_concat(['query ', Query, ' feed head[0] consume'], QueryText), write_list(['Exec: ', QueryText, '...\n']), !, % The result is not streamed, hence it can be thrown away without % falsifing the result. (once(getTime(secondo(QueryText, _Result), PTime)) -> ( write_list(['Exec: done.\n']), secondo('query SEC2COMMANDS feed sortby[CmdNr \c desc] head[1] project[ElapsedTime]consume', TResult), TResult=[[rel, [tuple, [['ElapsedTime', real]]]], [[ETime]]], ETimeMS is round(ETime * 1000), write_list(['ETime in ms: ', ETimeMS, ' Prolog eval time: ', PTime, '\n']) ) ; ( write_list(['failed: ', Query, '\n']), !, fail ) ). xcallstat(_Goal, _Sleep, 0, [-1, -1, -1], []) :- !. xcallstat(Goal, Sleep, Count, [AvgExecTime, MinTime, MaxTime], Times) :- Count>0, !, xcallAddTime(Goal, Sleep, Count, AllTimes), write_list(['All times: ', AllTimes, '\n']), /* (Count > 4 -> (Count > 9 -> ( remove_extreme_value(min, AllTimes, TMP1), remove_extreme_value(min, TMP1, TMP2), remove_extreme_value(max, TMP2, TMP3), remove_extreme_value(max, TMP3, Times) ) ; ( remove_extreme_value(min, AllTimes, TMP1), remove_extreme_value(max, TMP1, Times) ) ) ; Times=AllTimes ), */ Times=AllTimes, write_list(['NTimes: ', Times, '\n']), listSum(Times, TotalTime), min_list(Times, MinTime), max_list(Times, MaxTime), length(Times, Len), AvgExecTime is round(TotalTime / Len). madebug :- debugLevel(ma), debugLevel(ma3). /* May fail with an exception for vanished memoryValue terms. */ maTestEnum :- maTestEnum(select * from [orte, plz as p] where ort=p:ort). maTestEnum(Query) :- setStaticMemory(16), sql(Query), !, highNode(N), enumeratePaths(Path, N, Costs), (plan(Path, Plan)-> plan_to_atom(Plan, A);true), Costs1 is floor(Costs), write_list(['\n', Costs1, ' -> ', A]), fail. poginfo :- writeNodes, writefacts(costEdge). /* Prints some information about some relation like the row count, tuplesize etc. */ showRowCounts :- % non standard prolog, but now (new secondo checkout) there is more % secondo output between the different calls. Hence, the output invoked % by whis is collected and at last written to stdout. with_output_to(atom(A), showRowCounts2), write(A). showRowCounts2 :- FH='~w~17+ ~` t~w~15+ ~` t~w~15+ ~` t~w~15+ ~` t~w~15+ ~` t~w~15+ ~n', format(FH, ['Rel', 'Rows', 'Tuplesize', 'ExtTupleSize', 'RootTupleSize', 'Data MiB']), getAllNonSampleRels(Rels), findall(_, ( %member(R, ['Buildings', 'Natural', 'Places', 'Points', % 'Railways', 'Roads', 'Waterways']), member(R, Rels), showRowCounts(R) ), _). showRowCounts(Rel) :- showRowCounts(Rel, 'count', C), showRowCounts(Rel, 'tuplesize', T), showRowCounts(Rel, 'exttuplesize', ET), showRowCounts(Rel, 'roottuplesize', RT), DATAMiB is round((C*ET)/(1024**2)), F='~w~17+ &~` t~d~15+ &~` t~d~15+ &~` t~d~15+ &~` t~d~15+ &~` t~d~15+ \\\\~n', format(F, [Rel, C, T, ET, RT, DATAMiB]), !. showRowCounts(Rel, Op, R2) :- atomic_list_concat(['query ', Rel, ' ', Op], Atom), secondo(Atom, [_Type, R]), R2 is round(R), !. getAllNonSampleRels(Rels) :- databaseName(DB), findall(X, ( storedRel(DB, R, _), \+ sub_atom(R, _, _, _,sample), storedSpell(DB, R, S), atomic(S), % don't care here about the lc(_) case. upper(S, X) ), Rels). /* */ countCostEdgePaths :- once(resetCounter(cecount)), highNode(N), enumeratePaths(_Path, N, _Cost), nextCounter(cecount, C), (0 is mod(C, 10000) -> % show progress (write(C), nl) ; true ), false. countCostEdgePaths :- getCounter(cecount, C), write_list(['Path count: ', C, '\n']). /* The result should be the factorial of N (= !N) */ enumeratePOGPaths(Path, N, Costs) :- enumeratePOGPaths([], 0, N, Path, Costs). enumeratePOGPaths(Path, N, N, Path, 0). enumeratePOGPaths(PartPath, Node, N, RPath, RCosts) :- Node\=N, edge(Node, Target1, A, B, C, ECosts), E=edge(Node, Target1, A, B, C, ECosts), append(PartPath, [E], Path), enumeratePOGPaths(Path, Target1, N, RPath, Costs), RCosts is ECosts + Costs. countPOGEdgePaths :- once(resetCounter(cecount)), highNode(N), enumeratePOGPaths(_Path, N, _Cost), nextCounter(cecount, C), (0 is mod(C, 10000) -> % show progress (write(C), nl) ; true ), false. countPOGEdgePaths :- getCounter(cecount, C), write_list(['Path count: ', C, '\n']). % There is a mod function within swi-prolog 5.x.y but somehow it can't be % used within is/2 terms. :- arithmetic_function(mod/2). mod(A, B, M) :- M is A - (A div B) * B. /* Some facts and predicates to process my test data. */ maTestResults([ [2000, ['testrun_staticEqual(16).sec', 1108.06306775], ['testrun_static(16).sec', 1094.781208], ['testrun_static(256).sec', 1088.821897], ['testrun_staticMaxDivOpCount.sec', 1080.308661], ['testrun_staticMaxDivPOG.sec', 1084.16777075], ['testrun_modifiedDijkstra.sec', 1084.79048475], ['testrun_enumeratePaths.sec', 1068.23215975], ['testrun_impcosts.sec', 993.6681055], ['testrun_stdcosts.sec', 1007.610972]], [2001, ['testrun_staticEqual(16).sec', 13.04901475], ['testrun_static(16).sec', 12.68681375], ['testrun_static(256).sec', 12.984243125], ['testrun_staticMaxDivOpCount.sec', 13.2904455], ['testrun_staticMaxDivPOG.sec', 12.820770875], ['testrun_modifiedDijkstra.sec', 12.853974125], ['testrun_enumeratePaths.sec', 13.2094465], ['testrun_impcosts.sec', 13.377928375], ['testrun_stdcosts.sec', 13.134874875]], [2002, ['testrun_staticEqual(16).sec', 343.1777], ['testrun_static(16).sec', 326.04821625], ['testrun_static(256).sec', 284.706570875], ['testrun_staticMaxDivOpCount.sec', 287.180825], ['testrun_staticMaxDivPOG.sec', 286.37312725], ['testrun_modifiedDijkstra.sec', 288.9751645], ['testrun_enumeratePaths.sec', 289.381192625], ['testrun_impcosts.sec', 317.90451375], ['testrun_stdcosts.sec', 373.27153]], [2003, ['testrun_staticEqual(16).sec', 307.29982825], ['testrun_static(16).sec', 323.7392885], ['testrun_static(256).sec', 219.162557], ['testrun_staticMaxDivOpCount.sec', 210.65918125], ['testrun_staticMaxDivPOG.sec', 221.08666225], ['testrun_modifiedDijkstra.sec', 216.165868], ['testrun_enumeratePaths.sec', 213.490941125], ['testrun_impcosts.sec', 234.717237875], ['testrun_stdcosts.sec', 213.272788125]], [8001, ['testrun_staticEqual(16).sec', 354.8144545], ['testrun_static(16).sec', 337.43853675], ['testrun_static(256).sec', 321.000091], ['testrun_staticMaxDivOpCount.sec', 306.511835], ['testrun_staticMaxDivPOG.sec', 289.7197525], ['testrun_modifiedDijkstra.sec', 285.903809], ['testrun_enumeratePaths.sec', 289.52861475], ['testrun_impcosts.sec', 331.21191075], ['testrun_stdcosts.sec', 375.47679]], [8002, ['testrun_staticEqual(16).sec', 361.54869125], ['testrun_static(16).sec', 376.486375], ['testrun_static(256).sec', 359.250565], ['testrun_staticMaxDivOpCount.sec', 343.6269805], ['testrun_staticMaxDivPOG.sec', 348.521256], ['testrun_modifiedDijkstra.sec', 344.39644675], ['testrun_enumeratePaths.sec', 355.46775575], ['testrun_impcosts.sec', 347.81456025], ['testrun_stdcosts.sec', 120.74938925]], [8005, ['testrun_staticEqual(16).sec', 1001.965809], ['testrun_static(16).sec', 996.258335], ['testrun_static(256).sec', 942.573502], ['testrun_staticMaxDivOpCount.sec', 982.240451], ['testrun_staticMaxDivPOG.sec', 987.906534], ['testrun_modifiedDijkstra.sec', 963.548287], ['testrun_enumeratePaths.sec', 942.843598], ['testrun_impcosts.sec', 983.514721], ['testrun_stdcosts.sec', 967.498633]]]). maPTestResults :- maTestResults(T), maPTestResults2(T, V), write(V), V=[[_,R]|_], inpercent(R, V). inpercent(_R, []) :- !. inpercent(R, [[_,V]|X]) :- T is ((R-V)/R)*100, write_list(['\nT: ',T]), inpercent(R, X). maPTestResults2([[_NO|Vals]], Vals) :- !. maPTestResults2([[_NO|Vals]|R], ValsO) :- maPTestResults2(R, ValsR), addVals(Vals, ValsR, ValsO). addVals([], [], []). addVals([[L,V1]|VR1], [[_,V2]|VR2], [[L,R]|RR]) :- R is V1+V2, addVals(VR1, VR2, RR). % eof