import { roundTo, solveRootBisection } from "../shared/math"; import { createCobbDouglas, type Preference } from "./preferences"; export interface BreakevenXParams { pref: Preference; I: number; Px_ceiling: number; Py: number; baseUtility: number; } /** * Giải chính xác lượng x hòa vốn (nghiệm nhỏ hơn x <= I / (2 * Px_ceiling)) * sao cho U(x, (I - Px_ceiling * x) / Py) = baseUtility. */ export function findPriceCeilingBreakevenX({ pref, I, Px_ceiling, Py, baseUtility, }: BreakevenXParams): number { // Giải giải tích nếu là dạng tích U = X * Y hoặc alpha = 0.5 // X * (I - Px_c * X) / Py = baseUtility // <=> Px_c * X^2 - I * X + Py * baseUtility = 0 if (pref.productForm || Math.abs(pref.alpha - 0.5) < 1e-6) { const a = Px_ceiling; const b = -I; const c = Py * baseUtility; const disc = b * b - 4 * a * c; if (disc >= 0) { // Nghiệm nhỏ hơn: (-b - sqrt(disc)) / (2 * a) return (-b - Math.sqrt(disc)) / (2 * a); } } // Trường hợp tổng quát: giải số bằng phương pháp chia đôi const peakX = I / (2 * Px_ceiling); const fn = (x: number): number => { const y = (I - Px_ceiling * x) / Py; return pref.utility(x, Math.max(0, y)) - baseUtility; }; return solveRootBisection(fn, 1e-6, peakX, 1e-8, 200); } export interface InKindPoint { x: number; y: number; utility: number; mrs: number; label: string; } export interface InKindAnalysisParams { alpha: number; I0: number; transfer: number; Px?: number; Py?: number; } export interface InKindAnalysisResult { pointA: InKindPoint; pointB: InKindPoint; pointC: InKindPoint; bindingThresholdT: number; isDistorted: boolean; cashEquivalent: number; deadweightLoss: number; } /** * Phân tích đối sánh trợ cấp Hiện vật vs Tiền mặt (In-kind vs Cash Transfer). */ export function analyzeInKindVsCash({ alpha, I0, transfer, Px = 1, Py = 1, }: InKindAnalysisParams): InKindAnalysisResult { const pref = createCobbDouglas({ alpha, productForm: false }); const T = transfer; // 1. Điểm A: gốc trước trợ cấp const xA = roundTo((alpha * I0) / Px, 6); const yA = roundTo(((1 - alpha) * I0) / Py, 6); const uA = pref.utility(xA, yA); const mrsA = pref.mrs(xA, yA); const pointA: InKindPoint = { x: xA, y: yA, utility: uA, mrs: mrsA, label: "A", }; // 2. Điểm C: trợ cấp tiền mặt T const totalIncome = I0 + T; const xC = roundTo((alpha * totalIncome) / Px, 6); const yC = roundTo(((1 - alpha) * totalIncome) / Py, 6); const uC = pref.utility(xC, yC); const mrsC = pref.mrs(xC, yC); const pointC: InKindPoint = { x: xC, y: yC, utility: uC, mrs: mrsC, label: "C", }; // 3. Ngưỡng bắt đầu gây méo mó lựa chọn: T* = (alpha / (1 - alpha)) * I0 const bindingThresholdT = (alpha / (1 - alpha)) * I0; const isDistorted = T > bindingThresholdT + 1e-6; // 4. Điểm B: trợ cấp hiện vật let xB: number; let yB: number; let uB: number; let mrsB: number; if (isDistorted) { // Bị kẹt tại đỉnh gãy kink B(T / Px, I0 / Py) xB = roundTo(T / Px, 6); yB = roundTo(I0 / Py, 6); uB = pref.utility(xB, yB); mrsB = pref.mrs(xB, yB); } else { // Không bị méo mó: B trùng C xB = xC; yB = yC; uB = uC; mrsB = mrsC; } const pointB: InKindPoint = { x: xB, y: yB, utility: uB, mrs: mrsB, label: "B", }; // 5. Giá trị tiền mặt tương đương (Cash Equivalent) và Tổn thất vô ích (Deadweight Loss) let cashEquivalent: number; let deadweightLoss: number; if (isDistorted) { const requiredIncome = pref.expenditure(Px, Py, uB); cashEquivalent = Math.max(0, requiredIncome - I0); deadweightLoss = Math.max(0, T - cashEquivalent); } else { cashEquivalent = T; deadweightLoss = 0; } return { pointA, pointB, pointC, bindingThresholdT, isDistorted, cashEquivalent, deadweightLoss, }; }