From c7762c202470fc64cb59f9b1154a2ddbd988bd80 Mon Sep 17 00:00:00 2001 From: hoda_hamdy Date: Fri, 25 Apr 2025 12:37:42 +0200 Subject: [PATCH 1/6] init code for gui --- streamlit_gui.py | 441 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 streamlit_gui.py diff --git a/streamlit_gui.py b/streamlit_gui.py new file mode 100644 index 00000000..64cef2ec --- /dev/null +++ b/streamlit_gui.py @@ -0,0 +1,441 @@ +import streamlit as st +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +import os +import io +import zipfile +import json +import base64 +from datetime import datetime + + +from run_examples import run_scenarios + +# Set page config +st.set_page_config(page_title="FleetPy Web Interface", layout="wide") + + +# Define helper functions +def download_link(object_to_download, download_filename, download_link_text): + """ + Generates a link to download the given object_to_download. + """ + if isinstance(object_to_download, pd.DataFrame): + object_to_download = object_to_download.to_csv(index=False) + + # Some strings <-> bytes conversions necessary here + b64 = base64.b64encode(object_to_download.encode()).decode() + + return f'{download_link_text}' + + +def create_sample_files(): + """Create sample files for users to download""" + # Sample vehicle data + vehicles_df = pd.DataFrame({ + 'vehicle_id': ['v1', 'v2', 'v3'], + 'type': ['sedan', 'truck', 'van'], + 'capacity': [4, 2, 6], + 'start_location': ['depot', 'depot', 'depot'], + 'end_location': ['depot', 'depot', 'depot'] + }) + + # Sample request data + requests_df = pd.DataFrame({ + 'request_id': ['r1', 'r2', 'r3', 'r4', 'r5'], + 'pickup_location': ['A', 'B', 'C', 'D', 'E'], + 'dropoff_location': ['F', 'G', 'H', 'I', 'J'], + 'pickup_time_window_start': [0, 10, 20, 30, 40], + 'pickup_time_window_end': [30, 40, 50, 60, 70], + 'dropoff_time_window_start': [30, 40, 50, 60, 70], + 'dropoff_time_window_end': [60, 70, 80, 90, 100] + }) + + # Sample configuration + config = { + "scenario_name": "sample_scenario", + "algorithm": "insertion_heuristic", + "objective": "minimize_vehicles", + "max_computation_time": 60, + "random_seed": 42 + } + + return vehicles_df, requests_df, config + + +# Main app +def main(): + st.title("FleetPy Web Interface") + + # Sidebar + st.sidebar.header("Navigation") + page = st.sidebar.selectbox("Choose a page", + ["Home", "Create Scenario", "Upload Files", "Download Samples", "Visualize Results"]) + + if page == "Home": + st.header("Welcome to FleetPy Web Interface") + st.write(""" + This application allows you to interact with the FleetPy library for vehicle routing and scheduling. + Use the sidebar to navigate between different functionalities. + """) + + st.subheader("Quick Start") + st.markdown(""" + 1. Download sample files to understand the required format + 2. Create a new scenario or upload your own files + 3. Run the solver and visualize results + """) + + elif page == "Create Scenario": + st.header("Create New Scenario") + + # Basic scenario setup + scenario_name = st.text_input("Scenario Name", "my_scenario") + + st.subheader("Algorithm Settings") + algorithm = st.selectbox("Algorithm", + ["insertion_heuristic", "local_search", "column_generation"]) + + objective = st.selectbox("Objective", + ["minimize_vehicles", "minimize_distance", "minimize_duration"]) + + max_time = st.slider("Max Computation Time (seconds)", 10, 600, 60) + + random_seed = st.number_input("Random Seed", 0, 9999, 42) + + st.subheader("Advanced Settings") + show_advanced = st.checkbox("Show Advanced Settings") + + if show_advanced: + st.write("Advanced settings go here...") + + # Create config dictionary + config = { + "scenario_name": scenario_name, + "algorithm": algorithm, + "objective": objective, + "max_computation_time": max_time, + "random_seed": random_seed + } + + if st.button("Save Configuration"): + # Store configuration in session state + st.session_state['config'] = config + st.success(f"Configuration for scenario '{scenario_name}' saved!") + + # Display the configuration + st.json(config) + + # Create download link for the config + config_json = json.dumps(config, indent=2) + st.markdown(download_link(config_json, f"{scenario_name}_config.json", "Download Configuration"), + unsafe_allow_html=True) + + elif page == "Upload Files": + st.header("Upload Files") + + # Upload vehicles file + st.subheader("Upload Vehicles File") + vehicles_file = st.file_uploader("Choose a CSV file", type="csv", key="vehicles") + if vehicles_file is not None: + vehicles_df = pd.read_csv(vehicles_file) + st.session_state['vehicles_df'] = vehicles_df + st.success("Vehicles file uploaded successfully!") + st.write(vehicles_df) + + # Upload requests file + st.subheader("Upload Requests File") + requests_file = st.file_uploader("Choose a CSV file", type="csv", key="requests") + if requests_file is not None: + requests_df = pd.read_csv(requests_file) + st.session_state['requests_df'] = requests_df + st.success("Requests file uploaded successfully!") + st.write(requests_df) + + # Upload configuration file + st.subheader("Upload Configuration File") + config_file = st.file_uploader("Choose a JSON file", type="json", key="config") + if config_file is not None: + config = json.load(config_file) + st.session_state['config'] = config + st.success("Configuration file uploaded successfully!") + st.json(config) + + # Run solver + if st.button("Run FleetPy Solver"): + if 'vehicles_df' in st.session_state and 'requests_df' in st.session_state and 'config' in st.session_state: + st.info("Running solver...") + + # Create a progress bar + progress_bar = st.progress(0) + status_text = st.empty() + + # Here you would call FleetPy functions to run the solver + # For demonstration, we'll simulate progress: + + import time + + for i in range(101): + # Update progress bar + progress_bar.progress(i) + + # Update status text + if i < 30: + status_text.text(f"Setting up the problem... ({i}%)") + elif i < 60: + status_text.text(f"Optimizing routes... ({i}%)") + elif i < 90: + status_text.text(f"Finalizing solution... ({i}%)") + else: + status_text.text(f"Preparing results... ({i}%)") + + # Simulate computation time + time.sleep(0.05) + + # For real implementation, you'd run your FleetPy solver here + # and update the progress periodically based on solver status + run_fleetpy() + + # Remove progress elements when done + progress_bar.empty() + status_text.empty() + + # TODO show actual results + # For now, we'll just simulate a result + st.session_state['solution'] = { + "routes": [ + {"vehicle_id": "v1", "route": ["depot", "A", "F", "depot"]}, + {"vehicle_id": "v2", "route": ["depot", "B", "G", "C", "H", "depot"]}, + {"vehicle_id": "v3", "route": ["depot", "D", "I", "E", "J", "depot"]} + ], + "objective_value": 150.75, + "computation_time": 2.3 + } + + st.success("Solver completed!") + st.balloons() + + # Save the solution timestamp + st.session_state['solution_timestamp'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Redirect to visualization page + st.info("Redirecting to visualization page...") + # st.rerun() + else: + st.error("Please upload all required files first!") + + elif page == "Download Samples": + st.header("Download Sample Files") + + # Create sample files + vehicles_df, requests_df, config = create_sample_files() + + # Display sample files + st.subheader("Sample Vehicles File") + st.write(vehicles_df) + + st.subheader("Sample Requests File") + st.write(requests_df) + + st.subheader("Sample Configuration") + st.json(config) + + # Create download links + st.subheader("Download Links") + + # Convert dataframes to CSV + vehicles_csv = vehicles_df.to_csv(index=False) + requests_csv = requests_df.to_csv(index=False) + config_json = json.dumps(config, indent=2) + + # Create download links + st.markdown(download_link(vehicles_csv, "sample_vehicles.csv", "Download Sample Vehicles CSV"), + unsafe_allow_html=True) + st.markdown(download_link(requests_csv, "sample_requests.csv", "Download Sample Requests CSV"), + unsafe_allow_html=True) + st.markdown(download_link(config_json, "sample_config.json", "Download Sample Configuration JSON"), + unsafe_allow_html=True) + + # Create a zip file with all samples + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file: + zip_file.writestr('sample_vehicles.csv', vehicles_csv) + zip_file.writestr('sample_requests.csv', requests_csv) + zip_file.writestr('sample_config.json', config_json) + + zip_buffer.seek(0) + b64 = base64.b64encode(zip_buffer.read()).decode() + zip_link = f'Download All Sample Files' + st.markdown(zip_link, unsafe_allow_html=True) + + elif page == "Visualize Results": + st.header("Visualization") + + if 'solution' in st.session_state: + st.subheader("Solution Overview") + st.write(f"Solved at: {st.session_state.get('solution_timestamp', 'Unknown')}") + st.write(f"Objective Value: {st.session_state['solution']['objective_value']}") + st.write(f"Computation Time: {st.session_state['solution']['computation_time']} seconds") + + st.subheader("Routes") + for i, route in enumerate(st.session_state['solution']['routes']): + st.write(f"Vehicle {route['vehicle_id']}: {' β†’ '.join(route['route'])}") + + # Visualization tabs + viz_tabs = st.tabs(["Route Map", "Gantt Chart", "Performance Metrics"]) + + with viz_tabs[0]: + st.subheader("Route Map") + # Here you would normally use fleetpy's mapping functionality + # For now, we'll just create a simple plot + + # Create a simple plot + fig, ax = plt.subplots(figsize=(10, 6)) + + # Dummy locations + locations = { + 'depot': (0, 0), + 'A': (1, 2), 'F': (3, 2), + 'B': (2, 3), 'G': (4, 3), + 'C': (1, 4), 'H': (3, 4), + 'D': (2, 1), 'I': (4, 1), + 'E': (1, 0), 'J': (3, 0) + } + + # Plot locations + for loc, (x, y) in locations.items(): + ax.scatter(x, y, s=100, c='blue' if loc == 'depot' else 'green') + ax.text(x, y, loc, fontsize=12) + + # Plot routes + colors = ['red', 'orange', 'purple'] + for i, route in enumerate(st.session_state['solution']['routes']): + route_points = route['route'] + for j in range(len(route_points) - 1): + start = locations[route_points[j]] + end = locations[route_points[j + 1]] + ax.plot([start[0], end[0]], [start[1], end[1]], color=colors[i], linewidth=2) + + ax.set_title("Route Map") + ax.set_xlabel("X coordinate") + ax.set_ylabel("Y coordinate") + ax.grid(True) + + st.pyplot(fig) + + with viz_tabs[1]: + st.subheader("Gantt Chart") + # Create a simple Gantt chart + fig, ax = plt.subplots(figsize=(10, 6)) + + # Dummy time data + times = { + 'v1': [(0, 10), (10, 20), (20, 30)], + 'v2': [(0, 15), (15, 25), (25, 40), (40, 50)], + 'v3': [(5, 15), (15, 25), (25, 35), (35, 45)] + } + + # Plot Gantt chart + y_positions = {'v1': 3, 'v2': 2, 'v3': 1} + colors = {'v1': 'red', 'v2': 'orange', 'v3': 'purple'} + + for vehicle, intervals in times.items(): + for i, (start, end) in enumerate(intervals): + ax.barh(y_positions[vehicle], end - start, left=start, height=0.5, + color=colors[vehicle], alpha=0.7) + ax.text(start + (end - start) / 2, y_positions[vehicle], + f"{i}", ha='center', va='center') + + ax.set_yticks(list(y_positions.values())) + ax.set_yticklabels(list(y_positions.keys())) + ax.set_title("Vehicle Schedules") + ax.set_xlabel("Time") + ax.set_ylabel("Vehicle") + ax.grid(True, axis='x') + + st.pyplot(fig) + + with viz_tabs[2]: + st.subheader("Performance Metrics") + + # Create some dummy metrics + metrics = { + "Total Distance": 150.75, + "Total Duration": 180.5, + "Vehicle Utilization": 0.85, + "Average Waiting Time": 12.3, + "Requests Served": 5, + "Vehicles Used": 3 + } + + # Display metrics + col1, col2 = st.columns(2) + + with col1: + for metric, value in list(metrics.items())[:3]: + st.metric(metric, value) + + with col2: + for metric, value in list(metrics.items())[3:]: + st.metric(metric, value) + + # Add a chart + fig, ax = plt.subplots(figsize=(10, 6)) + + # Vehicle utilization chart + vehicle_utils = { + 'v1': 0.75, + 'v2': 0.90, + 'v3': 0.85 + } + + ax.bar(vehicle_utils.keys(), vehicle_utils.values(), color=['red', 'orange', 'purple']) + ax.set_ylim(0, 1) + ax.set_title("Vehicle Utilization") + ax.set_xlabel("Vehicle") + ax.set_ylabel("Utilization Rate") + + for i, v in enumerate(vehicle_utils.values()): + ax.text(i, v + 0.05, f"{v:.2f}", ha='center') + + st.pyplot(fig) + + # Export options + st.subheader("Export Results") + + # Create a results dataframe + results_df = pd.DataFrame([ + {"vehicle": route["vehicle_id"], "route": " β†’ ".join(route["route"])} + for route in st.session_state['solution']['routes'] + ]) + + st.write(results_df) + + # Download results + st.markdown( + download_link(results_df, "fleetpy_results.csv", "Download Results CSV"), + unsafe_allow_html=True + ) + + # JSON results + results_json = json.dumps(st.session_state['solution'], indent=2) + st.markdown( + download_link(results_json, "fleetpy_results.json", "Download Results JSON"), + unsafe_allow_html=True + ) + else: + st.info("No solution available. Please run the solver first!") + st.button("Go to Upload Files", on_click=lambda: st.session_state.update({"page": "Upload Files"})) + + +def run_fleetpy(): + scs_path = os.path.join(os.path.dirname(__file__), "studies", "example_study", "scenarios") + cc = os.path.join(scs_path, "constant_config_ir.csv") + sc = os.path.join(scs_path, "example_ir_only.csv") + log_level = "info" + run_scenarios(cc, sc, log_level=log_level, n_cpu_per_sim=1, n_parallel_sim=1) + + +if __name__ == "__main__": + main() From 7da2ef27d4b94c7053c7eb206c427d4bd85e3c5a Mon Sep 17 00:00:00 2001 From: hoda_hamdy Date: Fri, 9 May 2025 16:12:39 +0200 Subject: [PATCH 2/6] change styling --- streamlit_gui.py | 1076 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 895 insertions(+), 181 deletions(-) diff --git a/streamlit_gui.py b/streamlit_gui.py index 64cef2ec..fd997253 100644 --- a/streamlit_gui.py +++ b/streamlit_gui.py @@ -1,19 +1,72 @@ import streamlit as st import pandas as pd -import numpy as np import matplotlib.pyplot as plt import os -import io -import zipfile import json import base64 from datetime import datetime - from run_examples import run_scenarios -# Set page config -st.set_page_config(page_title="FleetPy Web Interface", layout="wide") +# Set page config with enhanced styling +st.set_page_config( + page_title="FleetPy Web Interface", + layout="wide", + initial_sidebar_state="expanded", + page_icon="πŸš—" +) + +# Custom CSS styling +st.markdown(""" + + """, unsafe_allow_html=True) # Define helper functions @@ -67,72 +120,466 @@ def create_sample_files(): # Main app def main(): st.title("FleetPy Web Interface") - - # Sidebar - st.sidebar.header("Navigation") - page = st.sidebar.selectbox("Choose a page", - ["Home", "Create Scenario", "Upload Files", "Download Samples", "Visualize Results"]) - - if page == "Home": + st.markdown(""" +
+ Optimize your fleet operations with advanced routing and scheduling algorithms +
+ """, unsafe_allow_html=True) + + # Tab-based navigation with improved styling + tabs = st.tabs([ + "🏠 Home", + "πŸ“‹ Scenario Management", + "πŸ“€ Upload Files", + "βš™οΈ Simulation Config", + "πŸš— Fleet Management", + "πŸ“Š Visualize Results" + ]) + + with tabs[0]: st.header("Welcome to FleetPy Web Interface") - st.write(""" - This application allows you to interact with the FleetPy library for vehicle routing and scheduling. - Use the sidebar to navigate between different functionalities. - """) - - st.subheader("Quick Start") st.markdown(""" - 1. Download sample files to understand the required format - 2. Create a new scenario or upload your own files - 3. Run the solver and visualize results - """) +
+

What is FleetPy?

+

FleetPy is a powerful tool for vehicle routing and scheduling optimization. + It helps you efficiently manage your fleet operations by finding optimal routes and schedules + for your vehicles.

+
+ """, unsafe_allow_html=True) - elif page == "Create Scenario": - st.header("Create New Scenario") - - # Basic scenario setup - scenario_name = st.text_input("Scenario Name", "my_scenario") - - st.subheader("Algorithm Settings") - algorithm = st.selectbox("Algorithm", - ["insertion_heuristic", "local_search", "column_generation"]) - - objective = st.selectbox("Objective", - ["minimize_vehicles", "minimize_distance", "minimize_duration"]) - - max_time = st.slider("Max Computation Time (seconds)", 10, 600, 60) - - random_seed = st.number_input("Random Seed", 0, 9999, 42) - - st.subheader("Advanced Settings") - show_advanced = st.checkbox("Show Advanced Settings") - - if show_advanced: - st.write("Advanced settings go here...") - - # Create config dictionary - config = { - "scenario_name": scenario_name, - "algorithm": algorithm, - "objective": objective, - "max_computation_time": max_time, - "random_seed": random_seed - } - - if st.button("Save Configuration"): - # Store configuration in session state - st.session_state['config'] = config - st.success(f"Configuration for scenario '{scenario_name}' saved!") - - # Display the configuration - st.json(config) - - # Create download link for the config - config_json = json.dumps(config, indent=2) - st.markdown(download_link(config_json, f"{scenario_name}_config.json", "Download Configuration"), - unsafe_allow_html=True) - - elif page == "Upload Files": + st.subheader("Quick Start") + col1, col2, col3 = st.columns(3) + with col1: + st.markdown(""" +
+

1. Create or Load Scenario

+

Start with a sample scenario or create your own in the Scenario Management tab

+
+ """, unsafe_allow_html=True) + with col2: + st.markdown(""" +
+

2. Configure Settings

+

Adjust simulation and fleet settings to match your requirements

+
+ """, unsafe_allow_html=True) + with col3: + st.markdown(""" +
+

3. Run and Visualize

+

Run the simulation and analyze the results

+
+ """, unsafe_allow_html=True) + + with tabs[1]: # Scenario Management Tab + st.header("Scenario Management") + + # Initialize session state for scenarios if not exists + if 'scenarios' not in st.session_state: + st.session_state.scenarios = {} + if 'templates' not in st.session_state: + st.session_state.templates = {} + + # Tab navigation within Scenario Management + scenario_tabs = st.tabs(["Create New", "Samples", "Templates", "Compare Scenarios"]) + + with scenario_tabs[0]: # Create New Scenario + st.subheader("Create New Scenario") + + # Basic Information + col1, col2 = st.columns(2) + with col1: + scenario_name = st.text_input("Scenario Name", key="new_scenario_name") + with col2: + scenario_type = st.selectbox("Scenario Type", + ["Single Simulation", "Batch Simulation"], + key="new_scenario_type") + + # Description and Tags + scenario_description = st.text_area("Description", + help="Describe the purpose and key features of this scenario", + key="new_scenario_desc") + scenario_tags = st.multiselect("Tags", + ["Urban", "Rural", "Ridepooling", "Delivery", "Mixed"], + help="Add tags to categorize the scenario", + key="new_scenario_tags") + + # Algorithm Settings + st.subheader("Algorithm Settings") + col1, col2 = st.columns(2) + with col1: + algorithm = st.selectbox("Algorithm", + ["insertion_heuristic", "local_search", "column_generation"], + help="Select the optimization algorithm to use", + key="scenario_algorithm") + with col2: + objective = st.selectbox("Objective", + ["minimize_vehicles", "minimize_distance", "minimize_duration"], + help="Select the optimization objective", + key="scenario_objective") + + # Performance Settings + st.subheader("Performance Settings") + col1, col2 = st.columns(2) + with col1: + max_time = st.slider("Max Computation Time (seconds)", 10, 600, 60, + help="Maximum time allowed for the solver to run", + key="scenario_max_time") + with col2: + random_seed = st.number_input("Random Seed", 0, 9999, 42, + help="Random seed for reproducible results", + key="scenario_random_seed") + + # Configuration Source + st.subheader("Configuration Source") + config_source = st.radio("Load configuration from:", + ["Current Settings", "Template", "New Configuration"], + key="new_scenario_source") + + if config_source == "Template": + template_name = st.selectbox("Select Template", + list(st.session_state.templates.keys()), + key="new_scenario_template") + elif config_source == "New Configuration": + st.info("Configure settings in the Simulation Config and Fleet Management tabs first") + + # Save Scenario + if st.button("Save Scenario", key="save_new_scenario"): + if not scenario_name: + st.error("Please enter a scenario name") + else: + # Collect configuration from current settings + scenario_config = { + "name": scenario_name, + "type": scenario_type, + "description": scenario_description, + "tags": scenario_tags, + "algorithm": algorithm, + "objective": objective, + "max_time": max_time, + "random_seed": random_seed, + "sim_config": st.session_state.get('sim_config', {}), + "fleet_config": st.session_state.get('fleet_config', {}), + "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "last_modified": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + + st.session_state.scenarios[scenario_name] = scenario_config + st.success(f"Scenario '{scenario_name}' saved successfully!") + + # # TODO fix Navigation buttons + # st.markdown("---") + # col1, col2, col3 = st.columns([1, 1, 1]) + # with col2: + # if st.button("Next: Upload Files", key="next_upload_files"): + # st.session_state.current_tab = "Upload Files" + # st.rerun() + + with scenario_tabs[1]: # Samples + st.subheader("Sample Scenarios") + st.info("These are pre-configured scenarios to help you get started. Load a sample to see how different configurations work.") + + # Sample scenarios + sample_scenarios = { + "Basic Urban Ridepooling": { + "description": "A simple urban ridepooling scenario with a small fleet", + "type": "Single Simulation", + "tags": ["Urban", "Ridepooling"], + "sim_config": { + "start_time": 0, + "end_time": 3600, + "time_step": 1, + "random_seed": 42, + "route_output": True, + "replay_output": False, + "network_type": "NetworkBase" + }, + "fleet_config": { + "composition": [ + {"type": "sedan", "quantity": 5, "capacity": 4} + ], + "control_strategy": "insertion_heuristic" + } + }, + "Large Fleet Delivery": { + "description": "A delivery scenario with a large mixed fleet", + "type": "Single Simulation", + "tags": ["Urban", "Delivery"], + "sim_config": { + "start_time": 0, + "end_time": 7200, + "time_step": 1, + "random_seed": 42, + "route_output": True, + "replay_output": True, + "network_type": "NetworkBase" + }, + "fleet_config": { + "composition": [ + {"type": "van", "quantity": 10, "capacity": 20}, + {"type": "truck", "quantity": 5, "capacity": 30} + ], + "control_strategy": "local_search" + } + }, + "Mixed Fleet Ridepooling": { + "description": "A ridepooling scenario with different vehicle types", + "type": "Single Simulation", + "tags": ["Urban", "Ridepooling", "Mixed"], + "sim_config": { + "start_time": 0, + "end_time": 10800, + "time_step": 1, + "random_seed": 42, + "route_output": True, + "replay_output": True, + "network_type": "NetworkDynamicNFDClusters", + "density_bin_size": 300, + "density_avg_duration": 900 + }, + "fleet_config": { + "composition": [ + {"type": "sedan", "quantity": 8, "capacity": 4}, + {"type": "van", "quantity": 4, "capacity": 8} + ], + "control_strategy": "column_generation" + } + } + } + + # Display sample scenarios + for name, scenario in sample_scenarios.items(): + with st.expander(f"πŸ“‹ {name}"): + st.write(f"**Description:** {scenario['description']}") + st.write(f"**Type:** {scenario['type']}") + st.write(f"**Tags:** {', '.join(scenario['tags'])}") + + # Show configuration preview + st.markdown("**Configuration Preview:**") + col1, col2 = st.columns(2) + + with col1: + st.markdown("**Simulation Configuration**") + st.json(scenario['sim_config']) + + with col2: + st.markdown("**Fleet Configuration**") + st.json(scenario['fleet_config']) + + if st.button("Load Sample", key=f"load_sample_{name}"): + st.session_state.sim_config = scenario['sim_config'] + st.session_state.fleet_config = scenario['fleet_config'] + st.success(f"Sample scenario '{name}' loaded! Configure additional settings in the Simulation Config and Fleet Management tabs.") + st.info("Note: You may need to upload your own data files in the Upload Files tab.") + + # # Navigation buttons + # st.markdown("---") + # col1, col2, col3 = st.columns([1, 1, 1]) + # with col2: + # if st.button("Next: Upload Files", key="next_upload_files_samples"): + # st.session_state.current_tab = "Upload Files" + # st.rerun() + + with scenario_tabs[2]: # Templates + st.subheader("Scenario Templates") + + # Create Template + st.markdown("### Create New Template") + template_name = st.text_input("Template Name", key="new_template_name") + template_description = st.text_area("Template Description", key="new_template_desc") + + # Show what will be saved in the template + st.markdown("### Template Contents") + st.info("This template will save the following configurations:") + + # Create columns for better layout + col1, col2 = st.columns(2) + + with col1: + st.markdown("**Simulation Configuration**") + if st.session_state.get('sim_config'): + sim_config = st.session_state.sim_config + st.json({ + "Time Settings": { + "Start Time": sim_config.get('start_time', 'Not set'), + "End Time": sim_config.get('end_time', 'Not set'), + "Time Step": sim_config.get('time_step', 'Not set') + }, + "Randomization": { + "Random Seed": sim_config.get('random_seed', 'Not set') + }, + "Output Settings": { + "Route Output": sim_config.get('route_output', 'Not set'), + "Replay Output": sim_config.get('replay_output', 'Not set') + }, + "Network Type": sim_config.get('network_type', 'Not set') + }) + else: + st.warning("No simulation configuration found. Configure settings in the Simulation Config tab first.") + + with col2: + st.markdown("**Fleet Configuration**") + if st.session_state.get('fleet_config'): + fleet_config = st.session_state.fleet_config + st.json({ + "Fleet Composition": fleet_config.get('composition', []), + "Control Strategy": fleet_config.get('control_strategy', 'Not set') + }) + else: + st.warning("No fleet configuration found. Configure settings in the Fleet Management tab first.") + + if st.button("Save as Template", key="save_template"): + if not template_name: + st.error("Please enter a template name") + elif not st.session_state.get('sim_config') or not st.session_state.get('fleet_config'): + st.error("Please configure both simulation and fleet settings before saving as template") + else: + template_config = { + "name": template_name, + "description": template_description, + "sim_config": st.session_state.get('sim_config', {}), + "fleet_config": st.session_state.get('fleet_config', {}), + "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + + st.session_state.templates[template_name] = template_config + st.success(f"Template '{template_name}' saved successfully!") + + # Display Templates + st.markdown("### Saved Templates") + if st.session_state.templates: + for template_name, template in st.session_state.templates.items(): + with st.expander(f"πŸ“‹ {template_name}"): + st.write(f"**Description:** {template['description']}") + st.write(f"**Created:** {template['created_at']}") + + # Show template contents + st.markdown("**Template Contents:**") + col1, col2 = st.columns(2) + + with col1: + st.markdown("**Simulation Configuration**") + st.json({ + "Time Settings": { + "Start Time": template['sim_config'].get('start_time', 'Not set'), + "End Time": template['sim_config'].get('end_time', 'Not set'), + "Time Step": template['sim_config'].get('time_step', 'Not set') + }, + "Randomization": { + "Random Seed": template['sim_config'].get('random_seed', 'Not set') + }, + "Output Settings": { + "Route Output": template['sim_config'].get('route_output', 'Not set'), + "Replay Output": template['sim_config'].get('replay_output', 'Not set') + }, + "Network Type": template['sim_config'].get('network_type', 'Not set') + }) + + with col2: + st.markdown("**Fleet Configuration**") + st.json({ + "Fleet Composition": template['fleet_config'].get('composition', []), + "Control Strategy": template['fleet_config'].get('control_strategy', 'Not set') + }) + + col1, col2 = st.columns(2) + with col1: + if st.button("Load Template", key=f"load_{template_name}"): + st.session_state.sim_config = template['sim_config'] + st.session_state.fleet_config = template['fleet_config'] + st.success(f"Template '{template_name}' loaded!") + with col2: + if st.button("Delete Template", key=f"delete_{template_name}"): + del st.session_state.templates[template_name] + st.success(f"Template '{template_name}' deleted!") + else: + st.info("No templates saved yet") + + # # Navigation buttons + # st.markdown("---") + # col1, col2, col3 = st.columns([1, 1, 1]) + # with col2: + # if st.button("Next: Upload Files", key="next_upload_files_templates"): + # st.session_state.current_tab = "Upload Files" + # st.rerun() + + with scenario_tabs[3]: # Compare Scenarios + st.subheader("Compare Scenarios") + + if len(st.session_state.scenarios) < 2: + st.info("You need at least 2 scenarios to compare") + else: + # Select scenarios to compare + selected_scenarios = st.multiselect("Select Scenarios to Compare", + list(st.session_state.scenarios.keys()), + default=list(st.session_state.scenarios.keys())[:2]) + + if len(selected_scenarios) >= 2: + # Basic Comparison + st.markdown("### Basic Comparison") + comparison_data = [] + for scenario_name in selected_scenarios: + scenario = st.session_state.scenarios[scenario_name] + comparison_data.append({ + "Name": scenario_name, + "Type": scenario["type"], + "Created": scenario["created_at"], + "Last Modified": scenario["last_modified"], + "Tags": ", ".join(scenario["tags"]), + "Fleet Size": sum(item['quantity'] for item in scenario["fleet_config"].get("composition", [])), + "Simulation Duration": f"{scenario['sim_config'].get('end_time', 0) - scenario['sim_config'].get('start_time', 0)} seconds" + }) + + comparison_df = pd.DataFrame(comparison_data) + st.dataframe(comparison_df) + + # Configuration Comparison + st.markdown("### Configuration Comparison") + config_tabs = st.tabs(["Simulation Config", "Fleet Config"]) + + with config_tabs[0]: + sim_configs = {} + for scenario_name in selected_scenarios: + sim_configs[scenario_name] = st.session_state.scenarios[scenario_name]["sim_config"] + + # Create a DataFrame for simulation configurations + sim_comparison = pd.DataFrame(sim_configs).T + st.dataframe(sim_comparison) + + with config_tabs[1]: + fleet_configs = {} + for scenario_name in selected_scenarios: + fleet_configs[scenario_name] = st.session_state.scenarios[scenario_name]["fleet_config"] + + # Create a DataFrame for fleet configurations + fleet_comparison = pd.DataFrame(fleet_configs).T + st.dataframe(fleet_comparison) + + # Export Comparison + if st.button("Export Comparison", key="export_comparison"): + # Create a dictionary with all comparison data + export_data = { + "basic_comparison": comparison_df.to_dict(), + "sim_config_comparison": sim_comparison.to_dict(), + "fleet_config_comparison": fleet_comparison.to_dict() + } + + # Convert to JSON + json_data = json.dumps(export_data, indent=2) + + # Create download link + st.markdown(download_link(json_data, "scenario_comparison.json", "πŸ“₯ Download Comparison"), + unsafe_allow_html=True) + + # # Navigation buttons + # st.markdown("---") + # col1, col2, col3 = st.columns([1, 1, 1]) + # with col2: + # if st.button("Next: Upload Files", key="next_upload_files_compare"): + # st.session_state.current_tab = "Upload Files" + # st.rerun() + + with tabs[2]: st.header("Upload Files") # Upload vehicles file @@ -155,7 +602,7 @@ def main(): # Upload configuration file st.subheader("Upload Configuration File") - config_file = st.file_uploader("Choose a JSON file", type="json", key="config") + config_file = st.file_uploader("Choose a JSON file", type="json") if config_file is not None: config = json.load(config_file) st.session_state['config'] = config @@ -221,79 +668,280 @@ def main(): # Redirect to visualization page st.info("Redirecting to visualization page...") - # st.rerun() + st.rerun() else: st.error("Please upload all required files first!") - elif page == "Download Samples": - st.header("Download Sample Files") - - # Create sample files - vehicles_df, requests_df, config = create_sample_files() - - # Display sample files - st.subheader("Sample Vehicles File") - st.write(vehicles_df) - - st.subheader("Sample Requests File") - st.write(requests_df) - - st.subheader("Sample Configuration") - st.json(config) - - # Create download links - st.subheader("Download Links") - - # Convert dataframes to CSV - vehicles_csv = vehicles_df.to_csv(index=False) - requests_csv = requests_df.to_csv(index=False) - config_json = json.dumps(config, indent=2) - - # Create download links - st.markdown(download_link(vehicles_csv, "sample_vehicles.csv", "Download Sample Vehicles CSV"), - unsafe_allow_html=True) - st.markdown(download_link(requests_csv, "sample_requests.csv", "Download Sample Requests CSV"), - unsafe_allow_html=True) - st.markdown(download_link(config_json, "sample_config.json", "Download Sample Configuration JSON"), - unsafe_allow_html=True) - - # Create a zip file with all samples - zip_buffer = io.BytesIO() - with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file: - zip_file.writestr('sample_vehicles.csv', vehicles_csv) - zip_file.writestr('sample_requests.csv', requests_csv) - zip_file.writestr('sample_config.json', config_json) - - zip_buffer.seek(0) - b64 = base64.b64encode(zip_buffer.read()).decode() - zip_link = f'Download All Sample Files' - st.markdown(zip_link, unsafe_allow_html=True) - - elif page == "Visualize Results": + # # Navigation buttons + # st.markdown("---") + # col1, col2, col3 = st.columns([1, 1, 1]) + # with col1: + # if st.button("← Back: Scenario Management", key="back_scenario_management"): + # st.session_state.current_tab = "Scenario Management" + # st.rerun() + # with col3: + # if st.button("Next: Simulation Config β†’", key="next_simulation_config"): + # st.session_state.current_tab = "Simulation Config" + # st.rerun() + + with tabs[3]: # Simulation Configuration Tab + st.header("Simulation Configuration") + + # Simulation Time Settings + st.subheader("Time Settings") + col1, col2, col3 = st.columns(3) + with col1: + start_time = st.number_input("Start Time (seconds)", 0, 86400, 0, + help="Simulation start time in seconds", + key="sim_start_time") + with col2: + end_time = st.number_input("End Time (seconds)", 0, 86400, 86400, + help="Simulation end time in seconds", + key="sim_end_time") + with col3: + time_step = st.number_input("Time Step (seconds)", 1, 3600, 1, + help="Simulation time step in seconds", + key="sim_time_step") + + # Random Seed and Output Settings + st.subheader("Randomization and Output") + col1, col2 = st.columns(2) + with col1: + random_seed = st.number_input("Random Seed", 0, 9999, 42, + help="Random seed for reproducible results", + key="sim_random_seed") + with col2: + route_output = st.checkbox("Output Complete Routes", True, + help="Output complete vehicle routes to files", + key="sim_route_output") + replay_output = st.checkbox("Output Node Passing Times", False, + help="Output times when vehicles pass nodes", + key="sim_replay_output") + + # Real-time Visualization Settings + st.subheader("Real-time Visualization") + realtime_plot = st.selectbox("Real-time Plot Mode", + ["None", "Live Plot", "Save Plots"], + help="Choose real-time visualization mode", + key="sim_realtime_plot") + + if realtime_plot != "None": + col1, col2 = st.columns(2) + with col1: + plot_extent = st.text_input("Plot Extent (min_lon, max_lon, min_lat, max_lat)", + help="Bounding box for visualization", + key="sim_plot_extent") + with col2: + vehicle_status = st.multiselect("Vehicle Status to Display", + ["driving", "charging", "idle", "boarding", "alighting"], + default=["driving", "charging"], + help="Select vehicle statuses to display in real-time", + key="sim_vehicle_status") + + # Network Configuration + st.subheader("Network Configuration") + network_type = st.selectbox("Network Type", + ["NetworkBase", "NetworkDynamicNFDClusters"], + help="Select the network representation type", + key="sim_network_type") + + if network_type == "NetworkDynamicNFDClusters": + col1, col2 = st.columns(2) + with col1: + density_bin_size = st.number_input("Density Bin Size (seconds)", 1, 3600, 300, + help="Time bin size for network density calculation", + key="sim_density_bin_size") + with col2: + density_avg_duration = st.number_input("Density Average Duration (seconds)", 1, 3600, 900, + help="Duration for averaging network density", + key="sim_density_avg_duration") + + # Save Configuration + if st.button("Save Simulation Configuration", key="sim_save_config"): + sim_config = { + "start_time": start_time, + "end_time": end_time, + "time_step": time_step, + "random_seed": random_seed, + "route_output": route_output, + "replay_output": replay_output, + "realtime_plot": realtime_plot, + "plot_extent": plot_extent if realtime_plot != "None" else None, + "vehicle_status": vehicle_status if realtime_plot != "None" else None, + "network_type": network_type, + "density_bin_size": density_bin_size if network_type == "NetworkDynamicNFDClusters" else None, + "density_avg_duration": density_avg_duration if network_type == "NetworkDynamicNFDClusters" else None + } + st.session_state['sim_config'] = sim_config + st.success("Simulation configuration saved!") + + # # Navigation buttons + # st.markdown("---") + # col1, col2, col3 = st.columns([1, 1, 1]) + # with col1: + # if st.button("← Back: Upload Files", key="back_upload_files"): + # st.session_state.current_tab = "Upload Files" + # st.rerun() + # with col3: + # if st.button("Next: Fleet Management β†’", key="next_fleet_management"): + # st.session_state.current_tab = "Fleet Management" + # st.rerun() + + with tabs[4]: # Fleet Management Tab + st.header("Fleet Management") + + # Fleet Composition + st.subheader("Fleet Composition") + if 'fleet_composition' not in st.session_state: + st.session_state.fleet_composition = [] + + col1, col2, col3 = st.columns(3) + with col1: + vehicle_type = st.selectbox("Vehicle Type", ["sedan", "truck", "van", "bus"], + key="fleet_vehicle_type") + with col2: + quantity = st.number_input("Quantity", 1, 100, 1, + key="fleet_quantity") + with col3: + capacity = st.number_input("Capacity", 1, 50, 4, + key="fleet_capacity") + + if st.button("Add Vehicle Type", key="fleet_add_vehicle"): + st.session_state.fleet_composition.append({ + "type": vehicle_type, + "quantity": quantity, + "capacity": capacity + }) + + # Display Current Fleet + st.subheader("Current Fleet") + if st.session_state.fleet_composition: + fleet_df = pd.DataFrame(st.session_state.fleet_composition) + st.dataframe(fleet_df) + + # Fleet Statistics + total_vehicles = sum(item['quantity'] for item in st.session_state.fleet_composition) + avg_capacity = sum(item['capacity'] * item['quantity'] for item in st.session_state.fleet_composition) / total_vehicles + + col1, col2 = st.columns(2) + with col1: + st.metric("Total Vehicles", total_vehicles) + with col2: + st.metric("Average Capacity", f"{avg_capacity:.1f}") + else: + st.info("No vehicles added to the fleet yet.") + + # Fleet Control Strategy + st.subheader("Fleet Control Strategy") + control_strategy = st.selectbox("Control Strategy", + ["insertion_heuristic", "local_search", "column_generation"], + help="Select the fleet control strategy", + key="fleet_control_strategy") + + if control_strategy == "insertion_heuristic": + st.info("Insertion Heuristic: Simple and fast, good for small to medium fleets") + elif control_strategy == "local_search": + st.info("Local Search: More sophisticated, better for larger fleets") + else: + st.info("Column Generation: Advanced optimization, best for complex scenarios") + + # Vehicle Status Monitoring + st.subheader("Vehicle Status Monitoring") + if 'vehicle_status' in st.session_state: + status_df = pd.DataFrame(st.session_state.vehicle_status) + st.dataframe(status_df) + + # Status Distribution Chart + fig, ax = plt.subplots() + status_counts = status_df['status'].value_counts() + ax.pie(status_counts, labels=status_counts.index, autopct='%1.1f%%') + st.pyplot(fig) + else: + st.info("No vehicle status data available. Run a simulation to see status information.") + + # Save Fleet Configuration + if st.button("Save Fleet Configuration", key="fleet_save_config"): + fleet_config = { + "composition": st.session_state.fleet_composition, + "control_strategy": control_strategy + } + st.session_state['fleet_config'] = fleet_config + st.success("Fleet configuration saved!") + + # # Navigation buttons + # st.markdown("---") + # col1, col2, col3 = st.columns([1, 1, 1]) + # with col1: + # if st.button("← Back: Simulation Config", key="back_simulation_config"): + # st.session_state.current_tab = "Simulation Config" + # st.rerun() + # with col3: + # if st.button("Next: Visualize Results β†’", key="next_visualize_results"): + # st.session_state.current_tab = "Visualize Results" + # st.rerun() + + with tabs[5]: # Visualize Results Tab st.header("Visualization") if 'solution' in st.session_state: - st.subheader("Solution Overview") - st.write(f"Solved at: {st.session_state.get('solution_timestamp', 'Unknown')}") - st.write(f"Objective Value: {st.session_state['solution']['objective_value']}") - st.write(f"Computation Time: {st.session_state['solution']['computation_time']} seconds") + # Solution overview in a card + st.markdown(""" +
+

Solution Overview

+
+ """, unsafe_allow_html=True) + + col1, col2, col3 = st.columns(3) + with col1: + st.metric("Solved at", st.session_state.get('solution_timestamp', 'Unknown')) + with col2: + st.metric("Objective Value", f"{st.session_state['solution']['objective_value']:.2f}") + with col3: + st.metric("Computation Time", f"{st.session_state['solution']['computation_time']:.2f} seconds") + + # Routes in a card + st.markdown(""" +
+

Routes

+
+ """, unsafe_allow_html=True) - st.subheader("Routes") for i, route in enumerate(st.session_state['solution']['routes']): - st.write(f"Vehicle {route['vehicle_id']}: {' β†’ '.join(route['route'])}") + st.markdown(f""" +
+

Vehicle {route['vehicle_id']}

+

Route: {' β†’ '.join(route['route'])}

+
+ """, unsafe_allow_html=True) - # Visualization tabs - viz_tabs = st.tabs(["Route Map", "Gantt Chart", "Performance Metrics"]) + # Visualization tabs with improved styling + viz_tabs = st.tabs(["πŸ—ΊοΈ Route Map", "πŸ“… Gantt Chart", "πŸ“Š Performance Metrics"]) with viz_tabs[0]: - st.subheader("Route Map") - # Here you would normally use fleetpy's mapping functionality - # For now, we'll just create a simple plot - - # Create a simple plot - fig, ax = plt.subplots(figsize=(10, 6)) - - # Dummy locations + st.markdown(""" +
+

Route Map

+
+ """, unsafe_allow_html=True) + + # Create a simple plot with improved styling + fig, ax = plt.subplots(figsize=(12, 8)) + plt.style.use('default') # Use default style as base + + # Set custom style parameters + plt.rcParams.update({ + 'font.size': 12, + 'axes.labelsize': 12, + 'axes.titlesize': 16, + 'xtick.labelsize': 10, + 'ytick.labelsize': 10, + 'axes.grid': True, + 'grid.alpha': 0.3, + 'axes.facecolor': 'white', + 'figure.facecolor': 'white' + }) + + # Dummy locations with improved styling locations = { 'depot': (0, 0), 'A': (1, 2), 'F': (3, 2), @@ -303,31 +951,52 @@ def main(): 'E': (1, 0), 'J': (3, 0) } - # Plot locations + # Plot locations with improved styling for loc, (x, y) in locations.items(): - ax.scatter(x, y, s=100, c='blue' if loc == 'depot' else 'green') - ax.text(x, y, loc, fontsize=12) + color = '#4CAF50' if loc == 'depot' else '#2196F3' + ax.scatter(x, y, s=150, c=color, edgecolors='white', linewidth=2) + ax.text(x, y, loc, fontsize=14, ha='center', va='center', color='white') - # Plot routes - colors = ['red', 'orange', 'purple'] + # Plot routes with improved styling + colors = ['#FF5722', '#9C27B0', '#FFC107'] for i, route in enumerate(st.session_state['solution']['routes']): route_points = route['route'] for j in range(len(route_points) - 1): start = locations[route_points[j]] end = locations[route_points[j + 1]] - ax.plot([start[0], end[0]], [start[1], end[1]], color=colors[i], linewidth=2) + ax.plot([start[0], end[0]], [start[1], end[1]], + color=colors[i], linewidth=3, alpha=0.8) - ax.set_title("Route Map") + ax.set_title("Route Map", pad=20) ax.set_xlabel("X coordinate") ax.set_ylabel("Y coordinate") - ax.grid(True) + plt.tight_layout() st.pyplot(fig) with viz_tabs[1]: - st.subheader("Gantt Chart") - # Create a simple Gantt chart - fig, ax = plt.subplots(figsize=(10, 6)) + st.markdown(""" +
+

Gantt Chart

+
+ """, unsafe_allow_html=True) + + # Create a Gantt chart with improved styling + fig, ax = plt.subplots(figsize=(12, 6)) + plt.style.use('default') # Use default style as base + + # Set custom style parameters + plt.rcParams.update({ + 'font.size': 12, + 'axes.labelsize': 12, + 'axes.titlesize': 16, + 'xtick.labelsize': 10, + 'ytick.labelsize': 10, + 'axes.grid': True, + 'grid.alpha': 0.3, + 'axes.facecolor': 'white', + 'figure.facecolor': 'white' + }) # Dummy time data times = { @@ -336,30 +1005,34 @@ def main(): 'v3': [(5, 15), (15, 25), (25, 35), (35, 45)] } - # Plot Gantt chart + # Plot Gantt chart with improved styling y_positions = {'v1': 3, 'v2': 2, 'v3': 1} - colors = {'v1': 'red', 'v2': 'orange', 'v3': 'purple'} + colors = {'v1': '#FF5722', 'v2': '#9C27B0', 'v3': '#FFC107'} for vehicle, intervals in times.items(): for i, (start, end) in enumerate(intervals): ax.barh(y_positions[vehicle], end - start, left=start, height=0.5, - color=colors[vehicle], alpha=0.7) + color=colors[vehicle], alpha=0.8, edgecolor='white', linewidth=1) ax.text(start + (end - start) / 2, y_positions[vehicle], - f"{i}", ha='center', va='center') + f"{i}", ha='center', va='center', color='white', fontweight='bold') ax.set_yticks(list(y_positions.values())) ax.set_yticklabels(list(y_positions.keys())) - ax.set_title("Vehicle Schedules") + ax.set_title("Vehicle Schedules", pad=20) ax.set_xlabel("Time") ax.set_ylabel("Vehicle") - ax.grid(True, axis='x') + plt.tight_layout() st.pyplot(fig) with viz_tabs[2]: - st.subheader("Performance Metrics") + st.markdown(""" +
+

Performance Metrics

+
+ """, unsafe_allow_html=True) - # Create some dummy metrics + # Create metrics with improved styling metrics = { "Total Distance": 150.75, "Total Duration": 180.5, @@ -369,40 +1042,68 @@ def main(): "Vehicles Used": 3 } - # Display metrics - col1, col2 = st.columns(2) - + # Display metrics in a grid + col1, col2, col3 = st.columns(3) with col1: - for metric, value in list(metrics.items())[:3]: - st.metric(metric, value) - + for metric, value in list(metrics.items())[:2]: + st.metric(metric, f"{value:.2f}") with col2: - for metric, value in list(metrics.items())[3:]: + for metric, value in list(metrics.items())[2:4]: + st.metric(metric, f"{value:.2f}") + with col3: + for metric, value in list(metrics.items())[4:]: st.metric(metric, value) - # Add a chart + # Add a chart with improved styling + st.markdown(""" +
+

Vehicle Utilization

+
+ """, unsafe_allow_html=True) + fig, ax = plt.subplots(figsize=(10, 6)) + plt.style.use('default') # Use default style as base + + # Set custom style parameters + plt.rcParams.update({ + 'font.size': 12, + 'axes.labelsize': 12, + 'axes.titlesize': 16, + 'xtick.labelsize': 10, + 'ytick.labelsize': 10, + 'axes.grid': True, + 'grid.alpha': 0.3, + 'axes.facecolor': 'white', + 'figure.facecolor': 'white' + }) - # Vehicle utilization chart vehicle_utils = { 'v1': 0.75, 'v2': 0.90, 'v3': 0.85 } - ax.bar(vehicle_utils.keys(), vehicle_utils.values(), color=['red', 'orange', 'purple']) + colors = ['#FF5722', '#9C27B0', '#FFC107'] + bars = ax.bar(vehicle_utils.keys(), vehicle_utils.values(), color=colors) ax.set_ylim(0, 1) - ax.set_title("Vehicle Utilization") + ax.set_title("Vehicle Utilization", pad=20) ax.set_xlabel("Vehicle") ax.set_ylabel("Utilization Rate") - for i, v in enumerate(vehicle_utils.values()): - ax.text(i, v + 0.05, f"{v:.2f}", ha='center') + for bar in bars: + height = bar.get_height() + ax.text(bar.get_x() + bar.get_width()/2., height + 0.05, + f"{height:.2f}", ha='center', va='bottom') + plt.tight_layout() st.pyplot(fig) - # Export options - st.subheader("Export Results") + # Export options with improved styling + st.markdown(""" +
+

Export Results

+
+ """, unsafe_allow_html=True) # Create a results dataframe results_df = pd.DataFrame([ @@ -410,26 +1111,39 @@ def main(): for route in st.session_state['solution']['routes'] ]) - st.write(results_df) + st.dataframe(results_df, use_container_width=True) - # Download results - st.markdown( - download_link(results_df, "fleetpy_results.csv", "Download Results CSV"), - unsafe_allow_html=True - ) - - # JSON results - results_json = json.dumps(st.session_state['solution'], indent=2) - st.markdown( - download_link(results_json, "fleetpy_results.json", "Download Results JSON"), - unsafe_allow_html=True - ) + # Download buttons with improved styling + col1, col2 = st.columns(2) + with col1: + st.markdown( + download_link(results_df, "fleetpy_results.csv", "πŸ“₯ Download Results CSV"), + unsafe_allow_html=True + ) + with col2: + results_json = json.dumps(st.session_state['solution'], indent=2) + st.markdown( + download_link(results_json, "fleetpy_results.json", "πŸ“₯ Download Results JSON"), + unsafe_allow_html=True + ) else: - st.info("No solution available. Please run the solver first!") - st.button("Go to Upload Files", on_click=lambda: st.session_state.update({"page": "Upload Files"})) + st.info("No solution available. Please run the solver first!") + + # # Navigation buttons + # st.markdown("---") + # col1, col2, col3 = st.columns([1, 1, 1]) + # with col1: + # if st.button("← Back: Fleet Management", key="back_fleet_management"): + # st.session_state.current_tab = "Fleet Management" + # st.rerun() + # with col2: + # if st.button("Start Over", key="start_over"): + # st.session_state.current_tab = "Home" + # st.rerun() def run_fleetpy(): + # TODO parameterize scs_path = os.path.join(os.path.dirname(__file__), "studies", "example_study", "scenarios") cc = os.path.join(scs_path, "constant_config_ir.csv") sc = os.path.join(scs_path, "example_ir_only.csv") From 45d4784ec0ac0c2c7ab76327c80b6a0ba95de120 Mon Sep 17 00:00:00 2001 From: hoda_hamdy Date: Mon, 16 Jun 2025 11:06:47 +0200 Subject: [PATCH 3/6] scenario creator in streamlit --- streamlit_gui.py | 1722 ++++++++++++++++------------------------------ 1 file changed, 605 insertions(+), 1117 deletions(-) diff --git a/streamlit_gui.py b/streamlit_gui.py index fd997253..23dac5b9 100644 --- a/streamlit_gui.py +++ b/streamlit_gui.py @@ -1,1155 +1,643 @@ -import streamlit as st -import pandas as pd -import matplotlib.pyplot as plt import os -import json -import base64 -from datetime import datetime - -from run_examples import run_scenarios - -# Set page config with enhanced styling -st.set_page_config( - page_title="FleetPy Web Interface", - layout="wide", - initial_sidebar_state="expanded", - page_icon="πŸš—" -) - -# Custom CSS styling -st.markdown(""" - - """, unsafe_allow_html=True) - - -# Define helper functions -def download_link(object_to_download, download_filename, download_link_text): - """ - Generates a link to download the given object_to_download. - """ - if isinstance(object_to_download, pd.DataFrame): - object_to_download = object_to_download.to_csv(index=False) - - # Some strings <-> bytes conversions necessary here - b64 = base64.b64encode(object_to_download.encode()).decode() - - return f'{download_link_text}' - - -def create_sample_files(): - """Create sample files for users to download""" - # Sample vehicle data - vehicles_df = pd.DataFrame({ - 'vehicle_id': ['v1', 'v2', 'v3'], - 'type': ['sedan', 'truck', 'van'], - 'capacity': [4, 2, 6], - 'start_location': ['depot', 'depot', 'depot'], - 'end_location': ['depot', 'depot', 'depot'] - }) +import sys +import streamlit as st +import multiprocessing as mp +import traceback +from pathlib import Path - # Sample request data - requests_df = pd.DataFrame({ - 'request_id': ['r1', 'r2', 'r3', 'r4', 'r5'], - 'pickup_location': ['A', 'B', 'C', 'D', 'E'], - 'dropoff_location': ['F', 'G', 'H', 'I', 'J'], - 'pickup_time_window_start': [0, 10, 20, 30, 40], - 'pickup_time_window_end': [30, 40, 50, 60, 70], - 'dropoff_time_window_start': [30, 40, 50, 60, 70], - 'dropoff_time_window_end': [60, 70, 80, 90, 100] - }) +# Add FleetPy path to system path +fleetpy_path = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(fleetpy_path) - # Sample configuration - config = { - "scenario_name": "sample_scenario", - "algorithm": "insertion_heuristic", - "objective": "minimize_vehicles", - "max_computation_time": 60, - "random_seed": 42 +from run_examples import run_scenarios +from src.scenario_gui.scenario_creator import ScenarioCreator, MODULE_PARAM_TO_DICT_LOAD, parameter_docs +from src.FleetSimulationBase import INPUT_PARAMETERS_FleetSimulationBase + + + +def get_abnormal_param_options(param): + if param == "network_name": + path = os.path.join(fleetpy_path, "data", "networks") + return [""] + os.listdir(path) + elif param == "demand_name": + path = os.path.join(fleetpy_path, "data", "demand") + return [""] + os.listdir(path) + elif param == "rq_file": + # Will be populated based on network and demand selection + return [""] + return None + +def categorize_parameters(param_names, param_dict): + """Categorize parameters into logical groups based on their prefixes and meanings""" + categories = { + "Basic Settings": [], + "Time Settings": [], + "Request Settings": [], + "Operator Settings": [], + "Parcel Settings": [], + "Vehicle Settings": [], + "Infrastructure": [], + "Other": [] } - - return vehicles_df, requests_df, config - - -# Main app -def main(): - st.title("FleetPy Web Interface") - st.markdown(""" -
- Optimize your fleet operations with advanced routing and scheduling algorithms -
- """, unsafe_allow_html=True) - - # Tab-based navigation with improved styling - tabs = st.tabs([ - "🏠 Home", - "πŸ“‹ Scenario Management", - "πŸ“€ Upload Files", - "βš™οΈ Simulation Config", - "πŸš— Fleet Management", - "πŸ“Š Visualize Results" - ]) - - with tabs[0]: - st.header("Welcome to FleetPy Web Interface") - st.markdown(""" -
-

What is FleetPy?

-

FleetPy is a powerful tool for vehicle routing and scheduling optimization. - It helps you efficiently manage your fleet operations by finding optimal routes and schedules - for your vehicles.

-
- """, unsafe_allow_html=True) - - st.subheader("Quick Start") - col1, col2, col3 = st.columns(3) - with col1: - st.markdown(""" -
-

1. Create or Load Scenario

-

Start with a sample scenario or create your own in the Scenario Management tab

-
- """, unsafe_allow_html=True) - with col2: - st.markdown(""" -
-

2. Configure Settings

-

Adjust simulation and fleet settings to match your requirements

-
- """, unsafe_allow_html=True) - with col3: - st.markdown(""" -
-

3. Run and Visualize

-

Run the simulation and analyze the results

-
- """, unsafe_allow_html=True) - - with tabs[1]: # Scenario Management Tab - st.header("Scenario Management") - - # Initialize session state for scenarios if not exists - if 'scenarios' not in st.session_state: - st.session_state.scenarios = {} - if 'templates' not in st.session_state: - st.session_state.templates = {} - - # Tab navigation within Scenario Management - scenario_tabs = st.tabs(["Create New", "Samples", "Templates", "Compare Scenarios"]) - - with scenario_tabs[0]: # Create New Scenario - st.subheader("Create New Scenario") - - # Basic Information - col1, col2 = st.columns(2) - with col1: - scenario_name = st.text_input("Scenario Name", key="new_scenario_name") - with col2: - scenario_type = st.selectbox("Scenario Type", - ["Single Simulation", "Batch Simulation"], - key="new_scenario_type") - - # Description and Tags - scenario_description = st.text_area("Description", - help="Describe the purpose and key features of this scenario", - key="new_scenario_desc") - scenario_tags = st.multiselect("Tags", - ["Urban", "Rural", "Ridepooling", "Delivery", "Mixed"], - help="Add tags to categorize the scenario", - key="new_scenario_tags") - - # Algorithm Settings - st.subheader("Algorithm Settings") - col1, col2 = st.columns(2) - with col1: - algorithm = st.selectbox("Algorithm", - ["insertion_heuristic", "local_search", "column_generation"], - help="Select the optimization algorithm to use", - key="scenario_algorithm") - with col2: - objective = st.selectbox("Objective", - ["minimize_vehicles", "minimize_distance", "minimize_duration"], - help="Select the optimization objective", - key="scenario_objective") - - # Performance Settings - st.subheader("Performance Settings") - col1, col2 = st.columns(2) - with col1: - max_time = st.slider("Max Computation Time (seconds)", 10, 600, 60, - help="Maximum time allowed for the solver to run", - key="scenario_max_time") - with col2: - random_seed = st.number_input("Random Seed", 0, 9999, 42, - help="Random seed for reproducible results", - key="scenario_random_seed") + + for param in param_names: + param_obj = param_dict.get(param) + if not param_obj: + continue - # Configuration Source - st.subheader("Configuration Source") - config_source = st.radio("Load configuration from:", - ["Current Settings", "Template", "New Configuration"], - key="new_scenario_source") - - if config_source == "Template": - template_name = st.selectbox("Select Template", - list(st.session_state.templates.keys()), - key="new_scenario_template") - elif config_source == "New Configuration": - st.info("Configure settings in the Simulation Config and Fleet Management tabs first") - - # Save Scenario - if st.button("Save Scenario", key="save_new_scenario"): - if not scenario_name: - st.error("Please enter a scenario name") - else: - # Collect configuration from current settings - scenario_config = { - "name": scenario_name, - "type": scenario_type, - "description": scenario_description, - "tags": scenario_tags, - "algorithm": algorithm, - "objective": objective, - "max_time": max_time, - "random_seed": random_seed, - "sim_config": st.session_state.get('sim_config', {}), - "fleet_config": st.session_state.get('fleet_config', {}), - "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - "last_modified": datetime.now().strftime("%Y-%m-%d %H:%M:%S") - } - - st.session_state.scenarios[scenario_name] = scenario_config - st.success(f"Scenario '{scenario_name}' saved successfully!") - - # # TODO fix Navigation buttons - # st.markdown("---") - # col1, col2, col3 = st.columns([1, 1, 1]) - # with col2: - # if st.button("Next: Upload Files", key="next_upload_files"): - # st.session_state.current_tab = "Upload Files" - # st.rerun() - - with scenario_tabs[1]: # Samples - st.subheader("Sample Scenarios") - st.info("These are pre-configured scenarios to help you get started. Load a sample to see how different configurations work.") - - # Sample scenarios - sample_scenarios = { - "Basic Urban Ridepooling": { - "description": "A simple urban ridepooling scenario with a small fleet", - "type": "Single Simulation", - "tags": ["Urban", "Ridepooling"], - "sim_config": { - "start_time": 0, - "end_time": 3600, - "time_step": 1, - "random_seed": 42, - "route_output": True, - "replay_output": False, - "network_type": "NetworkBase" - }, - "fleet_config": { - "composition": [ - {"type": "sedan", "quantity": 5, "capacity": 4} - ], - "control_strategy": "insertion_heuristic" - } - }, - "Large Fleet Delivery": { - "description": "A delivery scenario with a large mixed fleet", - "type": "Single Simulation", - "tags": ["Urban", "Delivery"], - "sim_config": { - "start_time": 0, - "end_time": 7200, - "time_step": 1, - "random_seed": 42, - "route_output": True, - "replay_output": True, - "network_type": "NetworkBase" - }, - "fleet_config": { - "composition": [ - {"type": "van", "quantity": 10, "capacity": 20}, - {"type": "truck", "quantity": 5, "capacity": 30} - ], - "control_strategy": "local_search" - } - }, - "Mixed Fleet Ridepooling": { - "description": "A ridepooling scenario with different vehicle types", - "type": "Single Simulation", - "tags": ["Urban", "Ridepooling", "Mixed"], - "sim_config": { - "start_time": 0, - "end_time": 10800, - "time_step": 1, - "random_seed": 42, - "route_output": True, - "replay_output": True, - "network_type": "NetworkDynamicNFDClusters", - "density_bin_size": 300, - "density_avg_duration": 900 - }, - "fleet_config": { - "composition": [ - {"type": "sedan", "quantity": 8, "capacity": 4}, - {"type": "van", "quantity": 4, "capacity": 8} - ], - "control_strategy": "column_generation" - } - } - } - - # Display sample scenarios - for name, scenario in sample_scenarios.items(): - with st.expander(f"πŸ“‹ {name}"): - st.write(f"**Description:** {scenario['description']}") - st.write(f"**Type:** {scenario['type']}") - st.write(f"**Tags:** {', '.join(scenario['tags'])}") - - # Show configuration preview - st.markdown("**Configuration Preview:**") - col1, col2 = st.columns(2) - - with col1: - st.markdown("**Simulation Configuration**") - st.json(scenario['sim_config']) - - with col2: - st.markdown("**Fleet Configuration**") - st.json(scenario['fleet_config']) - - if st.button("Load Sample", key=f"load_sample_{name}"): - st.session_state.sim_config = scenario['sim_config'] - st.session_state.fleet_config = scenario['fleet_config'] - st.success(f"Sample scenario '{name}' loaded! Configure additional settings in the Simulation Config and Fleet Management tabs.") - st.info("Note: You may need to upload your own data files in the Upload Files tab.") + if param.startswith(("start_time", "end_time", "time_step", "lock_time")): + categories["Time Settings"].append(param) + elif param.startswith("user_") or "wait_time" in param or "detour" in param: + categories["Request Settings"].append(param) + elif param.startswith("op_"): + if "parcel" in param: + categories["Parcel Settings"].append(param) + else: + categories["Operator Settings"].append(param) + elif param in ["network_name", "demand_name", "rq_file", "scenario_name", "study_name"]: + categories["Basic Settings"].append(param) + elif param.startswith("veh_") or "vehicle" in param or "fleet" in param: + categories["Vehicle Settings"].append(param) + elif param.startswith(("zone_", "infra_")): + categories["Infrastructure"].append(param) + else: + categories["Other"].append(param) + + # Remove empty categories + return {k: v for k, v in categories.items() if v} + +def render_parameter_input(param, param_obj, key_prefix=""): + """Render the appropriate input widget for a parameter based on its type and metadata""" + help_text = param_obj.doc_string if hasattr(param_obj, 'doc_string') else "" + if hasattr(param_obj, 'type') and param_obj.type: + type_info = f" (Expected type: {param_obj.type})" + help_text = f"{help_text}{type_info}" if help_text else type_info + default_value = param_obj.default_value if hasattr(param_obj, 'default_value') else None + param_type = param_obj.type if hasattr(param_obj, 'type') else "str" + + if hasattr(param_obj, 'options') and param_obj.options: + options = ["None"] if key_prefix == "optional_" else ["Choose..."] + options.extend(param_obj.options) + value = st.selectbox( + f"{param}", + options=options, + key=f"param_{key_prefix}{param}", + help=help_text + ) + if value not in ["None", "Choose..."]: + return value + elif param_type == "int": + try: + default = int(default_value) if default_value and str(default_value).strip() else 0 + except (ValueError, TypeError): + default = 0 + value = st.number_input( + f"{param}", + value=default, + key=f"param_{key_prefix}{param}", + help=help_text + ) + return str(value) + elif param_type == "float": + try: + default = float(default_value) if default_value and str(default_value).strip() else 0.0 + except (ValueError, TypeError): + default = 0.0 + value = st.number_input( + f"{param}", + value=default, + key=f"param_{key_prefix}{param}", + help=help_text + ) + return str(value) + elif param_type == "bool": + value = st.checkbox( + f"{param}", + value=bool(default_value) if default_value is not None else False, + key=f"param_{key_prefix}{param}", + help=help_text + ) + return str(value) + else: + value = st.text_input( + f"{param}", + value=str(default_value) if default_value is not None else "", + key=f"param_{key_prefix}{param}", + help=help_text + ) + return value if value else None + +def create_scenario_page(): + st.title("Create Scenario") + st.markdown("Use this page to create a new simulation scenario.") + + # Initialize the scenario creator if not already in session state + if 'scenario_creator' not in st.session_state: + st.session_state.scenario_creator = ScenarioCreator() + st.session_state.current_step = "modules" + st.session_state.network_selected = "" + st.session_state.demand_selected = "" + + sc = st.session_state.scenario_creator + + # Initialize the active tab in session state if it doesn't exist + if 'active_tab' not in st.session_state: + st.session_state.active_tab = "modules" + + # Create radio for tab selection + selected_tab = st.radio("", ["1. Select Modules", "2. Configure Parameters"], + index=0 if st.session_state.active_tab == "modules" else 1, + horizontal=True, + label_visibility="collapsed") + + # Create tabs and show content based on selection + if selected_tab == "1. Select Modules": + st.header("Module Selection") + st.write("Select required and optional modules for your scenario.") + + # Mandatory Modules + st.subheader("Mandatory Modules") + for module in sc._current_mandatory_modules: + options = ["Choose..."] + list(MODULE_PARAM_TO_DICT_LOAD[module]().keys()) if MODULE_PARAM_TO_DICT_LOAD.get(module) else ["Choose..."] + selected = st.selectbox( + f"{module}", + options=options, + key=f"mod_mandatory_{module}", + help=parameter_docs[module] + ) + if selected != "Choose...": + sc.select_module(module, selected) + + # Optional Modules + st.subheader("Optional Modules") + for module in sc._current_optional_modules: + options = ["None"] + list(MODULE_PARAM_TO_DICT_LOAD[module]().keys()) if MODULE_PARAM_TO_DICT_LOAD.get(module) else ["None"] + selected = st.selectbox( + f"{module}", + options=options, + key=f"mod_optional_{module}", + help=parameter_docs[module] + ) + if selected != "None": + sc.select_module(module, selected) + + # Add Next button at the bottom of module selection + st.markdown("---") # Add a visual separator + if st.button("Next", key="module_next_button"): + # Check if all mandatory modules are selected + mandatory_modules_selected = all( + sc._currently_selected_modules.get(module) is not None + for module in sc._current_mandatory_modules + ) - # # Navigation buttons - # st.markdown("---") - # col1, col2, col3 = st.columns([1, 1, 1]) - # with col2: - # if st.button("Next: Upload Files", key="next_upload_files_samples"): - # st.session_state.current_tab = "Upload Files" - # st.rerun() + if mandatory_modules_selected: + # Use session state to switch to parameters tab + st.session_state.active_tab = "parameters" + # Force a rerun to switch tabs + st.rerun() + else: + st.error("Please select all mandatory modules before proceeding.") + + elif selected_tab == "2. Configure Parameters": + st.header("Parameter Selection") - with scenario_tabs[2]: # Templates - st.subheader("Scenario Templates") - - # Create Template - st.markdown("### Create New Template") - template_name = st.text_input("Template Name", key="new_template_name") - template_description = st.text_area("Template Description", key="new_template_desc") - - # Show what will be saved in the template - st.markdown("### Template Contents") - st.info("This template will save the following configurations:") - - # Create columns for better layout + # Keep track of seen parameters + seen_params = set() + + # Special handling for Basic Settings which includes network and demand selection + with st.expander("Basic Settings", expanded=True): + # Handle special case parameters (network, demand, rq_file) + basic_params = ["network_name", "demand_name", "rq_file", "scenario_name", "study_name"] col1, col2 = st.columns(2) - with col1: - st.markdown("**Simulation Configuration**") - if st.session_state.get('sim_config'): - sim_config = st.session_state.sim_config - st.json({ - "Time Settings": { - "Start Time": sim_config.get('start_time', 'Not set'), - "End Time": sim_config.get('end_time', 'Not set'), - "Time Step": sim_config.get('time_step', 'Not set') - }, - "Randomization": { - "Random Seed": sim_config.get('random_seed', 'Not set') - }, - "Output Settings": { - "Route Output": sim_config.get('route_output', 'Not set'), - "Replay Output": sim_config.get('replay_output', 'Not set') - }, - "Network Type": sim_config.get('network_type', 'Not set') - }) - else: - st.warning("No simulation configuration found. Configure settings in the Simulation Config tab first.") - - with col2: - st.markdown("**Fleet Configuration**") - if st.session_state.get('fleet_config'): - fleet_config = st.session_state.fleet_config - st.json({ - "Fleet Composition": fleet_config.get('composition', []), - "Control Strategy": fleet_config.get('control_strategy', 'Not set') - }) - else: - st.warning("No fleet configuration found. Configure settings in the Fleet Management tab first.") - - if st.button("Save as Template", key="save_template"): - if not template_name: - st.error("Please enter a template name") - elif not st.session_state.get('sim_config') or not st.session_state.get('fleet_config'): - st.error("Please configure both simulation and fleet settings before saving as template") - else: - template_config = { - "name": template_name, - "description": template_description, - "sim_config": st.session_state.get('sim_config', {}), - "fleet_config": st.session_state.get('fleet_config', {}), - "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") - } + # First handle network and demand selection as they're needed for rq_file + for param in ["network_name", "demand_name"]: + if param not in sc._current_mandatory_params and param not in sc._current_optional_params: + continue - st.session_state.templates[template_name] = template_config - st.success(f"Template '{template_name}' saved successfully!") - - # Display Templates - st.markdown("### Saved Templates") - if st.session_state.templates: - for template_name, template in st.session_state.templates.items(): - with st.expander(f"πŸ“‹ {template_name}"): - st.write(f"**Description:** {template['description']}") - st.write(f"**Created:** {template['created_at']}") - - # Show template contents - st.markdown("**Template Contents:**") - col1, col2 = st.columns(2) - - with col1: - st.markdown("**Simulation Configuration**") - st.json({ - "Time Settings": { - "Start Time": template['sim_config'].get('start_time', 'Not set'), - "End Time": template['sim_config'].get('end_time', 'Not set'), - "Time Step": template['sim_config'].get('time_step', 'Not set') - }, - "Randomization": { - "Random Seed": template['sim_config'].get('random_seed', 'Not set') - }, - "Output Settings": { - "Route Output": template['sim_config'].get('route_output', 'Not set'), - "Replay Output": template['sim_config'].get('replay_output', 'Not set') - }, - "Network Type": template['sim_config'].get('network_type', 'Not set') - }) - - with col2: - st.markdown("**Fleet Configuration**") - st.json({ - "Fleet Composition": template['fleet_config'].get('composition', []), - "Control Strategy": template['fleet_config'].get('control_strategy', 'Not set') - }) - - col1, col2 = st.columns(2) - with col1: - if st.button("Load Template", key=f"load_{template_name}"): - st.session_state.sim_config = template['sim_config'] - st.session_state.fleet_config = template['fleet_config'] - st.success(f"Template '{template_name}' loaded!") - with col2: - if st.button("Delete Template", key=f"delete_{template_name}"): - del st.session_state.templates[template_name] - st.success(f"Template '{template_name}' deleted!") - else: - st.info("No templates saved yet") - - # # Navigation buttons - # st.markdown("---") - # col1, col2, col3 = st.columns([1, 1, 1]) - # with col2: - # if st.button("Next: Upload Files", key="next_upload_files_templates"): - # st.session_state.current_tab = "Upload Files" - # st.rerun() - - with scenario_tabs[3]: # Compare Scenarios - st.subheader("Compare Scenarios") - - if len(st.session_state.scenarios) < 2: - st.info("You need at least 2 scenarios to compare") - else: - # Select scenarios to compare - selected_scenarios = st.multiselect("Select Scenarios to Compare", - list(st.session_state.scenarios.keys()), - default=list(st.session_state.scenarios.keys())[:2]) + seen_params.add(param) + col = col1 if param == "network_name" else col2 - if len(selected_scenarios) >= 2: - # Basic Comparison - st.markdown("### Basic Comparison") - comparison_data = [] - for scenario_name in selected_scenarios: - scenario = st.session_state.scenarios[scenario_name] - comparison_data.append({ - "Name": scenario_name, - "Type": scenario["type"], - "Created": scenario["created_at"], - "Last Modified": scenario["last_modified"], - "Tags": ", ".join(scenario["tags"]), - "Fleet Size": sum(item['quantity'] for item in scenario["fleet_config"].get("composition", [])), - "Simulation Duration": f"{scenario['sim_config'].get('end_time', 0) - scenario['sim_config'].get('start_time', 0)} seconds" - }) + with col: + abnormal_options = get_abnormal_param_options(param) + param_obj = sc.parameter_dict.get(param) - comparison_df = pd.DataFrame(comparison_data) - st.dataframe(comparison_df) + if param_obj: + help_text = param_obj.doc_string if hasattr(param_obj, 'doc_string') else "" + + if abnormal_options: + selected = st.selectbox( + f"{param}", + options=abnormal_options, + key=f"param_mandatory_{param}", + help=help_text + ) + + if param == "network_name" and selected: + st.session_state.network_selected = selected + elif param == "demand_name" and selected: + st.session_state.demand_selected = selected + + if selected: + sc.select_param(param, selected) + + # Now handle rq_file after network and demand are set + if "rq_file" in sc._current_mandatory_params or "rq_file" in sc._current_optional_params: + seen_params.add("rq_file") + param_obj = sc.parameter_dict.get("rq_file") + if param_obj: + help_text = param_obj.doc_string if hasattr(param_obj, 'doc_string') else "" - # Configuration Comparison - st.markdown("### Configuration Comparison") - config_tabs = st.tabs(["Simulation Config", "Fleet Config"]) + if st.session_state.network_selected and st.session_state.demand_selected: + rq_path = os.path.join(fleetpy_path, "data", "demand", + st.session_state.demand_selected, "matched", + st.session_state.network_selected) + if os.path.exists(rq_path): + rq_options = [""] + os.listdir(rq_path) + selected = st.selectbox( + "rq_file", + options=rq_options, + key="param_mandatory_rq_file", + help=help_text + ) + if selected: + sc.select_param("rq_file", selected) + else: + st.warning("No request files found for the selected network and demand.") + else: + st.info("Please select both network and demand to view available request files.") + + # Handle remaining basic parameters + for param in ["scenario_name", "study_name"]: + if param not in sc._current_mandatory_params and param not in sc._current_optional_params: + continue - with config_tabs[0]: - sim_configs = {} - for scenario_name in selected_scenarios: - sim_configs[scenario_name] = st.session_state.scenarios[scenario_name]["sim_config"] - - # Create a DataFrame for simulation configurations - sim_comparison = pd.DataFrame(sim_configs).T - st.dataframe(sim_comparison) + seen_params.add(param) + col = col1 if param == "scenario_name" else col2 + + with col: + param_obj = sc.parameter_dict.get(param) + if param_obj: + help_text = param_obj.doc_string if hasattr(param_obj, 'doc_string') else "" + value = st.text_input( + f"{param}", + key=f"param_mandatory_{param}", + help=help_text + ) + if value: + sc.select_param(param, value) + + # Categorize remaining mandatory parameters + remaining_mandatory = [p for p in sc._current_mandatory_params if p not in seen_params] + mandatory_categories = categorize_parameters(remaining_mandatory, sc.parameter_dict) + + # Render mandatory parameters by category + for category, params in mandatory_categories.items(): + with st.expander(category, expanded=True): + col1, col2 = st.columns(2) + for i, param in enumerate(params): + if param in seen_params: + continue + seen_params.add(param) - with config_tabs[1]: - fleet_configs = {} - for scenario_name in selected_scenarios: - fleet_configs[scenario_name] = st.session_state.scenarios[scenario_name]["fleet_config"] - - # Create a DataFrame for fleet configurations - fleet_comparison = pd.DataFrame(fleet_configs).T - st.dataframe(fleet_comparison) + col = col1 if i % 2 == 0 else col2 + with col: + param_obj = sc.parameter_dict.get(param) + if param_obj: + value = render_parameter_input(param, param_obj) + if value: + sc.select_param(param, value) + + # Special handling for nr_mod_operators + if param == "nr_mod_operators" and value and value != "1": + try: + if int(value) > 1: + st.info("You have selected more than one operator. Please make sure to select different parameters for each operator. " + "Otherwise, the parameters will be the same for all operators. You can do this by separating the different " + "parameters by a comma ',' for all parameters starting with op_") + except ValueError: + pass + + # Categorize optional parameters + remaining_optional = [p for p in sc._current_optional_params if p not in seen_params] + optional_categories = categorize_parameters(remaining_optional, sc.parameter_dict) + + # Render optional parameters by category + st.subheader("Optional Parameters") + for category, params in optional_categories.items(): + with st.expander(category, expanded=False): + col1, col2 = st.columns(2) + for i, param in enumerate(params): + if param in seen_params: + continue + seen_params.add(param) - # Export Comparison - if st.button("Export Comparison", key="export_comparison"): - # Create a dictionary with all comparison data - export_data = { - "basic_comparison": comparison_df.to_dict(), - "sim_config_comparison": sim_comparison.to_dict(), - "fleet_config_comparison": fleet_comparison.to_dict() - } - - # Convert to JSON - json_data = json.dumps(export_data, indent=2) - - # Create download link - st.markdown(download_link(json_data, "scenario_comparison.json", "πŸ“₯ Download Comparison"), - unsafe_allow_html=True) - - # # Navigation buttons - # st.markdown("---") - # col1, col2, col3 = st.columns([1, 1, 1]) - # with col2: - # if st.button("Next: Upload Files", key="next_upload_files_compare"): - # st.session_state.current_tab = "Upload Files" - # st.rerun() - - with tabs[2]: - st.header("Upload Files") - - # Upload vehicles file - st.subheader("Upload Vehicles File") - vehicles_file = st.file_uploader("Choose a CSV file", type="csv", key="vehicles") - if vehicles_file is not None: - vehicles_df = pd.read_csv(vehicles_file) - st.session_state['vehicles_df'] = vehicles_df - st.success("Vehicles file uploaded successfully!") - st.write(vehicles_df) - - # Upload requests file - st.subheader("Upload Requests File") - requests_file = st.file_uploader("Choose a CSV file", type="csv", key="requests") - if requests_file is not None: - requests_df = pd.read_csv(requests_file) - st.session_state['requests_df'] = requests_df - st.success("Requests file uploaded successfully!") - st.write(requests_df) - - # Upload configuration file - st.subheader("Upload Configuration File") - config_file = st.file_uploader("Choose a JSON file", type="json") - if config_file is not None: - config = json.load(config_file) - st.session_state['config'] = config - st.success("Configuration file uploaded successfully!") - st.json(config) - - # Run solver - if st.button("Run FleetPy Solver"): - if 'vehicles_df' in st.session_state and 'requests_df' in st.session_state and 'config' in st.session_state: - st.info("Running solver...") + col = col1 if i % 2 == 0 else col2 + with col: + param_obj = sc.parameter_dict.get(param) + if param_obj: + value = render_parameter_input(param, param_obj, "optional_") + if value: + sc.select_param(param, value) + + # Save Button + if st.button("Save Scenario"): + try: + scenario_path = sc.create_filled_scenario_df() + st.success(f"Scenario saved successfully to {scenario_path}") + except Exception as e: + st.error(f"Error saving scenario: {str(e)}") - # Create a progress bar - progress_bar = st.progress(0) - status_text = st.empty() - # Here you would call FleetPy functions to run the solver - # For demonstration, we'll simulate progress: - - import time - - for i in range(101): - # Update progress bar - progress_bar.progress(i) - - # Update status text - if i < 30: - status_text.text(f"Setting up the problem... ({i}%)") - elif i < 60: - status_text.text(f"Optimizing routes... ({i}%)") - elif i < 90: - status_text.text(f"Finalizing solution... ({i}%)") - else: - status_text.text(f"Preparing results... ({i}%)") - - # Simulate computation time - time.sleep(0.05) - - # For real implementation, you'd run your FleetPy solver here - # and update the progress periodically based on solver status - run_fleetpy() - - # Remove progress elements when done - progress_bar.empty() - status_text.empty() - - # TODO show actual results - # For now, we'll just simulate a result - st.session_state['solution'] = { - "routes": [ - {"vehicle_id": "v1", "route": ["depot", "A", "F", "depot"]}, - {"vehicle_id": "v2", "route": ["depot", "B", "G", "C", "H", "depot"]}, - {"vehicle_id": "v3", "route": ["depot", "D", "I", "E", "J", "depot"]} - ], - "objective_value": 150.75, - "computation_time": 2.3 - } - - st.success("Solver completed!") - st.balloons() - - # Save the solution timestamp - st.session_state['solution_timestamp'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Redirect to visualization page - st.info("Redirecting to visualization page...") - st.rerun() - else: - st.error("Please upload all required files first!") - - # # Navigation buttons - # st.markdown("---") - # col1, col2, col3 = st.columns([1, 1, 1]) - # with col1: - # if st.button("← Back: Scenario Management", key="back_scenario_management"): - # st.session_state.current_tab = "Scenario Management" - # st.rerun() - # with col3: - # if st.button("Next: Simulation Config β†’", key="next_simulation_config"): - # st.session_state.current_tab = "Simulation Config" - # st.rerun() +def main(): + # Page navigation with vertical layout + st.sidebar.markdown(""" + + """, unsafe_allow_html=True) + + st.sidebar.title("FleetPy") + st.sidebar.markdown('
', unsafe_allow_html=True) + + # Navigation buttons + clicked = None + if st.sidebar.button( + "⚑️ Run Simulation", + key="nav_run", + help="Configure and run FleetPy simulations", + use_container_width=True, + type="secondary" + ): + clicked = "run" + + st.sidebar.markdown('
', unsafe_allow_html=True) + + if st.sidebar.button( + "πŸ“ Create Scenario", + key="nav_create", + help="Create a new simulation scenario", + use_container_width=True, + type="secondary" + ): + clicked = "create" + + st.sidebar.markdown('
', unsafe_allow_html=True) + st.sidebar.markdown("---") + + # Initialize the page selection in session state if not already present + if 'current_page' not in st.session_state: + st.session_state.current_page = "run" + + # Update current page based on button clicks + if clicked: + st.session_state.current_page = clicked + + # Show the appropriate page based on selection + if st.session_state.current_page == "create": + create_scenario_page() + else: + st.title("FleetPy Simulation Manager") + st.write("Upload or select configuration files to run FleetPy simulations") + + # Main area organization + st.header("Configuration Files") + + # Create two columns for the settings + col1, col2 = st.columns(2) - with tabs[3]: # Simulation Configuration Tab - st.header("Simulation Configuration") - - # Simulation Time Settings - st.subheader("Time Settings") - col1, col2, col3 = st.columns(3) with col1: - start_time = st.number_input("Start Time (seconds)", 0, 86400, 0, - help="Simulation start time in seconds", - key="sim_start_time") - with col2: - end_time = st.number_input("End Time (seconds)", 0, 86400, 86400, - help="Simulation end time in seconds", - key="sim_end_time") - with col3: - time_step = st.number_input("Time Step (seconds)", 1, 3600, 1, - help="Simulation time step in seconds", - key="sim_time_step") + # Log level selection + log_level = st.selectbox( + "Log Level", + ["info", "debug", "warning", "error"], + index=0 + ) - # Random Seed and Output Settings - st.subheader("Randomization and Output") - col1, col2 = st.columns(2) - with col1: - random_seed = st.number_input("Random Seed", 0, 9999, 42, - help="Random seed for reproducible results", - key="sim_random_seed") with col2: - route_output = st.checkbox("Output Complete Routes", True, - help="Output complete vehicle routes to files", - key="sim_route_output") - replay_output = st.checkbox("Output Node Passing Times", False, - help="Output times when vehicles pass nodes", - key="sim_replay_output") - - # Real-time Visualization Settings - st.subheader("Real-time Visualization") - realtime_plot = st.selectbox("Real-time Plot Mode", - ["None", "Live Plot", "Save Plots"], - help="Choose real-time visualization mode", - key="sim_realtime_plot") + # CPU configuration + n_cpu_per_sim = st.number_input( + "CPUs per Simulation", + min_value=1, + max_value=mp.cpu_count(), + value=1 + ) + + n_parallel_sim = st.number_input( + "Parallel Simulations", + min_value=1, + max_value=mp.cpu_count(), + value=1 + ) - if realtime_plot != "None": - col1, col2 = st.columns(2) - with col1: - plot_extent = st.text_input("Plot Extent (min_lon, max_lon, min_lat, max_lat)", - help="Bounding box for visualization", - key="sim_plot_extent") - with col2: - vehicle_status = st.multiselect("Vehicle Status to Display", - ["driving", "charging", "idle", "boarding", "alighting"], - default=["driving", "charging"], - help="Select vehicle statuses to display in real-time", - key="sim_vehicle_status") - - # Network Configuration - st.subheader("Network Configuration") - network_type = st.selectbox("Network Type", - ["NetworkBase", "NetworkDynamicNFDClusters"], - help="Select the network representation type", - key="sim_network_type") - - if network_type == "NetworkDynamicNFDClusters": - col1, col2 = st.columns(2) - with col1: - density_bin_size = st.number_input("Density Bin Size (seconds)", 1, 3600, 300, - help="Time bin size for network density calculation", - key="sim_density_bin_size") - with col2: - density_avg_duration = st.number_input("Density Average Duration (seconds)", 1, 3600, 900, - help="Duration for averaging network density", - key="sim_density_avg_duration") - - # Save Configuration - if st.button("Save Simulation Configuration", key="sim_save_config"): - sim_config = { - "start_time": start_time, - "end_time": end_time, - "time_step": time_step, - "random_seed": random_seed, - "route_output": route_output, - "replay_output": replay_output, - "realtime_plot": realtime_plot, - "plot_extent": plot_extent if realtime_plot != "None" else None, - "vehicle_status": vehicle_status if realtime_plot != "None" else None, - "network_type": network_type, - "density_bin_size": density_bin_size if network_type == "NetworkDynamicNFDClusters" else None, - "density_avg_duration": density_avg_duration if network_type == "NetworkDynamicNFDClusters" else None - } - st.session_state['sim_config'] = sim_config - st.success("Simulation configuration saved!") - - # # Navigation buttons - # st.markdown("---") - # col1, col2, col3 = st.columns([1, 1, 1]) - # with col1: - # if st.button("← Back: Upload Files", key="back_upload_files"): - # st.session_state.current_tab = "Upload Files" - # st.rerun() - # with col3: - # if st.button("Next: Fleet Management β†’", key="next_fleet_management"): - # st.session_state.current_tab = "Fleet Management" - # st.rerun() + st.divider() + + # File upload option + upload_method = st.radio( + "Choose how to provide configuration files", + ["Upload Files", "Select from Existing Files"] + ) + + const_cfg_file = None + scenario_cfg_file = None + + if upload_method == "Upload Files": + # Get list of existing studies for reference + studies_path = os.path.join(fleetpy_path, "studies") + existing_studies = [d for d in os.listdir(studies_path) + if os.path.isdir(os.path.join(studies_path, d))] + existing_studies.sort() + + # Study name input with existing studies as suggestions + study_name = st.text_input( + "Study Name", + placeholder="Enter a name for your study", + help="This will be used to organize your configuration files" + ) + + # Show existing studies as reference + with st.expander("View Existing Studies"): + st.write("Existing studies for reference:") + for study in existing_studies: + st.write(f"- {study}") + + const_cfg = st.file_uploader("Upload Constant Configuration File (YAML/CSV)", type=['yaml', 'csv']) + scenario_cfg = st.file_uploader("Upload Scenario Configuration File (YAML/CSV)", type=['yaml', 'csv']) + + if study_name and const_cfg and scenario_cfg: + # Create study directory structure + study_path = os.path.join(studies_path, study_name) + scenarios_path = os.path.join(study_path, "scenarios") + + # Create directories if they don't exist + os.makedirs(scenarios_path, exist_ok=True) + + # Generate file paths preserving original extensions + const_ext = os.path.splitext(const_cfg.name)[1] if const_cfg.name else ".yaml" + scenario_ext = os.path.splitext(scenario_cfg.name)[1] if scenario_cfg.name else ".csv" + + const_cfg_file = os.path.join(scenarios_path, f"const_cfg{const_ext}") + scenario_cfg_file = os.path.join(scenarios_path, f"scenario_cfg{scenario_ext}") + + # Save uploaded files + with open(const_cfg_file, "wb") as f: + f.write(const_cfg.getvalue()) + with open(scenario_cfg_file, "wb") as f: + f.write(scenario_cfg.getvalue()) + + st.success(f"Configuration files saved in study: {study_name}") + elif const_cfg and scenario_cfg: + st.error("Please enter a study name before uploading files") - with tabs[4]: # Fleet Management Tab - st.header("Fleet Management") - - # Fleet Composition - st.subheader("Fleet Composition") - if 'fleet_composition' not in st.session_state: - st.session_state.fleet_composition = [] - - col1, col2, col3 = st.columns(3) - with col1: - vehicle_type = st.selectbox("Vehicle Type", ["sedan", "truck", "van", "bus"], - key="fleet_vehicle_type") - with col2: - quantity = st.number_input("Quantity", 1, 100, 1, - key="fleet_quantity") - with col3: - capacity = st.number_input("Capacity", 1, 50, 4, - key="fleet_capacity") - - if st.button("Add Vehicle Type", key="fleet_add_vehicle"): - st.session_state.fleet_composition.append({ - "type": vehicle_type, - "quantity": quantity, - "capacity": capacity - }) - - # Display Current Fleet - st.subheader("Current Fleet") - if st.session_state.fleet_composition: - fleet_df = pd.DataFrame(st.session_state.fleet_composition) - st.dataframe(fleet_df) + else: + # Get list of available studies + studies_path = os.path.join(fleetpy_path, "studies") + studies = [] + + # Get list of valid studies (ones with scenarios directory) + for study in os.listdir(studies_path): + study_path = os.path.join(studies_path, study) + if os.path.isdir(study_path): + scenarios_path = os.path.join(study_path, "scenarios") + if os.path.exists(scenarios_path): + studies.append(study) + + # Sort studies alphabetically + studies.sort() + + # Study selection dropdown + selected_study = st.selectbox( + "Select Study", + studies, + format_func=lambda x: x.replace("_", " ").title() + ) - # Fleet Statistics - total_vehicles = sum(item['quantity'] for item in st.session_state.fleet_composition) - avg_capacity = sum(item['capacity'] * item['quantity'] for item in st.session_state.fleet_composition) / total_vehicles + # Get config files for selected study + config_files = [] + if selected_study: + scenarios_path = os.path.join(studies_path, selected_study, "scenarios") + for file in os.listdir(scenarios_path): + if file.endswith(('.yaml', '.csv')): + config_files.append(os.path.join(scenarios_path, file)) + + # Sort and filter files for constant config + def sort_key_for_const(filepath): + filename = os.path.basename(filepath).lower() + if "const" in filename: + return (0, filename) + return (1, filename) + + # Sort and filter files for scenario config + def sort_key_for_scenario(filepath): + filename = os.path.basename(filepath).lower() + if "scenario" in filename: + return (0, filename) + if "example" in filename: + return (1, filename) + return (2, filename) + + # Sort files separately for each config type + const_sorted_files = sorted(config_files, key=sort_key_for_const) + scenario_sorted_files = sorted(config_files, key=sort_key_for_scenario) + + const_cfg_file = st.selectbox( + "Select Constant Configuration File", + const_sorted_files, + format_func=lambda x: os.path.basename(x) + ) + scenario_cfg_file = st.selectbox( + "Select Scenario Configuration File", + scenario_sorted_files, + format_func=lambda x: os.path.basename(x) + ) + + # Add space before run section + st.write("") + st.write("") + + # Run simulation section + st.header("Run Simulation") + + # Show run button and configuration summary + if const_cfg_file and scenario_cfg_file: + # Show configuration summary + st.write("Configuration Summary:") col1, col2 = st.columns(2) with col1: - st.metric("Total Vehicles", total_vehicles) + st.write("πŸ“„ **Constant Config:**") + st.write(f"`{os.path.basename(const_cfg_file)}`") with col2: - st.metric("Average Capacity", f"{avg_capacity:.1f}") - else: - st.info("No vehicles added to the fleet yet.") - - # Fleet Control Strategy - st.subheader("Fleet Control Strategy") - control_strategy = st.selectbox("Control Strategy", - ["insertion_heuristic", "local_search", "column_generation"], - help="Select the fleet control strategy", - key="fleet_control_strategy") - - if control_strategy == "insertion_heuristic": - st.info("Insertion Heuristic: Simple and fast, good for small to medium fleets") - elif control_strategy == "local_search": - st.info("Local Search: More sophisticated, better for larger fleets") - else: - st.info("Column Generation: Advanced optimization, best for complex scenarios") - - # Vehicle Status Monitoring - st.subheader("Vehicle Status Monitoring") - if 'vehicle_status' in st.session_state: - status_df = pd.DataFrame(st.session_state.vehicle_status) - st.dataframe(status_df) + st.write("πŸ“„ **Scenario Config:**") + st.write(f"`{os.path.basename(scenario_cfg_file)}`") - # Status Distribution Chart - fig, ax = plt.subplots() - status_counts = status_df['status'].value_counts() - ax.pie(status_counts, labels=status_counts.index, autopct='%1.1f%%') - st.pyplot(fig) - else: - st.info("No vehicle status data available. Run a simulation to see status information.") - - # Save Fleet Configuration - if st.button("Save Fleet Configuration", key="fleet_save_config"): - fleet_config = { - "composition": st.session_state.fleet_composition, - "control_strategy": control_strategy - } - st.session_state['fleet_config'] = fleet_config - st.success("Fleet configuration saved!") - - # # Navigation buttons - # st.markdown("---") - # col1, col2, col3 = st.columns([1, 1, 1]) - # with col1: - # if st.button("← Back: Simulation Config", key="back_simulation_config"): - # st.session_state.current_tab = "Simulation Config" - # st.rerun() - # with col3: - # if st.button("Next: Visualize Results β†’", key="next_visualize_results"): - # st.session_state.current_tab = "Visualize Results" - # st.rerun() - - with tabs[5]: # Visualize Results Tab - st.header("Visualization") - - if 'solution' in st.session_state: - # Solution overview in a card - st.markdown(""" -
-

Solution Overview

-
- """, unsafe_allow_html=True) - - col1, col2, col3 = st.columns(3) - with col1: - st.metric("Solved at", st.session_state.get('solution_timestamp', 'Unknown')) + # Show run button centered + col1, col2, col3 = st.columns([1, 2, 1]) with col2: - st.metric("Objective Value", f"{st.session_state['solution']['objective_value']:.2f}") - with col3: - st.metric("Computation Time", f"{st.session_state['solution']['computation_time']:.2f} seconds") - - # Routes in a card - st.markdown(""" -
-

Routes

-
- """, unsafe_allow_html=True) - - for i, route in enumerate(st.session_state['solution']['routes']): - st.markdown(f""" -
-

Vehicle {route['vehicle_id']}

-

Route: {' β†’ '.join(route['route'])}

-
- """, unsafe_allow_html=True) - - # Visualization tabs with improved styling - viz_tabs = st.tabs(["πŸ—ΊοΈ Route Map", "πŸ“… Gantt Chart", "πŸ“Š Performance Metrics"]) - - with viz_tabs[0]: - st.markdown(""" -
-

Route Map

-
- """, unsafe_allow_html=True) - - # Create a simple plot with improved styling - fig, ax = plt.subplots(figsize=(12, 8)) - plt.style.use('default') # Use default style as base - - # Set custom style parameters - plt.rcParams.update({ - 'font.size': 12, - 'axes.labelsize': 12, - 'axes.titlesize': 16, - 'xtick.labelsize': 10, - 'ytick.labelsize': 10, - 'axes.grid': True, - 'grid.alpha': 0.3, - 'axes.facecolor': 'white', - 'figure.facecolor': 'white' - }) - - # Dummy locations with improved styling - locations = { - 'depot': (0, 0), - 'A': (1, 2), 'F': (3, 2), - 'B': (2, 3), 'G': (4, 3), - 'C': (1, 4), 'H': (3, 4), - 'D': (2, 1), 'I': (4, 1), - 'E': (1, 0), 'J': (3, 0) - } - - # Plot locations with improved styling - for loc, (x, y) in locations.items(): - color = '#4CAF50' if loc == 'depot' else '#2196F3' - ax.scatter(x, y, s=150, c=color, edgecolors='white', linewidth=2) - ax.text(x, y, loc, fontsize=14, ha='center', va='center', color='white') - - # Plot routes with improved styling - colors = ['#FF5722', '#9C27B0', '#FFC107'] - for i, route in enumerate(st.session_state['solution']['routes']): - route_points = route['route'] - for j in range(len(route_points) - 1): - start = locations[route_points[j]] - end = locations[route_points[j + 1]] - ax.plot([start[0], end[0]], [start[1], end[1]], - color=colors[i], linewidth=3, alpha=0.8) - - ax.set_title("Route Map", pad=20) - ax.set_xlabel("X coordinate") - ax.set_ylabel("Y coordinate") - plt.tight_layout() - - st.pyplot(fig) - - with viz_tabs[1]: - st.markdown(""" -
-

Gantt Chart

-
- """, unsafe_allow_html=True) - - # Create a Gantt chart with improved styling - fig, ax = plt.subplots(figsize=(12, 6)) - plt.style.use('default') # Use default style as base - - # Set custom style parameters - plt.rcParams.update({ - 'font.size': 12, - 'axes.labelsize': 12, - 'axes.titlesize': 16, - 'xtick.labelsize': 10, - 'ytick.labelsize': 10, - 'axes.grid': True, - 'grid.alpha': 0.3, - 'axes.facecolor': 'white', - 'figure.facecolor': 'white' - }) - - # Dummy time data - times = { - 'v1': [(0, 10), (10, 20), (20, 30)], - 'v2': [(0, 15), (15, 25), (25, 40), (40, 50)], - 'v3': [(5, 15), (15, 25), (25, 35), (35, 45)] - } - - # Plot Gantt chart with improved styling - y_positions = {'v1': 3, 'v2': 2, 'v3': 1} - colors = {'v1': '#FF5722', 'v2': '#9C27B0', 'v3': '#FFC107'} - - for vehicle, intervals in times.items(): - for i, (start, end) in enumerate(intervals): - ax.barh(y_positions[vehicle], end - start, left=start, height=0.5, - color=colors[vehicle], alpha=0.8, edgecolor='white', linewidth=1) - ax.text(start + (end - start) / 2, y_positions[vehicle], - f"{i}", ha='center', va='center', color='white', fontweight='bold') - - ax.set_yticks(list(y_positions.values())) - ax.set_yticklabels(list(y_positions.keys())) - ax.set_title("Vehicle Schedules", pad=20) - ax.set_xlabel("Time") - ax.set_ylabel("Vehicle") - plt.tight_layout() - - st.pyplot(fig) - - with viz_tabs[2]: - st.markdown(""" -
-

Performance Metrics

-
- """, unsafe_allow_html=True) - - # Create metrics with improved styling - metrics = { - "Total Distance": 150.75, - "Total Duration": 180.5, - "Vehicle Utilization": 0.85, - "Average Waiting Time": 12.3, - "Requests Served": 5, - "Vehicles Used": 3 - } - - # Display metrics in a grid - col1, col2, col3 = st.columns(3) - with col1: - for metric, value in list(metrics.items())[:2]: - st.metric(metric, f"{value:.2f}") - with col2: - for metric, value in list(metrics.items())[2:4]: - st.metric(metric, f"{value:.2f}") - with col3: - for metric, value in list(metrics.items())[4:]: - st.metric(metric, value) - - # Add a chart with improved styling - st.markdown(""" -
-

Vehicle Utilization

-
- """, unsafe_allow_html=True) - - fig, ax = plt.subplots(figsize=(10, 6)) - plt.style.use('default') # Use default style as base - - # Set custom style parameters - plt.rcParams.update({ - 'font.size': 12, - 'axes.labelsize': 12, - 'axes.titlesize': 16, - 'xtick.labelsize': 10, - 'ytick.labelsize': 10, - 'axes.grid': True, - 'grid.alpha': 0.3, - 'axes.facecolor': 'white', - 'figure.facecolor': 'white' - }) - - vehicle_utils = { - 'v1': 0.75, - 'v2': 0.90, - 'v3': 0.85 - } - - colors = ['#FF5722', '#9C27B0', '#FFC107'] - bars = ax.bar(vehicle_utils.keys(), vehicle_utils.values(), color=colors) - ax.set_ylim(0, 1) - ax.set_title("Vehicle Utilization", pad=20) - ax.set_xlabel("Vehicle") - ax.set_ylabel("Utilization Rate") - - for bar in bars: - height = bar.get_height() - ax.text(bar.get_x() + bar.get_width()/2., height + 0.05, - f"{height:.2f}", ha='center', va='bottom') - - plt.tight_layout() - st.pyplot(fig) - - # Export options with improved styling - st.markdown(""" -
-

Export Results

-
- """, unsafe_allow_html=True) - - # Create a results dataframe - results_df = pd.DataFrame([ - {"vehicle": route["vehicle_id"], "route": " β†’ ".join(route["route"])} - for route in st.session_state['solution']['routes'] - ]) - - st.dataframe(results_df, use_container_width=True) - - # Download buttons with improved styling - col1, col2 = st.columns(2) - with col1: - st.markdown( - download_link(results_df, "fleetpy_results.csv", "πŸ“₯ Download Results CSV"), - unsafe_allow_html=True - ) - with col2: - results_json = json.dumps(st.session_state['solution'], indent=2) - st.markdown( - download_link(results_json, "fleetpy_results.json", "πŸ“₯ Download Results JSON"), - unsafe_allow_html=True - ) + if st.button("▢️ Run Simulation", use_container_width=True): + try: + with st.spinner("Running simulation..."): + # Run the simulation + run_scenarios( + const_cfg_file, + scenario_cfg_file, + log_level=log_level, + n_cpu_per_sim=n_cpu_per_sim, + n_parallel_sim=n_parallel_sim + ) + + # Show success message with study name + study_name = os.path.basename(os.path.dirname(os.path.dirname(const_cfg_file))) + results_path = os.path.join(os.path.dirname(os.path.dirname(const_cfg_file)), "results") + st.success(f"βœ… Simulation completed successfully!") + st.info(f"Results saved in: `{results_path}`") + + except Exception as e: + st.error("❌ An error occurred during simulation:") + st.error(str(e)) + st.code(traceback.format_exc()) else: - st.info("No solution available. Please run the solver first!") - - # # Navigation buttons - # st.markdown("---") - # col1, col2, col3 = st.columns([1, 1, 1]) - # with col1: - # if st.button("← Back: Fleet Management", key="back_fleet_management"): - # st.session_state.current_tab = "Fleet Management" - # st.rerun() - # with col2: - # if st.button("Start Over", key="start_over"): - # st.session_state.current_tab = "Home" - # st.rerun() + st.info("Please select both configuration files to run the simulation") -def run_fleetpy(): - # TODO parameterize - scs_path = os.path.join(os.path.dirname(__file__), "studies", "example_study", "scenarios") - cc = os.path.join(scs_path, "constant_config_ir.csv") - sc = os.path.join(scs_path, "example_ir_only.csv") - log_level = "info" - run_scenarios(cc, sc, log_level=log_level, n_cpu_per_sim=1, n_parallel_sim=1) if __name__ == "__main__": + mp.freeze_support() main() From 40ac6756405a6f15489ed697bf1fb9ef74edff8a Mon Sep 17 00:00:00 2001 From: hoda_hamdy Date: Mon, 16 Jun 2025 13:22:39 +0200 Subject: [PATCH 4/6] reorganize code --- environment.yml | 2 + src/scenario_gui/parameter_utils.py | 71 ++++ src/scenario_gui/ui_utils.py | 117 ++++++ streamlit_gui.py | 610 +++++++++++----------------- 4 files changed, 431 insertions(+), 369 deletions(-) create mode 100644 src/scenario_gui/parameter_utils.py create mode 100644 src/scenario_gui/ui_utils.py diff --git a/environment.yml b/environment.yml index 4231db91..446e75fc 100644 --- a/environment.yml +++ b/environment.yml @@ -20,3 +20,5 @@ dependencies: - shapely==2.0.6 - tqdm==4.67.1 - dill==0.3.9 + - streamlit==1.28.0 + - contextily==1.4.0 diff --git a/src/scenario_gui/parameter_utils.py b/src/scenario_gui/parameter_utils.py new file mode 100644 index 00000000..e73e92d8 --- /dev/null +++ b/src/scenario_gui/parameter_utils.py @@ -0,0 +1,71 @@ +"""Utility functions for handling parameters in the FleetPy scenario GUI.""" +import os +from typing import Dict, List, Optional, Any + +def get_abnormal_param_options(param: str, fleetpy_path: str) -> Optional[List[str]]: + """Get special options for specific parameters that need to be populated from the filesystem. + + Args: + param: The parameter name to get options for + fleetpy_path: The path to the FleetPy installation + + Returns: + A list of options if the parameter has special handling, None otherwise + """ + if param == "network_name": + path = os.path.join(fleetpy_path, "data", "networks") + return [""] + os.listdir(path) + elif param == "demand_name": + path = os.path.join(fleetpy_path, "data", "demand") + return [""] + os.listdir(path) + elif param == "rq_file": + # Will be populated based on network and demand selection + return [""] + return None + +def categorize_parameters(param_names: List[str], param_dict: Dict[str, Any]) -> Dict[str, List[str]]: + """Categorize parameters into logical groups based on their prefixes and meanings. + + Args: + param_names: List of parameter names to categorize + param_dict: Dictionary of parameter objects with metadata + + Returns: + Dictionary mapping category names to lists of parameter names + """ + categories = { + "Basic Settings": [], + "Time Settings": [], + "Request Settings": [], + "Operator Settings": [], + "Parcel Settings": [], + "Vehicle Settings": [], + "Infrastructure": [], + "Other": [] + } + + for param in param_names: + param_obj = param_dict.get(param) + if not param_obj: + continue + + if param.startswith(("start_time", "end_time", "time_step", "lock_time")): + categories["Time Settings"].append(param) + elif param.startswith("user_") or "wait_time" in param or "detour" in param: + categories["Request Settings"].append(param) + elif param.startswith("op_"): + if "parcel" in param: + categories["Parcel Settings"].append(param) + else: + categories["Operator Settings"].append(param) + elif param in ["network_name", "demand_name", "rq_file", "scenario_name", "study_name"]: + categories["Basic Settings"].append(param) + elif param.startswith("veh_") or "vehicle" in param or "fleet" in param: + categories["Vehicle Settings"].append(param) + elif param.startswith(("zone_", "infra_")): + categories["Infrastructure"].append(param) + else: + categories["Other"].append(param) + + # Remove empty categories + return {k: v for k, v in categories.items() if v} diff --git a/src/scenario_gui/ui_utils.py b/src/scenario_gui/ui_utils.py new file mode 100644 index 00000000..6a33a8cf --- /dev/null +++ b/src/scenario_gui/ui_utils.py @@ -0,0 +1,117 @@ +"""UI utility functions for the FleetPy scenario GUI.""" +import streamlit as st +from typing import Any, Optional + +def render_parameter_input(param: str, param_obj: Any, key_prefix: str = "") -> Optional[str]: + """Render the appropriate input widget for a parameter based on its type and metadata. + + Args: + param: The parameter name to render + param_obj: The parameter object containing metadata + key_prefix: Prefix for the Streamlit widget key + + Returns: + The value from the input widget, or None if no value was entered + """ + help_text = param_obj.doc_string if hasattr(param_obj, 'doc_string') else "" + if hasattr(param_obj, 'type') and param_obj.type: + type_info = f" (Expected type: {param_obj.type})" + help_text = f"{help_text}{type_info}" if help_text else type_info + default_value = param_obj.default_value if hasattr(param_obj, 'default_value') else None + param_type = param_obj.type if hasattr(param_obj, 'type') else "str" + + if hasattr(param_obj, 'options') and param_obj.options: + options = ["None"] if key_prefix == "optional_" else ["Choose..."] + options.extend(param_obj.options) + value = st.selectbox( + f"{param}", + options=options, + key=f"param_{key_prefix}{param}", + help=help_text + ) + if value not in ["None", "Choose..."]: + return value + elif param_type == "int": + try: + default = int(default_value) if default_value and str(default_value).strip() else 0 + except (ValueError, TypeError): + default = 0 + value = st.number_input( + f"{param}", + value=default, + key=f"param_{key_prefix}{param}", + help=help_text + ) + return str(value) + elif param_type == "float": + try: + default = float(default_value) if default_value and str(default_value).strip() else 0.0 + except (ValueError, TypeError): + default = 0.0 + value = st.number_input( + f"{param}", + value=default, + key=f"param_{key_prefix}{param}", + help=help_text + ) + return str(value) + elif param_type == "bool": + value = st.checkbox( + f"{param}", + value=bool(default_value) if default_value is not None else False, + key=f"param_{key_prefix}{param}", + help=help_text + ) + return str(value) + else: + value = st.text_input( + f"{param}", + value=str(default_value) if default_value is not None else "", + key=f"param_{key_prefix}{param}", + help=help_text + ) + return value if value else None + +def apply_sidebar_styles() -> None: + """Apply custom CSS styles to the Streamlit sidebar.""" + st.sidebar.markdown(""" + + """, unsafe_allow_html=True) diff --git a/streamlit_gui.py b/streamlit_gui.py index 23dac5b9..7502a321 100644 --- a/streamlit_gui.py +++ b/streamlit_gui.py @@ -11,122 +11,11 @@ from run_examples import run_scenarios from src.scenario_gui.scenario_creator import ScenarioCreator, MODULE_PARAM_TO_DICT_LOAD, parameter_docs +from src.scenario_gui.parameter_utils import get_abnormal_param_options, categorize_parameters +from src.scenario_gui.ui_utils import render_parameter_input, apply_sidebar_styles from src.FleetSimulationBase import INPUT_PARAMETERS_FleetSimulationBase - -def get_abnormal_param_options(param): - if param == "network_name": - path = os.path.join(fleetpy_path, "data", "networks") - return [""] + os.listdir(path) - elif param == "demand_name": - path = os.path.join(fleetpy_path, "data", "demand") - return [""] + os.listdir(path) - elif param == "rq_file": - # Will be populated based on network and demand selection - return [""] - return None - -def categorize_parameters(param_names, param_dict): - """Categorize parameters into logical groups based on their prefixes and meanings""" - categories = { - "Basic Settings": [], - "Time Settings": [], - "Request Settings": [], - "Operator Settings": [], - "Parcel Settings": [], - "Vehicle Settings": [], - "Infrastructure": [], - "Other": [] - } - - for param in param_names: - param_obj = param_dict.get(param) - if not param_obj: - continue - - if param.startswith(("start_time", "end_time", "time_step", "lock_time")): - categories["Time Settings"].append(param) - elif param.startswith("user_") or "wait_time" in param or "detour" in param: - categories["Request Settings"].append(param) - elif param.startswith("op_"): - if "parcel" in param: - categories["Parcel Settings"].append(param) - else: - categories["Operator Settings"].append(param) - elif param in ["network_name", "demand_name", "rq_file", "scenario_name", "study_name"]: - categories["Basic Settings"].append(param) - elif param.startswith("veh_") or "vehicle" in param or "fleet" in param: - categories["Vehicle Settings"].append(param) - elif param.startswith(("zone_", "infra_")): - categories["Infrastructure"].append(param) - else: - categories["Other"].append(param) - - # Remove empty categories - return {k: v for k, v in categories.items() if v} - -def render_parameter_input(param, param_obj, key_prefix=""): - """Render the appropriate input widget for a parameter based on its type and metadata""" - help_text = param_obj.doc_string if hasattr(param_obj, 'doc_string') else "" - if hasattr(param_obj, 'type') and param_obj.type: - type_info = f" (Expected type: {param_obj.type})" - help_text = f"{help_text}{type_info}" if help_text else type_info - default_value = param_obj.default_value if hasattr(param_obj, 'default_value') else None - param_type = param_obj.type if hasattr(param_obj, 'type') else "str" - - if hasattr(param_obj, 'options') and param_obj.options: - options = ["None"] if key_prefix == "optional_" else ["Choose..."] - options.extend(param_obj.options) - value = st.selectbox( - f"{param}", - options=options, - key=f"param_{key_prefix}{param}", - help=help_text - ) - if value not in ["None", "Choose..."]: - return value - elif param_type == "int": - try: - default = int(default_value) if default_value and str(default_value).strip() else 0 - except (ValueError, TypeError): - default = 0 - value = st.number_input( - f"{param}", - value=default, - key=f"param_{key_prefix}{param}", - help=help_text - ) - return str(value) - elif param_type == "float": - try: - default = float(default_value) if default_value and str(default_value).strip() else 0.0 - except (ValueError, TypeError): - default = 0.0 - value = st.number_input( - f"{param}", - value=default, - key=f"param_{key_prefix}{param}", - help=help_text - ) - return str(value) - elif param_type == "bool": - value = st.checkbox( - f"{param}", - value=bool(default_value) if default_value is not None else False, - key=f"param_{key_prefix}{param}", - help=help_text - ) - return str(value) - else: - value = st.text_input( - f"{param}", - value=str(default_value) if default_value is not None else "", - key=f"param_{key_prefix}{param}", - help=help_text - ) - return value if value else None - def create_scenario_page(): st.title("Create Scenario") st.markdown("Use this page to create a new simulation scenario.") @@ -349,49 +238,234 @@ def create_scenario_page(): st.error(f"Error saving scenario: {str(e)}") +def run_simulation_page(): + st.title("FleetPy Simulation Manager") + st.write("Upload or select configuration files to run FleetPy simulations") + + # Main area organization + st.header("Configuration Files") + + # Create two columns for the settings + col1, col2 = st.columns(2) + + with col1: + # Log level selection + log_level = st.selectbox( + "Log Level", + ["info", "debug", "warning", "error"], + index=0 + ) + + with col2: + # CPU configuration + n_cpu_per_sim = st.number_input( + "CPUs per Simulation", + min_value=1, + max_value=mp.cpu_count(), + value=1 + ) + + n_parallel_sim = st.number_input( + "Parallel Simulations", + min_value=1, + max_value=mp.cpu_count(), + value=1 + ) + + st.divider() + + # File upload option + upload_method = st.radio( + "Choose how to provide configuration files", + ["Upload Files", "Select from Existing Files"] + ) + + const_cfg_file = None + scenario_cfg_file = None + + if upload_method == "Upload Files": + # Get list of existing studies for reference + studies_path = os.path.join(fleetpy_path, "studies") + existing_studies = [d for d in os.listdir(studies_path) + if os.path.isdir(os.path.join(studies_path, d))] + existing_studies.sort() + + # Study name input with existing studies as suggestions + study_name = st.text_input( + "Study Name", + placeholder="Enter a name for your study", + help="This will be used to organize your configuration files" + ) + + # Show existing studies as reference + with st.expander("View Existing Studies"): + st.write("Existing studies for reference:") + for study in existing_studies: + st.write(f"- {study}") + + const_cfg = st.file_uploader("Upload Constant Configuration File (YAML/CSV)", type=['yaml', 'csv']) + scenario_cfg = st.file_uploader("Upload Scenario Configuration File (YAML/CSV)", type=['yaml', 'csv']) + + if study_name and const_cfg and scenario_cfg: + # Create study directory structure + study_path = os.path.join(studies_path, study_name) + scenarios_path = os.path.join(study_path, "scenarios") + + # Create directories if they don't exist + os.makedirs(scenarios_path, exist_ok=True) + + # Generate file paths preserving original extensions + const_ext = os.path.splitext(const_cfg.name)[1] if const_cfg.name else ".yaml" + scenario_ext = os.path.splitext(scenario_cfg.name)[1] if scenario_cfg.name else ".csv" + + const_cfg_file = os.path.join(scenarios_path, f"const_cfg{const_ext}") + scenario_cfg_file = os.path.join(scenarios_path, f"scenario_cfg{scenario_ext}") + + # Save uploaded files + with open(const_cfg_file, "wb") as f: + f.write(const_cfg.getvalue()) + with open(scenario_cfg_file, "wb") as f: + f.write(scenario_cfg.getvalue()) + + st.success(f"Configuration files saved in study: {study_name}") + elif const_cfg and scenario_cfg: + st.error("Please enter a study name before uploading files") + + else: + # Get list of available studies + studies_path = os.path.join(fleetpy_path, "studies") + studies = [] + + # Get list of valid studies (ones with scenarios directory) + for study in os.listdir(studies_path): + study_path = os.path.join(studies_path, study) + if os.path.isdir(study_path): + scenarios_path = os.path.join(study_path, "scenarios") + if os.path.exists(scenarios_path): + studies.append(study) + + # Sort studies alphabetically + studies.sort() + + # Study selection dropdown + selected_study = st.selectbox( + "Select Study", + studies, + format_func=lambda x: x.replace("_", " ").title() + ) + + # Get config files for selected study + config_files = [] + if selected_study: + scenarios_path = os.path.join(studies_path, selected_study, "scenarios") + for file in os.listdir(scenarios_path): + if file.endswith(('.yaml', '.csv')): + config_files.append(os.path.join(scenarios_path, file)) + + # Sort and filter files for constant config + def sort_key_for_const(filepath): + filename = os.path.basename(filepath).lower() + if "const" in filename: + return (0, filename) + return (1, filename) + + # Sort and filter files for scenario config + def sort_key_for_scenario(filepath): + filename = os.path.basename(filepath).lower() + if "scenario" in filename: + return (0, filename) + if "example" in filename: + return (1, filename) + return (2, filename) + + # Sort files separately for each config type + const_sorted_files = sorted(config_files, key=sort_key_for_const) + scenario_sorted_files = sorted(config_files, key=sort_key_for_scenario) + + const_cfg_file = st.selectbox( + "Select Constant Configuration File", + const_sorted_files, + format_func=lambda x: os.path.basename(x) + ) + + scenario_cfg_file = st.selectbox( + "Select Scenario Configuration File", + scenario_sorted_files, + format_func=lambda x: os.path.basename(x) + ) + + # Add space before run section + st.write("") + st.write("") + + # Run simulation section + st.header("Run Simulation") + + # Show run button and configuration summary + if const_cfg_file and scenario_cfg_file: + # Show configuration summary and preview + st.write("Configuration Summary:") + col1, col2 = st.columns(2) + with col1: + st.write("πŸ“„ **Constant Config:**") + st.write(f"`{os.path.basename(const_cfg_file)}`") + with st.expander("Preview Constant Config", expanded=False): + try: + with open(const_cfg_file, 'r') as f: + content = f.read() + if const_cfg_file.endswith('.yaml'): + st.code(content, language='yaml') + else: + st.code(content) + except Exception as e: + st.error(f"Error reading file: {str(e)}") + + with col2: + st.write("πŸ“„ **Scenario Config:**") + st.write(f"`{os.path.basename(scenario_cfg_file)}`") + with st.expander("Preview Scenario Config", expanded=False): + try: + with open(scenario_cfg_file, 'r') as f: + content = f.read() + if scenario_cfg_file.endswith('.yaml'): + st.code(content, language='yaml') + else: + st.code(content) + except Exception as e: + st.error(f"Error reading file: {str(e)}") + + # Show run button centered + col1, col2, col3 = st.columns([1, 2, 1]) + with col2: + if st.button("▢️ Run Simulation", use_container_width=True): + try: + with st.spinner("Running simulation..."): + # Run the simulation + run_scenarios( + const_cfg_file, + scenario_cfg_file, + log_level=log_level, + n_cpu_per_sim=n_cpu_per_sim, + n_parallel_sim=n_parallel_sim + ) + + # Show success message with study name + study_name = os.path.basename(os.path.dirname(os.path.dirname(const_cfg_file))) + results_path = os.path.join(os.path.dirname(os.path.dirname(const_cfg_file)), "results") + st.success(f"βœ… Simulation completed successfully!") + st.info(f"Results saved in: `{results_path}`") + + except Exception as e: + st.error("❌ An error occurred during simulation:") + st.error(str(e)) + st.code(traceback.format_exc()) + else: + st.info("Please select both configuration files to run the simulation") + + def main(): - # Page navigation with vertical layout - st.sidebar.markdown(""" - - """, unsafe_allow_html=True) + # Apply sidebar styles + apply_sidebar_styles() st.sidebar.title("FleetPy") st.sidebar.markdown('
', unsafe_allow_html=True) @@ -399,243 +473,41 @@ def main(): # Navigation buttons clicked = None if st.sidebar.button( - "⚑️ Run Simulation", - key="nav_run", - help="Configure and run FleetPy simulations", + "πŸ“ Create Scenario", + key="nav_create", + help="Create a new simulation scenario", use_container_width=True, type="secondary" ): - clicked = "run" + clicked = "create" st.sidebar.markdown('
', unsafe_allow_html=True) if st.sidebar.button( - "πŸ“ Create Scenario", - key="nav_create", - help="Create a new simulation scenario", + "⚑️ Run Simulation", + key="nav_run", + help="Configure and run FleetPy simulations", use_container_width=True, type="secondary" ): - clicked = "create" + clicked = "run" st.sidebar.markdown('
', unsafe_allow_html=True) st.sidebar.markdown("---") # Initialize the page selection in session state if not already present if 'current_page' not in st.session_state: - st.session_state.current_page = "run" + st.session_state.current_page = "create" # Update current page based on button clicks if clicked: st.session_state.current_page = clicked # Show the appropriate page based on selection - if st.session_state.current_page == "create": - create_scenario_page() + if st.session_state.current_page == "run": + run_simulation_page() else: - st.title("FleetPy Simulation Manager") - st.write("Upload or select configuration files to run FleetPy simulations") - - # Main area organization - st.header("Configuration Files") - - # Create two columns for the settings - col1, col2 = st.columns(2) - - with col1: - # Log level selection - log_level = st.selectbox( - "Log Level", - ["info", "debug", "warning", "error"], - index=0 - ) - - with col2: - # CPU configuration - n_cpu_per_sim = st.number_input( - "CPUs per Simulation", - min_value=1, - max_value=mp.cpu_count(), - value=1 - ) - - n_parallel_sim = st.number_input( - "Parallel Simulations", - min_value=1, - max_value=mp.cpu_count(), - value=1 - ) - - st.divider() - - # File upload option - upload_method = st.radio( - "Choose how to provide configuration files", - ["Upload Files", "Select from Existing Files"] - ) - - const_cfg_file = None - scenario_cfg_file = None - - if upload_method == "Upload Files": - # Get list of existing studies for reference - studies_path = os.path.join(fleetpy_path, "studies") - existing_studies = [d for d in os.listdir(studies_path) - if os.path.isdir(os.path.join(studies_path, d))] - existing_studies.sort() - - # Study name input with existing studies as suggestions - study_name = st.text_input( - "Study Name", - placeholder="Enter a name for your study", - help="This will be used to organize your configuration files" - ) - - # Show existing studies as reference - with st.expander("View Existing Studies"): - st.write("Existing studies for reference:") - for study in existing_studies: - st.write(f"- {study}") - - const_cfg = st.file_uploader("Upload Constant Configuration File (YAML/CSV)", type=['yaml', 'csv']) - scenario_cfg = st.file_uploader("Upload Scenario Configuration File (YAML/CSV)", type=['yaml', 'csv']) - - if study_name and const_cfg and scenario_cfg: - # Create study directory structure - study_path = os.path.join(studies_path, study_name) - scenarios_path = os.path.join(study_path, "scenarios") - - # Create directories if they don't exist - os.makedirs(scenarios_path, exist_ok=True) - - # Generate file paths preserving original extensions - const_ext = os.path.splitext(const_cfg.name)[1] if const_cfg.name else ".yaml" - scenario_ext = os.path.splitext(scenario_cfg.name)[1] if scenario_cfg.name else ".csv" - - const_cfg_file = os.path.join(scenarios_path, f"const_cfg{const_ext}") - scenario_cfg_file = os.path.join(scenarios_path, f"scenario_cfg{scenario_ext}") - - # Save uploaded files - with open(const_cfg_file, "wb") as f: - f.write(const_cfg.getvalue()) - with open(scenario_cfg_file, "wb") as f: - f.write(scenario_cfg.getvalue()) - - st.success(f"Configuration files saved in study: {study_name}") - elif const_cfg and scenario_cfg: - st.error("Please enter a study name before uploading files") - - else: - # Get list of available studies - studies_path = os.path.join(fleetpy_path, "studies") - studies = [] - - # Get list of valid studies (ones with scenarios directory) - for study in os.listdir(studies_path): - study_path = os.path.join(studies_path, study) - if os.path.isdir(study_path): - scenarios_path = os.path.join(study_path, "scenarios") - if os.path.exists(scenarios_path): - studies.append(study) - - # Sort studies alphabetically - studies.sort() - - # Study selection dropdown - selected_study = st.selectbox( - "Select Study", - studies, - format_func=lambda x: x.replace("_", " ").title() - ) - - # Get config files for selected study - config_files = [] - if selected_study: - scenarios_path = os.path.join(studies_path, selected_study, "scenarios") - for file in os.listdir(scenarios_path): - if file.endswith(('.yaml', '.csv')): - config_files.append(os.path.join(scenarios_path, file)) - - # Sort and filter files for constant config - def sort_key_for_const(filepath): - filename = os.path.basename(filepath).lower() - if "const" in filename: - return (0, filename) - return (1, filename) - - # Sort and filter files for scenario config - def sort_key_for_scenario(filepath): - filename = os.path.basename(filepath).lower() - if "scenario" in filename: - return (0, filename) - if "example" in filename: - return (1, filename) - return (2, filename) - - # Sort files separately for each config type - const_sorted_files = sorted(config_files, key=sort_key_for_const) - scenario_sorted_files = sorted(config_files, key=sort_key_for_scenario) - - const_cfg_file = st.selectbox( - "Select Constant Configuration File", - const_sorted_files, - format_func=lambda x: os.path.basename(x) - ) - - scenario_cfg_file = st.selectbox( - "Select Scenario Configuration File", - scenario_sorted_files, - format_func=lambda x: os.path.basename(x) - ) - - # Add space before run section - st.write("") - st.write("") - - # Run simulation section - st.header("Run Simulation") - - # Show run button and configuration summary - if const_cfg_file and scenario_cfg_file: - # Show configuration summary - st.write("Configuration Summary:") - col1, col2 = st.columns(2) - with col1: - st.write("πŸ“„ **Constant Config:**") - st.write(f"`{os.path.basename(const_cfg_file)}`") - with col2: - st.write("πŸ“„ **Scenario Config:**") - st.write(f"`{os.path.basename(scenario_cfg_file)}`") - - # Show run button centered - col1, col2, col3 = st.columns([1, 2, 1]) - with col2: - if st.button("▢️ Run Simulation", use_container_width=True): - try: - with st.spinner("Running simulation..."): - # Run the simulation - run_scenarios( - const_cfg_file, - scenario_cfg_file, - log_level=log_level, - n_cpu_per_sim=n_cpu_per_sim, - n_parallel_sim=n_parallel_sim - ) - - # Show success message with study name - study_name = os.path.basename(os.path.dirname(os.path.dirname(const_cfg_file))) - results_path = os.path.join(os.path.dirname(os.path.dirname(const_cfg_file)), "results") - st.success(f"βœ… Simulation completed successfully!") - st.info(f"Results saved in: `{results_path}`") - - except Exception as e: - st.error("❌ An error occurred during simulation:") - st.error(str(e)) - st.code(traceback.format_exc()) - else: - st.info("Please select both configuration files to run the simulation") - - + create_scenario_page() if __name__ == "__main__": From 7af9dae0c4e0d8a8de32944579a3a972617e044a Mon Sep 17 00:00:00 2001 From: hoda_hamdy Date: Tue, 17 Jun 2025 14:13:56 +0200 Subject: [PATCH 5/6] some init visualization --- streamlit_gui.py | 511 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 423 insertions(+), 88 deletions(-) diff --git a/streamlit_gui.py b/streamlit_gui.py index 7502a321..a4353f07 100644 --- a/streamlit_gui.py +++ b/streamlit_gui.py @@ -1,20 +1,26 @@ +import json +import pandas as pd +import matplotlib.pyplot as plt +from src.python_plots.plot_classes import PyPlot +from src.ReplayFromResult import ReplayPyPlot +from src.FleetSimulationBase import INPUT_PARAMETERS_FleetSimulationBase +from src.scenario_gui.ui_utils import render_parameter_input, apply_sidebar_styles +from src.scenario_gui.parameter_utils import get_abnormal_param_options, categorize_parameters +from src.scenario_gui.scenario_creator import ScenarioCreator, MODULE_PARAM_TO_DICT_LOAD, parameter_docs +from run_examples import run_scenarios import os import sys import streamlit as st import multiprocessing as mp import traceback from pathlib import Path +import numpy as np +import geopandas as gpd # Add FleetPy path to system path fleetpy_path = os.path.dirname(os.path.abspath(__file__)) sys.path.append(fleetpy_path) -from run_examples import run_scenarios -from src.scenario_gui.scenario_creator import ScenarioCreator, MODULE_PARAM_TO_DICT_LOAD, parameter_docs -from src.scenario_gui.parameter_utils import get_abnormal_param_options, categorize_parameters -from src.scenario_gui.ui_utils import render_parameter_input, apply_sidebar_styles -from src.FleetSimulationBase import INPUT_PARAMETERS_FleetSimulationBase - def create_scenario_page(): st.title("Create Scenario") @@ -34,10 +40,9 @@ def create_scenario_page(): st.session_state.active_tab = "modules" # Create radio for tab selection - selected_tab = st.radio("", ["1. Select Modules", "2. Configure Parameters"], - index=0 if st.session_state.active_tab == "modules" else 1, - horizontal=True, - label_visibility="collapsed") + selected_tab = st.radio("", ["1. Select Modules", "2. Configure Parameters"], + index=0 if st.session_state.active_tab == "modules" else 1, + horizontal=True) # Create tabs and show content based on selection if selected_tab == "1. Select Modules": @@ -47,7 +52,8 @@ def create_scenario_page(): # Mandatory Modules st.subheader("Mandatory Modules") for module in sc._current_mandatory_modules: - options = ["Choose..."] + list(MODULE_PARAM_TO_DICT_LOAD[module]().keys()) if MODULE_PARAM_TO_DICT_LOAD.get(module) else ["Choose..."] + options = ["Choose..."] + list(MODULE_PARAM_TO_DICT_LOAD[module]( + ).keys()) if MODULE_PARAM_TO_DICT_LOAD.get(module) else ["Choose..."] selected = st.selectbox( f"{module}", options=options, @@ -60,7 +66,8 @@ def create_scenario_page(): # Optional Modules st.subheader("Optional Modules") for module in sc._current_optional_modules: - options = ["None"] + list(MODULE_PARAM_TO_DICT_LOAD[module]().keys()) if MODULE_PARAM_TO_DICT_LOAD.get(module) else ["None"] + options = ["None"] + list(MODULE_PARAM_TO_DICT_LOAD[module]().keys() + ) if MODULE_PARAM_TO_DICT_LOAD.get(module) else ["None"] selected = st.selectbox( f"{module}", options=options, @@ -78,7 +85,7 @@ def create_scenario_page(): sc._currently_selected_modules.get(module) is not None for module in sc._current_mandatory_modules ) - + if mandatory_modules_selected: # Use session state to switch to parameters tab st.session_state.active_tab = "parameters" @@ -89,31 +96,33 @@ def create_scenario_page(): elif selected_tab == "2. Configure Parameters": st.header("Parameter Selection") - + # Keep track of seen parameters seen_params = set() # Special handling for Basic Settings which includes network and demand selection with st.expander("Basic Settings", expanded=True): # Handle special case parameters (network, demand, rq_file) - basic_params = ["network_name", "demand_name", "rq_file", "scenario_name", "study_name"] + basic_params = ["network_name", "demand_name", + "rq_file", "scenario_name", "study_name"] col1, col2 = st.columns(2) - + # First handle network and demand selection as they're needed for rq_file for param in ["network_name", "demand_name"]: if param not in sc._current_mandatory_params and param not in sc._current_optional_params: continue - + seen_params.add(param) col = col1 if param == "network_name" else col2 - + with col: - abnormal_options = get_abnormal_param_options(param) + abnormal_options = get_abnormal_param_options(param, fleetpy_path) param_obj = sc.parameter_dict.get(param) - + if param_obj: - help_text = param_obj.doc_string if hasattr(param_obj, 'doc_string') else "" - + help_text = param_obj.doc_string if hasattr( + param_obj, 'doc_string') else "" + if abnormal_options: selected = st.selectbox( f"{param}", @@ -121,26 +130,27 @@ def create_scenario_page(): key=f"param_mandatory_{param}", help=help_text ) - + if param == "network_name" and selected: st.session_state.network_selected = selected elif param == "demand_name" and selected: st.session_state.demand_selected = selected - + if selected: sc.select_param(param, selected) - + # Now handle rq_file after network and demand are set if "rq_file" in sc._current_mandatory_params or "rq_file" in sc._current_optional_params: seen_params.add("rq_file") param_obj = sc.parameter_dict.get("rq_file") if param_obj: - help_text = param_obj.doc_string if hasattr(param_obj, 'doc_string') else "" - + help_text = param_obj.doc_string if hasattr( + param_obj, 'doc_string') else "" + if st.session_state.network_selected and st.session_state.demand_selected: - rq_path = os.path.join(fleetpy_path, "data", "demand", - st.session_state.demand_selected, "matched", - st.session_state.network_selected) + rq_path = os.path.join(fleetpy_path, "data", "demand", + st.session_state.demand_selected, "matched", + st.session_state.network_selected) if os.path.exists(rq_path): rq_options = [""] + os.listdir(rq_path) selected = st.selectbox( @@ -152,22 +162,25 @@ def create_scenario_page(): if selected: sc.select_param("rq_file", selected) else: - st.warning("No request files found for the selected network and demand.") + st.warning( + "No request files found for the selected network and demand.") else: - st.info("Please select both network and demand to view available request files.") - + st.info( + "Please select both network and demand to view available request files.") + # Handle remaining basic parameters for param in ["scenario_name", "study_name"]: if param not in sc._current_mandatory_params and param not in sc._current_optional_params: continue - + seen_params.add(param) col = col1 if param == "scenario_name" else col2 - + with col: param_obj = sc.parameter_dict.get(param) if param_obj: - help_text = param_obj.doc_string if hasattr(param_obj, 'doc_string') else "" + help_text = param_obj.doc_string if hasattr( + param_obj, 'doc_string') else "" value = st.text_input( f"{param}", key=f"param_mandatory_{param}", @@ -177,8 +190,10 @@ def create_scenario_page(): sc.select_param(param, value) # Categorize remaining mandatory parameters - remaining_mandatory = [p for p in sc._current_mandatory_params if p not in seen_params] - mandatory_categories = categorize_parameters(remaining_mandatory, sc.parameter_dict) + remaining_mandatory = [ + p for p in sc._current_mandatory_params if p not in seen_params] + mandatory_categories = categorize_parameters( + remaining_mandatory, sc.parameter_dict) # Render mandatory parameters by category for category, params in mandatory_categories.items(): @@ -188,7 +203,7 @@ def create_scenario_page(): if param in seen_params: continue seen_params.add(param) - + col = col1 if i % 2 == 0 else col2 with col: param_obj = sc.parameter_dict.get(param) @@ -202,14 +217,16 @@ def create_scenario_page(): try: if int(value) > 1: st.info("You have selected more than one operator. Please make sure to select different parameters for each operator. " - "Otherwise, the parameters will be the same for all operators. You can do this by separating the different " - "parameters by a comma ',' for all parameters starting with op_") + "Otherwise, the parameters will be the same for all operators. You can do this by separating the different " + "parameters by a comma ',' for all parameters starting with op_") except ValueError: pass # Categorize optional parameters - remaining_optional = [p for p in sc._current_optional_params if p not in seen_params] - optional_categories = categorize_parameters(remaining_optional, sc.parameter_dict) + remaining_optional = [ + p for p in sc._current_optional_params if p not in seen_params] + optional_categories = categorize_parameters( + remaining_optional, sc.parameter_dict) # Render optional parameters by category st.subheader("Optional Parameters") @@ -220,12 +237,13 @@ def create_scenario_page(): if param in seen_params: continue seen_params.add(param) - + col = col1 if i % 2 == 0 else col2 with col: param_obj = sc.parameter_dict.get(param) if param_obj: - value = render_parameter_input(param, param_obj, "optional_") + value = render_parameter_input( + param, param_obj, "optional_") if value: sc.select_param(param, value) @@ -271,7 +289,7 @@ def run_simulation_page(): max_value=mp.cpu_count(), value=1 ) - + st.divider() # File upload option @@ -286,47 +304,53 @@ def run_simulation_page(): if upload_method == "Upload Files": # Get list of existing studies for reference studies_path = os.path.join(fleetpy_path, "studies") - existing_studies = [d for d in os.listdir(studies_path) - if os.path.isdir(os.path.join(studies_path, d))] + existing_studies = [d for d in os.listdir(studies_path) + if os.path.isdir(os.path.join(studies_path, d))] existing_studies.sort() - + # Study name input with existing studies as suggestions study_name = st.text_input( "Study Name", placeholder="Enter a name for your study", help="This will be used to organize your configuration files" ) - + # Show existing studies as reference with st.expander("View Existing Studies"): st.write("Existing studies for reference:") for study in existing_studies: st.write(f"- {study}") - - const_cfg = st.file_uploader("Upload Constant Configuration File (YAML/CSV)", type=['yaml', 'csv']) - scenario_cfg = st.file_uploader("Upload Scenario Configuration File (YAML/CSV)", type=['yaml', 'csv']) - + + const_cfg = st.file_uploader( + "Upload Constant Configuration File (YAML/CSV)", type=['yaml', 'csv']) + scenario_cfg = st.file_uploader( + "Upload Scenario Configuration File (YAML/CSV)", type=['yaml', 'csv']) + if study_name and const_cfg and scenario_cfg: # Create study directory structure study_path = os.path.join(studies_path, study_name) scenarios_path = os.path.join(study_path, "scenarios") - + # Create directories if they don't exist os.makedirs(scenarios_path, exist_ok=True) - + # Generate file paths preserving original extensions - const_ext = os.path.splitext(const_cfg.name)[1] if const_cfg.name else ".yaml" - scenario_ext = os.path.splitext(scenario_cfg.name)[1] if scenario_cfg.name else ".csv" - - const_cfg_file = os.path.join(scenarios_path, f"const_cfg{const_ext}") - scenario_cfg_file = os.path.join(scenarios_path, f"scenario_cfg{scenario_ext}") - + const_ext = os.path.splitext(const_cfg.name)[ + 1] if const_cfg.name else ".yaml" + scenario_ext = os.path.splitext(scenario_cfg.name)[ + 1] if scenario_cfg.name else ".csv" + + const_cfg_file = os.path.join( + scenarios_path, f"const_cfg{const_ext}") + scenario_cfg_file = os.path.join( + scenarios_path, f"scenario_cfg{scenario_ext}") + # Save uploaded files with open(const_cfg_file, "wb") as f: f.write(const_cfg.getvalue()) with open(scenario_cfg_file, "wb") as f: f.write(scenario_cfg.getvalue()) - + st.success(f"Configuration files saved in study: {study_name}") elif const_cfg and scenario_cfg: st.error("Please enter a study name before uploading files") @@ -335,7 +359,7 @@ def run_simulation_page(): # Get list of available studies studies_path = os.path.join(fleetpy_path, "studies") studies = [] - + # Get list of valid studies (ones with scenarios directory) for study in os.listdir(studies_path): study_path = os.path.join(studies_path, study) @@ -343,21 +367,22 @@ def run_simulation_page(): scenarios_path = os.path.join(study_path, "scenarios") if os.path.exists(scenarios_path): studies.append(study) - + # Sort studies alphabetically studies.sort() - + # Study selection dropdown selected_study = st.selectbox( "Select Study", studies, format_func=lambda x: x.replace("_", " ").title() ) - + # Get config files for selected study config_files = [] if selected_study: - scenarios_path = os.path.join(studies_path, selected_study, "scenarios") + scenarios_path = os.path.join( + studies_path, selected_study, "scenarios") for file in os.listdir(scenarios_path): if file.endswith(('.yaml', '.csv')): config_files.append(os.path.join(scenarios_path, file)) @@ -387,7 +412,7 @@ def sort_key_for_scenario(filepath): const_sorted_files, format_func=lambda x: os.path.basename(x) ) - + scenario_cfg_file = st.selectbox( "Select Scenario Configuration File", scenario_sorted_files, @@ -397,10 +422,10 @@ def sort_key_for_scenario(filepath): # Add space before run section st.write("") st.write("") - + # Run simulation section st.header("Run Simulation") - + # Show run button and configuration summary if const_cfg_file and scenario_cfg_file: # Show configuration summary and preview @@ -419,7 +444,7 @@ def sort_key_for_scenario(filepath): st.code(content) except Exception as e: st.error(f"Error reading file: {str(e)}") - + with col2: st.write("πŸ“„ **Scenario Config:**") st.write(f"`{os.path.basename(scenario_cfg_file)}`") @@ -433,7 +458,7 @@ def sort_key_for_scenario(filepath): st.code(content) except Exception as e: st.error(f"Error reading file: {str(e)}") - + # Show run button centered col1, col2, col3 = st.columns([1, 2, 1]) with col2: @@ -448,13 +473,15 @@ def sort_key_for_scenario(filepath): n_cpu_per_sim=n_cpu_per_sim, n_parallel_sim=n_parallel_sim ) - + # Show success message with study name - study_name = os.path.basename(os.path.dirname(os.path.dirname(const_cfg_file))) - results_path = os.path.join(os.path.dirname(os.path.dirname(const_cfg_file)), "results") + study_name = os.path.basename( + os.path.dirname(os.path.dirname(const_cfg_file))) + results_path = os.path.join(os.path.dirname( + os.path.dirname(const_cfg_file)), "results") st.success(f"βœ… Simulation completed successfully!") st.info(f"Results saved in: `{results_path}`") - + except Exception as e: st.error("❌ An error occurred during simulation:") st.error(str(e)) @@ -463,36 +490,342 @@ def sort_key_for_scenario(filepath): st.info("Please select both configuration files to run the simulation") + + return None, None, None + + +def load_vehicle_data(scenario_path, selected_time): + """Load and process vehicle data from operator statistics file.""" + stats_file = os.path.join(scenario_path, "2-0_op-stats.csv") + if not os.path.exists(stats_file): + raise FileNotFoundError(f"Operator statistics file not found: {stats_file}") + + df = pd.read_csv(stats_file) + + # Filter rows that include the selected time + relevant_rows = df[ + (df['start_time'] <= selected_time) & + (df['end_time'] >= selected_time) + ].copy() + + # Process trajectory data + def get_position_id_at_time(row, target_time): + """Get the vehicle's position ID at the given time.""" + # Use start position if no trajectory + if pd.isna(row['trajectory']) or pd.isna(row['route']): + pos = row['start_pos'].split(';')[0] if pd.notna(row['start_pos']) else None + return pos or "unknown" + + try: + # Parse route and trajectory + route_points = row['route'].split(';') + trajectory_points = row['trajectory'].split(',') + + # Create a list of (time, position) tuples + time_pos_pairs = [] + for entry in trajectory_points: + if ':' not in entry: + continue + try: + parts = entry.strip().split(':') + if len(parts) == 2: + time = float(parts[1]) + time_pos_pairs.append((time, parts[0])) + except (ValueError, IndexError): + continue + + if not time_pos_pairs: + return row['start_pos'].split(';')[0] + + # Sort by time difference to target + time_pos_pairs.sort(key=lambda x: abs(x[0] - target_time)) + closest_time, closest_pos = time_pos_pairs[0] + + # Find the position in the route + if closest_pos in route_points: + return closest_pos + + # If position not found in route, use closest route point + try: + idx = route_points.index(closest_pos) + return route_points[idx] + except ValueError: + return route_points[0] if route_points else row['start_pos'].split(';')[0] + + except Exception as e: + # If anything goes wrong, return the start position + return row['start_pos'].split(';')[0] if pd.notna(row['start_pos']) else "unknown" + + # Extract positions + relevant_rows['node_id'] = relevant_rows.apply( + lambda row: get_position_id_at_time(row, selected_time), axis=1 + ) + + # Convert string occupancy to int + relevant_rows['occupancy'] = relevant_rows['occupancy'].fillna(0) + relevant_rows['occupancy'] = relevant_rows.apply( + lambda row: len(str(row['rq_on_board']).split(';')) if pd.notna(row['rq_on_board']) else 0, + axis=1 + ) + + return relevant_rows + + +def plot_vehicle_status(veh_states, time_step): + """Create a vehicle status plot for a given time step""" + if veh_states is None: + return None + + current_states = veh_states[veh_states['time'] == time_step] + if current_states.empty: + return None + + fig, ax = plt.subplots(figsize=(10, 6)) + status_counts = current_states['status'].value_counts() + ax.bar(status_counts.index, status_counts.values) + ax.set_title('Vehicle Status Distribution') + ax.set_ylabel('Number of Vehicles') + plt.xticks(rotation=45) + return fig + + +def plot_vehicle_occupancy(vehicle_data): + """Create an occupancy distribution plot using matplotlib.""" + fig = plt.figure(figsize=(10, 6)) + ax = fig.add_subplot(111) + + # Count vehicles by status and occupancy + status_counts = {} + for _, row in vehicle_data.iterrows(): + status = row['status'] + occ = int(row['occupancy']) + key = f"{status} ({occ})" + status_counts[key] = status_counts.get(key, 0) + 1 + + # Sort keys for consistent display + sorted_keys = sorted(status_counts.keys()) + values = [status_counts[k] for k in sorted_keys] + + # Create color map + colors = plt.cm.Set3(np.linspace(0, 1, len(sorted_keys))) + + # Create bar plot + bars = ax.bar(range(len(sorted_keys)), values, color=colors) + + # Customize plot + ax.set_xticks(range(len(sorted_keys))) + ax.set_xticklabels(sorted_keys, rotation=45, ha='right') + ax.set_title('Vehicle Status and Occupancy Distribution') + ax.set_ylabel('Number of Vehicles') + + # Add value labels + for rect in bars: + height = rect.get_height() + ax.text(rect.get_x() + rect.get_width()/2., height, + '%d' % int(height), + ha='center', va='bottom') + + plt.tight_layout() + return fig + + +def visualization_page(): + st.title("FleetPy Visualization") + st.write("View simulation results and visualizations") + + # Get list of available studies + studies_path = os.path.join(fleetpy_path, "studies") + studies = [] + + for study in os.listdir(studies_path): + study_path = os.path.join(studies_path, study) + if os.path.isdir(study_path): + results_path = os.path.join(study_path, "results") + if os.path.exists(results_path): + studies.append(study) + + studies.sort() + + if not studies: + st.warning("No studies with results found. Please run some simulations first.") + return + + # Study selection + selected_study = st.selectbox( + "Select Study", + studies, + format_func=lambda x: x.replace("_", " ").title() + ) + + if selected_study: + results_path = os.path.join(studies_path, selected_study, "results") + scenarios = [d for d in os.listdir(results_path) + if os.path.isdir(os.path.join(results_path, d))] + scenarios.sort() + + if not scenarios: + st.warning("No scenario results found in this study.") + return + + # Scenario selection + selected_scenario = st.selectbox( + "Select Scenario", + scenarios, + format_func=lambda x: x.replace("_", " ").title() + ) + + if selected_scenario: + scenario_path = os.path.join(results_path, selected_scenario) + + try: + # Load operator statistics + stats_file = os.path.join(scenario_path, "2-0_op-stats.csv") + op_stats = pd.read_csv(stats_file) + + # Get time range from the data + min_time = op_stats['start_time'].min() + max_time = op_stats['end_time'].max() + + # Time selection with minutes display + selected_time = st.slider( + "Select Time", + min_value=float(min_time), + max_value=float(max_time), + value=float(min_time), + format="%.1f minutes" + ) + + # Load vehicle data for selected time + vehicle_data = load_vehicle_data(scenario_path, selected_time) + + col1, col2 = st.columns(2) + + with col1: + st.subheader("Vehicle Status") + fig = plot_vehicle_occupancy(vehicle_data) + st.pyplot(fig) + plt.close(fig) # Clean up matplotlib figure + + with col2: + st.subheader("Current Fleet State") + # Create a summary table of vehicle states + status_summary = vehicle_data.groupby(['status', 'occupancy']).size().reset_index() + status_summary.columns = ['Status', 'Passengers', 'Count'] + st.table(status_summary) + + # Display statistics + st.subheader("Fleet Statistics") + stats_col1, stats_col2, stats_col3, stats_col4 = st.columns(4) + + with stats_col1: + st.metric("Total Vehicles", len(vehicle_data)) + + with stats_col2: + active_vehicles = len(vehicle_data[vehicle_data['status'] != 'idle']) + st.metric("Active Vehicles", active_vehicles) + + with stats_col3: + total_passengers = vehicle_data['occupancy'].sum() + st.metric("Total Passengers", total_passengers) + + with stats_col4: + avg_occupancy = vehicle_data['occupancy'].mean() + st.metric("Average Occupancy", f"{avg_occupancy:.2f}") + + # Network and vehicle positions + st.subheader("Vehicle Positions") + network_file = os.path.join(scenario_path, "network_data.json") + if os.path.exists(network_file): + with open(network_file, 'r') as f: + network_data = json.load(f) + st.write("Network visualization will be added in future updates") + else: + st.info("Network data not available for visualization") + + # Show detailed vehicle table with filtering + st.subheader("Vehicle Details") + + # Add filters + col1, col2 = st.columns(2) + with col1: + status_filter = st.multiselect( + "Filter by Status", + options=sorted(vehicle_data['status'].unique()), + default=[] + ) + + with col2: + occupancy_filter = st.multiselect( + "Filter by Occupancy", + options=sorted(vehicle_data['occupancy'].unique()), + default=[] + ) + + # Apply filters + filtered_data = vehicle_data.copy() + if status_filter: + filtered_data = filtered_data[filtered_data['status'].isin(status_filter)] + if occupancy_filter: + filtered_data = filtered_data[filtered_data['occupancy'].isin(occupancy_filter)] + + # Display filtered data + display_cols = ['vehicle_id', 'vehicle_type', 'status', 'occupancy', + 'driven_distance', 'node_id'] + st.dataframe( + filtered_data[display_cols].sort_values('vehicle_id'), + use_container_width=True + ) + + except Exception as e: + st.error(f"Error loading scenario data: {str(e)}") + st.code(traceback.format_exc()) + + def main(): # Apply sidebar styles apply_sidebar_styles() - + st.sidebar.title("FleetPy") - st.sidebar.markdown('
', unsafe_allow_html=True) - + st.sidebar.markdown( + '
', unsafe_allow_html=True) + # Navigation buttons clicked = None if st.sidebar.button( - "πŸ“ Create Scenario", - key="nav_create", + "πŸ“ Create Scenario", + key="nav_create", help="Create a new simulation scenario", use_container_width=True, type="secondary" ): clicked = "create" - - st.sidebar.markdown('
', unsafe_allow_html=True) - + + st.sidebar.markdown( + '
', unsafe_allow_html=True) + if st.sidebar.button( - "⚑️ Run Simulation", - key="nav_run", + "⚑️ Run Simulation", + key="nav_run", help="Configure and run FleetPy simulations", use_container_width=True, type="secondary" ): clicked = "run" - st.sidebar.markdown('
', unsafe_allow_html=True) + st.sidebar.markdown( + '
', unsafe_allow_html=True) + + if st.sidebar.button( + "πŸ“ˆ Visualizations", + key="nav_viz", + help="View simulation results and visualizations", + use_container_width=True, + type="secondary" + ): + clicked = "viz" + + st.sidebar.markdown( + '
', unsafe_allow_html=True) st.sidebar.markdown("---") # Initialize the page selection in session state if not already present @@ -506,6 +839,8 @@ def main(): # Show the appropriate page based on selection if st.session_state.current_page == "run": run_simulation_page() + elif st.session_state.current_page == "viz": + visualization_page() else: create_scenario_page() From 2b6a768f777de2c7de7db810b9d48cdd0c281e78 Mon Sep 17 00:00:00 2001 From: hoda_hamdy Date: Wed, 25 Jun 2025 22:10:27 +0200 Subject: [PATCH 6/6] docs: add instructions for using the Streamlit GUI --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fd264f7..bf36709a 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,6 @@ For now, you can inspect the data structures and files in the examples provided * πŸ“ `FleetPy/studies` More detailed descriptions of the **data structure, preprocessing steps, and result data** will be provided in upcoming versions. -Additionally, a **GUI to set up scenarios** (with a choice of submodules and data) is planned for the future. 🎨 In general, you can **save your data and study definitions** in the mentioned directories. These are included in `.gitignore`. @@ -118,6 +117,28 @@ In general, you can **save your data and study definitions** in the mentioned di --- +## πŸ–₯️ Using the FleetPy GUI + +FleetPy provides a graphical user interface (GUI) for creating scenarios, running simulations, and visualizing results. + +To launch the GUI: + +1. Activate your Conda environment: + ```bash + conda activate fleetpy + ``` +2. Start the GUI with: + ```bash + streamlit run streamlit_gui.py + ``` + +This will open the FleetPy GUI in your web browser, where you can: +- **Create new simulation scenarios** with guided parameter selection. +- **Run simulations** by uploading or selecting configuration files. +- **Visualize results** interactively. + +--- + ## πŸ“Š Running a Simulation To test an example scenario: