import simpy
import random
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
import math
import copy
def calculate_distance(node_1, node_2):
'''
calculates distance between two nodes.
inputs: node1, node2
output: distance
'''
position1 = node_positions[node_1]
position2 = node_positions[node_2]
# Calculate the Euclidean distance between the two positions
distance = math.sqrt((position1[0] - position2[0])**2 + (position1[1] - position2[1])**2)
return distance
def calculate_ellipse_points(cx, cy, a, b, num_points): # (center_x_coord, center_y_coord, major axis, minor axis, num of points)
# determine the points along the satellites' elliptical path
points = []
angle_increment = 2 * math.pi / num_points
for i in range(num_points):
theta = i * angle_increment
x = cx + a * math.cos(theta)
y = cy + b * math.sin(theta)
points.append((x, y))
return points
def update_links(env):
global communication_range, gs_capacity, visibility_tables
# Iterate over all pairs of satellite and terrestrial nodes (sat-gs links)
for node1 in G.nodes():
if node1.startswith('Satellite'):
for node2 in nodes:
if (node2 != node1) and (node2.startswith('Node')):
distance = calculate_distance(node1, node2)
# print("distance bw",node1," and ",node2," : ",distance)
# Check if the distance is within the communication range
if distance <= communication_range:
# If the edge does not exist, add it
if not G.has_edge(node1, node2):
# print("adding a ground-air link")
G.add_edge(node1, node2)
G.edges[node1, node2]['capacity'] = simpy.Container(env, init=1000, capacity=1000)
else:
# If the edge exists, remove it
if G.has_edge(node1, node2):
G.remove_edge(node1, node2)
def move_satellites(graph):
global satellite_nodes, satellite_path, current_satellite_positions, satellite_positions, node_positions
for satellite in satellite_nodes:
current_pos = graph.nodes[satellite]['pos']
#print('current pos of ',satellite,' is ',current_pos)
next_pos = satellite_path[(satellite_path.index(current_pos) + 1) %len(satellite_path)] # next position in orbit
#print('next pos of ',satellite,' is ',next_pos)
current_satellite_positions[satellite] = next_pos
graph.nodes[satellite]['pos'] = next_pos # Update the position of the node
satellite_positions = list(current_satellite_positions.values())
node_positions = {**gateway_positions, **current_satellite_positions}
update_links(env) #tear down old ground to air links, and make new ones, as needed (done every time satellites move)
def simulate_satellite_motion(env, time_step, topology_change_event):
global G, num_of_iter
# while True:
for i in range(num_of_iter):
# print(time_step)
# Wait for the predetermined interval before changing the topology
yield env.timeout(time_step)
# Perform the topology update
move_satellites(G)
# Signal that the topology has changed
topology_change_event.succeed()
topology_change_event = env.event()
print('topology has changed at time'+str(env.now))
def store_network_changes():
global G, num_of_iter, topologies
topologies.append(G) # storing original topology
# Create a deep copy of the original topology
og_topology_copy = copy.deepcopy(G)
for i in range(num_of_iter):
move_satellites(og_topology_copy)
topologies.append(og_topology_copy)
print(topologies)
def generate_traffic_requests(env, graph, request_store, runtime):
global traffic_requests
id = 0
while True:
# for i in range(5):
# Generate a new request at random intervals following a Poisson distribution
# interval = np.random.exponential(scale=60) # Scale parameter of 60 seconds (mean interval)
interval = random.randint(30,300)
# interval = 30
yield env.timeout(interval)
source = random.choice(list(graph.nodes))
dest = random.choice(list(graph.nodes))
while source == dest:
dest = random.choice(list(graph.nodes))
req = (source, dest)
required_rate = random.randint(5, 800)
# required_rate = 500
time_req = random.randint(60, 480) # Random time requirement between 1 min and 8 minid+=1
# time_req = (i+1)*100
while time_req > (runtime - env.now):
time_req = random.randint(60, 480) # make sure request duration doesn't go past simulation runtime(for requests generated
# later in the simulation)
id+=1
# requests = {}
# with threading.Lock():
# requests[req] = {'required_rate': required_rate, 'time_req': time_req, 'new_request':True}
# traffic_requests.update(requests)
# Signal that a new request is generated
request_store.put({'id':id, 'req': req, 'required_rate': required_rate, 'time_req': time_req})
# request_event = env.event()
print('request GENERATED at time '+str(env.now) + ', requesting network_bandwidths for '+str(time_req)+' sec(id:'+str(id)+')')
# yield from embed_request(env, graph, req, required_rate, time_req, time_step, id)
# yield from embed_request(env, graph, req, required_rate, time_req, time_step, id)
def embed_request(env, graph, request, req_rate, req_time, time_step, id):
global request_status, count_of_reqs_embedded
# Rest of the code for handling the request remains the same
source, destination = request
# Find all simple paths between the source and destination nodes
paths = nx.all_simple_paths(graph, source, destination)
# Sort the paths by length (shortest to longest)
sorted_paths = sorted(paths, key=len)
remaining_capacity_in_network = 0
for edge in list(graph.edges):
remaining_capacity_in_network += graph.edges[edge]['capacity'].level
print('time: '+str(env.now)+'remaining_capacity_in_network'': '+ str(remaining_capacity_in_network))
# Iterate over the sorted paths
for path in sorted_paths:
# Check if the path has sufficient capacity
# for e in zip(path, path[1:]):
# print(e)
print('req_rate: ', req_rate)
if all(graph.edges[e]['capacity'].level >= req_rate for e in zip(path, path[1:])):
# Check if the path will exist for the duration of the request
if path_viable(env, path, source, destination, req_time, time_step):
count_of_reqs_embedded += 1
# with network_bandwidth.request() as req:
# Deduct the capacity from the edges in the path
for e in zip(path, path[1:]):
graph.edges[e]['capacity'].get(req_rate)
remaining_capacity_in_network = 0
for edge in list(graph.edges):
remaining_capacity_in_network += graph.edges[edge]['capacity'].level
# yield req
print('- - - - - - - -- - - - - - - - - - - - - - -- - - - - - - -path just after yield req: ',path,'id: ', id)
request_status[request] = True
print('request '+ ', requesting resources for '+str(req_time)+' sec successfully embedded at time '+str(env.now)+'(id:'+str(id)+')')
print('time: '+str(env.now)+'remaining_capacity_in_network when resources have just been given to '+str(id)+': '+ str(remaining_capacity_in_network))
yield env.timeout(req_time)
print('- - - - - - - -- - - - - - - - - - - - - - -- - - - - - - -path just after yield env.timeout(req_time): ',path,'id: ',id)
# Release the network_bandwidths used
for e in zip(path, path[1:]):
print(path)
print('e:',e)
# for e in graph.edges:
# print(e)
# G.edges[e]['capacity'].put(req_rate)
graph.edges[e]['capacity'].put(req_rate)
print('id of req which is returning resurces: ',id)
print('network_bandwidths successfully regained at time '+str(env.now)+'(id:'+str(id)+')')
remaining_capacity_in_network = 0
for edge in list(graph.edges):
remaining_capacity_in_network += graph.edges[edge]['capacity'].level
return True
else:
request_status[request] = False
print('request embedding unsuccessful'+ '(which was requesting network_bandwidths for '+str(req_time)+' sec)'+'(id:'+str(id)+')')
return False
def handle_requests(env, graph, request_store, time_step):
while True:
# Wait for a new request to be generated
request_info = yield request_store.get()
# Extract the request details
req = request_info['req']
required_rate = request_info['required_rate']
time_req = request_info['time_req']
id = request_info['id']
print('HANDLING request'+ ', requesting network_bandwidths for '+str(time_req)+' sec at time '+str(env.now)+'(id:'+str(id)+')')
# yield from embed_request(env, graph, req, required_rate, time_req, time_step, id)
remcap = 0
for edge in list(graph.edges):
remcap += graph.edges[edge]['capacity'].level
print(env.now,' rem capacity in handle_req(bef embed_req runs for ',id,'): ',remcap)
# Embed the request
env.process(embed_request(env, graph, req, required_rate, time_req, time_step, id))
def path_exists(path, source, destination, next_change_time, time_step):
# returns true if the path provided exists in the network topology at next_change_time
global topologies
index = next_change_time / time_step
graph = topologies[int(index-1)]
paths = nx.all_simple_paths(graph, source, destination) # Find all simple paths between the source and destination nodes
if path in paths: # check if the path chosen will exist in the next version of the topology
return True
return False
def path_viable(env, path, source, destination, req_duration, time_step):
survives_topo_changes = True
time_to_next_top_change = time_step - (env.now % time_step)
print('time_to_next_top_change: ',time_to_next_top_change)
# determine topology changes that will occur during the duration of the request
while time_to_next_top_change < req_duration:
# determine if the current path will exist in all of those changed topologies
next_change_time = env.now + time_to_next_top_change
print('next_change_time: ', next_change_time)
if path_exists(path, source, destination, next_change_time, time_step) == False:
survives_topo_changes = False
break
req_duration -= time_to_next_top_change
current_time = next_change_time
time_to_next_top_change = time_step - (current_time % time_step)
if survives_topo_changes == False:
return False
else:
print('========= path viable =======')
return True
def initialize_simulation(env):
global num_points_along_orbit, gateway_positions, satellite_nodes, current_satellite_positions, satellite_path, satellite_positions, node_positions, total_capacity, satellite_capacity, ground_capacity, G
gateway_positions = {
'Node1': (33.7, -91.13),
'Node2': (41.20, -85.9),
'Node3': (50.06, -91.63)
}
# satellite_path = calculate_ellipse_points(center_x, center_y, a, b, num_points)
# leo semi-major axis: 8413 km, major axis: 2*semi-major=16826, after scaling down, major axis = 16.826, minor axis = 8.413, num_points = 20
satellite_path = calculate_ellipse_points(41.7, -90.7, 16.826, 8.413, num_points_along_orbit) # list of tuples: [(48.7, -90.7), (48.35739561406608, -89.15491502812526),...]
for satellite in satellite_nodes:# {'satellite1': (48.7, -90.7)...}
current_satellite_positions[satellite] = satellite_path[satellite_nodes.index(satellite)]
satellite_positions=list(current_satellite_positions.values())
node_positions = {**gateway_positions, **current_satellite_positions} # combining dictionaries
# adding nodes to empty graph
for i in range(num_of_nodes):
G.add_node(nodes[i], pos=list(node_positions.values())[i]) # Assign initial positions as node attributes
# Initial topology
G.add_edges_from([('Node1','Node3'),('Node2','Node3'),('Node1','Node2'),('Satellite1','Satellite2'),('Satellite2','Satellite3'),('Satellite3','Satellite4'),('Satellite4','Satellite5'),('Satellite6','Satellite7'),('Satellite7','Satellite8'),('Satellite6','Satellite5')])
# Add SimPy containers for each edge
for edge in G.edges:
source, target = edge
if source.startswith('Node') and target.startswith('Node'):
# Check if the nodes are ground nodes
capacity = 5000
else:
# For non-ground nodes, set a different capacity
capacity = 1000
# Create a SimPy container with the determined capacity
G.edges[source, target]['capacity'] = simpy.Container(env, init=capacity, capacity=capacity)
# Check if one node is a ground node and the other is a satellite
# elif (source.startswith('Node') and target.startswith('Satellite')) or (source.startswith('Satellite') and target.startswith('Node')) or (source.startswith('Satellite') and target.startswith('Satellite')):
# capacity = simpy.Container(env, capacity=1000)
# G[source][target][capacity].put(1000)
update_links(env) # Air to ground links added based on communication range
print('simulation initialised')
def run_simulation(env, graph):
global num_points_along_orbit
period = 7680 # 7680 sec = 128 min (leo period)
timestep = period//num_points_along_orbit # time between 2 topology changes
# Run the simulation for a fixed time
runtime = timestep * num_of_iter
# Initialize the topology change event
topology_change_event = env.event()
request_event = env.event()
remaining_capacity_in_network = 0
for edge in list(graph.edges):
remaining_capacity_in_network += graph.edges[edge]['capacity'].level
print('############################################################################### # # # ###cap at start: ',remaining_capacity_in_network)
# Initialize the request store
request_store = simpy.Store(env)
print('beginning...')
# Start the handle_requests process in parallel with other code
env.process(handle_requests(env, graph, request_store, timestep))
# Start the request generation process
env.process(generate_traffic_requests(env, graph, request_store, runtime))
# Start the topology change process
env.process(simulate_satellite_motion(env, timestep, topology_change_event))
print('time between topology changes: '+str(timestep)+' sec('+str(round(timestep/60, 2))+' min)')
print('runtime '+ str(runtime)+' sec('+str(round(runtime/60, 2))+' min)')
env.run(until=runtime) # run the simulation
print('ending...')
remaining_capacity_in_network = 0
for edge in list(graph.edges):
remaining_capacity_in_network += graph.edges[edge]['capacity'].level
print('############################################################################### # # # ###cap at end: ',remaining_capacity_in_network)
communication_range = 7.5 # arbitrary value (scaled down 150 times from 1125 km)
terrestrial_nodes = ['Node1', 'Node2', 'Node3']
satellite_nodes = ['Satellite1', 'Satellite2', 'Satellite3', 'Satellite4', 'Satellite5', 'Satellite6', 'Satellite7', 'Satellite8']
nodes = terrestrial_nodes + satellite_nodes
num_of_nodes = len(nodes)
gateway_positions = {}
current_satellite_positions = {} # {'satellite1': (48.7, -90.7)...}
num_points_along_orbit = 20
G=nx.Graph()
visibility_tables = {}
topologies=[]
# Store the status of each request for visualization
request_status = {}
count_of_reqs_embedded = 0
traffic_requests={}
num_of_iter = 5 # Number of steps taken along orbit in this simulation
env = simpy.Environment()
initialize_simulation(env)
# store graphs of network at all topology changes
store_network_changes()
run_simulation(env, G)