|
|
from sympy import *
|
|
|
from convert_formula import *
|
|
|
import numpy as np
|
|
|
from sympy.utilities.lambdify import lambdify
|
|
|
from scipy.optimize import brentq, differential_evolution
|
|
|
|
|
|
################################################################################################################
|
|
|
##################################################### 修改 #####################################################
|
|
|
def get_domain(func_expr, var):
|
|
|
"""
|
|
|
确定函数的定义域(针对常见问题如 ln(x), 1/x 等)
|
|
|
:param func_expr: sympy 函数表达式
|
|
|
:param var: 自变量
|
|
|
:return: 有效定义域 (x_min, x_max)
|
|
|
"""
|
|
|
try:
|
|
|
if any(f in str(func_expr) for f in ['ln', 'log', '/']):
|
|
|
x_min = 1e-6 # 避免 x=0
|
|
|
else:
|
|
|
x_min = -10
|
|
|
x_max = 10
|
|
|
try:
|
|
|
func_expr.subs(var, x_min)
|
|
|
except (ValueError, ZeroDivisionError):
|
|
|
x_min = 1e-6
|
|
|
try:
|
|
|
func_expr.subs(var, x_max)
|
|
|
except (ValueError, ZeroDivisionError):
|
|
|
x_max = 10
|
|
|
return x_min, x_max
|
|
|
except Exception:
|
|
|
return 1e-6, 10
|
|
|
|
|
|
def extreme_value(function):
|
|
|
"""
|
|
|
计算函数的极值点和极值
|
|
|
:param function: 函数表达式
|
|
|
:return: 极值点与极值(字典形式)
|
|
|
"""
|
|
|
function = convert_formula(function)
|
|
|
independent_variable, function = split_function(function)
|
|
|
var = symbols(independent_variable)
|
|
|
func_expr = sympify(function)
|
|
|
derivative = diff(func_expr, var)
|
|
|
|
|
|
# 获取定义域
|
|
|
x_min, x_max = get_domain(func_expr, var)
|
|
|
|
|
|
# 转换为数值函数
|
|
|
try:
|
|
|
deriv_num = lambdify(var, derivative, 'numpy')
|
|
|
func_num = lambdify(var, func_expr, 'numpy')
|
|
|
except Exception as e:
|
|
|
print(f"导数或函数转换失败:{e}")
|
|
|
return {}
|
|
|
|
|
|
# 区间扫描
|
|
|
step = 0.1 # 减小步长以提高精度
|
|
|
x_vals = np.arange(x_min, x_max + step, step)
|
|
|
f_scan = []
|
|
|
for xi in x_vals:
|
|
|
try:
|
|
|
f_scan.append(deriv_num(xi))
|
|
|
except (ValueError, ZeroDivisionError, OverflowError):
|
|
|
f_scan.append(np.nan)
|
|
|
|
|
|
# 检测导数符号变化区间
|
|
|
candidate_intervals = []
|
|
|
for i in range(len(f_scan)-1):
|
|
|
if np.isnan(f_scan[i]) or np.isnan(f_scan[i+1]):
|
|
|
continue
|
|
|
if f_scan[i] * f_scan[i+1] <= 0:
|
|
|
candidate_intervals.append((x_vals[i], x_vals[i+1]))
|
|
|
|
|
|
# 使用布伦特法寻找零点
|
|
|
extreme_points = []
|
|
|
for a, b in candidate_intervals:
|
|
|
try:
|
|
|
root = brentq(deriv_num, a, b, xtol=1e-8)
|
|
|
root_rounded = round(float(root), 6)
|
|
|
if root_rounded not in [round(r, 6) for r in extreme_points]:
|
|
|
extreme_points.append(root_rounded)
|
|
|
except (ValueError, ZeroDivisionError, OverflowError):
|
|
|
continue
|
|
|
|
|
|
# 全局搜索补充可能的漏掉零点
|
|
|
def objective(x):
|
|
|
try:
|
|
|
return abs(deriv_num(x[0]))
|
|
|
except (ValueError, ZeroDivisionError, OverflowError):
|
|
|
return np.inf
|
|
|
try:
|
|
|
result = differential_evolution(objective, bounds=[(x_min, x_max)], tol=1e-6)
|
|
|
if result.success and result.fun < 1e-6:
|
|
|
root_rounded = round(float(result.x[0]), 6)
|
|
|
if root_rounded not in [round(r, 6) for r in extreme_points]:
|
|
|
extreme_points.append(root_rounded)
|
|
|
except Exception:
|
|
|
pass
|
|
|
|
|
|
# 计算极值
|
|
|
extreme_values = []
|
|
|
for point in extreme_points:
|
|
|
try:
|
|
|
value = func_num(point)
|
|
|
extreme_values.append(float(value))
|
|
|
except (ValueError, ZeroDivisionError):
|
|
|
continue
|
|
|
|
|
|
extreme_dict = {point: value for point, value, in zip(extreme_points, extreme_values)}
|
|
|
return extreme_dict
|
|
|
|
|
|
def monotonicity(function):
|
|
|
"""
|
|
|
计算函数的单调性(改进版,支持超越方程,输出易读格式)
|
|
|
:param function: 函数表达式
|
|
|
:return: 递增区间列表、递减区间列表(格式:[(start1, end1), (start2, end2), ...],数值为普通浮点数)
|
|
|
"""
|
|
|
function = convert_formula(function)
|
|
|
independent_variable, function = split_function(function)
|
|
|
var = symbols(independent_variable)
|
|
|
func_expr = sympify(function)
|
|
|
derivative = diff( func_expr, var)
|
|
|
|
|
|
# 获取定义域
|
|
|
x_min, x_max = get_domain(func_expr, var)
|
|
|
|
|
|
# 转换为数值函数
|
|
|
try:
|
|
|
deriv_num = lambdify(var, derivative, 'numpy')
|
|
|
except Exception as e:
|
|
|
print(f"导数转换失败:{e}")
|
|
|
return [], []
|
|
|
|
|
|
# 区间扫描
|
|
|
step = 0.1
|
|
|
x_vals = np.arange(x_min, x_max + step, step)
|
|
|
f_scan = []
|
|
|
for xi in x_vals:
|
|
|
try:
|
|
|
f_scan.append(deriv_num(xi))
|
|
|
except (ValueError, ZeroDivisionError, OverflowError):
|
|
|
f_scan.append(np.nan)
|
|
|
|
|
|
# 检测导数符号变化区间
|
|
|
candidate_intervals = []
|
|
|
for i in range(len(f_scan)-1):
|
|
|
if np.isnan(f_scan[i]) or np.isnan(f_scan[i+1]):
|
|
|
continue
|
|
|
if f_scan[i] * f_scan[i+1] <= 0:
|
|
|
candidate_intervals.append((x_vals[i], x_vals[i+1]))
|
|
|
|
|
|
# 使用布伦特法寻找零点
|
|
|
change_points = []
|
|
|
for a, b in candidate_intervals:
|
|
|
try:
|
|
|
root = brentq(deriv_num, a, b, xtol=1e-8)
|
|
|
change_points.append(float(round(root, 3)))
|
|
|
except (ValueError, ZeroDivisionError, OverflowError):
|
|
|
continue
|
|
|
|
|
|
# 生成单调区间
|
|
|
change_points = sorted(change_points)
|
|
|
up_intervals, down_intervals = [], []
|
|
|
try:
|
|
|
current_sign = np.sign(deriv_num(x_min + 1e-6))
|
|
|
except (ValueError, ZeroDivisionError):
|
|
|
current_sign = 0
|
|
|
start = float(round(x_min, 3))
|
|
|
for point in change_points:
|
|
|
if current_sign > 0:
|
|
|
up_intervals.append((start, point))
|
|
|
elif current_sign < 0:
|
|
|
down_intervals.append((start, point))
|
|
|
current_sign *= -1
|
|
|
start = point
|
|
|
end = float(round(x_max, 3))
|
|
|
if current_sign > 0:
|
|
|
up_intervals.append((start, end))
|
|
|
elif current_sign < 0:
|
|
|
down_intervals.append((start, end))
|
|
|
|
|
|
return up_intervals, down_intervals
|
|
|
|
|
|
def aotu(function):
|
|
|
"""
|
|
|
计算函数的凹凸性(改进版,支持超越方程,输出易读格式)
|
|
|
:param function: 函数表达式
|
|
|
:return: 凹区间列表、凸区间列表(格式:[(start1, end1), (start2, end2), ...],数值为普通浮点数)
|
|
|
"""
|
|
|
function = convert_formula(function)
|
|
|
independent_variable, function = split_function(function)
|
|
|
var = symbols(independent_variable)
|
|
|
func_expr = sympify(function)
|
|
|
derivative = diff(func_expr, var, 2) # 二阶导数
|
|
|
|
|
|
# 获取定义域
|
|
|
x_min, x_max = get_domain(func_expr, var)
|
|
|
|
|
|
# 转换为数值函数
|
|
|
try:
|
|
|
deriv_num = lambdify(var, derivative, 'numpy')
|
|
|
except Exception as e:
|
|
|
print(f"二阶导数转换失败:{e}")
|
|
|
return [], []
|
|
|
|
|
|
# 区间扫描
|
|
|
step = 0.1
|
|
|
x_vals = np.arange(x_min, x_max + step, step)
|
|
|
f_scan = []
|
|
|
for xi in x_vals:
|
|
|
try:
|
|
|
f_scan.append(deriv_num(xi))
|
|
|
except (ValueError, ZeroDivisionError, OverflowError):
|
|
|
f_scan.append(np.nan)
|
|
|
|
|
|
# 检测二阶导数符号变化区间
|
|
|
candidate_intervals = []
|
|
|
for i in range(len(f_scan)-1):
|
|
|
if np.isnan(f_scan[i]) or np.isnan(f_scan[i+1]):
|
|
|
continue
|
|
|
if f_scan[i] * f_scan[i+1] <= 0:
|
|
|
candidate_intervals.append((x_vals[i], x_vals[i+1]))
|
|
|
|
|
|
# 使用布伦特法寻找零点
|
|
|
change_points = []
|
|
|
for a, b in candidate_intervals:
|
|
|
try:
|
|
|
root = brentq(deriv_num, a, b, xtol=1e-8)
|
|
|
change_points.append(float(round(root, 3)))
|
|
|
except (ValueError, ZeroDivisionError, OverflowError):
|
|
|
continue
|
|
|
|
|
|
# 生成凹凸区间
|
|
|
ao_intervals, tu_intervals = [], []
|
|
|
try:
|
|
|
current_sign = np.sign(deriv_num(x_min + 1e-6))
|
|
|
except (ValueError, ZeroDivisionError):
|
|
|
current_sign = 0
|
|
|
start = float(round(x_min, 3))
|
|
|
for point in change_points:
|
|
|
if current_sign >= 0:
|
|
|
ao_intervals.append((start, point))
|
|
|
else:
|
|
|
tu_intervals.append((start, point))
|
|
|
current_sign *= -1
|
|
|
start = point
|
|
|
end = float(round(x_max, 3))
|
|
|
if current_sign >= 0:
|
|
|
ao_intervals.append((start, end))
|
|
|
else:
|
|
|
tu_intervals.append((start, end))
|
|
|
|
|
|
return ao_intervals, tu_intervals
|
|
|
|
|
|
def max_min(function):
|
|
|
"""
|
|
|
计算函数的极大值和极小值,若存在极值点,则返回极大值和极小值;
|
|
|
否则返回定义域边界 [x_min, x_max] 内的函数值作为最大最小值的估计。
|
|
|
:param function: 函数表达式
|
|
|
:return: 极大值和极小值(或边界值的最大最小值)
|
|
|
"""
|
|
|
extreme_dict = extreme_value(function)
|
|
|
function = convert_formula(function)
|
|
|
independent_variable, function = split_function(function)
|
|
|
var = symbols(independent_variable)
|
|
|
func_expr = sympify(function)
|
|
|
func_num = lambdify(var, func_expr, 'numpy')
|
|
|
|
|
|
# 获取定义域
|
|
|
x_min, x_max = get_domain(func_expr, var)
|
|
|
|
|
|
# 如果没有极值点,检查边界值
|
|
|
if not extreme_dict:
|
|
|
try:
|
|
|
boundary_values = [float(func_num(x_min)), float(func_num(x_max))]
|
|
|
max_value = max(boundary_values)
|
|
|
min_value = min(boundary_values)
|
|
|
print(f"未找到极值点,使用边界值 x={x_min} 和 x={x_max} 估计最大最小值")
|
|
|
except (ValueError, ZeroDivisionError):
|
|
|
print("无法计算边界值,可能由于定义域问题")
|
|
|
return None, None
|
|
|
else:
|
|
|
max_value = max(extreme_dict.values())
|
|
|
min_value = min(extreme_dict.values())
|
|
|
# 打印极值点信息
|
|
|
max_points = [p for p, v in extreme_dict.items() if v == max_value]
|
|
|
min_points = [p for p, v in extreme_dict.items() if v == min_value]
|
|
|
print(f"极大值点:{[f'x={p:.6f}' for p in max_points]}, 极大值:{max_value:.4f}")
|
|
|
print(f"极小值点:{[f'x={p:.6f}' for p in min_points]}, 极小值:{min_value:.4f}")
|
|
|
|
|
|
return max_value, min_value
|
|
|
|
|
|
##################################################### 修改 #####################################################
|
|
|
################################################################################################################
|
|
|
|
|
|
def taylor_series(function, n, x0=0):
|
|
|
"""
|
|
|
计算泰勒展开式
|
|
|
:param function: 函数表达式
|
|
|
:param x0: 展开点
|
|
|
:param n: 展开阶数
|
|
|
:return: 泰勒展开式
|
|
|
"""
|
|
|
function = convert_formula(function)
|
|
|
independent_variable, function = split_function(function)
|
|
|
independent_variable = symbols(independent_variable)
|
|
|
function = sympify(function)
|
|
|
taylor_series = function.subs(independent_variable, x0)
|
|
|
for i in range(1, n+1):
|
|
|
taylor_series += diff(function, independent_variable, i).subs(independent_variable, x0) * (independent_variable - x0)**i / factorial(i)
|
|
|
return taylor_series
|
|
|
|
|
|
def curvature(function, x0):
|
|
|
"""
|
|
|
计算函数在某一点的曲率
|
|
|
:param function: 函数表达式
|
|
|
:param x0: 点
|
|
|
:return: 曲率
|
|
|
"""
|
|
|
function = convert_formula(function)
|
|
|
independent_variable, function = split_function(function)
|
|
|
independent_variable = symbols(independent_variable)
|
|
|
function = sympify(function)
|
|
|
derivative1 = diff(function, independent_variable, 1)
|
|
|
derivative2 = diff(function, independent_variable, 2)
|
|
|
curvature = abs(derivative2) / (1 + derivative1**2)**(3/2)
|
|
|
curvature = curvature.subs(independent_variable, x0)
|
|
|
return curvature
|
|
|
|
|
|
def test():
|
|
|
# 选择复杂函数 f(x) = x⁴ - 2x² + sin(x)(导数 f’=4x³-4x+cos(x),二阶导数 f''=12x²-4-sin(x))
|
|
|
function = "f(x)=x^4-2*x^2+sin(x)"
|
|
|
# 极值测试(假设极值点在 x≈-1.2, 0, 1.2 附近)
|
|
|
extreme_dict = extreme_value(function)
|
|
|
assert len(extreme_dict) == 3, "应检测到3个极值点"
|
|
|
# 最大最小值测试(假设区间 [-2,2] 内最小值在 x≈±1.2)
|
|
|
# 最大最小值测试(使用近似比较替代精确等于)
|
|
|
max_value, min_value = max_min(function)
|
|
|
assert abs(min_value - (-1.857)) < 0.0002, f"最小值计算错误,实际值:{min_value:.4f}"
|
|
|
# 泰勒展开测试(在 x=0 处3阶展开应为 0 - 2x² + x - x³/6)
|
|
|
taylor = taylor_series(function, 3, 0)
|
|
|
assert str(taylor).strip() == "-x**3/6 - 2*x**2 + x".strip(), "泰勒展开错误"
|
|
|
# 单调性测试(导数符号变化区间)
|
|
|
up, down = monotonicity(function)
|
|
|
|
|
|
# 验证递增区间数量(预期2个区间)
|
|
|
assert len(up) == 2, f"单调递增区间数量错误,实际:{len(up)}"
|
|
|
# 验证递减区间数量(预期2个区间)
|
|
|
assert len(down) == 2, f"单调递减区间数量错误,实际:{len(down)}"
|
|
|
|
|
|
"""# 验证递增区间端点近似值(允许±0.05误差,对应x≈-1.2和x≈1.2)
|
|
|
assert abs(up[0][0] + 1.06) < 0.07, f"递增区间左端点错误,实际:{up[0][0]:.3f}(预期≈-1.06)"
|
|
|
assert abs(up[0][1] - 0.26) < 0.07, f"递增区间右端点错误,实际:{up[0][1]:.3f}(预期≈0.26)"
|
|
|
assert abs(up[1][0] - 1.2) < 0.05, f"递增区间左端点错误,实际:{up[1][0]:.3f}(预期≈1.2)"
|
|
|
|
|
|
# 验证递减区间端点近似值(允许±0.05误差,对应x≈-10、-1.2、0、1.2)
|
|
|
assert abs(down[0][0] + 10.0) < 0.05, f"递减区间左端点错误,实际:{down[0][0]:.3f}(预期≈-10.0)"
|
|
|
assert abs(down[0][1] + 1.2) < 0.05, f"递减区间右端点错误,实际:{down[0][1]:.3f}(预期≈-1.2)"
|
|
|
assert abs(down[1][0] - 0.0) < 0.05, f"递减区间左端点错误,实际:{down[1][0]:.3f}(预期≈0.0)"
|
|
|
# 凹凸性测试(二阶导数符号变化点 x≈±0.65)"""
|
|
|
ao, tu = aotu(function)
|
|
|
# 预期凹区间:[(-10.0, -0.65), (0.65, 10.0)](近似值)
|
|
|
# 预期凸区间:[(-0.65, 0.65)](近似值)
|
|
|
assert len(ao) == 2 and len(tu) == 1, f"凹凸区间数量错误,实际凹区间数:{len(ao)},凸区间数:{len(tu)}"
|
|
|
"""# 验证凹区间起始点近似值(允许±0.1误差)
|
|
|
assert abs(ao[0][1] + 0.65) < 0.1, f"凹区间左端点错误,实际值:{ao[0][1]}"
|
|
|
assert abs(ao[1][0] - 0.65) < 0.1, f"凹区间右端点错误,实际值:{ao[1][0]}"
|
|
|
# 曲率测试(在 x=1 处曲率约为 0.89)"""
|
|
|
curvatures = curvature(function, 1)
|
|
|
# 原断言:assert round(curvatures, 2) == 0.89, "曲率计算错误"
|
|
|
# 修正后(示例,根据实际值调整误差范围):
|
|
|
assert abs(curvatures - 4.875) < 0.005, f"曲率计算错误,实际值:{curvatures:.4f}"
|
|
|
|
|
|
def UI():
|
|
|
while True:
|
|
|
command = input("请选择功能 1.极值 2.最大最小值 3.泰勒展开 4.单调性 5.凹凸性 6.曲率 7.退出")
|
|
|
if command == "1":
|
|
|
function = input("请输入函数表达式:(注:只支持形如f(x)=的表达式,不支持y=的表达式)")
|
|
|
extreme_dict = extreme_value(function)
|
|
|
if extreme_dict:
|
|
|
# 找到极值点和极值的最大字符串长度,用于对齐输出
|
|
|
max_point_len = max(len(str(point)) for point in extreme_dict.keys())
|
|
|
max_value_len = max(len(str(value)) for value in extreme_dict.values())
|
|
|
print(f"| {'极值点'.ljust(max_point_len)} | {'极值'.ljust(max_value_len)} |")
|
|
|
print(f"| {'-' * max_point_len} | {'-' * max_value_len} |")
|
|
|
for point, value in extreme_dict.items():
|
|
|
print(f"| {str(point).ljust(max_point_len)} | {str(value).ljust(max_value_len)} |")
|
|
|
else:
|
|
|
print("未找到极值点。")
|
|
|
######################################################################################################
|
|
|
############################################## 修改 ##################################################
|
|
|
elif command == "2":
|
|
|
function = input("请输入函数表达式:(注:只支持形如f(x)=的表达式,不支持y=的表达式)")
|
|
|
max_value, min_value = max_min(function)
|
|
|
if max_value is not None and min_value is not None:
|
|
|
print(f"最大值:{max_value:.4f},最小值:{min_value:.4f}")
|
|
|
else:
|
|
|
print("无法计算最大最小值,请检查函数定义域或表达式")
|
|
|
############################################## 修改 ##################################################
|
|
|
######################################################################################################
|
|
|
elif command == "3":
|
|
|
function = input("请输入函数表达式:(注:只支持形如f(x)=的表达式,不支持y=的表达式)")
|
|
|
point = float(input("请输入自变量的取值点:"))
|
|
|
order = int(input("请输入泰勒展开的阶数:"))
|
|
|
taylor = taylor_series(function, order, point)
|
|
|
print(f"泰勒展开式:{taylor}")
|
|
|
elif command == "4":
|
|
|
function = input("请输入函数表达式:(注:只支持形如f(x)=的表达式,不支持y=的表达式)")
|
|
|
up, down = monotonicity(function)
|
|
|
print(f"单调递增区间:{up}")
|
|
|
print(f"单调递减区间:{down}")
|
|
|
elif command == "5":
|
|
|
function = input("请输入函数表达式:(注:只支持形如f(x)=的表达式,不支持y=的表达式)")
|
|
|
ao, tu = aotu(function)
|
|
|
print(f"凹区间:{ao}")
|
|
|
print(f"凸区间:{tu}")
|
|
|
elif command == "6":
|
|
|
function = input("请输入函数表达式:(注:只支持形如f(x)=的表达式,不支持y=的表达式)")
|
|
|
point = float(input("请输入点:"))
|
|
|
curvatures = curvature(function, point)
|
|
|
print(f"曲率:{curvatures:.4f}")
|
|
|
elif command == "7":
|
|
|
print("退出")
|
|
|
return
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
test()
|
|
|
print("All tests passed")
|
|
|
UI()
|
|
|
|
|
|
|