| {-# LANGUAGE DeriveGeneric #-} |
|
|
| module ConsensusVoting |
| ( castVote |
| , consensusRound |
| , detectConflict |
| , resolveConflict |
| , syncAgentStates |
| , multiAgentVote |
| , getConsensusObservations |
| , aggregateVotesForObservation |
| , updateWorldModelWithConsensus |
| , anomalyScoring |
| , conflictThreshold |
| ) where |
|
|
| import ConsensusTypes |
| import qualified Data.Map as Map |
| import Data.Map (Map) |
| import Data.List (sortBy, groupBy, maximumBy, nub) |
| import Data.Ord (comparing, Down(..)) |
| import GHC.Generics (Generic) |
|
|
| |
| |
| |
|
|
| |
| conflictThreshold :: Double |
| conflictThreshold = 0.25 |
|
|
| |
| minVotesForConsensus :: Int |
| minVotesForConsensus = 2 |
|
|
| |
| consensusThreshold :: Double |
| consensusThreshold = 0.66 |
|
|
| |
| anomalySeverityThreshold :: Double |
| anomalySeverityThreshold = 0.5 |
|
|
| |
| |
| |
|
|
| |
| castVote :: ConsensusState -> AgentId -> ObservationId -> Double -> Int -> ConsensusState |
| castVote state voterId obsId agreeScore round = |
| let vote = makeVote voterId obsId agreeScore round 0 |
| newVotes = votes state ++ [vote] |
| in state { votes = newVotes } |
|
|
| |
| aggregateVotesForObservation :: ConsensusState -> ObservationId -> (Double, Int) |
| aggregateVotesForObservation state obsId = |
| let obsVotes = votesForObservation (votes state) obsId |
| voteCount = length obsVotes |
| in if voteCount < minVotesForConsensus |
| then (0.0, 0) |
| else (consensusScore obsVotes, voteCount) |
|
|
| |
| getConsensusObservations :: ConsensusState -> [Observation] |
| getConsensusObservations state = |
| let allObs = observations state |
| in filter (\obs -> let (score, count) = aggregateVotesForObservation state (obsId obs) |
| in count >= minVotesForConsensus && score > 0.33) allObs |
|
|
| |
| |
| |
|
|
| |
| consensusRound :: ConsensusState -> [AgentId] -> Int -> ConsensusState |
| consensusRound state participatingAgents roundNum = |
| let |
| obsGroups = groupObservationsById state |
|
|
| |
| consensusResults = map (\(obsId, obs) -> |
| let (score, voteCount) = aggregateVotesForObservation state obsId |
| vots = votesForObservation (votes state) obsId |
| in (obsId, obs, score, voteCount, vots) |
| ) obsGroups |
|
|
| |
| (consensusObs, conflictedObs) = partitionConsensus consensusResults |
|
|
| |
| detectedConflicts = detectConflictsBatch state conflictedObs |
|
|
| |
| resolvedObs = map (\(o, c) -> resolveConflictVia state o c) (zip conflictedObs detectedConflicts) |
|
|
| |
| finalConsensusObs = consensusObs ++ resolvedObs |
|
|
| |
| updatedModel = updateWorldModelWithConsensus (worldModel state) finalConsensusObs state |
|
|
| |
| globalConf = if null finalConsensusObs |
| then confidence state |
| else averageDouble (map (\(_, _, s, _, _) -> s) consensusResults) |
|
|
| |
| voteRound = VoteRound roundNum 0 (votes state) (observations state) |
|
|
| |
| newGen = generation state + 1 |
|
|
| in state |
| { worldModel = updatedModel |
| , confidence = globalConf |
| , voteRounds = voteRounds state ++ [voteRound] |
| , conflicts = conflicts state ++ detectedConflicts |
| , generation = newGen |
| } |
|
|
| |
| groupObservationsById :: ConsensusState -> [(ObservationId, [Observation])] |
| groupObservationsById state = |
| let grouped = groupBy (\o1 o2 -> obsId o1 == obsId o2) |
| (sortBy (comparing obsId) (observations state)) |
| in map (\g -> (obsId (head g), g)) grouped |
|
|
| |
| partitionConsensus :: [(ObservationId, [Observation], Double, Int, [Vote])] |
| -> ([(ObservationId, [Observation], Double, Int, [Vote])], |
| [(ObservationId, [Observation], Double, Int, [Vote])]) |
| partitionConsensus results = |
| let isConflicted (_, _, score, count, _) = |
| count >= minVotesForConsensus && score <= 0.33 |
| in (filter (not . isConflicted) results, filter isConflicted results) |
|
|
| |
| |
| |
|
|
| |
| detectConflictsBatch :: ConsensusState |
| -> [(ObservationId, [Observation], Double, Int, [Vote])] |
| -> [Conflict] |
| detectConflictsBatch state conflictedGroups = |
| map (\(obsId, obs, _, _, vots) -> |
| let agents = map agentId obs |
| measDiff = if length obs >= 2 |
| then |
| let m1 = measurements (head obs) |
| m2 = measurements (obs !! 1) |
| in measurementDifference m1 m2 |
| else 0.0 |
| in Conflict obsId (RegionId 0) agents measDiff False |
| ) conflictedGroups |
|
|
| |
| detectConflict :: Observation -> Observation -> Bool |
| detectConflict obs1 obs2 = |
| let dist = measurementDifference (measurements obs1) (measurements obs2) |
| in dist > conflictThreshold && vectorDistance (coordinates obs1) (coordinates obs2) < 0.1 |
|
|
| |
| resolveConflict :: ConsensusState -> Conflict -> Observation |
| resolveConflict state conflict = |
| case filter (\o -> obsId o == conflictObsId conflict) (observations state) of |
| [] -> error "Conflict references non-existent observation" |
| (o:_) -> o |
|
|
| |
| resolveConflictVia :: ConsensusState -> (ObservationId, [Observation], Double, Int, [Vote]) |
| -> Conflict -> Observation |
| resolveConflictVia state (obsId, obs, _, _, votes) conflict = |
| if null obs then error "resolveConflictVia: no observations in conflict group" |
| else if null votes |
| then head obs |
| else |
| |
| let obsWithScores = [(o, averageDouble [agreement v | v <- votes, votedObsId v == obsId o]) | o <- obs] |
| in if null obsWithScores |
| then head obs |
| else let (winningObs, _) = maximumBy (comparing snd) obsWithScores |
| in winningObs |
|
|
| |
| |
| |
|
|
| |
| multiAgentVote :: ConsensusState -> Observation -> [AgentId] -> Int -> ConsensusState |
| multiAgentVote state newObs voterIds round = |
| let |
| obsIdToVoteOn = obsId newObs |
|
|
| |
| votes = concatMap (\voterId -> |
| let otherObs = observationsByAgent (observations state) voterId |
| |
| similarObs = filter (\o -> case (regionType newObs, regionType o) of |
| (Just r1, Just r2) -> r1 == r2 |
| _ -> False) otherObs |
| in if null similarObs |
| then [makeVote voterId obsIdToVoteOn 0.0 round 0] |
| else let avgDiff = averageDouble [measurementDifference (measurements newObs) (measurements o) | o <- similarObs] |
| agreeScore = 1.0 - min 1.0 (avgDiff / 0.5) |
| in [makeVote voterId obsIdToVoteOn agreeScore round 0] |
| ) voterIds |
|
|
| newVotes = votes state ++ votes |
| in state { votes = newVotes } |
|
|
| |
| |
| |
|
|
| |
| updateWorldModelWithConsensus :: WorldModel -> [Observation] -> ConsensusState -> WorldModel |
| updateWorldModelWithConsensus model consensusObs state = |
| let |
| regionsFromObs = [(RegionId i, regionType o) | (i, o) <- zip [0..] consensusObs, regionType o /= Nothing] |
| newRegionMap = Map.fromList [(rId, rt) | (rId, Just rt) <- regionsFromObs] |
|
|
| |
| newAgentPositions = Map.fromList [(agentId o, coordinates o) | o <- consensusObs] |
|
|
| |
| detectedAnomalies = anomalyScoring state consensusObs |
|
|
| |
| frontierIds = [RegionId i | (i, _) <- zip [0..] consensusObs, any (\a -> anomalySeverity a > 0.6) detectedAnomalies] |
|
|
| |
| obsConfidenceValues = map (\o -> o.confidence) consensusObs |
|
|
| in WorldModel |
| { regionTypes = Map.union newRegionMap (regionTypes model) |
| , agentPositions = Map.union newAgentPositions (agentPositions model) |
| , anomalies = anomalies model ++ detectedAnomalies |
| , frontierRegions = nub (frontierRegions model ++ frontierIds) |
| , modelConfidence = averageDouble obsConfidenceValues |
| , modelGeneration = modelGeneration model + 1 |
| } |
|
|
| |
| |
| |
|
|
| |
| anomalyScoring :: ConsensusState -> [Observation] -> [Anomaly] |
| anomalyScoring state consensusObs = |
| let |
| highVarianceObs = filter (\o -> confidence o < 0.5) consensusObs |
|
|
| |
| locGroups = groupBy (\o1 o2 -> vectorDistance (coordinates o1) (coordinates o2) < 0.05) |
| (sortBy (comparing coordinates) highVarianceObs) |
|
|
| |
| anomalies = concatMap (\group -> |
| if length group > 0 |
| then let avgLoc = Vector |
| (averageDouble (map (\o -> vx (coordinates o)) group)) |
| (averageDouble (map (\o -> vy (coordinates o)) group)) |
| (averageDouble (map (\o -> vz (coordinates o)) group)) |
| agentSet = map agentId group |
| severity = 1.0 - averageDouble (map confidence group) |
| in [Anomaly (length (anomalies state)) avgLoc severity 0 agentSet 0.5] |
| else [] |
| ) locGroups |
| in anomalies |
|
|
| |
| |
| |
|
|
| |
| syncAgentStates :: ConsensusState -> ConsensusState |
| syncAgentStates state = |
| |
| |
| |
| state |
|
|
| |
| |
| |
|
|
| |
| sealObservation :: Observation -> Int -> Observation |
| sealObservation obs round = obs { wormSealed = True, sealRound = Just round } |
|
|
| |
| sealObservationsInRound :: ConsensusState -> Int -> ConsensusState |
| sealObservationsInRound state round = |
| let sealedObs = map (\o -> sealObservation o round) (observations state) |
| in state { observations = sealedObs } |
|
|