This commit is contained in:
ChuXun
2025-10-19 20:28:31 +08:00
parent c81f8a8b03
commit eaab9a762a
100 changed files with 23416 additions and 0 deletions

276
utils/gpaPredictor.js Normal file
View File

@@ -0,0 +1,276 @@
/**
* GPA预测算法 - 多项式回归
*/
/**
* 多项式回归预测
* @param {Array} data - 历史GPA数据 [{semester: '2023-1', gpa: 3.5}, ...]
* @param {Number} degree - 多项式阶数默认2(二次)
* @param {Number} future - 预测未来几个学期
* @returns {Object} 预测结果
*/
function polynomialRegression(data, degree = 2, future = 3) {
if (!data || data.length < 2) {
return {
predictions: [],
trend: 0,
confidence: 0
};
}
// 提取数据点
const x = data.map((_, index) => index); // 学期编号 [0, 1, 2, ...]
const y = data.map(item => item.gpa); // GPA值
// 构建范德蒙德矩阵并求解系数
const coefficients = fitPolynomial(x, y, degree);
// 预测未来学期
const predictions = [];
const startIndex = data.length;
for (let i = 0; i < future; i++) {
const xValue = startIndex + i;
const yValue = evaluatePolynomial(coefficients, xValue);
// 限制GPA在合理范围内 [0, 4.0]
const predictedGPA = Math.max(0, Math.min(4.0, yValue));
predictions.push({
semester: generateSemesterName(data.length + i + 1),
gpa: Number(predictedGPA.toFixed(2)),
isPrediction: true
});
}
// 计算趋势(与当前学期对比)
const currentGPA = y[y.length - 1];
const nextGPA = predictions[0].gpa;
const trend = ((nextGPA - currentGPA) / currentGPA * 100).toFixed(1);
// 计算置信度基于R²
const rSquared = calculateRSquared(x, y, coefficients);
const confidence = Math.round(rSquared * 100);
return {
predictions,
trend: Number(trend),
confidence,
coefficients
};
}
/**
* 拟合多项式 - 使用最小二乘法
*/
function fitPolynomial(x, y, degree) {
const n = x.length;
const m = degree + 1;
// 构建设计矩阵 X 和目标向量 Y
const X = [];
for (let i = 0; i < n; i++) {
const row = [];
for (let j = 0; j <= degree; j++) {
row.push(Math.pow(x[i], j));
}
X.push(row);
}
// 计算 X^T * X
const XTX = multiplyMatrices(transpose(X), X);
// 计算 X^T * Y
const XTY = multiplyMatrixVector(transpose(X), y);
// 求解方程 (X^T * X) * coefficients = X^T * Y
const coefficients = solveLinearSystem(XTX, XTY);
return coefficients;
}
/**
* 计算多项式值
*/
function evaluatePolynomial(coefficients, x) {
let result = 0;
for (let i = 0; i < coefficients.length; i++) {
result += coefficients[i] * Math.pow(x, i);
}
return result;
}
/**
* 计算R²决定系数
*/
function calculateRSquared(x, y, coefficients) {
const n = x.length;
const yMean = y.reduce((sum, val) => sum + val, 0) / n;
let ssTotal = 0;
let ssResidual = 0;
for (let i = 0; i < n; i++) {
const yPred = evaluatePolynomial(coefficients, x[i]);
ssTotal += Math.pow(y[i] - yMean, 2);
ssResidual += Math.pow(y[i] - yPred, 2);
}
return 1 - (ssResidual / ssTotal);
}
/**
* 矩阵转置
*/
function transpose(matrix) {
const rows = matrix.length;
const cols = matrix[0].length;
const result = [];
for (let j = 0; j < cols; j++) {
const row = [];
for (let i = 0; i < rows; i++) {
row.push(matrix[i][j]);
}
result.push(row);
}
return result;
}
/**
* 矩阵乘法
*/
function multiplyMatrices(a, b) {
const rowsA = a.length;
const colsA = a[0].length;
const colsB = b[0].length;
const result = [];
for (let i = 0; i < rowsA; i++) {
const row = [];
for (let j = 0; j < colsB; j++) {
let sum = 0;
for (let k = 0; k < colsA; k++) {
sum += a[i][k] * b[k][j];
}
row.push(sum);
}
result.push(row);
}
return result;
}
/**
* 矩阵向量乘法
*/
function multiplyMatrixVector(matrix, vector) {
const rows = matrix.length;
const result = [];
for (let i = 0; i < rows; i++) {
let sum = 0;
for (let j = 0; j < vector.length; j++) {
sum += matrix[i][j] * vector[j];
}
result.push(sum);
}
return result;
}
/**
* 求解线性方程组 - 高斯消元法
*/
function solveLinearSystem(A, b) {
const n = A.length;
const augmented = A.map((row, i) => [...row, b[i]]);
// 前向消元
for (let i = 0; i < n; i++) {
// 选主元
let maxRow = i;
for (let k = i + 1; k < n; k++) {
if (Math.abs(augmented[k][i]) > Math.abs(augmented[maxRow][i])) {
maxRow = k;
}
}
// 交换行
[augmented[i], augmented[maxRow]] = [augmented[maxRow], augmented[i]];
// 消元
for (let k = i + 1; k < n; k++) {
const factor = augmented[k][i] / augmented[i][i];
for (let j = i; j <= n; j++) {
augmented[k][j] -= factor * augmented[i][j];
}
}
}
// 回代
const x = new Array(n);
for (let i = n - 1; i >= 0; i--) {
x[i] = augmented[i][n];
for (let j = i + 1; j < n; j++) {
x[i] -= augmented[i][j] * x[j];
}
x[i] /= augmented[i][i];
}
return x;
}
/**
* 生成学期名称
*/
function generateSemesterName(index) {
const year = 2023 + Math.floor(index / 2);
const term = index % 2 === 1 ? '1' : '2';
return `${year}-${term}`;
}
/**
* 计算GPA根据分数
* @param {Number} score - 分数
* @returns {Number} GPA绩点
*/
function calculateGPA(score) {
if (score >= 60) {
return (score / 10 - 5);
}
return 0;
}
/**
* 批量计算GPA
*/
function batchCalculateGPA(courses) {
return courses.map(course => ({
...course,
gpa: calculateGPA(course.score)
}));
}
/**
* 计算平均GPA
*/
function calculateAverageGPA(courses) {
if (!courses || courses.length === 0) return 0;
const totalCredits = courses.reduce((sum, c) => sum + (c.credit || 1), 0);
const weightedSum = courses.reduce((sum, c) => {
const gpa = c.gpa || calculateGPA(c.score);
return sum + gpa * (c.credit || 1);
}, 0);
return (weightedSum / totalCredits).toFixed(2);
}
module.exports = {
polynomialRegression,
calculateGPA,
batchCalculateGPA,
calculateAverageGPA
};