Skip to content
This repository has been archived by the owner on Jun 9, 2022. It is now read-only.

Commit

Permalink
ADD - Path discovery and inspection report
Browse files Browse the repository at this point in the history
Signed-off-by: RaenonX <[email protected]>
  • Loading branch information
RaenonX committed Nov 16, 2020
1 parent 152f100 commit d307921
Show file tree
Hide file tree
Showing 24 changed files with 1,086 additions and 179 deletions.
Binary file added assets/reports/images/1116/1116-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/reports/images/1116/1116-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/reports/images/1116/1116-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/reports/images/1116/1116-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 40 additions & 6 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,57 @@
import time
from datetime import datetime

from msnmetrosim.views import (
test_run,
# test_run,
# Simulation graph benchmarking
# benchmark_gen_map,
benchmark_map_construction,
# Simulation benchmark plotting
# gen_map_only_df8d234f
plot_trip_count_cdf, post_152f1000_pruned_no_detouring
)


def main():
test_run()
# Benchmark with pruning, max transfer 1~5

travel_time = 1800

results = []

for max_transfer in range(5):
results.extend(benchmark_map_construction(
datetime(2020, 9, 2, 15),
[travel_time],
(43.069451, -89.401168),
max_transfer=max_transfer
))

print(f"{'=' * 50} RESULTS {'=' * 50}")
for max_transfer, result in enumerate(results):
print()
print(f"{'=' * 20} Max transfer = {max_transfer}")
print()
print(result)


def main2():
pass
travel_time = 2700

results = benchmark_map_construction(
datetime(2020, 9, 2, 15),
[travel_time],
(43.069451, -89.401168),
)
print(f"{'=' * 50} RESULTS {'=' * 50}")
print()
print(results[0])
print()


def main3():
plot_trip_count_cdf(post_152f1000_pruned_no_detouring)


if __name__ == '__main__':
_start = time.time()
main()
main3()
print(f"Time spent: {(time.time() - _start) * 1000:.3f} ms")
4 changes: 0 additions & 4 deletions msnmetrosim/controllers/stop_at_cross.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,6 @@ def get_all_stop_remove_results(self, range_km: float, interval_km: float,
For 1153 records, it takes ~5 mins to run.
"""
# ThreadPoolExecutor won't help on performance boosting
# OPTIMIZE: Try to reduce the calculation time of this
# - Each agent is expanding its circle to find the closest stop
# - Change the above to each stop expanding its circle to "touch" the closest agent instead?
# - Any possible use of numpy for performance boost?
ret: List[CrossStopRemovalResult] = []

total_count = len(self.all_data)
Expand Down
26 changes: 25 additions & 1 deletion msnmetrosim/controllers/stop_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,32 @@ def get_stop_schedule_sim_by_arrival(self, start_dt: datetime, time_range: float
# Blocks to selectively included
end_time = start_dt + timedelta(seconds=time_range)

# --- Find the next stop and add it if exists

# Cache to store the converted next :class:`MMTStopScheduleSim`
# Key is (trip_id, stop_sequence), value is the next stop for the corresponding stop
next_cache: Dict[Tuple[int, int]] = {}

# In-line function to fill the next stop cache
def fill_next_cache(tid, seq):
if seq >= self._by_trip_len[tid]:
return None

next_raw = self._by_trip_id[tid][seq]

# Check if the arrival time is in the range
next_sim = fill_next_cache(tid, seq + 1)
next_cache[(tid, seq)] = MMTStopScheduleSim.from_raw(next_raw, start_dt.date(), next_stop=next_sim)

return next_sim

for data in self._get_stop_schedules(end_time.hour, trip_ids):
schedule_sim = MMTStopScheduleSim.from_raw(data, end_time.date())
cache_key = (data.trip_id, data.stop_sequence)

if cache_key not in next_cache:
fill_next_cache(data.trip_id, data.stop_sequence)

schedule_sim = MMTStopScheduleSim.from_raw(data, end_time.date(), next_stop=next_cache.get(cache_key))
if schedule_sim.arrival_time <= end_time:
ret.append(schedule_sim)

Expand Down
3 changes: 2 additions & 1 deletion msnmetrosim/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""Various utils and helpers."""
from .calc import normalize_vector, normalize_cumulate_vector
from .colorgen import get_color
from .deco_warning import temporary_func
from .dt_convert import time_from_seconds
from .geo import distance, offset, generate_points, travel_time
from .mixin import TimeableMixin
from .perf import time_function
from .plane import get_plane, Plane
from .plot import plot_twin_y, plot_single, plot_multiple
from .plot import plot_twin_y, plot_single, plot_multiple, plot_discrete_distribution_normalized
from .progress import Progress
from .stats import DataMetrics
27 changes: 27 additions & 0 deletions msnmetrosim/utils/calc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Functions for easier calculations."""
from typing import List, Iterable

__all__ = ("cumulate_vector", "normalize_vector", "normalize_cumulate_vector")


def cumulate_vector(vector: Iterable[float]) -> List[float]:
"""Cumulate ``vector``."""
ret: List[float] = []
for entry in vector:
ret.append((ret[-1] if len(ret) > 0 else 0) + entry)

return ret


def normalize_vector(vector: Iterable[float]) -> List[float]:
"""Normalize ``vector``, letting the sum of the vector to be 1."""
total = sum(vector)

return [item / total for item in vector]


def normalize_cumulate_vector(vector: Iterable[float]) -> List[float]:
"""Normalize and cumulate ``vector``."""
normalized = normalize_vector(vector)

return cumulate_vector(normalized)
125 changes: 99 additions & 26 deletions msnmetrosim/utils/plot.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,58 @@
"""Helper functions for plotting the data."""
from typing import Iterable, Tuple, Optional, Union
from typing import Iterable, Tuple, Optional, Union, Dict, Sequence

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.axes import Axes

__all__ = ("plot_twin_y", "plot_single", "plot_multiple")
from .calc import normalize_vector

__all__ = ("plot_twin_y", "plot_single", "plot_multiple", "plot_discrete_distribution_normalized")

def plot_single(x_data: Iterable[float], x_name: str, y_data: Iterable[float], y_name: str,
title: Optional[str] = None):
"""Generate a plot with a single X and Y."""
fig, axe = plt.subplots()

def plot_base(fn):
"""Wrapper for a plotting function to auto-fill the axe."""

def plot_base_inner(*args, **kwargs):
auto_gen = False

if 'axe' not in kwargs or kwargs['axe'] is None:
auto_gen = True
fig, axe = plt.subplots()
fig.tight_layout() # otherwise the right y-label is slightly clipped

kwargs['axe'] = axe

fn(*args, **kwargs)

if auto_gen:
plt.show()

return plot_base_inner


@plot_base
def plot_single(x_data: Iterable[float], x_name: str, y_data: Iterable[float], y_name: str, /,
title: Optional[str] = None, log_x: bool = False, log_y: bool = False, axe: Optional[Axes] = None):
"""Generate a plot with a single X and Y."""
axe.set_xlabel(x_name)
if log_x:
axe.set_xscale('log')
axe.set_ylabel(y_name)
if log_y:
axe.set_yscale('log')
axe.plot(x_data, y_data)
axe.tick_params(axis="y")

if title:
axe.set_title(title)
fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.show()


@plot_base
def plot_multiple(x_data: Iterable[float], x_name: str,
y_data_collection: Iterable[Tuple[str, str, Iterable[float]]], y_name: str,
legend_title: str, legend_loc: Union[Tuple[float, float], str] = "lower right",
title: Optional[str] = None):
title: Optional[str] = None, axe: Optional[Axes] = None):
"""
Generate a plot with a single X and multiple Y.
Expand All @@ -35,25 +62,22 @@ def plot_multiple(x_data: Iterable[float], x_name: str,
Check https://matplotlib.org/3.3.2/api/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D.set_linestyle
for the line style string.
"""
fig, axe = plt.subplots()

axe.set_xlabel(x_name)
axe.set_ylabel(y_name)
for name, line_style, y_data in y_data_collection:
axe.plot(x_data, y_data, label=name, linestyle=line_style)
axe.tick_params(axis="y")

fig.legend(title=legend_title, loc=legend_loc)
axe.legend(title=legend_title, loc=legend_loc)
if title:
axe.set_title(title)
fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.show()


@plot_base
def plot_twin_y(x_data: Iterable[float], x_name: str,
y1_data: Iterable[float], y1_name: str,
y2_data: Iterable[float], y2_name: str,
title: Optional[str] = None):
y2_data: Iterable[float], y2_name: str, /,
title: Optional[str] = None, axe: Optional[Axes] = None):
"""
Generate a plot with 2 different series sharing the same X but different Y.
Expand All @@ -62,22 +86,71 @@ def plot_twin_y(x_data: Iterable[float], x_name: str,
.. note::
Copied and modified from https://matplotlib.org/gallery/api/two_scales.html
"""
fig, ax1 = plt.subplots()

color = "tab:red"
ax1.set_xlabel(x_name)
ax1.set_ylabel(y1_name, color=color)
ax1.plot(x_data, y1_data, color=color)
ax1.tick_params(axis="y", labelcolor=color)
axe.set_xlabel(x_name)
axe.set_ylabel(y1_name, color=color)
axe.plot(x_data, y1_data, color=color)
axe.tick_params(axis="y", labelcolor=color)

ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
ax2 = axe.twinx() # instantiate a second axes that shares the same x-axis

color = "tab:blue"
ax2.set_ylabel(y2_name, color=color) # we already handled the x-label with ax1
ax2.plot(x_data, y2_data, color=color, linestyle="dashed")
ax2.tick_params(axis="y", labelcolor=color)

if title:
ax1.set_title(title)
fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.show()
axe.set_title(title)


@plot_base
def plot_discrete_distribution_normalized(categories: Sequence[str], results: Dict[str, Sequence[float]], /,
title: Optional[str] = None, axe: Optional[Axes] = None):
"""
Generate a normalized discrete distribution chart.
.. note::
Copied and modified from
https://matplotlib.org/3.3.3/gallery/lines_bars_and_markers/horizontal_barchart_distribution.html
"""
# pylint: disable=too-many-locals

# Pre-process the data
labels = [f"{label}\n({sum(results[label])})" for label in results.keys()]
data_normalized = np.array([normalize_vector(vector) for vector in results.values()]) # Normalize data
data_counts = list(results.values()) # To be used for indicating the data count
data_cum = data_normalized.cumsum(axis=1) # To be used for centering the texts

# Get the color map to be used
category_colors = plt.get_cmap("plasma")(np.linspace(0.15, 0.85, data_normalized.shape[1]))

# Set the figure width to be 2 times the height
axe.figure.set_size_inches(axe.figure.get_size_inches()[1] * 2, axe.figure.get_size_inches()[1])

axe.invert_yaxis()
axe.xaxis.set_visible(False)
axe.set_xlim(0, 1)

for cat_idx, (colname, color) in enumerate(zip(categories, category_colors)):
bar_widths = data_normalized[:, cat_idx]
bar_starts = data_cum[:, cat_idx] - bar_widths
axe.barh(labels, bar_widths, left=bar_starts, height=0.5, label=colname, color=color)

# Text plotting
r, g, _, _ = color # pylint: disable=invalid-name
text_color = 'black' if max(r, g) > 0.5 else 'white' # max(r, g) gets the B of HSB
xcenters = bar_starts + bar_widths / 2

# Plot texts
for bar_idx, (xcenter, data_val) in enumerate(zip(xcenters, bar_widths)):
if data_val > 0: # Only plot the text if the data value is > 0
axe.text(xcenter, bar_idx, f"{data_counts[bar_idx][cat_idx]}\n{data_val:.2%}",
ha='center', va='center', color=text_color)

# Plot the legend at the left side
axe.legend(ncol=len(categories), bbox_to_anchor=(0, 1),
loc='lower left', fontsize='small')

# Set title
if title:
axe.set_title(title)
2 changes: 1 addition & 1 deletion msnmetrosim/utils/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def get_quantile(self, n: int) -> List[float]: # pylint: disable=invalid-name

def get_quantile_cdf(self, n: int) -> Tuple[List[float], List[float]]: # pylint: disable=invalid-name
"""
Same as ``get_quantile()``, but the return will be ``(X_ARRAY, Y_ARRAY)`` for easier plotting.
Same as ``get_quantile()``, but the return will be ``(X_ARRAY, Y_ARRAY)`` for plotting.
Y will be 0 <= x <= 1, and it will always starts from 0 and ends at 1.
"""
Expand Down
1 change: 1 addition & 0 deletions msnmetrosim/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
)
from .rm_stop import * # noqa
from .sim_benchmark import * # noqa
from .sim_graph import SimulationMap
from .simulate import test_run
from .stop import get_stops_without_ridership, get_distance_to_stop
Loading

0 comments on commit d307921

Please sign in to comment.