import type { Point2D } from "../../../../engine/shared/types"; import { createCobbDouglas } from "../../../../engine/micro/preferences"; import { createLinearBudget, createRationedBudget, } from "../../../../engine/micro/constraints"; import { solveConsumerOptimum, type OptimalChoice, } from "../../../../engine/micro/solver"; import { defineScenario } from "../../../types"; import { RationingExplain } from "./explain"; export interface RationingParams extends Record { quotaX: number; } export interface RationingResult { optA: OptimalChoice; optB: OptimalChoice; isBinding: boolean; deltaU: number; quotaX: number; boundaryPoints: Point2D[]; vertices: Point2D[]; } export const rationingScenario = defineScenario({ meta: { id: "micro.consumer.rationing", domain: "micro", category: "consumer", chapter: "PR Ch.3 • Câu 10a", title: "Phân phối định lượng hàng hóa", subtitle: "Tác động của hạn mức tiêu dùng tối đa (Rationing) lên tập ngân sách và phúc lợi", source: "Pindyck, R. S., & Rubinfeld, D. L. (2015). Kinh tế học vi mô (Ấn bản thứ 8, Chương 3, Câu 10a, tr. 107). NXB Kinh tế TP.HCM.", notebook_id: "e8a30630-0df2-4932-8740-9ffe7d8882bc", tags: ["Vi mô", "Người tiêu dùng", "Định lượng", "Tập ngân sách gãy khúc"], inputBanner: [ { label: "Thu nhập I", value: "200 USD" }, { label: "Giá Px", value: "10 USD" }, { label: "Giá Py", value: "10 USD" }, { label: "Hàm thỏa dụng", value: "U(X, Y) = X • Y" }, { label: "Tối ưu gốc A", value: "(10, 10)" }, ], }, params: { quotaX: { id: "quotaX", label: "Hạn mức định lượng X̄", symbol: "\\bar{X}", min: 2, max: 14, step: 0.5, default: 6, unit: "đơn vị", marks: [ { value: 2, label: "2" }, { value: 6, label: "6 (Gốc)" }, { value: 10, label: "10 (Tự do)" }, { value: 14, label: "14" }, ], description: "Kéo thanh trượt để thay đổi mức trần tiêu dùng tối đa hàng hóa X.", }, }, presets: { strict: { id: "strict", label: "Siết chặt: X̄ = 6", badge: "Thiếu hụt", params: { quotaX: 6 }, }, mild: { id: "mild", label: "Hạn mức nhẹ: X̄ = 8", params: { quotaX: 8 }, }, non_binding: { id: "non_binding", label: "Không ràng buộc: X̄ = 12", badge: "Tự do", params: { quotaX: 12 }, }, }, model: (params) => { const pref = createCobbDouglas({ alpha: 0.5, productForm: true }); const linearBudget = createLinearBudget({ I: 200, Px: 10, Py: 10 }); const optA = solveConsumerOptimum(pref, linearBudget); const rationedBudget = createRationedBudget({ I: 200, Px: 10, Py: 10, quotaX: params.quotaX, }); const optB = solveConsumerOptimum(pref, rationedBudget); const isBinding = optB.isBinding; const deltaU = optB.utility - optA.utility; return { optA, optB, isBinding, deltaU, quotaX: params.quotaX, boundaryPoints: rationedBudget.boundaryPoints, vertices: rationedBudget.vertices, }; }, view: (result, params) => { const isBinding = result.isBinding; const quotaX = params.quotaX; const lossU = Math.abs(result.deltaU).toFixed(1); return { graph: { title: "Đồ thị Lựa chọn Người tiêu dùng", subtitle: "Đường ngân sách gãy khúc và đường bàng quan", xAxis: { label: "Hàng hóa X", unit: "đơn vị", max: 24, step: 4 }, yAxis: { label: "Hàng hóa Y", unit: "đơn vị", max: 24, step: 4 }, legends: [ { label: "Đường ngân sách gốc", color: "#94a3b8", style: "dashed" }, { label: "Ngân sách định lượng", color: "#4f46e5", style: "solid" }, { label: "Đường bàng quan U0: 100", color: "#0ea5e9", style: "solid" }, { label: "Đường bàng quan UB: " + result.optB.utility.toFixed(0), color: "#f43f5e", style: "dashed", }, { label: "Vùng ngân sách khả thi", color: "#6366f1", style: "area" }, ], regions: [ { id: "feasible-region", points: result.vertices, fill: "#6366f1", opacity: 0.1, label: "Tập khả thi", }, ], polylines: [ { id: "orig-budget", points: [ { x: 0, y: 20 }, { x: 20, y: 0 }, ], color: "#94a3b8", width: 2, dashed: true, label: "Ngân sách gốc", }, { id: "effective-budget", points: result.boundaryPoints, color: "#4f46e5", width: 3, label: "Ngân sách định lượng", }, ], curves: [ { id: "curve-u0", fn: (x) => 100 / x, xMin: 4, xMax: 24, color: "#0ea5e9", width: 2, label: "U0: 100", labelAtX: 18, }, { id: "curve-ub", fn: (x) => result.optB.utility / x, xMin: 3, xMax: 24, color: "#f43f5e", width: 2.2, dashed: true, label: "UB: " + result.optB.utility.toFixed(0), labelAtX: 16, }, ], guides: [ { id: "guide-a", point: { x: result.optA.x, y: result.optA.y }, color: "#94a3b8", labelX: "XA: " + result.optA.x.toFixed(0), labelY: "YA: " + result.optA.y.toFixed(0), }, { id: "guide-b", point: { x: result.optB.x, y: result.optB.y }, color: "#6366f1", labelX: "XB: " + result.optB.x.toFixed(1), labelY: "YB: " + result.optB.y.toFixed(1), }, { id: "guide-quota", verticalX: quotaX, color: "#e11d48", dashed: true, topLabel: "Hạn mức X̄: " + quotaX.toFixed(1), }, ], arrows: isBinding ? [ { id: "shift-ab", from: { x: result.optA.x, y: result.optA.y }, to: { x: result.optB.x, y: result.optB.y }, color: "#e11d48", label: "Dịch chuyển ép buộc", }, ] : [], points: [ { id: "pt-a", x: result.optA.x, y: result.optA.y, label: "A", sublabel: "(10, 10)", color: "#0ea5e9", labelOffset: { dx: 14, dy: -14 }, }, { id: "pt-b", x: result.optB.x, y: result.optB.y, label: "B", sublabel: "(" + result.optB.x.toFixed(1) + ", " + result.optB.y.toFixed(1) + ")", color: isBinding ? "#e11d48" : "#0ea5e9", labelOffset: { dx: -18, dy: -18 }, }, ], tooltipEvaluator: (x, y) => [ { label: "Thỏa dụng U", value: (x * y).toFixed(1) }, { label: "Chi phí", value: (10 * x + 10 * y).toFixed(0) + " USD", }, ], }, stats: [ { id: "opt-a", label: "Tối ưu gốc (Điểm A)", value: "(10, 10)", subtext: "U: 100 • MRS: 1.00", color: "blue", }, { id: "opt-b", label: "Lựa chọn mới (Điểm B)", value: "(" + result.optB.x.toFixed(1) + ", " + result.optB.y.toFixed(1) + ")", subtext: "U: " + result.optB.utility.toFixed(1) + " • MRS: " + result.optB.mrs.toFixed(2), color: isBinding ? "rose" : "emerald", }, { id: "delta-u", label: "Biến thiên thỏa dụng", value: (result.optB.utility - 100).toFixed(1), delta: { value: Math.abs(result.optB.utility - 100).toFixed(1), direction: isBinding ? "down" : "neutral", }, subtext: isBinding ? "Tổn thất phúc lợi" : "Bảo toàn phúc lợi", color: isBinding ? "rose" : "emerald", }, { id: "mrs", label: "Tỷ lệ thay thế biên MRS", value: result.optB.mrs.toFixed(2), unit: "đơn vị", subtext: isBinding ? "MRS > Px/Py (Thèm muốn X)" : "MRS = Px/Py (Cân bằng)", color: "purple", }, ], steps: [ { stepNumber: 1, title: "Xác định điểm cân bằng tự do ban đầu A", latexFormula: "\\max U(X,Y) = X \\cdot Y \\quad \\text{với} \\quad 10X + 10Y = 200", explanation: "Tại trạng thái chưa áp đặt hạn mức định lượng, người tiêu dùng cân bằng tại tiếp điểm giữa đường ngân sách và đường bàng quan.", resultText: "A(10, 10) với UA = 100", }, { stepNumber: 2, title: "Đánh giá hiệu lực của hạn mức định ngạch X̄", latexFormula: "X_B = \\min(\\bar{X}, 10)", explanation: isBinding ? "Do hạn mức X̄ = " + quotaX.toFixed(1) + " nhỏ hơn nhu cầu tự do 10 đơn vị, người tiêu dùng bị chặn tại đỉnh gãy của đường ngân sách." : "Do hạn mức X̄ = " + quotaX.toFixed(1) + " lớn hơn hoặc bằng nhu cầu tự do 10 đơn vị, người tiêu dùng tự do lựa chọn điểm A.", resultText: "XB = " + result.optB.x.toFixed(1) + ", YB = " + result.optB.y.toFixed(1), }, { stepNumber: 3, title: "Tính toán mức thỏa dụng và MRS tại lựa chọn mới", latexFormula: "U_B = X_B \\cdot Y_B, \\quad MRS_B = \\frac{Y_B}{X_B}", explanation: isBinding ? "Tại điểm B, tỷ lệ thay thế biên MRS lớn hơn tỷ số giá Px/Py = 1.00. Người tiêu dùng đánh giá cao hàng hóa X nhưng không thể mua thêm." : "Tại điểm B trùng A, MRS đúng bằng tỷ số giá Px/Py = 1.00.", resultText: "UB = " + result.optB.utility.toFixed(1) + ", MRS = " + result.optB.mrs.toFixed(2), }, ], comparison: { headers: ["Tiêu chí", "Điểm A (Gốc)", "Điểm B (Hiện tại)", "Nhận xét"], rows: [ { criterion: "Rổ hàng (X, Y)", baseValue: "(10, 10)", currentValue: "(" + result.optB.x.toFixed(1) + ", " + result.optB.y.toFixed(1) + ")", note: isBinding ? "Bị ép giảm X và dồn tiền mua Y" : "Giữ nguyên rổ hàng tối ưu", }, { criterion: "Mức thỏa dụng U", baseValue: "100", currentValue: result.optB.utility.toFixed(1), note: isBinding ? "Tổn thất " + lossU + " đơn vị thỏa dụng" : "Phúc lợi đạt mức tối đa", }, { criterion: "Độ dốc MRS", baseValue: "1.00", currentValue: result.optB.mrs.toFixed(2), note: isBinding ? "MRS lớn hơn Px/Py: Thèm muốn thêm hàng hóa X" : "MRS bằng Px/Py: Tối ưu nội tại", }, ], }, statusBanner: isBinding ? { text: "Chính sách định lượng hạn chế X̄ = " + quotaX.toFixed(1) + " làm giảm mức thỏa dụng từ 100 xuống " + result.optB.utility.toFixed(1) + ". Người tiêu dùng sẵn sàng trả giá cao hơn để có thêm hàng hóa X.", type: "warning", } : { text: "Hạn mức định lượng X̄ = " + quotaX.toFixed(1) + " cao hơn mức tiêu dùng tự do 10 đơn vị. Ràng buộc không có hiệu lực, phúc lợi được giữ nguyên.", type: "success", }, bridgeLinks: [ { title: "Kiểm soát giá trần và Thiếu hụt nguồn cung", targetScenarioId: "micro.consumer.price_ceiling", domain: "micro", description: "Khảo sát khi chính phủ ấn định giá trần Px = 5 USD và lượng cung trên thị trường bị thiếu hụt.", }, { title: "Kiểm soát hành chính và Phân bổ nguồn lực vĩ mô", domain: "macro", description: "Trong kinh tế vĩ mô, các biện pháp kiểm soát số lượng thay vì giá cả dẫn đến thị trường chợ đen và phân bổ sai lệch nguồn lực tổng thể.", }, ], footnote: "U(X, Y) = X • Y; I = Px • X + Py • Y; điều kiện ràng buộc bổ sung: X <= X̄.", }; }, explain: RationingExplain, });