Skip to content

Commit fc58339

Browse files
authored
Merge pull request #7 from stepeos/nsga
Add NSGA2 and NRMSE
2 parents 3765fa2 + 3f787fe commit fc58339

24 files changed

+953060
-276
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
## private stuff
66
.results/
7+
test_data/result
8+
test_data/01_WorldPositions_selection.csv
79
.tmp/
810
dist/
911
## data

carmodel_calibration/cmd.py

Lines changed: 272 additions & 154 deletions
Large diffs are not rendered by default.

carmodel_calibration/control_program/calibration_analysis.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def _get_weighted_errors(param_sets, sim_result, ident, results_chunk,
9393
weighted_error = results_chunk["weightedError"].values[jdx-1]
9494
if weighted_error == 0 or force_simulated:
9595
weighted_error = objective_function(gt, pred)
96+
weighted_error = np.sum(weighted_error)
9697
weighted_errors.append(weighted_error)
9798
return weighted_errors
9899

@@ -167,6 +168,7 @@ def _plot_single(identification, results_chunk, simulation_result,
167168
pred, gt = _get_results(
168169
{1: simulation_result}, identification, jdx-1)
169170
weighted_error = results_chunk["weightedError"].values[jdx-1]
171+
weighted_error = np.sum(weighted_error)
170172
time = pred["time"].values
171173
covered_distance = pred["coveredDistanceFollower"].values
172174
distance = pred["distance"].values

carmodel_calibration/control_program/calibration_handling.py

Lines changed: 335 additions & 81 deletions
Large diffs are not rendered by default.

carmodel_calibration/optimization.py

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@
66

77
import numpy as np
88
import pandas as pd
9+
from pymoo.core.problem import Problem
910

1011
# from carmodel_calibration.exceptions import OptimizationFailed
1112
from carmodel_calibration.fileaccess.parameter import ModelParameters
1213
from carmodel_calibration.sumo.sumo_project import SumoProject
1314

1415
_LOGGER = logging.getLogger(__name__)
1516

17+
1618
def measure_of_performance_factory(objectives=["distance"],
17-
weights=None, gof="rmse", **__):
19+
weights=None, gof="rmse", reduce_mmop2smop=True,
20+
**__):
1821
"""
1922
this function will return the appropiate objective function handle
2023
depending on the desired MOP and the calibration object
@@ -58,6 +61,7 @@ def measure_of_performance_factory(objectives=["distance"],
5861
objectives = weights_renamed
5962
for weights_value, objective in zip(weights_values, objectives):
6063
weights[objective] = weights_value
64+
6165
def rmse(ground_truth, prediction):
6266
"""
6367
RMSE(v)
@@ -70,7 +74,31 @@ def rmse(ground_truth, prediction):
7074
prediction[sop].values
7175
- ground_truth[sop].values))
7276
sums[idx] = np.sqrt(sums[idx] / len(ground_truth)) * weights[sop]
73-
return np.sum(sums)
77+
if reduce_mmop2smop:
78+
return np.sum(sums)
79+
else:
80+
return sums
81+
82+
def nrmse(ground_truth, prediction):
83+
"""
84+
NRMSE(v)
85+
see 'About calibration of car-following dynamics of automated and
86+
human-driven vehicles: Methodology, guidelines and codes'
87+
"""
88+
sums = np.zeros(len(objectives))
89+
for idx, sop in enumerate(objectives):
90+
rmse_error = np.sum(np.square(
91+
prediction[sop].values
92+
- ground_truth[sop].values))
93+
rmse_error = np.sqrt(rmse_error / len(ground_truth))
94+
gt_root = np.sqrt(np.sum(np.square(ground_truth[sop].values))
95+
/ len(ground_truth))
96+
sums[idx] = rmse_error / (gt_root) * weights[sop]
97+
if reduce_mmop2smop:
98+
return np.sum(sums)
99+
else:
100+
return sums
101+
74102
def rmspe(ground_truth, prediction):
75103
"""
76104
RMSPE(v)
@@ -84,7 +112,11 @@ def rmspe(ground_truth, prediction):
84112
- ground_truth[sop].values)
85113
/ ground_truth[sop].values))
86114
sums[idx] = np.sqrt(sums[idx] / len(ground_truth)) * weights[sop]
87-
return np.sum(sums)
115+
if reduce_mmop2smop:
116+
return np.sum(sums)
117+
else:
118+
return sums
119+
88120
def theils_u(ground_truth, prediction):
89121
"""
90122
theils U(v)
@@ -102,16 +134,23 @@ def theils_u(ground_truth, prediction):
102134
gt_root = np.sqrt(np.sum(np.square(ground_truth[sop].values))
103135
/ len(ground_truth))
104136
sums[idx] = rmse_error / (sim_root + gt_root) * weights[sop]
105-
return np.sum(sums)
137+
if reduce_mmop2smop:
138+
return np.sum(sums)
139+
else:
140+
return sums
106141

107142
def model_output(_, prediction):
108143
"""only sum model outputs"""
109144
sums = np.zeros(len(objectives))
110145
for idx, sop in enumerate(objectives):
111146
sums[idx] = prediction[sop].values[-1] * weights[sop]
112-
return np.sum(sums)
113-
gof_handles = {"rmse": rmse, "rmsep": rmspe, "modelOutput": model_output,
114-
"theils_u": theils_u}
147+
if reduce_mmop2smop:
148+
return np.sum(sums)
149+
else:
150+
return sums
151+
152+
gof_handles = {"rmse": rmse, "nrmse": nrmse, "rmsep": rmspe,
153+
"modelOutput": model_output, "theils_u": theils_u}
115154

116155
def get_weighted_error(ground_truth, prediction):
117156
"""calculate weighted error on case1"""
@@ -124,6 +163,7 @@ def get_weighted_error(ground_truth, prediction):
124163

125164
return get_weighted_error
126165

166+
127167
def factory_wrapper(factory_kwargs):
128168
"""invokes factory from results data"""
129169
if isinstance(factory_kwargs, pd.DataFrame):
@@ -142,31 +182,33 @@ def factory_wrapper(factory_kwargs):
142182
else:
143183
raise TypeError(
144184
"`factory_kwargs` must either be of type dict or pandas DattaFrame"
145-
)
185+
)
186+
146187

147188
def _get_results(simulation_results, identification, lane):
148189
"""returns the simulation result for specific identification"""
149190
edge_name = f"B{lane}A{lane}"
150191
condition = (
151-
(simulation_results[1]["prediction"]["edgeName"]==edge_name)
192+
(simulation_results[1]["prediction"]["edgeName"] == edge_name)
152193
)
153194
prediction_result = simulation_results[1]["prediction"][condition]
154195
leader = identification[0]
155196
recording_id = identification[2]
156197
follower_condition = (
157-
simulation_results[1]["ground_truth"]["follower"]==identification[1])
198+
simulation_results[1]["ground_truth"]["follower"] == identification[1])
158199
condition = (
159-
(simulation_results[1]["ground_truth"]["leader"]==leader)
200+
(simulation_results[1]["ground_truth"]["leader"] == leader)
160201
& (follower_condition)
161202
& (simulation_results[1]["ground_truth"]
162-
["recordingId"]==recording_id)
203+
["recordingId"] == recording_id)
163204
& (simulation_results[1]["ground_truth"]
164-
["counter"]==lane)
205+
["counter"] == lane)
165206
)
166207
ground_truth_result = (
167208
simulation_results[1]["ground_truth"][condition])
168209
return prediction_result, ground_truth_result
169210

211+
170212
def _run_sumo(identification, sumo_interface, param_sets, project_path, model, timestep):
171213
cfmodels = []
172214
route_count = SumoProject.get_number_routes(
@@ -177,7 +219,7 @@ def _run_sumo(identification, sumo_interface, param_sets, project_path, model, t
177219
sumo_interface.start_simulation_module()
178220
for idx, param_set in enumerate(param_sets):
179221
cfmodel = ModelParameters.create_parameter_set(f"set{idx}.json", model,
180-
**param_set)
222+
**param_set)
181223
cfmodels.append(cfmodel)
182224
SumoProject.write_followers_leader(
183225
project_path / "calibration_routes.rou.xml",
@@ -186,13 +228,15 @@ def _run_sumo(identification, sumo_interface, param_sets, project_path, model, t
186228
identification=identification)
187229
return simulation_results
188230

231+
189232
def _calculate_performance(idx, simulation_results, identification,
190-
objective_function):
233+
objective_function):
191234
prediction, ground_truth = _get_results(
192-
simulation_results, identification, idx)
235+
simulation_results, identification, idx)
193236
objective_function = factory_wrapper(objective_function)
194237
return objective_function(ground_truth, prediction)
195238

239+
196240
def _vectorized_target(params, *data):
197241
# params.shape = (Number params, number of solutions)
198242
params = np.atleast_2d(params).T
@@ -206,7 +250,7 @@ def _vectorized_target(params, *data):
206250
# keys of the paramters that are optimized
207251
param_names = data["param_names"]
208252
param_sets = []
209-
if params.shape[1]==1:
253+
if params.shape[1] == 1:
210254
params = params.T
211255
for solution in params:
212256
params_dict = data["default_parameters"].copy()
@@ -215,22 +259,36 @@ def _vectorized_target(params, *data):
215259
param_sets.append(params_dict)
216260
simulation_results = _run_sumo(
217261
identification, sumo_interface, param_sets, project_path, model, timestep)
218-
with Pool(os.cpu_count()//2) as pool:
262+
with Pool(os.cpu_count() // 2) as pool:
219263
results = []
220264
for idx in range(len(params)):
221265
results.append((idx, simulation_results, identification,
222266
objective_function))
223267
performance = list(pool.starmap(_calculate_performance, results))
224-
#performance = []
225-
#for idx in range(len(params)):
226-
# performance.append(_calculate_performance(idx, simulation_results, identification,
227-
# objective_function))
268+
performance = []
269+
for idx in range(len(params)):
270+
performance.append(_calculate_performance(idx, simulation_results, identification,
271+
objective_function))
228272
return performance
229273

230274

231275
def target_factory(data, invert_error=False):
232276
"""factory for creating a target callback"""
233-
def _vectorized_wrapper(params, solution_indexes):
277+
def _vectorized_wrapper(ga_instance, params, solution_indexes):
278+
solutions = _vectorized_target(params.T, data)
279+
# OutComment this when using adaptive mutation
280+
# if solution_indexes is None:
281+
# return None
282+
if invert_error:
283+
return [1 / solution for solution in solutions]
284+
else:
285+
return solutions
286+
return _vectorized_wrapper
287+
288+
289+
def target_factory_nsga2(data, invert_error=False):
290+
"""factory for creating a target callback"""
291+
def _vectorized_wrapper(ga_instance, params, solution_indexes):
234292
solutions = _vectorized_target(params.T, data)
235293
if solution_indexes is None:
236294
return None
@@ -239,3 +297,14 @@ def _vectorized_wrapper(params, solution_indexes):
239297
else:
240298
return solutions
241299
return _vectorized_wrapper
300+
301+
302+
class target_factory_mo_de(Problem):
303+
304+
def __init__(self, data, **kwargs):
305+
super().__init__(**kwargs)
306+
self.data = data
307+
308+
def _evaluate(self, X, out, *args, **kwargs):
309+
# store the function values and return them.
310+
out["F"] = np.array(_vectorized_target(X.T, self.data))

carmodel_calibration/sumo/sumo_project.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
configure_logging()
1616
_LOGGER = logging.getLogger(__name__)
1717

18+
1819
def _get_edge_number(edge):
1920
return int(float(re.findall(r"([\+\-]*\d*\.*\d+)",
20-
str(edge.attrib["id"]))[0]))
21+
str(edge.attrib["id"]))[0]))
22+
23+
2124
class SumoProject:
2225
"""class used to build sumo project for calibration purposes"""
2326

@@ -30,7 +33,7 @@ def create_sumo(project_path: Path, model: str, number_network_lanes: int, times
3033
"""
3134
default_params = ModelParameters.get_defaults_dict()
3235
cfmodel = ModelParameters.create_parameter_set(".tmp/params", model,
33-
**default_params)
36+
**default_params)
3437
if not project_path.exists():
3538
project_path.mkdir(parents=True)
3639
network_path = project_path / "calibration_network.xml"
@@ -42,7 +45,8 @@ def create_sumo(project_path: Path, model: str, number_network_lanes: int, times
4245
network_path)
4346
SumoProject.create_routes_from_network(network_path, routes_path,
4447
number_network_lanes, timestep)
45-
SumoProject.create_config(network_path, routes_path, config_path, timestep)
48+
SumoProject.create_config(
49+
network_path, routes_path, config_path, timestep)
4650
SumoProject.write_followers_leader(routes_path,
4751
[cfmodel] * number_network_lanes)
4852

@@ -63,8 +67,13 @@ def create_network(x_number, y_number, file_path):
6367
try:
6468
outs, errs = proc.communicate(timeout=10)
6569
_LOGGER.debug("%s \noutput:%s",
66-
" ".join(cmd),
67-
str(outs))
70+
" ".join(cmd),
71+
str(outs))
72+
if proc.returncode:
73+
_LOGGER.error("Network creation failed with %s and error %s",
74+
outs,
75+
errs)
76+
raise RuntimeError("Network creation failed")
6877
except TimeoutExpired as exc:
6978
proc.kill()
7079
outs, errs = proc.communicate()
@@ -91,8 +100,8 @@ def create_routes_from_network(network_path: Path, out_path: Path,
91100
for edge in net_root.findall('edge'):
92101
if re.match(pattern, str(edge.attrib['id'])):
93102
num_edges += 1
94-
edges = net_root.findall('edge')
95-
edges = sorted(edges, key= lambda x: _get_edge_number(edge))
103+
edges = net_root.findall('edge')
104+
edges = sorted(edges, key=lambda x: _get_edge_number(edge))
96105
for edge in edges:
97106
if re.match(pattern, str(edge.attrib['id'])):
98107
source = str(edge.attrib['id'])
@@ -119,7 +128,7 @@ def create_routes_from_network(network_path: Path, out_path: Path,
119128
# <route id="r_TOP" edges="B249A249 A249A248 A248A249\
120129
# A249B249"/>
121130
edges = [f"{source}", f"A{num_edges-1}B{num_edges-2}",
122-
dest]
131+
dest]
123132
else:
124133
# <route id="r_MIDDLE" edges="B145A145 A145B145"/>
125134
edges = [source, dest]
@@ -146,9 +155,9 @@ def create_config(network_file: Path, routes_file: Path, out_path: Path, timeste
146155
file_type = "routes"
147156
file = str(routes_file)
148157
_LOGGER.error("%s-file not found at %s"
149-
"\ncreate network-file first",
150-
file_type,
151-
str(file))
158+
"\ncreate network-file first",
159+
file_type,
160+
str(file))
152161
raise FileNotFoundError
153162
cmd = [
154163
"sumo",
@@ -171,8 +180,8 @@ def create_config(network_file: Path, routes_file: Path, out_path: Path, timeste
171180
try:
172181
outs, errs = proc.communicate(timeout=10)
173182
_LOGGER.debug("%s \noutput:%s",
174-
" ".join(cmd),
175-
str(outs))
183+
" ".join(cmd),
184+
str(outs))
176185
except TimeoutExpired as exc:
177186
proc.kill()
178187
outs, errs = proc.communicate()
@@ -270,7 +279,7 @@ def get_network_info(network_file):
270279
for edge in network_root.findall("edge"):
271280
if re.match(pattern, str(edge.attrib['id'])):
272281
edge_numbers = (re.findall(r"([\+\-]*\d*\.*\d+)",
273-
str(edge.attrib["id"])))
282+
str(edge.attrib["id"])))
274283
edge_numbers = [int(float(item)) for item in edge_numbers]
275284
if edge_numbers[0] != edge_numbers[1]:
276285
print(str(edge.attrib['id']))

requirements.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ scipy
33
numpy==1.20.0
44
matplotlib
55
lxml
6-
pygad==2.19.0
6+
pygad==3.3.1
77
regex
88
argparse
99
entrypoints>=0.2.2
1010
SALib==1.4.5
1111
tqdm
12-
cloudpickle==2.2.1
12+
cloudpickle==2.2.1
13+
pymoo
14+
pymoode

0 commit comments

Comments
 (0)