-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrsaenv.py
More file actions
211 lines (171 loc) · 6.77 KB
/
Copy pathrsaenv.py
File metadata and controls
211 lines (171 loc) · 6.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import gymnasium as gym
import numpy as np
import pandas as pd
from gymnasium import spaces
from nwutil import (
Request, generate_sample_graph, get_available_paths,
find_available_wavelength, allocate_lightpath,
release_expired_lightpaths, get_network_state_vector
)
class RSAEnv(gym.Env):
# Custom Gymnasium environment for Routing and Spectrum Allocation (RSA) problem
# Simulates an optical network where requests arrive sequentially
# and need to be assigned to paths with available wavelengths
metadata = {'render_modes': []}
def __init__(self, request_file=None, capacity=20):
super(RSAEnv, self).__init__()
self.capacity = capacity
self.graph = generate_sample_graph(capacity=capacity)
self.num_links = len(self.graph.edges())
# Load requests from CSV file
self.request_file = request_file
self.requests = []
if request_file:
self._load_requests(request_file)
# Simulation state
self.current_time = 0
self.request_idx = 0
self.current_request = None
self.blocked_count = 0
self.total_requests = 0
# Action space: 8 paths + 1 block action
self.action_space = spaces.Discrete(9)
# Observation space: link utilizations + request features
# 12 link utilizations + 3 request features (src, dst, holding_time normalized)
obs_dim = self.num_links + 3
self.observation_space = spaces.Box(
low=0.0, high=1.0, shape=(obs_dim,), dtype=np.float32
)
def _load_requests(self, file_path):
# Load requests from CSV file
df = pd.read_csv(file_path)
self.requests = []
for _, row in df.iterrows():
self.requests.append(
Request(
source=int(row['source']),
destination=int(row['destination']),
holding_time=int(row['holding_time'])
)
)
def _get_observation(self):
# Generate the observation vector
# Link utilizations (12 values) + current request normalized features (3 values)
network_state = get_network_state_vector(self.graph)
if self.current_request:
# Normalize request features
src_norm = self.current_request.source / 8.0 # Max node ID is 8
dst_norm = self.current_request.destination / 8.0
hold_norm = min(self.current_request.holding_time / 50.0, 1.0) # Cap at 50
request_features = [src_norm, dst_norm, hold_norm]
else:
request_features = [0.0, 0.0, 0.0]
obs = np.array(network_state + request_features, dtype=np.float32)
return obs
def _get_path_for_action(self, action):
# Map action index to path based on current request's src-dst pair
# Returns None if action is invalid or represents blocking
if action == 8: # Block action
return None
if not self.current_request:
return None
src = self.current_request.source
dst = self.current_request.destination
paths = get_available_paths(src, dst)
# Map action to path index
# Actions 0-1: paths for (0,3)
# Actions 2-3: paths for (0,4)
# Actions 4-5: paths for (7,3)
# Actions 6-7: paths for (7,4)
path_map = {
(0, 3): [0, 1],
(0, 4): [2, 3],
(7, 3): [4, 5],
(7, 4): [6, 7]
}
valid_actions = path_map.get((src, dst), [])
if action in valid_actions:
path_idx = valid_actions.index(action)
if path_idx < len(paths):
return paths[path_idx]
return None
def step(self, action):
# Execute one step in the environment
reward = 0.0
info = {'blocked': False, 'invalid_action': False}
# Release expired lightpaths
release_expired_lightpaths(self.graph, self.current_time)
# Get the path for this action
path = self._get_path_for_action(action)
if path is None:
# Block action or invalid action
if action == 8:
# Explicit block
reward = -1.0
self.blocked_count += 1
info['blocked'] = True
else:
# Invalid action for current request
reward = -2.0
self.blocked_count += 1
info['blocked'] = True
info['invalid_action'] = True
else:
# Try to allocate on this path
wavelength = find_available_wavelength(self.graph, path)
if wavelength is not None:
# Successfully allocate
allocate_lightpath(self.graph, path, wavelength,
self.current_request, self.current_time)
reward = 1.0
info['allocated'] = True
info['path'] = path
info['wavelength'] = wavelength
else:
# Path has no available wavelength - blocking
reward = -1.0
self.blocked_count += 1
info['blocked'] = True
# Move to next request
self.current_time += 1
self.request_idx += 1
self.total_requests += 1
# Check if episode is done
terminated = self.request_idx >= len(self.requests)
truncated = False
if not terminated:
self.current_request = self.requests[self.request_idx]
else:
self.current_request = None
observation = self._get_observation()
# Add blocking rate to info
info['blocking_rate'] = self.blocked_count / self.total_requests if self.total_requests > 0 else 0.0
return observation, reward, terminated, truncated, info
def reset(self, seed=None, options=None):
# Reset the environment to initial state
super().reset(seed=seed)
# Handle options for request file
if options and 'request_file' in options:
self.request_file = options['request_file']
self._load_requests(self.request_file)
# Reset graph
self.graph = generate_sample_graph(capacity=self.capacity)
# Reset simulation state
self.current_time = 0
self.request_idx = 0
self.blocked_count = 0
self.total_requests = 0
# Load first request
if len(self.requests) > 0:
self.current_request = self.requests[0]
else:
self.current_request = None
observation = self._get_observation()
info = {}
return observation, info
def render(self):
# Render the environment (not implemented)
pass
def close(self):
# Clean up resources
pass