-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Update visualization metrics and enhance product tracking funct… #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -211,3 +211,5 @@ __marimo__/ | |
|
|
||
| # vscode | ||
| .vscode/ | ||
|
|
||
| backup/core/model/modelisation.py | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| 2: 1 - 1 [3088] - 1 (3088) - 1 [3847] - 3 (3847) - 1 [4467] - 6 (4467) - 1 [3635] - 5 (2341) - 4 (1294) - 1 [4038] - 7 (4038) - 1 [3708] - 4 (3708) - 1 [3333] - 1 (911) - 2 (2422) - 1 | ||
| 2: 1(0.0) - 1(0.0) - 1(0.0) - 1(0.0) - 1(0.0) - 1(0.0) - 1(0.0) - 0(16.1) - 0(16.1) - 0(16.1) - 0(16.1) - 0(16.1) - 2(62.4) - 2(62.4) - 2(62.4) - 2(62.4) - 2(62.4) - 2(62.4) | ||
|
|
||
| 1 | ||
| 2 | ||
| 62.40 | ||
| 531.68 | ||
| x86_64 | ||
| 593.913 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -289,6 +289,8 @@ function parseDatSolution(text) { | |||||||||||||||||||||
| const solution = { | ||||||||||||||||||||||
| routes: {}, | ||||||||||||||||||||||
| depotLoads: {}, // Track loading quantities at depots | ||||||||||||||||||||||
| productLines: {}, | ||||||||||||||||||||||
| segmentMeta: {}, | ||||||||||||||||||||||
| metrics: {} | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -315,10 +317,19 @@ function parseDatSolution(text) { | |||||||||||||||||||||
| productsLineRaw = productsLineRaw.replace(/^\s*\d+\s*:\s*/, ''); | ||||||||||||||||||||||
| lineIdx++; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const parseProductState = (token) => { | ||||||||||||||||||||||
| const m = String(token).trim().match(/^(-?\d+)\s*(?:\(([-+]?\d*\.?\d+)\))?$/); | ||||||||||||||||||||||
| if (!m) return null; | ||||||||||||||||||||||
| const idx = parseInt(m[1], 10); | ||||||||||||||||||||||
| return Number.isNaN(idx) ? null : idx; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Parse the route (split by " - ") and build segments | ||||||||||||||||||||||
| const routeParts = routeLine.split(' - ').map(p => p.trim()); | ||||||||||||||||||||||
| const productStates = productsLineRaw.split(' - ').map(p => parseProductState(p)); | ||||||||||||||||||||||
| const segments = []; | ||||||||||||||||||||||
| const vehicleLoads = []; // Track loads for this vehicle | ||||||||||||||||||||||
| const vehicleSegmentMeta = []; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const extractNodeInfo = (token, position, lastPosition) => { | ||||||||||||||||||||||
| // Token may be: "12", "12 [qty]", "12 (qty)", or typed "G2"/"D1"/"S5". | ||||||||||||||||||||||
|
|
@@ -328,17 +339,21 @@ function parseDatSolution(text) { | |||||||||||||||||||||
| // Extract quantity from brackets [qty] (depot load) | ||||||||||||||||||||||
| const bracketMatch = raw.match(/\[(\d+(?:\.\d+)?)\]/); | ||||||||||||||||||||||
| const loadQty = bracketMatch ? parseFloat(bracketMatch[1]) : 0; | ||||||||||||||||||||||
| // Extract quantity from parentheses (station delivery in route line) | ||||||||||||||||||||||
| const parenMatch = raw.match(/\(([-+]?\d*\.?\d+)\)/); | ||||||||||||||||||||||
| const deliveryQty = parenMatch ? parseFloat(parenMatch[1]) : 0; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const typed = base.match(/^([GDS])(\d+)$/i); | ||||||||||||||||||||||
| if (typed) { | ||||||||||||||||||||||
| return { | ||||||||||||||||||||||
| id: `${typed[1].toUpperCase()}${parseInt(typed[2], 10)}`, | ||||||||||||||||||||||
| loadQty | ||||||||||||||||||||||
| loadQty, | ||||||||||||||||||||||
| deliveryQty | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const numeric = base.match(/^(?:N)?(\d+)$/); | ||||||||||||||||||||||
| if (!numeric) return { id: null, loadQty: 0 }; | ||||||||||||||||||||||
| if (!numeric) return { id: null, loadQty: 0, deliveryQty: 0 }; | ||||||||||||||||||||||
| const n = parseInt(numeric[1], 10); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // New convention (no prefixes): infer by markers/position. | ||||||||||||||||||||||
|
|
@@ -348,7 +363,7 @@ function parseDatSolution(text) { | |||||||||||||||||||||
| else if (position === 0 || position === lastPosition) nodeId = `G#${n}`; | ||||||||||||||||||||||
| else nodeId = `G#${n}`; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return { id: nodeId, loadQty }; | ||||||||||||||||||||||
| return { id: nodeId, loadQty, deliveryQty }; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const lastPos = routeParts.length - 1; | ||||||||||||||||||||||
|
|
@@ -361,6 +376,12 @@ function parseDatSolution(text) { | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (fromInfo.id && toInfo.id) { | ||||||||||||||||||||||
| segments.push([fromInfo.id, toInfo.id]); | ||||||||||||||||||||||
| vehicleSegmentMeta.push({ | ||||||||||||||||||||||
| deliveryQty: toInfo.deliveryQty || 0, | ||||||||||||||||||||||
| loadQty: toInfo.loadQty || 0, | ||||||||||||||||||||||
| productRaw: productStates[i] ?? null, | ||||||||||||||||||||||
| nextProductRaw: productStates[i + 1] ?? null | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| // Track depot load if going to a depot with a load qty | ||||||||||||||||||||||
| if (toInfo.loadQty > 0) { | ||||||||||||||||||||||
| vehicleLoads.push({ | ||||||||||||||||||||||
|
|
@@ -374,6 +395,8 @@ function parseDatSolution(text) { | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| solution.routes[`V${vehicleId}`] = segments; | ||||||||||||||||||||||
| solution.depotLoads[`V${vehicleId}`] = vehicleLoads; | ||||||||||||||||||||||
| solution.productLines[`V${vehicleId}`] = productStates; | ||||||||||||||||||||||
| solution.segmentMeta[`V${vehicleId}`] = vehicleSegmentMeta; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Skip empty line separators | ||||||||||||||||||||||
| while (lineIdx < lines.length && lines[lineIdx].trim() === '') { | ||||||||||||||||||||||
|
|
@@ -450,6 +473,32 @@ function setTextContentById(id, value) { | |||||||||||||||||||||
| el.textContent = value; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function detectProductIndexingBase(productLines, numProducts) { | ||||||||||||||||||||||
| const allRaw = Object.values(productLines || {}) | ||||||||||||||||||||||
| .flat() | ||||||||||||||||||||||
| .filter(v => Number.isInteger(v) && v >= 0); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (allRaw.length === 0) return 'zero'; | ||||||||||||||||||||||
| if (allRaw.includes(0)) return 'zero'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const maxRaw = Math.max(...allRaw); | ||||||||||||||||||||||
| if (maxRaw === numProducts) return 'one'; | ||||||||||||||||||||||
| if (maxRaw < numProducts) return 'zero'; | ||||||||||||||||||||||
| return 'one'; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function normalizeProductIndex(rawProduct, base, numProducts) { | ||||||||||||||||||||||
| if (!Number.isInteger(rawProduct) || numProducts <= 0) return null; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (base === 'one') { | ||||||||||||||||||||||
| if (rawProduct < 1 || rawProduct > numProducts) return null; | ||||||||||||||||||||||
| return rawProduct - 1; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (rawProduct < 0 || rawProduct >= numProducts) return null; | ||||||||||||||||||||||
| return rawProduct; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function updateFileStatus(type, filename) { | ||||||||||||||||||||||
| const statusEl = document.getElementById(type + 'Status'); | ||||||||||||||||||||||
| const zoneEl = document.getElementById(type + 'Zone'); | ||||||||||||||||||||||
|
|
@@ -1033,79 +1082,74 @@ function calculateCurrentExchangesAndDeliveries() { | |||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const numProducts = instance.num_products || 1; | ||||||||||||||||||||||
| const numGarages = instance.num_garages || 1; | ||||||||||||||||||||||
| const numDepots = instance.num_depots || 2; | ||||||||||||||||||||||
| const numStations = instance.num_stations || 5; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Parse product lines from solution if available | ||||||||||||||||||||||
| const productLines = solution.productLines || {}; | ||||||||||||||||||||||
| const segmentMeta = solution.segmentMeta || {}; | ||||||||||||||||||||||
| const productBase = detectProductIndexingBase(productLines, numProducts); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| trucks.forEach((t, truckIdx) => { | ||||||||||||||||||||||
| const vehicleKey = t.id; | ||||||||||||||||||||||
| const completedSegs = Math.floor(Math.min(progress, t.segments.length)); | ||||||||||||||||||||||
| const vehicleProducts = productLines[vehicleKey] || []; | ||||||||||||||||||||||
| const vehicleMeta = segmentMeta[vehicleKey] || []; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| let lastProduct = null; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| for (let i = 0; i < completedSegs; i++) { | ||||||||||||||||||||||
| const toNode = t.segments[i][1]; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Check for product changes (exchanges) | ||||||||||||||||||||||
| // In absence of detailed product tracking, estimate based on segment transitions | ||||||||||||||||||||||
| // A product change typically happens when visiting different types of stations | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Track deliveries to stations | ||||||||||||||||||||||
| const segMeta = vehicleMeta[i] || {}; | ||||||||||||||||||||||
| const fromProduct = normalizeProductIndex( | ||||||||||||||||||||||
| vehicleProducts[i] ?? segMeta.productRaw, | ||||||||||||||||||||||
| productBase, | ||||||||||||||||||||||
| numProducts | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| const toProduct = normalizeProductIndex( | ||||||||||||||||||||||
| vehicleProducts[i + 1] ?? segMeta.nextProductRaw, | ||||||||||||||||||||||
| productBase, | ||||||||||||||||||||||
| numProducts | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| const activeProduct = fromProduct ?? toProduct; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Track station deliveries for the single active product of that segment. | ||||||||||||||||||||||
| if (toNode && toNode.startsWith('S')) { | ||||||||||||||||||||||
| const stationId = toNode; | ||||||||||||||||||||||
| // Simulate delivery - distribute evenly across products for now | ||||||||||||||||||||||
| // In a real implementation, this would use the solution's product line | ||||||||||||||||||||||
| if (stationDeliveriesPerProduct[stationId]) { | ||||||||||||||||||||||
| if (stationDeliveriesPerProduct[stationId] && activeProduct !== null) { | ||||||||||||||||||||||
| const stationDemand = stationDemandsPerProduct[stationId] || []; | ||||||||||||||||||||||
| stationDemand.forEach((demand, pIdx) => { | ||||||||||||||||||||||
| if (demand > 0) { | ||||||||||||||||||||||
| // Calculate how much should be delivered per visit | ||||||||||||||||||||||
| const totalVisits = stationVisits[stationId] || 1; | ||||||||||||||||||||||
| const deliveryPerVisit = demand / totalVisits; | ||||||||||||||||||||||
| stationDeliveriesPerProduct[stationId][pIdx] += deliveryPerVisit; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| const demandForProduct = stationDemand[activeProduct] || 0; | ||||||||||||||||||||||
| if (demandForProduct > 0) { | ||||||||||||||||||||||
| const explicitDelivery = Number(segMeta.deliveryQty || 0); | ||||||||||||||||||||||
| const deliveryQty = explicitDelivery > 0 | ||||||||||||||||||||||
|
Comment on lines
+1119
to
+1120
|
||||||||||||||||||||||
| const explicitDelivery = Number(segMeta.deliveryQty || 0); | |
| const deliveryQty = explicitDelivery > 0 | |
| let explicitDelivery = null; | |
| if (segMeta.deliveryQty !== undefined && segMeta.deliveryQty !== null && segMeta.deliveryQty !== '') { | |
| const parsedDelivery = Number(segMeta.deliveryQty); | |
| if (!Number.isNaN(parsedDelivery)) { | |
| explicitDelivery = parsedDelivery; | |
| } | |
| } | |
| const deliveryQty = explicitDelivery !== null |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -51,7 +51,7 @@ <h2><a href="../index.html" style="text-decoration: none; color: inherit;">MPVRP | |||||
| </div> | ||||||
| <div class="metric"> | ||||||
| <span class="metric-value" id="stat-routing">0.00</span> | ||||||
| <span class="metric-label">Routing Cost</span> | ||||||
| <span class="metric-label">Changes Cost</span> | ||||||
|
||||||
| <span class="metric-label">Changes Cost</span> | |
| <span class="metric-label">Routing Cost</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deliveryQtyis defaulted to0when no parentheses are present, which makes it impossible to distinguish “no explicit delivery quantity provided” from an explicit “(0)” in the route. This later causes station deliveries to fall back to demand/visits even when the solution explicitly encodes a 0 delivery. Consider storingdeliveryQtyasnull/undefinedwhen no parentheses are present (or keep a separatehasDeliveryQtyflag) so downstream logic can respect explicit zeros.