/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of PeerfactSim.KOM.
*
* PeerfactSim.KOM 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
* any later version.
*
* PeerfactSim.KOM 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 PeerfactSim.KOM. If not, see .
*
*/
package de.tud.kom.p2psim.impl.topology.movement.modularosm.groups.groupforming;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import de.tud.kom.p2psim.api.scenario.ConfigurationException;
import de.tud.kom.p2psim.api.topology.movement.SimLocationActuator;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.ISocialGroupMovementAnalyzer;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.SocialGroupMovementModel;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.attraction.BasicAttractionPoint;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.attraction.IAttractionProvider;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.attraction.hostcount.HostAtAttractionPointCounter;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.groups.SocialMovementGroup;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.groups.MovementGroupContainer;
import de.tud.kom.p2psim.impl.topology.util.PositionVector;
import de.tud.kom.p2psim.impl.util.Tuple;
import de.tudarmstadt.maki.simonstrator.api.Event;
import de.tudarmstadt.maki.simonstrator.api.EventHandler;
import de.tudarmstadt.maki.simonstrator.api.Monitor;
import de.tudarmstadt.maki.simonstrator.api.Randoms;
import de.tudarmstadt.maki.simonstrator.api.Time;
import de.tudarmstadt.maki.simonstrator.api.common.graph.INodeID;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.IAttractionPoint;
import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor;
/**
* Abstract class implementation for implementing GroupFormingStrategies.
*
* @author Marcel Verst, Julian Zobel
* @version 1.1, 30.01.2020
*/
public abstract class AbstractGroupForming implements IGroupFormingBehavior {
protected Random rand = Randoms.getRandom(AbstractGroupForming.class);
protected SocialGroupMovementModel movementModel;
protected MovementGroupContainer groupCon;
// Holds time information of nodes for the groups (stay duration, last update)
protected Map> stayDuration;
// GROUP VARIABLES FROM CONFIGS
protected boolean enableGroups;
protected int minGroupSize;
protected int maxGroupSize;
protected double probabilityToJoin;
protected int maxNumberOfGroups;
protected long groupRejoinWaitTime;
protected long groupFormationSetupDelay;
private long groupFormationDelay;
public static AbstractGroupForming instance;
@XMLConfigurableConstructor({"enableGroups", "maxNumberOfGroups", "minGroupSize", "maxGroupSize", "probabilityToJoin", "groupFormationSetupDelay", "groupFormationDelay","groupRejoinWaitTime"})
public AbstractGroupForming(boolean enableGroups, int maxNumberOfGroups, int minGroupSize, int maxGroupSize, double probabilityToJoin, long groupFormationSetupDelay, long groupFormationDelay, long groupRejoinWaitTime) {
instance = this;
this.enableGroups = enableGroups;
if(enableGroups) {
if (probabilityToJoin < 0 || probabilityToJoin > 1.0) {
throw new ConfigurationException(
"GroupForming: probabilityToJoin must be between 0.0 and 1.0!");
}
if (maxNumberOfGroups <= 0) {
throw new ConfigurationException(
"GroupForming: maxNumberOfGroups must be >= 1!");
}
if (minGroupSize < 0) {
throw new ConfigurationException(
"GroupForming: minGroupSize must be >= 1!");
}
if (maxGroupSize < minGroupSize) {
throw new ConfigurationException(
"GroupForming: maxGroupSize cannot be bigger than minGroupSize!");
}
if (groupFormationSetupDelay <= 0) {
throw new ConfigurationException(
"GroupForming: groupFormationSetupDelay must be > 0!");
}
if (groupFormationDelay < -1) {
throw new ConfigurationException(
"GroupForming: groupFormationDelay must be >= -1!");
}
if (groupRejoinWaitTime < 0) {
throw new ConfigurationException(
"GroupForming: groupRejoinWaitTime must be >= 1!");
}
}
this.maxNumberOfGroups = maxNumberOfGroups;
this.minGroupSize = minGroupSize;
this.maxGroupSize = maxGroupSize;
this.probabilityToJoin = probabilityToJoin;
this.groupFormationSetupDelay = groupFormationSetupDelay;
this.groupFormationDelay = groupFormationDelay;
this.groupRejoinWaitTime = groupRejoinWaitTime;
}
@Override
public void initialize(SocialGroupMovementModel movementModel) {
this.movementModel = movementModel;
groupCon = MovementGroupContainer.getInstance();
stayDuration = new LinkedHashMap>();
for(SimLocationActuator host : movementModel.getAllLocationActuators()) {
stayDuration.put(host.getHost().getId(), new Tuple(0L, Time.getCurrentTime()));
}
if(!enableGroups) {
return;
}
for(int g = 0; g < maxNumberOfGroups; g++) {
long delay = Math.max(Time.MINUTE, (long) ((groupFormationSetupDelay + rand.nextGaussian() * groupFormationSetupDelay )));
Monitor.log(this.getClass(), Monitor.Level.INFO, "Group: "+g+" Initial Group Formation Time", Time.getFormattedTime(delay));
Event.scheduleWithDelay(delay, new EventHandler() {
@Override
public void eventOccurred(Object content, int type) {
assembleGroup();
}
}, null, 0);
}
}
/**
* Returns a time delay between removal and creation of a group. A random part of up to half of the
* stated formation delay is added to/substracted from the formation delay.
*
* @return
*/
protected long rndGroupFormationDelay() {
return (long) ((rand.nextDouble() + 0.5) * groupFormationDelay);
}
/**
* Returns a random integer value between the minimum and maximumn group size boundaries
* with an additional upper boundary.
*
* @return
*/
protected int rndGroupSize(int max) {
return Math.min(rndGroupSize(), max);
}
/**
* Returns a random integer value between the minimum and maximumn group size boundaries.
*
* @return
*/
protected int rndGroupSize() {
if(maxGroupSize == minGroupSize)
return minGroupSize;
else {
// int groupsize = (int) Math.max(1, rand.nextGaussian() * 0.93 + 2.76);
int groupsize = rand.nextInt(maxGroupSize - minGroupSize + 1) + minGroupSize;
//System.out.println("[AbstractGroupForming] Group Size: " + groupsize + " ("+minGroupSize+"/"+maxGroupSize+")");
return groupsize;
}
//return rand.nextInt(maxGroupSize - minGroupSize) + minGroupSize;
}
/**
* Creates a group with the given set of members.
*
* @param Set The members which want to form a group.
*/
protected void createGroup(LinkedHashSet members) {
SocialMovementGroup group = new SocialMovementGroup(members);
// force a new attraction point assignment on the leader
IAttractionPoint currentDestination = movementModel.getAttractionAssignmentStrategy().getAssignment(group.getLeader());
movementModel.getAttractionAssignmentStrategy().addComponent(group.getLeader());
IAttractionPoint targetDestination = movementModel.getAttractionAssignmentStrategy().getAssignment(group.getLeader());
// Add Offset to group destination
PositionVector destination = movementModel.addOffset(new PositionVector(targetDestination),
targetDestination.getRadius() / 3);
group.setDestination(destination);
setGroupMeetingPoint(group);
for(SimLocationActuator member : group.getMembers()) {
// only set the attraction point for non-leaders, as the leader was already assigned an attraction point above
if(group.getLeader() != member) {
member.setTargetAttractionPoint(targetDestination);
}
// remove any left-over entries
if(groupCon.hostWasGroupMember(member)) {
groupCon.removeLeftGroupAtTimeEntry(member);
}
}
groupCon.addGroup(group);
// Inform analyzer of new group
if(Monitor.hasAnalyzer(ISocialGroupMovementAnalyzer.class)) {
Monitor.getOrNull(ISocialGroupMovementAnalyzer.class).onGroupForming(group, currentDestination, targetDestination);
}
}
/**
* Removes a group.
*
* @param SocialMovementGroup The group to be removed.
*
*/
@Override
public void removeGroup(SocialMovementGroup group) {
// Inform analyzer of group removal
if(Monitor.hasAnalyzer(ISocialGroupMovementAnalyzer.class)) {
Monitor.getOrNull(ISocialGroupMovementAnalyzer.class).onGroupDisbanding(group);
}
// If this group is removed, reassign the group target attraction point as new target attraction point
// for each former group member (prevents errors when routing was not reset correctly)
if(group.getGroupSize() > 0 || group.getLeader() != null) {
IAttractionPoint currentAP = group.getLeader().getCurrentTargetAttractionPoint();
for (SimLocationActuator host : group.getMembers()) {
host.setTargetAttractionPoint(currentAP);
}
}
groupCon.removeGroup(group);
long delay = 0;
if(groupFormationDelay == -1) {
if(group.getLeader() == null) {
delay = movementModel.getAttractionAssignmentStrategy().getPauseTime(null);
}
else {
delay = movementModel.getAttractionAssignmentStrategy().getPauseTime(group.getLeader().getCurrentTargetAttractionPoint());
}
}
else {
delay = rndGroupFormationDelay();
}
Event.scheduleWithDelay(delay, new EventHandler() {
@Override
public void eventOccurred(Object content, int type) {
assembleGroup();
}
}, null, 0);
}
public static void nodeready() {
instance.assembleGroup();
}
protected abstract void assembleGroup();
/**
* Returns the first AttractionPoint which contains the host with the greatest stayDuration of all hosts located in AttractionPoints.
*
* @return AttractionPoint
*
*/
protected IAttractionPoint getAttractionPointWithOldestHost() {
IAttractionPoint result = null;
long maxDuration = 0;
for(IAttractionPoint ap : movementModel.getAttractionPoints()) {
for(SimLocationActuator host : HostAtAttractionPointCounter.getHostsOfAttractionPoint(ap, movementModel.getAllLocationActuators())) {
INodeID id = host.getHost().getId();
long duration = stayDuration.get(id).getA();
if(duration > maxDuration) {
result = ap;
maxDuration = duration;
}
}
}
return result;
}
protected IAttractionPoint getAttractionPointWithMostHosts() {
IAttractionPoint apCandidate = null;
int size = 0;
for(IAttractionPoint ap : movementModel.getAttractionPoints()) {
int numberOfHostsInAP = HostAtAttractionPointCounter.getHostCountOfAttractionPoint(ap, movementModel.getAllLocationActuators());
if(numberOfHostsInAP > size) {
apCandidate = ap;
size = numberOfHostsInAP;
}
}
return apCandidate;
}
/**
* Checks if a host wants to join a group or not based on a given probability.
* Returns true if willing to join, false else.
*
* @return boolean
*/
protected boolean wantsToJoin() {
return (rand.nextDouble() <= probabilityToJoin);
}
/**
* Checks if a host has waited long enough before he is allowed to join groups again.
* Returns true, if the host has waited long enough, false otherwise.
*
* @param SimLocationActuator The host to be checked.
* @return boolean
*/
protected boolean groupRejoinTimerExpired(SimLocationActuator host) {
return groupCon.getTimeSinceHostLeftLastGroup(host) >= groupRejoinWaitTime;
}
/**
* Returns true, if a host has been in a group before at least once. False else.
*
* @param SimLocacationActuator The host to be checked.
* @return Boolean
*/
protected boolean beenInGroup(SimLocationActuator host) {
return groupCon.hostWasGroupMember(host);
}
/*
* =====================================================================================================
* === MEETING POINT FUNCTIONS
* =====================================================================================================
*/
/**
* Sets the meeting point of a group after the group has been created. Before walking towards the destination,
* the group members first have to meet at the meeting point.
*
* @param SocialMovementGroup The group to set the meeting point.
*/
protected void setGroupMeetingPoint(SocialMovementGroup group) {
PositionVector leaderPos = group.getLeader().getRealPosition();
BasicAttractionPoint meetingPoint =
new BasicAttractionPoint("Group Movement Meeting Point of Leader #"+group.getLeader().getHost().getId(), leaderPos);
group.setMeetingPoint(meetingPoint);
}
@Override
public int getMinGroupSize() {
return minGroupSize;
}
@Override
public int getMaxGroupSize() {
return maxGroupSize;
}
}