import type { SolutionType } from "../shared/types"; import { approxEqual, roundTo } from "../shared/math"; import type { Preference } from "./preferences"; import type { BudgetConstraint } from "./constraints"; export interface OptimalChoice { x: number; y: number; utility: number; mrs: number; solutionType: SolutionType; isBinding: boolean; priceRatio: number; } /** * Giải bài toán tối đa hóa thỏa dụng của người tiêu dùng trên tập ngân sách: * Maximize U(X, Y) s.t. (X, Y) in Feasible Set */ export function solveConsumerOptimum( pref: Preference, budget: BudgetConstraint ): OptimalChoice { const priceRatio = budget.Px / budget.Py; // 1. Phân tích điểm tối ưu không ràng buộc trên đường dốc Px * X + Py * Y = I let freeX: number; let freeY: number; if (pref.type === "cobb_douglas") { if (pref.productForm) { freeX = budget.I / (2 * budget.Px); freeY = budget.I / (2 * budget.Py); } else { freeX = (pref.alpha * budget.I) / budget.Px; freeY = ((1 - pref.alpha) * budget.I) / budget.Py; } } else if (pref.type === "shifted_cobb_douglas") { const s = pref.shift ?? 10; const effIncome = budget.I + budget.Px * s + budget.Py * s; const interiorX = (pref.alpha * effIncome) / budget.Px - s; const interiorY = ((1 - pref.alpha) * effIncome) / budget.Py - s; // Kiểm tra chạm trục if (interiorY < -1e-6) { // Góc trên trục hoành: X = I / Px, Y = 0 freeX = budget.I / budget.Px; freeY = 0; } else if (interiorX < -1e-6) { // Góc trên trục tung: X = 0, Y = I / Py freeX = 0; freeY = budget.I / budget.Py; } else { freeX = interiorX; freeY = interiorY; } } else { // Dự phòng tìm kiếm số cho các dạng preference khác freeX = (pref.alpha * budget.I) / budget.Px; freeY = ((1 - pref.alpha) * budget.I) / budget.Py; } // 2. Xử lý theo từng loại ràng buộc ngân sách if (budget.type === "rationed" || budget.type === "price_ceiling") { // Có giới hạn maxX và đỉnh gãy tại kinkPoint if (budget.kinkPoint && freeX > budget.kinkPoint.x + 1e-4) { const optX = budget.kinkPoint.x; const optY = budget.kinkPoint.y; const u = pref.utility(optX, optY); const mrsVal = pref.mrs(optX, optY); return { x: optX, y: optY, utility: u, mrs: mrsVal, solutionType: "corner_kink", isBinding: true, priceRatio, }; } // Không bị ràng buộc bởi hạn mức/mức cung const u = pref.utility(freeX, freeY); const mrsVal = pref.mrs(freeX, freeY); const isCornerAxis = approxEqual(freeX, 0, 1e-4) || approxEqual(freeY, 0, 1e-4); return { x: freeX, y: freeY, utility: u, mrs: mrsVal, solutionType: isCornerAxis ? "corner_axis" : "interior", isBinding: false, priceRatio, }; } if (budget.type === "inkind") { // Ràng buộc trợ cấp hiện vật: đỉnh gãy tại kinkPoint = (T / Px, I0 / Py) if (budget.kinkPoint) { const kinkX = budget.kinkPoint.x; const kinkY = budget.kinkPoint.y; // Nếu điểm tiền mặt tự do freeX < kinkX: người dùng bị kẹt tại đỉnh gãy kinkPoint if (freeX < kinkX - 1e-4) { const u = pref.utility(kinkX, kinkY); const mrsVal = pref.mrs(kinkX, kinkY); return { x: kinkX, y: kinkY, utility: u, mrs: mrsVal, solutionType: "corner_kink", isBinding: true, priceRatio, }; } } // Nếu freeX >= kinkX: điểm tiền mặt khả thi trên đoạn dốc của ngân sách hiện vật const u = pref.utility(freeX, freeY); const mrsVal = pref.mrs(freeX, freeY); const isCornerAxis = approxEqual(freeX, 0, 1e-4) || approxEqual(freeY, 0, 1e-4); return { x: freeX, y: freeY, utility: u, mrs: mrsVal, solutionType: isCornerAxis ? "corner_axis" : "interior", isBinding: false, priceRatio, }; } // Linear budget const optX = freeX; const optY = freeY; const u = pref.utility(optX, optY); const mrsVal = pref.mrs(optX, optY); const isCornerAxis = approxEqual(optX, 0, 1e-4) || approxEqual(optY, 0, 1e-4); return { x: roundTo(optX, 6), y: roundTo(optY, 6), utility: u, mrs: mrsVal, solutionType: isCornerAxis ? "corner_axis" : "interior", isBinding: false, priceRatio, }; }