%-----------------------------------------------------------------------------%
% Copyright (C) 1995-1996 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
%
% file: graph_colour.m
% main author: conway.
%
% This file contains functionality to find a 'good' colouring of a graph.
% The predicate graph_colour__group_elements(set(set(T)), set(set(T))),
% takes a set of sets each containing elements that touch, and returns
% a set of sets each containing elements that can be assigned the same
% colour, ensuring that touching elements have different colours.
% ("Good" means using as few colours as possible.)
%
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

:- module graph_colour.

:- interface.

:- import_module set.

:- pred graph_colour__group_elements(set(set(T)), set(set(T))).
:- mode graph_colour__group_elements(in, out) is det.

:- implementation.

:- import_module list, require.

graph_colour__group_elements(Constraints, Colours) :-
	set__power_union(Constraints, AllVars),
	set__init(EmptySet),
	set__delete(Constraints, EmptySet, Constraints1),
	set__to_sorted_list(Constraints1, ConstraintList),
	graph_colour__find_all_colours(ConstraintList, AllVars, ColourList),
	set__list_to_set(ColourList, Colours),
	true.
%	% performance reducing sanity check....
%	(
%		set__power_union(Colours, AllColours),
%		(set__member(Var, AllVars) => set__member(Var, AllColours))
%	->
%		error("graph_colour__group_elements: sanity check failed")
%	;
%		true
%	).

%------------------------------------------------------------------------------%

:- pred graph_colour__find_all_colours(list(set(T)), set(T), list(set(T))).
:- mode graph_colour__find_all_colours(in, in, out) is det.

	% Iterate the assignment of a new colour untill all constraints
	% are satisfied.
graph_colour__find_all_colours(ConstraintList, Vars, ColourList) :-
	(
		ConstraintList = []
	->
		ColourList = []
	;
		graph_colour__next_colour(Vars, ConstraintList,
					RemainingConstraints, Colour),
		set__difference(Vars, Colour, RestVars),
		graph_colour__find_all_colours(RemainingConstraints, RestVars,
					ColourList0),
		ColourList = [Colour|ColourList0]
	).

%------------------------------------------------------------------------------%

:- pred graph_colour__next_colour(set(T), list(set(T)), list(set(T)), set(T)).
:- mode graph_colour__next_colour(in, in, out, out) is det.

graph_colour__next_colour(Vars, ConstraintList, Remainder, SameColour) :-
	(
			% If there are any constraints left to be
			% satisfied,
		ConstraintList \= []
	->
			% Select a variable to assign a colour,
		graph_colour__choose_var(Vars, Var, Vars1),
			% and divide the constraints into those that
			% may be the same colour as that var and those
			% that may not.
		graph_colour__divide_constraints(Var, ConstraintList,
				WereContaining, NotContaining, Vars1, RestVars),
		(
				% if there are sets that can
				% share a colour with the selected var,
			NotContaining \= []
		->
			(
					% and if there is at least
					% one variable that can share
					% a colour with the selected
					% variable,
				\+ set__empty(RestVars)
			->
					% then recusively use the remaining
					% constraints to assign a colour
					% to one of the remaining vars,
					% and assemble the constraint
					% residues.
				graph_colour__next_colour(RestVars,
					NotContaining, ResidueSets,
							SameColour0),
					% add this variable to the
					% variables of the current
					% colour.
				set__insert(SameColour0, Var, SameColour)
			;
					% There were no variables left
					% that could share a colour, so
					% create a singleton set containing
					% this variable.
				set__singleton_set(SameColour, Var),
				ResidueSets = NotContaining
			)
		;
				% There were no more constraints
				% which could be satisfied by assigning
				% any variable a colour the same as the
				% current variable, so create a signleton
				% set with the current var, and assign
				% the residue to the empty set.
			set__singleton_set(SameColour, Var),
			ResidueSets = []
		),
			% The remaining constraints are the residue
			% sets that could not be satisfied by assigning
			% any variable to the current colour, and the
			% constraints that were already satisfied by
			% the assignment of the current variable to
			% this colour.
		list__append(ResidueSets, WereContaining, Remainder)
	;
			% If there were no constraints, then no colours
			% were needed.
		Remainder = [],
		set__init(SameColour)
	).

%------------------------------------------------------------------------------%

:- pred graph_colour__divide_constraints(T, list(set(T)), list(set(T)),
				list(set(T)), set(T), set(T)).
:- mode graph_colour__divide_constraints(in, in, out, out, in, out) is det.

% graph_colour__divide_constraints takes a var and a list of sets of var,
% and divides the list into two lists: a list of sets containing the
% given variable and a list of sets not containing that variable. The
% sets in the list containing the variable have that variable removed.
% Additionally, a set of variables is threaded through the computation,
% and any variables that were in sets that also contained the given
% variables are removed from the threaded set.

graph_colour__divide_constraints(_Var, [], [], [], Vars, Vars).
graph_colour__divide_constraints(Var, [S|Ss], C, NC, Vars0, Vars) :-
	graph_colour__divide_constraints(Var, Ss, C0, NC0, Vars0, Vars1),
	(
		set__member(Var, S)
	->
		set__delete(S, Var, T),
		(
			set__empty(T)
		->
			C = C0
		;
			C = [T|C0]
		),
		NC = NC0,
		set__difference(Vars1, T, Vars)
	;
		C = C0,
		NC = [S|NC0],
		Vars = Vars1
	).

%------------------------------------------------------------------------------%

:- pred graph_colour__choose_var(set(T), T, set(T)).
:- mode graph_colour__choose_var(in, out, out) is det.

% graph_colour__choose_var/3, given a set of variables, chooses
% one, returns it and the set with that variable removed. The
% use of higher order preds could be used to make the heuristic
% for which variable to choose user-defined.

graph_colour__choose_var(Vars, Var, Vars1) :-
	set__to_sorted_list(Vars, VarList),
	(
		VarList = [VarA|Vars1A]
	->
		Var = VarA,
		set__list_to_set(Vars1A, Vars1)
	;
		error("graph_colour__choose_var: no vars!")
	).

%------------------------------------------------------------------------------%
%------------------------------------------------------------------------------%
