| {-# LANGUAGE DeriveGeneric #-} |
| {-# LANGUAGE DuplicateRecordFields #-} |
|
|
| module ConsensusTypes where |
|
|
| import qualified Data.Map as Map |
| import Data.Map (Map) |
| import GHC.Generics (Generic) |
| import Data.ByteString (ByteString) |
| import qualified Data.ByteString as BS |
| import Data.Hashable |
| import Data.List (sortBy, groupBy) |
| import Data.Ord (comparing) |
|
|
| |
| |
| |
|
|
| |
| newtype AgentId = AgentId Int |
| deriving (Eq, Ord, Show, Generic) |
| |
| instance Hashable AgentId |
| |
| |
| newtype ObservationId = ObservationId Int |
| deriving (Eq, Ord, Show, Generic) |
| |
| instance Hashable ObservationId |
| |
| |
| newtype RegionId = RegionId Int |
| deriving (Eq, Ord, Show, Generic) |
| |
| instance Hashable RegionId |
| |
| |
| data Vector = Vector { vx :: Double, vy :: Double, vz :: Double } |
| deriving (Eq, Show, Generic) |
| |
| instance Hashable Vector |
| |
| |
| data RegionType = RegionType |
| { rtName :: String |
| , rtDensity :: Double |
| , rtCurvature :: Double |
| , rtAnomalyLevel :: Double |
| } deriving (Eq, Show, Generic, Ord) |
| |
| instance Hashable RegionType |
| |
| |
| data Measurement = Measurement |
| { measTimestamp :: Int |
| , measValues :: Map String Double |
| } deriving (Eq, Show, Generic) |
| |
| |
| data Observation = Observation |
| { obsId :: ObservationId |
| , agentId :: AgentId |
| , timestamp :: Int |
| , coordinates :: Vector |
| , measurements :: Map String Double |
| , confidence :: Double |
| , regionType :: Maybe RegionType |
| , wormSealed :: Bool |
| , sealRound :: Maybe Int |
| } deriving (Eq, Show, Generic) |
| |
| |
| data Vote = Vote |
| { voterId :: AgentId |
| , votedObsId :: ObservationId |
| , agreement :: Double |
| , voteRound :: Int |
| , voteTimestamp :: Int |
| } deriving (Eq, Show, Generic) |
| |
| |
| data VoteRound = VoteRound |
| { roundNum :: Int |
| , roundTimestamp :: Int |
| , roundVotes :: [Vote] |
| , roundObservations :: [Observation] |
| } deriving (Eq, Show, Generic) |
| |
| |
| data Anomaly = Anomaly |
| { anomalyId :: Int |
| , anomalyLocation :: Vector |
| , anomalySeverity :: Double |
| , anomalyRound :: Int |
| , anomalyAgents :: [AgentId] |
| , anomalyConfidence :: Double |
| } deriving (Eq, Show, Generic) |
| |
| |
| data Conflict = Conflict |
| { conflictObsId :: ObservationId |
| , conflictRegionId :: RegionId |
| , conflictAgents :: [AgentId] |
| , conflictMeasureDiff :: Double |
| , conflictResolved :: Bool |
| } deriving (Eq, Show, Generic) |
| |
| |
| data WorldModel = WorldModel |
| { regionTypes :: Map RegionId RegionType |
| , agentPositions :: Map AgentId Vector |
| , anomalies :: [Anomaly] |
| , frontierRegions :: [RegionId] |
| , modelConfidence :: Double |
| , modelGeneration :: Int |
| } deriving (Eq, Show, Generic) |
| |
| |
| data ConsensusState = ConsensusState |
| { observations :: [Observation] |
| , votes :: [Vote] |
| , voteRounds :: [VoteRound] |
| , worldModel :: WorldModel |
| , confidence :: Double |
| , conflicts :: [Conflict] |
| , wormSealLog :: [SealRecord] |
| , generation :: Int |
| } deriving (Eq, Show, Generic) |
| |
| |
| data SealRecord = SealRecord |
| { sealRound :: Int |
| , sealTimestamp :: Int |
| , sealedObsCount :: Int |
| , sealedVoteCount :: Int |
| , sealHash :: ByteString |
| , sealAgents :: [AgentId] |
| } deriving (Eq, Show, Generic) |
| |
| |
| |
| |
| |
| |
| emptyWorldModel :: WorldModel |
| emptyWorldModel = WorldModel |
| { regionTypes = Map.empty |
| , agentPositions = Map.empty |
| , anomalies = [] |
| , frontierRegions = [] |
| , modelConfidence = 0.0 |
| , modelGeneration = 0 |
| } |
| |
| |
| emptyConsensusState :: ConsensusState |
| emptyConsensusState = ConsensusState |
| { observations = [] |
| , votes = [] |
| , voteRounds = [] |
| , worldModel = emptyWorldModel |
| , confidence = 0.0 |
| , conflicts = [] |
| , wormSealLog = [] |
| , generation = 0 |
| } |
| |
| |
| makeObservation :: ObservationId -> AgentId -> Int -> Vector |
| -> Map String Double -> Double -> Maybe RegionType |
| -> Observation |
| makeObservation oid aid ts pos meas conf regType = Observation |
| { obsId = oid |
| , agentId = aid |
| , timestamp = ts |
| , coordinates = pos |
| , measurements = meas |
| , confidence = max 0.0 (min 1.0 conf) |
| , regionType = regType |
| , wormSealed = False |
| , sealRound = Nothing |
| } |
| |
| |
| makeVote :: AgentId -> ObservationId -> Double -> Int -> Int -> Vote |
| makeVote voter obsId agrmt round ts = Vote |
| { voterId = voter |
| , votedObsId = obsId |
| , agreement = max (-1.0) (min 1.0 agrmt) |
| , voteRound = round |
| , voteTimestamp = ts |
| } |
| |
| |
| |
| |
| |
| |
| vectorDistance :: Vector -> Vector -> Double |
| vectorDistance v1 v2 = sqrt ((vx v1 - vx v2)^2 + (vy v1 - vy v2)^2 + (vz v1 - vz v2)^2) |
| |
| |
| averageDouble :: [Double] -> Double |
| averageDouble [] = 0.0 |
| averageDouble xs = sum xs / fromIntegral (length xs) |
| |
| |
| votesForObservation :: [Vote] -> ObservationId -> [Vote] |
| votesForObservation vs obsId = filter (\v -> votedObsId v == obsId) vs |
| |
| |
| observationsByAgent :: [Observation] -> AgentId -> [Observation] |
| observationsByAgent obs aid = filter (\o -> agentId o == aid) obs |
| |
| |
| consensusScore :: [Vote] -> Double |
| consensusScore [] = 0.0 |
| consensusScore vs = averageDouble (map agreement vs) |
| |
| |
| hasConsensus :: [Vote] -> Bool |
| hasConsensus vs |
| | null vs = False |
| | otherwise = let score = consensusScore vs |
| positiveVotes = fromIntegral (length (filter (\v -> agreement v > 0.5) vs)) :: Double |
| ratioVotes = positiveVotes / fromIntegral (length vs) |
| in score > 0.33 && ratioVotes >= 0.66 |
| |
| |
| measurementDifference :: Map String Double -> Map String Double -> Double |
| measurementDifference m1 m2 = |
| let allKeys = Map.keys m1 ++ Map.keys m2 |
| diffs = map (\k -> let v1 = Map.findWithDefault 0.0 k m1 |
| v2 = Map.findWithDefault 0.0 k m2 |
| in abs (v1 - v2)) allKeys |
| in if null diffs then 0.0 else averageDouble diffs |
| |
| |
| groupByRegion :: [Observation] -> Map RegionId [Observation] |
| groupByRegion obs = |
| let grouped = groupBy (\o1 o2 -> regionType o1 == regionType o2) |
| (sortBy (comparing regionType) obs) |
| in Map.fromList [(RegionId i, g) | (i, g) <- zip [0..] grouped] |
| |
| |
| extractAgentIds :: ConsensusState -> [AgentId] |
| extractAgentIds state = |
| let fromObs = map agentId (observations state) |
| fromVotes = map voterId (votes state) |
| in nub (fromObs ++ fromVotes) |
| where |
| nub [] = [] |
| nub (x:xs) = x : nub (filter (/= x) xs) |
|
|