import {
Inequality,
InequalitySolution,
Range
} from '../../types-advanced-math.js';
/**
* Solver for mathematical inequalities
* Handles parsing, solving, and range determination
*/
export class InequalitySolver {
/**
* Solves an inequality
* @param inequality The inequality string (e.g., "2x + 3 > 7")
* @returns Solution to the inequality
*/
solveInequality(inequality: string): InequalitySolution {
try {
// Parse the inequality
const parsedInequality = this.parseInequality(inequality);
// Extract and validate variable
const variable = this.extractPrimaryVariable(parsedInequality);
// Rearrange to standard form: variable op constant
const standardForm = this.standardizeInequality(parsedInequality, variable);
// Determine the range based on the operator and constant
const { ranges, boundaryPoints } = this.determineRange(standardForm);
// Generate explanation
const explanation = this.generateExplanation(parsedInequality, standardForm, ranges, variable);
return {
variable,
ranges,
boundaryPoints,
explanation
};
} catch (error) {
// Return a default solution with error explanation
return {
variable: 'x',
ranges: [],
boundaryPoints: [],
explanation: `Error solving inequality: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Parses an inequality string into structured form
* @param inequality The inequality string
* @returns Parsed inequality
*/
private parseInequality(inequality: string): Inequality {
// Extract operator
const operatorMatch = inequality.match(/([<>]=?|!=|==)/);
if (!operatorMatch) {
throw new Error(`No valid operator found in inequality: ${inequality}`);
}
const operator = operatorMatch[1] as '<' | '<=' | '>' | '>=' | '!=' | '==';
// Check for unsupported operators
if (operator === '!=' || operator === '==') {
throw new Error(`Cannot solve inequalities with ${operator} operator`);
}
// Type guard to ensure operator is valid
const validOperator = operator as '<' | '<=' | '>' | '>=';
// Split by operator
const [leftSide, rightSide] = inequality.split(operatorMatch[0]).map(s => s.trim());
if (!leftSide || !rightSide) {
throw new Error(`Invalid inequality format: ${inequality}`);
}
return {
expression: leftSide,
operator: validOperator,
rightSide
};
}
/**
* Extracts the primary variable from an inequality
* @param inequality The parsed inequality
* @returns The variable name
*/
private extractPrimaryVariable(inequality: Inequality): string {
// Extract variables from both sides
const leftVars = this.extractVariables(inequality.expression);
const rightVars = this.extractVariables(inequality.rightSide);
// Combine variables
const allVars = [...new Set([...leftVars, ...rightVars])];
if (allVars.length === 0) {
throw new Error('No variables found in inequality');
}
if (allVars.length > 1) {
throw new Error(`Multiple variables found: ${allVars.join(', ')}. Only single-variable inequalities are supported.`);
}
return allVars[0];
}
/**
* Extracts variables from an expression
* @param expression The expression string
* @returns Array of variable names
*/
private extractVariables(expression: string): string[] {
const matches = expression.match(/[a-zA-Z]+/g) || [];
return [...new Set(matches)].filter(match =>
// Filter out function names
!['sin', 'cos', 'tan', 'log', 'ln', 'sqrt', 'abs'].includes(match)
);
}
/**
* Standardizes an inequality to the form: variable op constant
* @param inequality The parsed inequality
* @param variable The variable to isolate
* @returns Standardized inequality
*/
private standardizeInequality(inequality: Inequality, variable: string): {
operator: '<' | '<=' | '>' | '>=';
constant: number;
} {
// Move everything to the left side
const combined = `${inequality.expression} - (${inequality.rightSide})`;
// Parse and simplify the expression
const { coefficient, constant } = this.extractLinearCoefficients(combined, variable);
// Standard form: coefficient * variable + constant op 0
// Rearrange to: variable op -constant/coefficient
// We know operator is valid because parseInequality already validated it
let operator = inequality.operator as '<' | '<=' | '>' | '>=';
let finalConstant = -constant / coefficient;
// If coefficient is negative, flip the inequality
if (coefficient < 0) {
operator = this.flipInequalityOperator(operator);
}
return {
operator,
constant: finalConstant
};
}
/**
* Extracts linear coefficients from an expression
* @param expression The expression string
* @param variable The variable name
* @returns Coefficient and constant
*/
private extractLinearCoefficients(expression: string, variable: string): {
coefficient: number;
constant: number;
} {
// Normalize expression
let normalized = expression.replace(/\s+/g, '');
// Handle parentheses
normalized = this.expandExpression(normalized);
// Extract terms
const terms = normalized.match(/[+-]?[^+-]+/g) || [];
let coefficient = 0;
let constant = 0;
for (const term of terms) {
if (term.includes(variable)) {
// Variable term
const coefStr = term.replace(variable, '').trim();
const coef = !coefStr || coefStr === '+' ? 1 :
coefStr === '-' ? -1 :
parseFloat(coefStr);
coefficient += coef;
} else {
// Constant term
const value = parseFloat(term);
if (!isNaN(value)) {
constant += value;
}
}
}
if (coefficient === 0) {
throw new Error('Variable does not appear linearly in the inequality');
}
return { coefficient, constant };
}
/**
* Expands an expression by handling parentheses
* @param expression Expression with possible parentheses
* @returns Expanded expression
*/
private expandExpression(expression: string): string {
while (expression.includes('(')) {
// Find and expand innermost parentheses
const match = expression.match(/([+-]?\d*\.?\d*)\s*\(([^()]+)\)/);
if (!match) break;
const [full, factor, inner] = match;
const multiplier = factor ? parseFloat(factor) || (factor === '-' ? -1 : 1) : 1;
// Expand the parentheses
const innerTerms = inner.match(/[+-]?[^+-]+/g) || [];
const expanded = innerTerms.map(term => {
const value = parseFloat(term) || 1;
const hasVariable = /[a-zA-Z]/.test(term);
if (hasVariable) {
const variable = term.match(/[a-zA-Z]+/)?.[0] || '';
const coef = parseFloat(term.replace(variable, '')) || (term.startsWith('-') ? -1 : 1);
const newCoef = multiplier * coef;
return newCoef === 1 ? variable :
newCoef === -1 ? `-${variable}` :
`${newCoef >= 0 ? '+' : ''}${newCoef}${variable}`;
} else {
const newValue = multiplier * value;
return `${newValue >= 0 ? '+' : ''}${newValue}`;
}
}).join('');
expression = expression.replace(full, expanded);
}
return expression;
}
/**
* Flips an inequality operator when multiplying by a negative number
* @param operator The original operator
* @returns The flipped operator
*/
private flipInequalityOperator(operator: '<' | '<=' | '>' | '>='): '<' | '<=' | '>' | '>=' {
switch (operator) {
case '<': return '>';
case '<=': return '>=';
case '>': return '<';
case '>=': return '<=';
default:
return '>='; // Should never happen
}
}
/**
* Determines the range of solutions for a standardized inequality
* @param standardForm The standardized inequality
* @returns Ranges and boundary points
*/
private determineRange(standardForm: { operator: '<' | '<=' | '>' | '>='; constant: number }): {
ranges: Range[];
boundaryPoints: number[];
} {
const { operator, constant } = standardForm;
const boundaryPoints = [constant];
let ranges: Range[] = [];
switch (operator) {
case '<':
ranges = [{
min: '-∞',
max: constant,
includeMin: false,
includeMax: false
}];
break;
case '<=':
ranges = [{
min: '-∞',
max: constant,
includeMin: false,
includeMax: true
}];
break;
case '>':
ranges = [{
min: constant,
max: '∞',
includeMin: false,
includeMax: false
}];
break;
case '>=':
ranges = [{
min: constant,
max: '∞',
includeMin: true,
includeMax: false
}];
break;
}
return { ranges, boundaryPoints };
}
/**
* Generates an explanation for an inequality solution
* @param original The original parsed inequality
* @param standardForm The standardized form
* @param ranges The solution ranges
* @param variable The variable name
* @returns Explanation string
*/
private generateExplanation(
original: Inequality,
standardForm: { operator: '<' | '<=' | '>' | '>='; constant: number },
ranges: Range[],
variable: string
): string {
let explanation = `Original inequality: ${original.expression} ${original.operator} ${original.rightSide}\n\n`;
// Show solving steps
explanation += `Solving for ${variable}:\n`;
explanation += `${variable} ${standardForm.operator} ${this.formatNumber(standardForm.constant)}\n\n`;
// Describe solution
explanation += `Solution: `;
if (ranges.length === 0) {
explanation += 'No solution (empty set)';
} else {
for (const range of ranges) {
explanation += this.formatRange(range, variable);
}
}
// Add interval notation
explanation += '\n\nInterval notation: ';
explanation += this.formatIntervalNotation(ranges);
return explanation;
}
/**
* Formats a range for display
* @param range The range to format
* @param variable The variable name
* @returns Formatted range string
*/
private formatRange(range: Range, variable: string): string {
if (range.min === '-∞') {
return `${variable} ${range.includeMax ? '≤' : '<'} ${this.formatNumber(range.max as number)}`;
} else if (range.max === '∞') {
return `${variable} ${range.includeMin ? '≥' : '>'} ${this.formatNumber(range.min as number)}`;
} else {
return `${this.formatNumber(range.min as number)} ${range.includeMin ? '≤' : '<'} ${variable} ${range.includeMax ? '≤' : '<'} ${this.formatNumber(range.max as number)}`;
}
}
/**
* Formats ranges in interval notation
* @param ranges Array of ranges
* @returns Interval notation string
*/
private formatIntervalNotation(ranges: Range[]): string {
if (ranges.length === 0) return '∅';
return ranges.map(range => {
const leftBracket = range.includeMin ? '[' : '(';
const rightBracket = range.includeMax ? ']' : ')';
const min = range.min === '-∞' ? '-∞' : this.formatNumber(range.min as number);
const max = range.max === '∞' ? '∞' : this.formatNumber(range.max as number);
return `${leftBracket}${min}, ${max}${rightBracket}`;
}).join(' ∪ ');
}
/**
* Formats a number for display
* @param num The number to format
* @returns Formatted string
*/
private formatNumber(num: number): string {
const rounded = Math.round(num * 1e10) / 1e10;
return rounded === Math.round(rounded) ? rounded.toString() : rounded.toFixed(6).replace(/\.?0+$/, '');
}
/**
* Solves compound inequalities (e.g., "2 < x < 5")
* @param compound The compound inequality string
* @returns Solution to the compound inequality
*/
solveCompoundInequality(compound: string): InequalitySolution {
// Split by variable
const variable = this.extractVariables(compound)[0];
if (!variable) {
return {
variable: 'x',
ranges: [],
boundaryPoints: [],
explanation: 'No variable found in compound inequality'
};
}
// Parse the compound inequality
const parts = compound.split(variable).map(p => p.trim());
if (parts.length !== 3) {
// Not a standard compound inequality
return this.solveInequality(compound);
}
const leftValue = parseFloat(parts[0].replace(/[<>]=?$/, ''));
const leftOp = parts[0].match(/[<>]=?$/)?.[0] || '<';
const rightOp = parts[1].match(/^[<>]=?/)?.[0] || '<';
const rightValue = parseFloat(parts[1].replace(/^[<>]=?/, ''));
// Determine range
const range: Range = {
min: leftValue,
max: rightValue,
includeMin: leftOp === '<=' || leftOp === '>=',
includeMax: rightOp === '<=' || rightOp === '>='
};
const explanation = `Compound inequality: ${compound}\n` +
`Solution: ${this.formatRange(range, variable)}\n` +
`Interval notation: ${this.formatIntervalNotation([range])}`;
return {
variable,
ranges: [range],
boundaryPoints: [leftValue, rightValue],
explanation
};
}
}