Skip to content

Commit b90de91

Browse files
new solver (no memory leak)
1 parent 2f6c538 commit b90de91

3 files changed

Lines changed: 175 additions & 18 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ mod_id=formicapi
2828
mod_name=Formic API
2929
mod_license=MIT
3030

31-
mod_version=2.3.2
31+
mod_version=2.3.3
3232

3333
create_version = 6.0.9-216
3434
flywheel_version = 1.0.6

src/main/java/com/rae/formicapi/FormicApiLang.java

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,31 @@ public class FormicApiLang extends Lang {
1818
static TreeMap<Double, String> MULTIPLE_SYMBOLS = new TreeMap<>();
1919

2020
static {
21-
MULTIPLE_SYMBOLS.put(1e18, "E");
22-
MULTIPLE_SYMBOLS.put(1e15, "P");
23-
MULTIPLE_SYMBOLS.put(1e12, "T");
24-
MULTIPLE_SYMBOLS.put(1e9, "G");
25-
MULTIPLE_SYMBOLS.put(1e6, "M");
26-
MULTIPLE_SYMBOLS.put(1e3, "k");
27-
MULTIPLE_SYMBOLS.put(1.0, "");
28-
MULTIPLE_SYMBOLS.put(1e-3, "m");
29-
MULTIPLE_SYMBOLS.put(1e-6, "µ");
30-
MULTIPLE_SYMBOLS.put(1e-9, "n");
31-
MULTIPLE_SYMBOLS.put(1e-12, "p");
32-
MULTIPLE_SYMBOLS.put(1e-15, "f");
33-
MULTIPLE_SYMBOLS.put(1e-18, "a");
21+
MULTIPLE_SYMBOLS.put(1e30, "Q"); // quetta (2022)
22+
MULTIPLE_SYMBOLS.put(1e27, "R"); // ronna (2022)
23+
MULTIPLE_SYMBOLS.put(1e24, "Y"); // yotta (1991)
24+
MULTIPLE_SYMBOLS.put(1e21, "Z"); // zetta (1991)
25+
MULTIPLE_SYMBOLS.put(1e18, "E"); // exa
26+
MULTIPLE_SYMBOLS.put(1e15, "P"); // peta
27+
MULTIPLE_SYMBOLS.put(1e12, "T"); // tera
28+
MULTIPLE_SYMBOLS.put(1e9, "G"); // giga
29+
MULTIPLE_SYMBOLS.put(1e6, "M"); // mega
30+
MULTIPLE_SYMBOLS.put(1e3, "k"); // kilo
31+
MULTIPLE_SYMBOLS.put(1e2, "h"); // hecto
32+
MULTIPLE_SYMBOLS.put(1e1, "da"); // deca
33+
MULTIPLE_SYMBOLS.put(1.0, "");
34+
MULTIPLE_SYMBOLS.put(1e-1, "d"); // deci
35+
MULTIPLE_SYMBOLS.put(1e-2, "c"); // centi
36+
MULTIPLE_SYMBOLS.put(1e-3, "m"); // milli
37+
MULTIPLE_SYMBOLS.put(1e-6, "µ"); // micro
38+
MULTIPLE_SYMBOLS.put(1e-9, "n"); // nano
39+
MULTIPLE_SYMBOLS.put(1e-12, "p"); // pico
40+
MULTIPLE_SYMBOLS.put(1e-15, "f"); // femto
41+
MULTIPLE_SYMBOLS.put(1e-18, "a"); // atto
42+
MULTIPLE_SYMBOLS.put(1e-21, "z"); // zepto (1991)
43+
MULTIPLE_SYMBOLS.put(1e-24, "y"); // yocto (1991)
44+
MULTIPLE_SYMBOLS.put(1e-27, "r"); // ronto (2022)
45+
MULTIPLE_SYMBOLS.put(1e-30, "q"); // quecto (2022)
3446
}
3547

3648
private static Component getUnitSymbol(IUnit unit) {
@@ -49,13 +61,42 @@ public static LangBuilder builder() {
4961
return new LangBuilder(FormicAPI.MODID);
5062
}
5163

64+
/**
65+
* Formats a double value with the most appropriate SI metric prefix symbol,
66+
* producing a human-readable mantissa and prefix pair.
67+
*
68+
* <p>The method selects the largest prefix whose factor is less than or equal
69+
* to the absolute value of {@code d}, then divides {@code d} by that factor
70+
* to obtain the mantissa. For example:
71+
* <pre>
72+
* numberWithSymbol(1_500_000) → "1.5 M"
73+
* numberWithSymbol(0.000_042) → "42 µ"
74+
* numberWithSymbol(-3_200) → "-3.2 k"
75+
* </pre>
76+
*
77+
* <p>If no floor entry exists (i.e. the value is smaller than the smallest
78+
* registered prefix), the smallest available prefix is used instead. If the
79+
* prefix table is empty, the raw formatted value is returned with a trailing
80+
* space and no symbol.
81+
*
82+
* <p>Prefix selection is based on the absolute value of {@code d}, so
83+
* negative numbers are handled symmetrically — the sign is preserved in
84+
* the mantissa.
85+
*
86+
* @param d the value to format; may be negative, zero, or positive
87+
* @return a {@link LangBuilder} containing the formatted mantissa and prefix
88+
* symbol (e.g. {@code "1.5 M"}), or the raw formatted value if no
89+
* suitable prefix is available
90+
* @see #MULTIPLE_SYMBOLS
91+
* @see LangNumberFormat#format(double)
92+
*/
5293
public static LangBuilder numberWithSymbol(double d) {
53-
Map.Entry<Double, String> entry = MULTIPLE_SYMBOLS.floorEntry(d);
94+
double abs = Math.abs(d);
95+
Map.Entry<Double, String> entry = MULTIPLE_SYMBOLS.floorEntry(abs);
96+
if (entry == null)
97+
entry = MULTIPLE_SYMBOLS.ceilingEntry(abs);
5498
if (entry == null)
55-
entry = MULTIPLE_SYMBOLS.ceilingEntry(d);
56-
if (entry == null) {
5799
return builder().text(LangNumberFormat.format(d) + " ");
58-
}
59100
return builder().text(LangNumberFormat.format(d / entry.getKey()) + " " + entry.getValue());
60101
}
61102

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.rae.formicapi.fondation.math.solvers;
2+
3+
import com.rae.formicapi.fondation.math.operators.Matrix;
4+
5+
public class LeastSquare2 {
6+
7+
/**
8+
* Convenience overload: zero initial guess, allocates working buffers internally.
9+
* Use only when the caller has no long-lived matrix to cache buffers on — prefer
10+
* the pre-allocated overload for any hot path.
11+
*/
12+
public static double[] solve(Matrix A, double[] b, int maxIter, float tol) {
13+
int m = A.cols();
14+
int n = A.rows();
15+
return solve(A, b, maxIter, tol,
16+
new double[m], new double[m], new double[m], new double[m], new double[m], new double[n]);
17+
}
18+
19+
/**
20+
* Solve {@code Ax = b} in the least-squares sense using CG on the normal equations
21+
* {@code AᵀA x = Aᵀb}, with caller-supplied working buffers.
22+
*
23+
* <p>Pass pre-allocated arrays from a long-lived object (e.g. {@code PhysicsMatrix})
24+
* to avoid allocating ~4 × n doubles on every call. At 376 832 voxels and 20 ticks/s
25+
* the naive version allocates ~240 MB/s; this overload allocates nothing after warmup.
26+
*
27+
* <p>The contents of all four working arrays are overwritten on every call.
28+
* Their values between calls are undefined and must not be read by the caller.
29+
*
30+
* @param A input matrix (square or rectangular)
31+
* @param b right-hand side, length {@code A.rows()}
32+
* @param maxIter maximum CG iterations
33+
* @param tol convergence tolerance on the residual norm
34+
* @param r pre-allocated residual buffer, length ≥ {@code A.cols()}
35+
* @param p pre-allocated search-direction buffer, length ≥ {@code A.cols()}
36+
* @param Ap pre-allocated AᵀA·p buffer, length ≥ {@code A.cols()}
37+
* @param temp pre-allocated A·p intermediate buffer, length ≥ {@code A.rows()}
38+
* @return solution vector x (a new array of length {@code A.cols()})
39+
*/
40+
public static double[] solve(Matrix A, double[] b, int maxIter, float tol,
41+
double[] initialX, double[] r, double[] p, double[] Atb, double[] Ap, double[] temp) {
42+
int n = A.rows();
43+
int m = A.cols();
44+
45+
if (b.length != n)
46+
throw new IllegalArgumentException(
47+
"RHS length (" + b.length + ") != matrix rows (" + n + ")");
48+
if (r.length < m || p.length < m || Ap.length < m)
49+
throw new IllegalArgumentException(
50+
"Working buffers r/p/Ap must have length >= A.cols() = " + m);
51+
if (temp.length < n)
52+
throw new IllegalArgumentException(
53+
"Working buffer temp must have length >= A.rows() = " + n);
54+
if (initialX.length != m)
55+
throw new IllegalArgumentException(
56+
"Initial guess length (" + initialX.length + ") does not match matrix columns (" + m + ")"
57+
);
58+
59+
// Aᵀb — written into r temporarily, then copied to Atb slot
60+
A.transposeMultiply(b, Atb);
61+
62+
return conjugateGradientNormalEq(A, initialX, Atb, maxIter, tol, r, p, Ap, temp);
63+
}
64+
65+
/**
66+
* CG on AᵀA x = Aᵀb without forming AᵀA explicitly.
67+
* All working arrays are passed in and reused across calls.
68+
*/
69+
private static double[] conjugateGradientNormalEq(
70+
Matrix A, double[] x, double[] Atb, int maxIter, double tol,
71+
double[] r, double[] p, double[] Ap, double[] temp) {
72+
73+
int n = A.rows();
74+
int m = A.cols();
75+
76+
// r = Atb - AᵀA·x (x is zero, so r = Atb on first call)
77+
multiplyAtA(A, x, temp, Ap);
78+
for (int i = 0; i < m; i++) {
79+
r[i] = Atb[i] - Ap[i];
80+
p[i] = r[i];
81+
}
82+
83+
double rsold = dot(r, r, m);
84+
85+
for (int k = 0; k < maxIter; k++) {
86+
multiplyAtA(A, p, temp, Ap);
87+
88+
double dotPAp = dot(p, Ap, m);
89+
if (dotPAp == 0) break;
90+
double alpha = rsold / dotPAp;
91+
92+
for (int i = 0; i < m; i++) x[i] += alpha * p[i];
93+
for (int i = 0; i < m; i++) r[i] -= alpha * Ap[i];
94+
95+
double rsnew = dot(r, r, m);
96+
if (Math.sqrt(rsnew) < tol) break;
97+
98+
double beta = rsnew / rsold;
99+
for (int i = 0; i < m; i++) p[i] = r[i] + beta * p[i];
100+
rsold = rsnew;
101+
}
102+
return x;
103+
}
104+
105+
private static void multiplyAtA(Matrix A, double[] p, double[] temp, double[] result) {
106+
A.multiply(p, temp);
107+
A.transposeMultiply(temp, result);
108+
}
109+
110+
private static double dot(double[] a, double[] b, int len) {
111+
double sum = 0;
112+
for (int i = 0; i < len; i++) sum += a[i] * b[i];
113+
return sum;
114+
}
115+
}
116+

0 commit comments

Comments
 (0)