Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,24 @@ jobs:
:parser:test \
:engine:testDebugUnitTest \
:app:testDebugUnitTest

# 单测只编译测试源集,Compose 界面与 GL 代码的编译错误不会被发现。
# 引擎运行时资产不入库,这里产出的 APK 不具代表性,只作编译检查。
- name: Compile app sources
shell: bash
run: ./gradlew --no-daemon -Pmaxmath.buildNative=false :app:assembleDebug

# 暂不阻断:lint 从未在本仓库跑过,先让历史问题可见,基线清理干净后
# 去掉 continue-on-error 变成硬门禁。
- name: Android Lint
continue-on-error: true
shell: bash
run: ./gradlew --no-daemon -Pmaxmath.buildNative=false :app:lintDebug

- name: Upload lint report
if: always()
uses: actions/upload-artifact@v4
with:
name: lint-report
path: app/build/reports/lint-results-debug.html
if-no-files-found: ignore
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
<a href="README.zh-CN.md"><strong>简体中文</strong></a>
</p>

[Download MaxMath 1.0.0 APK](https://github.com/yueye6811/MaxMath/releases/download/v1.0.0/MaxMath-v1.0.0-arm64-v8a.apk)
· [Release notes](https://github.com/yueye6811/MaxMath/releases/tag/v1.0.0)
[Download MaxMath 1.0.1 APK](https://github.com/yueye6811/MaxMath/releases/download/v1.0.1/MaxMath-v1.0.1-arm64-v8a.apk)
· [Release notes](https://github.com/yueye6811/MaxMath/releases/tag/v1.0.1)

MaxMath is an offline Android app for higher-algebra computation and interactive
plotting, powered by GNU Maxima. Its interface is built with Kotlin and Jetpack
Compose, mathematical input is handled by a pure Kotlin parser, and complex
symbolic computations run in a separate engine process.

> Current release: 1.0.0. Minimum supported version: Android 8.0 (API 26).
> Current release: 1.0.1. Minimum supported version: Android 8.0 (API 26).
> The native computation engine currently provides an `arm64-v8a` build
> workflow only.

Expand Down
6 changes: 3 additions & 3 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
<strong>简体中文</strong>
</p>

[下载 MaxMath 1.0.0 APK](https://github.com/yueye6811/MaxMath/releases/download/v1.0.0/MaxMath-v1.0.0-arm64-v8a.apk)
· [发布说明](https://github.com/yueye6811/MaxMath/releases/tag/v1.0.0)
[下载 MaxMath 1.0.1 APK](https://github.com/yueye6811/MaxMath/releases/download/v1.0.1/MaxMath-v1.0.1-arm64-v8a.apk)
· [发布说明](https://github.com/yueye6811/MaxMath/releases/tag/v1.0.1)

基于 GNU Maxima 的离线 Android 高等代数计算与交互式绘图应用。界面使用
Kotlin 与 Jetpack Compose,数学输入由纯 Kotlin 解析器处理,复杂符号计算在独立
引擎进程中执行。

> 当前版本 1.0.0;最低系统 Android 8.0(API 26);原生计算引擎目前仅提供
> 当前版本 1.0.1;最低系统 Android 8.0(API 26);原生计算引擎目前仅提供
> `arm64-v8a` 构建流程。

## 功能
Expand Down
16 changes: 16 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# MaxMath 1.0.1

性能与体积优化版(2026-08-07),详见 `docs/OPTIMIZATION.md`。

## 本版改进

- 引擎资产精简 38.3 MB:去除与 jniLibs 重复的可执行文件、share 文档与
ECL 链接期产物;安装器整目录重建,升级后不再残留旧文件。
- Maxima 子进程常驻复用:完成信号改为 `maxmath_done()` 刷新哨兵,
请求间 `kill(all)` 复位,省去每次请求重新载入 ECL 映像的开销;
`:engine` 进程保留 60 秒空闲期,取消/超时/异常退出才重建子进程。
- 2D/3D 绘图热路径去装箱:曲线采样、刻度、画笔/路径、网格与等值线
全部改用复用缓冲与预分配数组;GL 位置查询与常量提升到链接期。
- 零点/极值探测合并为一次引擎往返;图像解码移到 IO 线程。
- CI 增加 `assembleDebug` 编译检查与非阻断 lint。

# MaxMath 1.0.0

首个正式发布版本(2026-08-06)。
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ android {
applicationId = "com.paruh.maxmath"
minSdk = 26
targetSdk = 36
versionCode = 14
versionName = "1.0.0"
versionCode = 15
versionName = "1.0.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

Expand Down
88 changes: 75 additions & 13 deletions app/src/main/java/com/paruh/maxmath/ui/plot/CurveSampler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,101 @@ import kotlin.math.abs
/**
* 2D 曲线采样:在 [x0, x1] 上取 n 个点,遇到非有限值或数值跳变
* (超过 [maxJump],用于 tan 等渐近线)时断成多个线段。
*
* 拖动/缩放时每帧都要重新采样,因此主入口 [sampleInto] 写进调用方复用的
* [Samples] 缓冲:按 Pair 返回会让每帧产生上千个装箱对象。
* [segments] 是等价的装箱版本,只用于测试与非热路径。
*/
object CurveSampler {

fun segments(
/** 可复用的采样结果。坐标交错存放,段边界用下标表示,全程零装箱。 */
class Samples {
/** 交错的 x,y;有效范围是前 [pointCount] * 2 个元素。 */
var xy: DoubleArray = DoubleArray(0)
private set

/** 已写入的点数(所有段之和)。 */
var pointCount: Int = 0
internal set

/** 第 i 段的结束点下标(不含);起点是前一段的结束下标,首段为 0。 */
var segmentEnds: IntArray = IntArray(0)
private set

/** 有效段数。 */
var segmentCount: Int = 0
internal set

/** 第 [index] 段的起点下标(含)。 */
fun segmentStart(index: Int): Int = if (index == 0) 0 else segmentEnds[index - 1]

internal fun ensureCapacity(points: Int) {
if (xy.size < points * 2) xy = DoubleArray(points * 2)
// 每段至少两个点,段数不会超过 points / 2;+1 容纳收尾段。
if (segmentEnds.size < points / 2 + 1) segmentEnds = IntArray(points / 2 + 1)
}
}

/** 采样进复用缓冲;[dest] 的既有内容会被覆盖。 */
fun sampleInto(
dest: Samples,
f: (Double) -> Double,
x0: Double,
x1: Double,
n: Int,
maxJump: Double = Double.POSITIVE_INFINITY,
): List<List<Pair<Double, Double>>> {
val points = ArrayList<Pair<Double, Double>>(n)
val result = ArrayList<List<Pair<Double, Double>>>()
) {
dest.ensureCapacity(n)
val xy = dest.xy
val ends = dest.segmentEnds
val checkJump = maxJump.isFinite()
var write = 0
var segStart = 0
var segCount = 0
var lastY = Double.NaN

for (i in 0 until n) {
val x = if (n == 1) x0 else x0 + (x1 - x0) * i / (n - 1)
val y = f(x)
// write > segStart 等价于“当前段已有点”:段刚断开时不比较跳变,
// 否则会拿上一段的 lastY 去判定新段的第一个点。
val broken = !y.isFinite() ||
(maxJump.isFinite() && points.isNotEmpty() && abs(y - lastY) > maxJump)
(checkJump && write > segStart && abs(y - lastY) > maxJump)
if (broken) {
flush(points, result)
if (write - segStart >= 2) ends[segCount++] = write else write = segStart
segStart = write
} else {
points += x to y
xy[write * 2] = x
xy[write * 2 + 1] = y
write++
lastY = y
}
}
flush(points, result)
return result
if (write - segStart >= 2) ends[segCount++] = write else write = segStart

dest.pointCount = write
dest.segmentCount = segCount
}

private fun flush(points: MutableList<Pair<Double, Double>>, result: MutableList<List<Pair<Double, Double>>>) {
if (points.size >= 2) {
result += points.toList()
fun segments(
f: (Double) -> Double,
x0: Double,
x1: Double,
n: Int,
maxJump: Double = Double.POSITIVE_INFINITY,
): List<List<Pair<Double, Double>>> {
val samples = Samples()
sampleInto(samples, f, x0, x1, n, maxJump)
val result = ArrayList<List<Pair<Double, Double>>>(samples.segmentCount)
for (seg in 0 until samples.segmentCount) {
val start = samples.segmentStart(seg)
val end = samples.segmentEnds[seg]
val points = ArrayList<Pair<Double, Double>>(end - start)
for (p in start until end) {
points += samples.xy[p * 2] to samples.xy[p * 2 + 1]
}
result += points
}
points.clear()
return result
}
}
Loading
Loading