From c4ea0ee0a21583258a20b2499cc46bfd8ffcc75c Mon Sep 17 00:00:00 2001 From: Kenza Taibi Date: Tue, 16 Dec 2025 17:33:29 +0100 Subject: [PATCH 1/2] Add files --- Dockerfile | 10 ++++++++++ environment.yml | 10 ++++++++++ requirements.txt | 3 +++ velo_python_3.12.def | 11 +++++++++++ 4 files changed, 34 insertions(+) create mode 100644 Dockerfile create mode 100644 environment.yml create mode 100644 requirements.txt create mode 100644 velo_python_3.12.def diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bd8cf3b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["python", "1_basic_single_sim/run_single.py"] diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..8cb460f --- /dev/null +++ b/environment.yml @@ -0,0 +1,10 @@ +name: velo +channels: + - conda-forge + - defaults +dependencies: + - python=3.12 + - numpy + - matplotlib + - pandas + - pytest diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..68ea094 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +numpy +matplotlib +pandas diff --git a/velo_python_3.12.def b/velo_python_3.12.def new file mode 100644 index 0000000..ecc545c --- /dev/null +++ b/velo_python_3.12.def @@ -0,0 +1,11 @@ +Bootstrap: docker +From: python:3.12-slim + +%post +pip install --no-cache-dir numpy matplotlib pandas pytest + +%files +./ /app/ + +%runscript +python /app/1_basic_single_sim/run_single.py From 6e6bb98165c9173cbcfc5b09821a5289e4d8ebae Mon Sep 17 00:00:00 2001 From: Kenza Taibi Date: Wed, 17 Dec 2025 16:42:41 +0100 Subject: [PATCH 2/2] Phase 1:basic simulation fini --- 1_basic_single_sim/model.py | 82 +++++++++++++++----------------- 1_basic_single_sim/run_single.py | 80 +++++++++++++++++-------------- 2 files changed, 82 insertions(+), 80 deletions(-) diff --git a/1_basic_single_sim/model.py b/1_basic_single_sim/model.py index 6ea32b9..f102ab3 100644 --- a/1_basic_single_sim/model.py +++ b/1_basic_single_sim/model.py @@ -26,24 +26,25 @@ def step( rng: np.random.Generator, metrics: Dict[str, int], ) -> State: - """Simulate one time step of the bike-sharing system. + #Mailly -> Moulin + if rng.random() < p1: + if state.mailly > 0: + state.mailly -= 1 + state.moulin += 1 + else: + state.unmet_mailly += 1 + metrics["unmet_mailly"] += 1 - Args: - state: Current state of the system (bike counts at each station) - p1: Probability of a user wanting to go from Mailly to Moulin - p2: Probability of a user wanting to go from Moulin to Mailly - rng: Random number generator for stochastic events - metrics: Dictionary to track simulation metrics (unmet demand, etc.) + #Moulin -> Mailly + if rng.random() < p2: + if state.moulin > 0: + state.moulin -= 1 + state.mailly += 1 + else: + state.unmet_moulin += 1 + metrics["unmet_moulin"] += 1 - Returns: - Updated state after one simulation step - - Note: - - If a station has no bikes available, increment the appropriate unmet demand counter - - Update the state by moving bikes between stations based on probabilities - """ - # User tries to go from mailly -> moulin with prob p1 - pass + return state def run_simulation( @@ -54,30 +55,25 @@ def run_simulation( p2: float, seed: int, ) -> Tuple[pd.DataFrame, Dict[str, int]]: - """Run a complete bike-sharing simulation. - - Args: - initial_mailly: Initial number of bikes at Mailly station - initial_moulin: Initial number of bikes at Moulin station - steps: Number of simulation steps to run - p1: Probability of movement from Mailly to Moulin - p2: Probability of movement from Moulin to Mailly - seed: Random seed for reproducibility - - Returns: - Tuple containing: - - DataFrame with columns ['time', 'mailly', 'moulin'] tracking bike counts over time - - Dictionary with metrics including: - - mailly: Number of bikes at Mailly station - - moulin: Number of bikes at Moulin station - - 'unmet_mailly': Number of unmet requests at Mailly - - 'unmet_moulin': Number of unmet requests at Moulin - - 'final_imbalance': Final difference between station bike counts - - Note: - - Create the state object with initial bike counts - - Initialize metrics dictionary with appropriate counters - - Record state at each time step for the DataFrame - - Calculate final imbalance as mailly - moulin - """ - pass + rng = np.random.default_rng(seed) + + state = State(mailly=int(initial_mailly), moulin=int(initial_moulin)) + + metrics: Dict[str, int] = { + "unmet_mailly": 0, + "unmet_moulin": 0, + } + + rows = [{"time": 0, "mailly": state.mailly, "moulin": state.moulin}] + + for t in range(1, steps + 1): + step(state, p1, p2, rng, metrics) + rows.append({"time": t, "mailly": state.mailly, "moulin": state.moulin}) + + df = pd.DataFrame(rows, columns=["time", "mailly", "moulin"]) + + metrics["mailly"] = state.mailly + metrics["moulin"] = state.moulin + metrics["final_imbalance"] = state.mailly - state.moulin + + return df, metrics diff --git a/1_basic_single_sim/run_single.py b/1_basic_single_sim/run_single.py index 5ff64b3..9ca71a5 100644 --- a/1_basic_single_sim/run_single.py +++ b/1_basic_single_sim/run_single.py @@ -7,47 +7,53 @@ def parse_args(): - """Parse command line arguments for single simulation run. - - Returns: - Parsed arguments containing: - - steps: Number of simulation steps - - p1: Probability of movement from Mailly to Moulin - - p2: Probability of movement from Moulin to Mailly - - init_mailly: Initial bikes at Mailly station - - init_moulin: Initial bikes at Moulin station - - seed: Random seed (default: 0) - - out_csv: Output CSV file path - - plot: Boolean flag to generate plots - - Note: - Use argparse.ArgumentParser to define all required and optional arguments - """ - # TODO: Implement argument parsing - pass + parser = argparse.ArgumentParser(description="Run a single bike-sharing simulation.") + + parser.add_argument("--steps", type=int, required=True, help="Number of simulation steps") + parser.add_argument("--p1", type=float, required=True, help="Prob Mailly -> Moulin") + parser.add_argument("--p2", type=float, required=True, help="Prob Moulin -> Mailly") + parser.add_argument("--init-mailly", dest="init_mailly", type=int, required=True, help="Initial bikes at Mailly") + parser.add_argument("--init-moulin", dest="init_moulin", type=int, required=True, help="Initial bikes at Moulin") + parser.add_argument("--seed", type=int, default=0, help="Random seed (default: 0)") + parser.add_argument("--out-csv", dest="out_csv", type=str, required=True, help="Output CSV file path") + parser.add_argument("--plot", action="store_true", help="Generate plots") + + return parser.parse_args() def main(): - """Main function to run a single bike-sharing simulation. - - This function should: - 1. Parse command line arguments - 2. Run the simulation with specified parameters - 3. Save results to CSV files (timeseries and metrics) - 4. Optionally generate and save plots - - Output files: - - Timeseries data: CSV with time, mailly, moulin columns - - Metrics data: CSV with key-value pairs of simulation metrics - - Optional plot: PNG showing bike counts over time for both stations - - Note: - Create output directories if they don't exist - Save metrics as tab-separated key-value pairs - """ - # TODO: Implement main simulation workflow - pass + args = parse_args() + + out_csv = Path(args.out_csv) + out_dir = out_csv.parent + out_dir.mkdir(parents=True, exist_ok=True) + + df, metrics = run_simulation( + initial_mailly=args.init_mailly, + initial_moulin=args.init_moulin, + steps=args.steps, + p1=args.p1, + p2=args.p2, + seed=args.seed, + ) + + df.to_csv(out_csv, index=False) + + metrics_path = out_dir / f"{out_csv.stem}_metrics.tsv" + with open(metrics_path, "w", encoding="utf-8") as f: + for k, v in metrics.items(): + f.write(f"{k}\t{v}\n") + if args.plot: + plt.figure() + plt.plot(df["time"], df["mailly"], label="Mailly") + plt.plot(df["time"], df["moulin"], label="Moulin") + plt.xlabel("time") + plt.ylabel("bikes") + plt.legend() + plot_path = out_dir / f"{out_csv.stem}.png" + plt.savefig(plot_path, dpi=150, bbox_inches="tight") + plt.close() if __name__ == "__main__": main()