% ----------------------------------------------------------------------
% BEGIN LICENSE BLOCK
% Version: CMPL 1.1
%
% The contents of this file are subject to the Cisco-style Mozilla Public
% License Version 1.1 (the "License"); you may not use this file except
% in compliance with the License.  You may obtain a copy of the License
% at www.eclipse-clp.org/license.
% 
% Software distributed under the License is distributed on an "AS IS"
% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
% the License for the specific language governing rights and limitations
% under the License. 
% 
% The Original Code is  The ECLiPSe Constraint Logic Programming System. 
% The Initial Developer of the Original Code is  Cisco Systems, Inc. 
% Portions created by the Initial Developer are
% Copyright (C) 1994-2006 Cisco Systems, Inc.  All Rights Reserved.
% 
% Contributor(s): ECRC GmbH
% Contributor(s): IC-Parc, Imperal College London
% 
% END LICENSE BLOCK
%
% System:	ECLiPSe Constraint Logic Programming System
% Version:	$Id: par_util.pl,v 1.2 2009/07/16 09:11:24 jschimpf Exp $
% ----------------------------------------------------------------------

%
% ECLiPSe PROLOG LIBRARY MODULE
%
% IDENTIFICATION:	par_util.pl
%
% AUTHOR:		Joachim Schimpf
%
% CONTENTS:
%			par_member(?Element, +List)
%			par_delete(?Element, +List, ?Rest)
%			par_between(+From, +To, ?I)
%			par_maplist(+Pred, +ListIn, ?ListOut)
%
%			statistics_par
%			statistics_par_reset
%
%			Goal1 & Goal2
%
% DESCRIPTION:		Parallel versions of various predicates.
%			The semantics of par_member/2, par_maplist/3
%			and &/2 is not exactly the same as the
%			corresponding sequential version.
%

:- module(par_util).
:- pragma(nodebug).

:- comment(categories, ["Algorithms"]).
:- comment(summary, "Parallel versions of various predicates").
:- comment(author, "Joachim Schimpf, ECRC Munich").
:- comment(copyright, "Cisco Systems, Inc").
:- comment(date, "$Date: 2009/07/16 09:11:24 $").
:- comment(par_member/2, [template:"par_member(?Element, +List)",
    summary:"Parallel version of member/2",
    desc:html("Parallel version of member/2, i.e.  selects elements from
    the given list in parallel.  Note that it cannot work backwards
    and generate lists like member/2 can, the list must be a proper list.")]).
:- comment(par_delete/3, [template:"par_delete(?Element, ?List, ?Rest)",
    summary:"Parallel version of delete/3"]).
:- comment(par_between/3, [template:"par_between(+From, +To, ?I)",
    summary:"Generates integers between From and To in parallel",
    see_also:[fork/2, between/3, between/4]]).
:- comment(par_maplist/3, [template:"par_maplist(+Pred, +In, ?Out)",
    summary:"Parallel version of maplist/3",
    desc:html("Parallel version of maplist/3.  The semantics is not
    exactly the same as maplist/3:  It does not work backwards and it
    does not cope with aliasing between the In and the Out list, since
    it is implemented on top of findall/3.  There will only be a
    performance gain if the mapping predicate does enough computation
    to make the overhead pay off."),
    see_also:[maplist/3]]).
:- comment((&)/2, [template:"Goal1 & Goal2",
    summary:"Parallel AND operator implemented on top of OR-parallelism",
    desc:html("Parallel AND operator implemented on top of
    OR-parallelism.  This will only pay off for sufficiently
    coarse-grained computations in Goal1 and Goal2.")]).

:- export
	(&)/2,
	par_between/3,
	par_delete/3,
	par_maplist/3,
	par_member/2,
	statistics_par/0,
	statistics_par_reset/0.

:- export op(950, xfy, (&)).


:- import worker_statistics/2, worker_statistics_reset/1 from sepia_kernel.

% Parallel member(?, +List), it can't generate lists!

par_member(X, List) :-
	List = [_|_],
	Arr =.. [arr|List],
	functor(Arr, arr, N),
	N1 is N+1,
	fork(N, I),
	I1 is N1-I,
	arg(I1, Arr, X).


:- parallel par_delete/3.
par_delete(A, [A|C], C).
par_delete(A, [B|C], [B|D]) :-
	par_delete(A, C, D).


par_between(From, To, X) :-
	To1 is To+1,
	N is To1-From,
	N > 0,
	fork(N, I),
	X is To1-I.


:- tool((&)/2, '&_body'/3).

'&_body'(Goal1, Goal2, Module) :-
	findall(Sol, parand(Sol, Goal1, Goal2, Module), Bag),
	member(a-Goal1, Bag),
	member(b-Goal2, Bag).

:- parallel parand/4.
:- mode parand(-,+,+,+).
parand(a-Goal1, Goal1, _, Module) :- call(Goal1)@Module.
parand(b-Goal2, _, Goal2, Module) :- call(Goal2)@Module.


:- tool(par_maplist/3, par_maplist_body/4).

par_maplist_body(Pred, In, Out, Module) :-
	findall(Sol, map_elements(Pred, In, Sol, Module), Out0),
	sort(1, >=, Out0, Out1),
	strip_key(Out1, Out).

map_elements(Pred, In, I-Xout, Module) :-
	Pred =.. PL,
	append(PL, [Xin, Xout], NewPred),
	Call =.. NewPred,
	InArr =.. [in|In],
	functor(InArr, in, N),
	N1 is N+1,
	fork(N, I),
	I1 is N1-I,
	arg(I1, InArr, Xin),
	( call(Call)@Module, true -> true ).

strip_key([], []).
strip_key([_-X|Xs], [X|Ys]) :- strip_key(Xs, Ys).


% Parallel statistics

statistics_par :-
	writeln(" Wrkr Jobs Prun Published Copy      Copied  Idling Working Copying Scheduling"),
	writeln("   ID    #    # cpts alts    #       bytes      ms      ms      ms      ms\n"),
	get_flag(workerids, _Host:AliveIds+SleepIds),
	(member(Wid, AliveIds) ; member(Wid, SleepIds)),
	worker_statistics(Wid, Data),
	arg(1, Data, Jobs),
	arg(2, Data, Prunes),
	arg(4, Data, CopyFromCnt),
	arg(5, Data, CopyFromBytes),
	arg(8, Data, _Publish),
	arg(9, Data, PubChpts),
	arg(10, Data, PubAlts),
	IdleMs is arg(14, Data) + arg(23, Data),
	WorkMs is arg(15, Data),
	CopyMs is arg(17, Data) + arg(18, Data) + arg(22, Data),
	SchedMs is arg(16, Data) + arg(19, Data) + arg(20, Data) + arg(21, Data),
	printf("%5d%5d%5d%5d%5d%5d%12d%8.0f%8.0f%8.0f%8.0f\n",
		[Wid, Jobs, Prunes, PubChpts, PubAlts, CopyFromCnt,
		 CopyFromBytes, IdleMs, WorkMs, CopyMs, SchedMs]),
	fail.
statistics_par.

statistics_par_reset :-
	get_flag(workerids, _Host:AliveIds+SleepIds),
	(member(Wid, AliveIds) ; member(Wid, SleepIds)),
	worker_statistics_reset(Wid),
	fail.
statistics_par_reset.

