diff --git a/README.md b/README.md index 7fab878e7193874c455a1e2f66dbebab687dc755..86370f05afaf6cb9a6766de1f20b5b7f2e0000f6 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,62 @@ -# Aep Schedule Generator Rusty +# Générateur d'horaire de l'AEP v2 -**Disclaimer** This is not yet the official AEP schedule. +## ðŸ› ï¸ Objectif du projet -## The Stack -The stack is LAC (Leptos + Axum + CSV) +Le Générateur d'horaire de l'AEP v2 est une refonte complète du générateur d'horaire étudiant, conçu pour être rapide, flexible et accessible. Ce projet modernise une base de code vieille de 15 ans et optimise l'expérience utilisateur en adoptant des technologies modernes. +## â“ FAQ -Leptos is a great frontend framework in Rust with web assembly to make a responsive UI. Axum is the fast backend serving the HTTP in Rust. The storage is done with CSV, but that's only because Poly give us CSV to represent all the courses. +### Pourquoi une refonte complète ? -## Programming Languages used to make this -- Rust +L'ancien générateur, en maintenance depuis plus de 15 ans, était obsolète avec une base de code mêlant 5 langages différents. Une refonte était essentielle pour garantir la maintenabilité et améliorer les performances. -## Credit -Marc-Antoine Manningham for the backend and the frontend +### Comment est-il aussi rapide ? -Raphael Salvas, Achille Saint-Hillier, Sunnee Chevalier and Gabriel Billard have made a figma that's the inspiration for the style \ No newline at end of file +- **Traitement local** : Les horaires sont générés directement dans le navigateur via WebAssembly, éliminant les délais liés aux serveurs. +- **Optimisation intelligente** : Seuls les meilleurs horaires sont calculés, grâce à des algorithmes comme le *branch and bound* pour réduire l'espace de recherche et respecter les contraintes définies. + +### Où puis-je soumettre des suggestions ou rapports de bugs ? + +- Utilisez le bouton **Signaler un bug** pour soumettre vos idées ou signaler un problème. +- Contributions bienvenues ! Rejoignez-nous si vous avez des compétences en Rust. + +## 👥 Crédits + +- **Développement** : Marc-Antoine Manningham (front-end & back-end) +- **Design** : Raphael Salvas, Achille Saint-Hillier, Sunnee Chevalier et Gabriel Billard, inspirés par leur maquette Figma. + + +Voici une version mise à jour du README, incluant des instructions pour l'auto-hébergement avec Docker : + +## âš™ï¸ Auto-hébergement avec Docker + +### Prérequis + +1. **Docker** : Assurez-vous que Docker est installé. [Installation Docker](https://docs.docker.com/get-docker/) +2. **Docker Compose** : (optionnel, selon votre configuration). + +### Instructions + +1. **Téléchargez et exécutez l'image Docker** : + ```bash + docker pull git.step.polymtl.ca/Lemark/aep-schedule-generator:latest + docker run -d -p 8080:8080 --name aep-schedule-generator git.step.polymtl.ca/Lemark/aep-schedule-generator:latest + ``` + +2. **Accédez à l'application** : + Ouvrez votre navigateur à l'adresse [http://localhost:8080](http://localhost:8080). + +3. **Configurer avec Docker Compose** (facultatif) : + Créez un fichier `docker-compose.yml` : + ```yaml + version: '3' + services: + aep-schedule-generator: + image: git.step.polymtl.ca/Lemark/aep-schedule-generator:latest + ports: + - "8080:8080" + ``` + Lancez les services : + ```bash + docker-compose up -d + ``` diff --git a/aep-schedule-website/Cargo.toml b/aep-schedule-website/Cargo.toml index 57f1e941dd66fb6037fcd4151666bd1c8b73e87d..9c2731f4cda3718d46c5567aae35bc24140ce2a3 100644 --- a/aep-schedule-website/Cargo.toml +++ b/aep-schedule-website/Cargo.toml @@ -11,10 +11,10 @@ axum = { version = "0.7", optional = true, features = ["macros"] } console_error_panic_hook = "0.1" console_log = "1" cfg-if = "1" -leptos = { version = "0.6", features = ["nightly"] } -leptos_axum = { version = "0.6", optional = true } -leptos_meta = { version = "0.6", features = ["nightly"] } -leptos_router = { version = "0.6", features = ["nightly"] } +leptos = { version = "0.7.0-rc0", features = ["nightly"] } +leptos_axum = { version = "0.7.0-rc0", optional = true } +leptos_meta = { version = "0.7.0-rc0", features = ["nightly"] } +leptos_router = { version = "0.7.0-rc0", features = ["nightly"] } log = "0.4" rand = { version = "0.8", optional = true } simple_logger = "5" diff --git a/combinatoric-solver/Cargo.lock b/combinatoric-solver/Cargo.lock deleted file mode 100644 index d5d26d1b0e334c49a64c5ea48b05e6f8fa414172..0000000000000000000000000000000000000000 --- a/combinatoric-solver/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "combinatoric-solver" -version = "0.1.0" diff --git a/combinatoric-solver/Cargo.toml b/combinatoric-solver/Cargo.toml deleted file mode 100644 index cd0e8603bee10ba489005d64ce0a9c7fdf095a14..0000000000000000000000000000000000000000 --- a/combinatoric-solver/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "combinatoric-solver" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/combinatoric-solver/README.md b/combinatoric-solver/README.md deleted file mode 100644 index 4da455b212cb9e2cd84ac201a8b10bd3f348da73..0000000000000000000000000000000000000000 --- a/combinatoric-solver/README.md +++ /dev/null @@ -1,26 +0,0 @@ -## Purpose - -This crate is a purely generic combinatoric solver. - -It allows to solve the problem of having n items to choose each with multiple variants and choosing the best combination of variants. - -A more pratical example is the original reason for writing this solver: given a list of university course that needs to be taken with multiple groups what is the top combination of group to maximise the number of day off and hours during the morning. - -## Limitation - -The algorithm explores all of the solution space for the number of combination even if it efficiently cut bad solutions. The problem is still NP-hard, so a combinatorial explosion can still happen with weak bounds and a large solution space. - -## Assumptions - -It function on the following assumptions: - -- Adding an item to the collection of item can only worsen or keep the score the same. In other words, the score function must be monotonic and decreasing. -- An invalid combination can't become valid when adding the next variant. -- The item object is really expensive to clone at lot of time, so a variant representation which is cheap to clone is used to iterate. -- Only the top N combinations are interesting. - -## How it works - -- It iterate recursively on all variants until it has a variant for each item. -- If an incomplete collection doesn't respect the constraint the rest of combination will not be explored. -- If an incomplete collection have a worst score than the worst of the best n collection, the rest of the combination of this collection will not be explored. (Branch and bound) diff --git a/combinatoric-solver/src/collection.rs b/combinatoric-solver/src/collection.rs deleted file mode 100644 index 831e7a80a89aa2933ef05d30e0c47e9d6b038971..0000000000000000000000000000000000000000 --- a/combinatoric-solver/src/collection.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::{item::ItemVariant, score::Score}; - -#[derive(Clone)] -pub struct Collection<const MAX_ITEMS: usize, S, V> -where - S: Score, - V: ItemVariant, -{ - score: S, - item_variant: [V; MAX_ITEMS], -} - -impl<const MAX_ITEMS: usize, S: Score, V: ItemVariant> Collection<MAX_ITEMS, S, V> { - #[inline] - pub fn push(&self, iteration: usize, item_variant: V) -> Self { - let mut new = self.clone(); - - new.item_variant[iteration] = item_variant; - new - } - #[inline] - pub fn item_variant(&self, iteration: usize) -> &[V] { - &self.item_variant[0..iteration] - } -} - -impl<const MAX_ITEMS: usize, S: Score, V: ItemVariant> Default for Collection<MAX_ITEMS, S, V> { - #[inline] - fn default() -> Self { - let score = S::default(); - let item_variant = [V::default(); MAX_ITEMS]; - Self { - score, - item_variant, - } - } -} - -impl<const MAX_ITEMS: usize, S: Score, V: ItemVariant> PartialEq for Collection<MAX_ITEMS, S, V> { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.item_variant == other.item_variant - } -} - -impl<const MAX_ITEMS: usize, S: Score, V: ItemVariant> PartialOrd for Collection<MAX_ITEMS, S, V> { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - self.score.partial_cmp(&other.score) - } -} - -impl<const MAX_ITEMS: usize, S: Score, V: ItemVariant> Eq for Collection<MAX_ITEMS, S, V> {} - -impl<const MAX_ITEMS: usize, S: Score, V: ItemVariant> Ord for Collection<MAX_ITEMS, S, V> { - #[inline] - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.score.cmp(&other.score) - } -} diff --git a/combinatoric-solver/src/item.rs b/combinatoric-solver/src/item.rs deleted file mode 100644 index a37121c21ec510789e597288e4195971d908b067..0000000000000000000000000000000000000000 --- a/combinatoric-solver/src/item.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub trait ItemVariant: Eq + Copy + Default {} - -pub trait CombinatoricItem { - type ItemVariant; - fn variants(&self) -> impl IntoIterator<Item = Self::ItemVariant>; -} diff --git a/combinatoric-solver/src/lib.rs b/combinatoric-solver/src/lib.rs deleted file mode 100644 index 4d7930bea4208e8642fcfb7a10eece233851d46f..0000000000000000000000000000000000000000 --- a/combinatoric-solver/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod collection; -pub mod item; -pub mod parameters; -pub mod score; -pub mod solver; - -pub use crate::solver::Solver; diff --git a/combinatoric-solver/src/parameters.rs b/combinatoric-solver/src/parameters.rs deleted file mode 100644 index 2974100acf3d8d2b0c30afb6ee2228f0a821099e..0000000000000000000000000000000000000000 --- a/combinatoric-solver/src/parameters.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::item::ItemVariant; - -pub trait Parameters<V> -where - V: ItemVariant, -{ - fn is_valid(&self, previous_variants: &[V], new_variant: V) -> bool; -} diff --git a/combinatoric-solver/src/score.rs b/combinatoric-solver/src/score.rs deleted file mode 100644 index 426175d2839de0a0eba7cdd3b72fdc98a3cf8fa3..0000000000000000000000000000000000000000 --- a/combinatoric-solver/src/score.rs +++ /dev/null @@ -1 +0,0 @@ -pub trait Score: Ord + Default + Clone {} diff --git a/combinatoric-solver/src/solver.rs b/combinatoric-solver/src/solver.rs deleted file mode 100644 index a8f47fac2dcf04c4c187ef0d206f18ff91a7b8cf..0000000000000000000000000000000000000000 --- a/combinatoric-solver/src/solver.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::{cmp::Reverse, collections::BinaryHeap, num::NonZeroUsize}; - -use crate::{ - collection::Collection, - item::{CombinatoricItem, ItemVariant}, - parameters::Parameters, - score::Score, -}; - -pub struct Solver<const MAX_ITEMS: usize, S, V, I, Parameters> -where - S: Score, - V: ItemVariant, - I: CombinatoricItem, -{ - /// Min-heap to keep the best schedules and fetch the worst of the best in O(1) - top_items: BinaryHeap<Reverse<Collection<MAX_ITEMS, S, V>>>, - max_capacity: NonZeroUsize, - parameters: Parameters, - items: Vec<I>, -} - -impl<'a, const MAX_ITEMS: usize, S, V, I, P> Solver<MAX_ITEMS, S, V, I, P> -where - S: Score, - V: ItemVariant, - I: CombinatoricItem<ItemVariant = V>, - P: Parameters<V>, -{ - pub fn new(max_capacity: NonZeroUsize, items: Vec<I>, parameters: P) -> Self { - let top_items = BinaryHeap::with_capacity(max_capacity.into()); - Self { - top_items, - max_capacity, - parameters, - items, - } - } - pub fn solve(mut self) -> Vec<I> { - let item = &self.items; - Self::rec_iter( - 0, - Collection::default(), - self.max_capacity, - &self.parameters, - item, - &mut self.top_items, - ); - vec![] - } - fn rec_iter( - i: usize, - collection: Collection<MAX_ITEMS, S, V>, - max_capacity: NonZeroUsize, - parameters: &P, - items: &'a [I], - top_items: &mut BinaryHeap<Reverse<Collection<MAX_ITEMS, S, V>>>, - ) { - if i >= items.len() { - // If it reach this branch it is already valid and better than the worst top schedule, so we can push - if top_items.len() >= max_capacity.into() { - top_items.pop(); - } - top_items.push(Reverse(collection)); - return; - } - - for variant in items[i].variants() { - if !parameters.is_valid(collection.item_variant(i), variant) { - return; - } - let collections = collection.push(i, variant); - // SAFETY: if the len is greater than the max_capacity, it is greater than 0 enforced by the NonZeroUsize, which allows us to unwrap without checking if it is None - if top_items.len() >= max_capacity.into() - && unsafe { top_items.peek().unwrap_unchecked().0 >= collections } - { - return; - } - Self::rec_iter( - i + 1, - collections, - max_capacity, - parameters, - items, - top_items, - ); - } - } -} diff --git a/flake.lock b/flake.lock index 26bce6555228969182f4b326643b5a876962c97f..c662d3b62f2f6808466a383e4c9091ce21334639 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1716137900, - "narHash": "sha256-sowPU+tLQv8GlqtVtsXioTKeaQvlMz/pefcdwg8MvfM=", + "lastModified": 1729880355, + "narHash": "sha256-RP+OQ6koQQLX5nw0NmcDrzvGL8HDLnyXt/jHhL1jwjM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6c0b7a92c30122196a761b440ac0d46d3d9954f1", + "rev": "18536bf04cd71abd345f9579158841376fdd0c5a", "type": "github" }, "original": { @@ -42,11 +42,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": {