{-# LANGUAGE CPP #-}
{-
	Copyright (C) 2018 Dr. Alistair Ward

	This file is part of BishBosh.

	BishBosh is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	BishBosh is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with BishBosh.  If not, see <http://www.gnu.org/licenses/>.
-}
{- |
 [@AUTHOR@]	Dr. Alistair Ward

 [@DESCRIPTION@]	The unweighted values of each criterion used to assess the fitness of a position, & the resulting weighted mean.
-}

module BishBosh.Metric.WeightedMeanAndCriterionValues(
-- * Types
-- ** Data-types
	WeightedMeanAndCriterionValues(
--		MkWeightedMeanAndCriterionValues,
		getWeightedMean,
		getCriterionValues
	),
-- * Constants
	criterionValuesTag,
	weightedMeanTag,
-- * Functions
	negateWeightedMean,
	calculateWeightedMean,
-- ** Constructor
	mkWeightedMeanAndCriterionValues
) where

import			Control.Arrow((&&&))
import qualified	BishBosh.Metric.CriterionValue	as Metric.CriterionValue
import qualified	BishBosh.Metric.CriterionWeight	as Metric.CriterionWeight
import qualified	BishBosh.Property.ShowFloat	as Property.ShowFloat
import qualified	BishBosh.Text.ShowList		as Text.ShowList
import qualified	BishBosh.Type.Mass		as Type.Mass
import qualified	Control.DeepSeq
import qualified	Factory.Math.Statistics

#ifdef PARALLELISE
import qualified	Control.Parallel.Strategies
#endif

-- | Qualifies output.
criterionValuesTag :: String
criterionValuesTag :: String
criterionValuesTag	= String
"criterion-values"

-- | Qualifies output.
weightedMeanTag :: String
weightedMeanTag :: String
weightedMeanTag		= String
"weighted-mean"

-- | A /weighted mean/ & the individual unweighted criterion-values from which it was composed.
data WeightedMeanAndCriterionValues	= MkWeightedMeanAndCriterionValues {
	WeightedMeanAndCriterionValues -> WeightedMean
getWeightedMean		:: Type.Mass.WeightedMean,			-- ^ The weighted mean of a list of criterion-values.
	WeightedMeanAndCriterionValues -> [WeightedMean]
getCriterionValues	:: [Metric.CriterionValue.CriterionValue]	-- ^ The unweighted /CriterionValue/s from which the weighted mean was composed.
} deriving (WeightedMeanAndCriterionValues
-> WeightedMeanAndCriterionValues -> Bool
(WeightedMeanAndCriterionValues
 -> WeightedMeanAndCriterionValues -> Bool)
-> (WeightedMeanAndCriterionValues
    -> WeightedMeanAndCriterionValues -> Bool)
-> Eq WeightedMeanAndCriterionValues
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: WeightedMeanAndCriterionValues
-> WeightedMeanAndCriterionValues -> Bool
$c/= :: WeightedMeanAndCriterionValues
-> WeightedMeanAndCriterionValues -> Bool
== :: WeightedMeanAndCriterionValues
-> WeightedMeanAndCriterionValues -> Bool
$c== :: WeightedMeanAndCriterionValues
-> WeightedMeanAndCriterionValues -> Bool
Eq, Int -> WeightedMeanAndCriterionValues -> ShowS
[WeightedMeanAndCriterionValues] -> ShowS
WeightedMeanAndCriterionValues -> String
(Int -> WeightedMeanAndCriterionValues -> ShowS)
-> (WeightedMeanAndCriterionValues -> String)
-> ([WeightedMeanAndCriterionValues] -> ShowS)
-> Show WeightedMeanAndCriterionValues
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [WeightedMeanAndCriterionValues] -> ShowS
$cshowList :: [WeightedMeanAndCriterionValues] -> ShowS
show :: WeightedMeanAndCriterionValues -> String
$cshow :: WeightedMeanAndCriterionValues -> String
showsPrec :: Int -> WeightedMeanAndCriterionValues -> ShowS
$cshowsPrec :: Int -> WeightedMeanAndCriterionValues -> ShowS
Show)

instance Control.DeepSeq.NFData WeightedMeanAndCriterionValues where
	rnf :: WeightedMeanAndCriterionValues -> ()
rnf MkWeightedMeanAndCriterionValues { getWeightedMean :: WeightedMeanAndCriterionValues -> WeightedMean
getWeightedMean = WeightedMean
weightedMean }	= WeightedMean -> ()
forall a. NFData a => a -> ()
Control.DeepSeq.rnf WeightedMean
weightedMean	-- The other field is a prerequisite.

instance Property.ShowFloat.ShowFloat WeightedMeanAndCriterionValues where
	showsFloat :: (WeightedMean -> ShowS) -> WeightedMeanAndCriterionValues -> ShowS
showsFloat WeightedMean -> ShowS
fromDouble MkWeightedMeanAndCriterionValues {
		getWeightedMean :: WeightedMeanAndCriterionValues -> WeightedMean
getWeightedMean		= WeightedMean
weightedMean,
		getCriterionValues :: WeightedMeanAndCriterionValues -> [WeightedMean]
getCriterionValues	= [WeightedMean]
criterionValues
	} = [(String, ShowS)] -> ShowS
Text.ShowList.showsAssociationList' [
		(String
weightedMeanTag, WeightedMean -> ShowS
fromDouble (WeightedMean -> ShowS) -> WeightedMean -> ShowS
forall a b. (a -> b) -> a -> b
$! WeightedMean -> WeightedMean
forall a b. (Real a, Fractional b) => a -> b
realToFrac WeightedMean
weightedMean),
		(String
criterionValuesTag, (WeightedMean -> ShowS) -> [WeightedMean] -> ShowS
forall a. (a -> ShowS) -> [a] -> ShowS
Text.ShowList.showsFormattedList' (WeightedMean -> ShowS
fromDouble (WeightedMean -> ShowS)
-> (WeightedMean -> WeightedMean) -> WeightedMean -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WeightedMean -> WeightedMean
forall a b. (Real a, Fractional b) => a -> b
realToFrac) [WeightedMean]
criterionValues)
	 ]

-- | Constructor.
mkWeightedMeanAndCriterionValues
	:: Type.Mass.WeightedMean
	-> [Metric.CriterionValue.CriterionValue]
	-> WeightedMeanAndCriterionValues
mkWeightedMeanAndCriterionValues :: WeightedMean -> [WeightedMean] -> WeightedMeanAndCriterionValues
mkWeightedMeanAndCriterionValues	= WeightedMean -> [WeightedMean] -> WeightedMeanAndCriterionValues
MkWeightedMeanAndCriterionValues

{- |
	* Negate the /weightedMean/, but leave the criterion-values unaltered.

	* This can be used to assess the fitness of a position from the perspective of one's opponent.
-}
negateWeightedMean :: WeightedMeanAndCriterionValues -> WeightedMeanAndCriterionValues
negateWeightedMean :: WeightedMeanAndCriterionValues -> WeightedMeanAndCriterionValues
negateWeightedMean weightedMeanAndCriterionValues :: WeightedMeanAndCriterionValues
weightedMeanAndCriterionValues@MkWeightedMeanAndCriterionValues { getWeightedMean :: WeightedMeanAndCriterionValues -> WeightedMean
getWeightedMean = WeightedMean
weightedMean }	= WeightedMeanAndCriterionValues
weightedMeanAndCriterionValues { getWeightedMean :: WeightedMean
getWeightedMean = WeightedMean -> WeightedMean
forall a. Num a => a -> a
negate WeightedMean
weightedMean }

{- |
	* Calculates the /weighted mean/ of the specified /criterion-value/s using the corresponding /criterion-weight/s.

	* Also writes individual unweighted /criterionValue/s, to facilitate post-analysis;
	if the corresponding weight is @0@, evaluation of the criterion is avoided, for efficiency.

	* CAVEAT: if all weights are @0@, then the result is indeterminate.
-}
calculateWeightedMean :: [(Metric.CriterionValue.CriterionValue, Metric.CriterionWeight.CriterionWeight)] -> WeightedMeanAndCriterionValues
{-# INLINABLE calculateWeightedMean #-}
calculateWeightedMean :: [(WeightedMean, CriterionWeight)] -> WeightedMeanAndCriterionValues
calculateWeightedMean	= (WeightedMean -> [WeightedMean] -> WeightedMeanAndCriterionValues)
-> (WeightedMean, [WeightedMean]) -> WeightedMeanAndCriterionValues
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry WeightedMean -> [WeightedMean] -> WeightedMeanAndCriterionValues
mkWeightedMeanAndCriterionValues ((WeightedMean, [WeightedMean]) -> WeightedMeanAndCriterionValues)
-> ([(WeightedMean, CriterionWeight)]
    -> (WeightedMean, [WeightedMean]))
-> [(WeightedMean, CriterionWeight)]
-> WeightedMeanAndCriterionValues
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (
	[(WeightedMean, WeightedMean)] -> WeightedMean
forall (foldable :: * -> *) result value weight.
(Foldable foldable, Fractional result, Real value, Real weight) =>
foldable (value, weight) -> result
Factory.Math.Statistics.getWeightedMean ([(WeightedMean, WeightedMean)] -> WeightedMean)
-> ([(WeightedMean, WeightedMean)] -> [WeightedMean])
-> [(WeightedMean, WeightedMean)]
-> (WeightedMean, [WeightedMean])
forall (a :: * -> * -> *) b c c'.
Arrow a =>
a b c -> a b c' -> a b (c, c')
&&& ((WeightedMean, WeightedMean) -> WeightedMean)
-> [(WeightedMean, WeightedMean)] -> [WeightedMean]
forall a b. (a -> b) -> [a] -> [b]
map (WeightedMean -> WeightedMean
forall a b. (Real a, Fractional b) => a -> b
realToFrac (WeightedMean -> WeightedMean)
-> ((WeightedMean, WeightedMean) -> WeightedMean)
-> (WeightedMean, WeightedMean)
-> WeightedMean
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (WeightedMean, WeightedMean) -> WeightedMean
forall a b. (a, b) -> a
fst)
 )
#ifdef PARALLELISE
	([(WeightedMean, WeightedMean)] -> (WeightedMean, [WeightedMean]))
-> ([(WeightedMean, CriterionWeight)]
    -> [(WeightedMean, WeightedMean)])
-> [(WeightedMean, CriterionWeight)]
-> (WeightedMean, [WeightedMean])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Strategy [(WeightedMean, WeightedMean)]
-> [(WeightedMean, WeightedMean)] -> [(WeightedMean, WeightedMean)]
forall a. Strategy a -> a -> a
Control.Parallel.Strategies.withStrategy (
		Strategy (WeightedMean, WeightedMean)
-> Strategy [(WeightedMean, WeightedMean)]
forall a. Strategy a -> Strategy [a]
Control.Parallel.Strategies.parList (Strategy (WeightedMean, WeightedMean)
 -> Strategy [(WeightedMean, WeightedMean)])
-> Strategy (WeightedMean, WeightedMean)
-> Strategy [(WeightedMean, WeightedMean)]
forall a b. (a -> b) -> a -> b
$ Strategy WeightedMean
-> Strategy WeightedMean -> Strategy (WeightedMean, WeightedMean)
forall a b. Strategy a -> Strategy b -> Strategy (a, b)
Control.Parallel.Strategies.evalTuple2 Strategy WeightedMean
forall a. NFData a => Strategy a
Control.Parallel.Strategies.rdeepseq Strategy WeightedMean
forall a. Strategy a
Control.Parallel.Strategies.r0
	)
#endif
	([(WeightedMean, WeightedMean)] -> [(WeightedMean, WeightedMean)])
-> ([(WeightedMean, CriterionWeight)]
    -> [(WeightedMean, WeightedMean)])
-> [(WeightedMean, CriterionWeight)]
-> [(WeightedMean, WeightedMean)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((WeightedMean, CriterionWeight) -> (WeightedMean, WeightedMean))
-> [(WeightedMean, CriterionWeight)]
-> [(WeightedMean, WeightedMean)]
forall a b. (a -> b) -> [a] -> [b]
map (
		\(WeightedMean
criterionValue, CriterionWeight
criterionWeight) -> (WeightedMean -> WeightedMean
forall a b. (Real a, Fractional b) => a -> b
realToFrac WeightedMean
criterionValue, CriterionWeight -> WeightedMean
forall a b. (Real a, Fractional b) => a -> b
realToFrac CriterionWeight
criterionWeight) :: (Type.Mass.CriterionValue, Type.Mass.CriterionWeight)
	) ([(WeightedMean, CriterionWeight)]
 -> [(WeightedMean, WeightedMean)])
-> ([(WeightedMean, CriterionWeight)]
    -> [(WeightedMean, CriterionWeight)])
-> [(WeightedMean, CriterionWeight)]
-> [(WeightedMean, WeightedMean)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((WeightedMean, CriterionWeight) -> Bool)
-> [(WeightedMean, CriterionWeight)]
-> [(WeightedMean, CriterionWeight)]
forall a. (a -> Bool) -> [a] -> [a]
filter (
		(CriterionWeight -> CriterionWeight -> Bool
forall a. Eq a => a -> a -> Bool
/= CriterionWeight
0) (CriterionWeight -> Bool)
-> ((WeightedMean, CriterionWeight) -> CriterionWeight)
-> (WeightedMean, CriterionWeight)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (WeightedMean, CriterionWeight) -> CriterionWeight
forall a b. (a, b) -> b
snd {-criterion-weight-}	-- Avoid unnecessaily evaluating criterion-values.
	)