diff --git a/pom.xml b/pom.xml
index a1b53c06..59106150 100644
--- a/pom.xml
+++ b/pom.xml
@@ -512,6 +512,12 @@
+
+
+ org.ojalgo
+ ojalgo
+ 49.0.0
+
diff --git a/src/main/java/org/simulator/fba/OjAlgoLinearProgramSolver.java b/src/main/java/org/simulator/fba/OjAlgoLinearProgramSolver.java
new file mode 100644
index 00000000..8de844aa
--- /dev/null
+++ b/src/main/java/org/simulator/fba/OjAlgoLinearProgramSolver.java
@@ -0,0 +1,205 @@
+package org.simulator.fba;
+
+import java.util.ArrayList;
+
+import org.ojalgo.optimisation.ExpressionsBasedModel;
+import org.ojalgo.optimisation.Optimisation;
+import org.ojalgo.optimisation.Variable;
+import org.ojalgo.optimisation.Expression;
+
+import scpsolver.constraints.Constraint;
+import scpsolver.constraints.LinearBiggerThanEqualsConstraint;
+import scpsolver.constraints.LinearEqualsConstraint;
+import scpsolver.constraints.LinearSmallerThanEqualsConstraint;
+import scpsolver.lpsolver.LinearProgramSolver;
+import scpsolver.problems.LinearProgram;
+
+/**
+ * Pure Java implementation of {@link LinearProgramSolver} using ojAlgo as backend.
+ *
+ * This solver does not require any native libraries (no GLPK, no JNI).
+ *
+ * NOTE: The incremental constraint methods (addLinear*Constraint) are not
+ * currently supported and will throw UnsupportedOperationException. SBSCL
+ * only uses {@link #solve(LinearProgram)} with constraints already stored
+ * in the {@link LinearProgram}.
+ */
+public class OjAlgoLinearProgramSolver implements LinearProgramSolver {
+
+ /**
+ * Optional time limit (seconds). Currently stored but not enforced.
+ */
+ private int timeConstraintSeconds = -1;
+
+ @Override
+ public double[] solve(LinearProgram lp) {
+
+ if (lp == null) {
+ throw new IllegalArgumentException("LinearProgram must not be null");
+ }
+
+ // Build an ojAlgo model
+ final ExpressionsBasedModel model = new ExpressionsBasedModel();
+
+ // Objective coefficients
+ final double[] c = lp.getC();
+ final int n = c.length;
+
+ // Variable bounds (may or may not be set)
+ final boolean hasBounds;
+ final double[] lower;
+ final double[] upper;
+ try {
+ hasBounds = lp.hasBounds();
+ if (hasBounds) {
+ lower = lp.getLowerbound();
+ upper = lp.getUpperbound();
+ } else {
+ lower = null;
+ upper = null;
+ }
+ } catch (NoSuchMethodError e) {
+ // Very old SCPSolver versions would not have hasBounds(); not expected here.
+ throw new IllegalStateException("SCPSolver LinearProgram is missing bounds methods", e);
+ }
+
+ // Integrality and boolean flags (may all be false in typical FBA)
+ boolean[] integerVars = null;
+ boolean[] booleanVars = null;
+ try {
+ integerVars = lp.getIsinteger();
+ } catch (NoSuchMethodError e) {
+ // ignore, treat as continuous
+ }
+ try {
+ booleanVars = lp.getIsboolean();
+ } catch (NoSuchMethodError e) {
+ // ignore, treat as continuous
+ }
+
+ // Create variables
+ final Variable[] vars = new Variable[n];
+ for (int i = 0; i < n; i++) {
+ Variable v = Variable.make("x" + i);
+
+ if (hasBounds && lower != null && upper != null) {
+ v.lower(lower[i]).upper(upper[i]);
+ }
+
+ boolean isBool = (booleanVars != null && i < booleanVars.length && booleanVars[i]);
+ boolean isInt = (integerVars != null && i < integerVars.length && integerVars[i]);
+
+ if (isBool) {
+ v.lower(0.0).upper(1.0).integer(true);
+ } else if (isInt) {
+ v.integer(true);
+ }
+
+ vars[i] = v;
+ model.addVariable(v);
+ }
+
+ // Objective: set variable weights and always minimise.
+ // For maximisation problems, we negate the coefficients.
+ final boolean isMin = lp.isMinProblem();
+ for (int i = 0; i < n; i++) {
+ double coeff = c[i];
+ if (!isMin) {
+ coeff = -coeff;
+ }
+ if (coeff != 0.0) {
+ vars[i].weight(coeff);
+ }
+ }
+
+ // Constraints from the LinearProgram
+ final ArrayList constraints = lp.getConstraints();
+ int constrIndex = 0;
+ for (Constraint con : constraints) {
+ if (!(con instanceof scpsolver.constraints.LinearConstraint)) {
+ // Ignore non-linear constraints (not expected in FBA)
+ continue;
+ }
+
+ scpsolver.constraints.LinearConstraint lc =
+ (scpsolver.constraints.LinearConstraint) con;
+ double[] coeff = lc.getC();
+ double rhs = lc.getT();
+
+ Expression expr = model.addExpression("c" + constrIndex++);
+ for (int j = 0; j < coeff.length && j < n; j++) {
+ if (coeff[j] != 0.0) {
+ expr.set(vars[j], coeff[j]);
+ }
+ }
+
+ if (con instanceof LinearEqualsConstraint) {
+ expr.level(rhs); // sum(coeff * x) = rhs
+ } else if (con instanceof LinearBiggerThanEqualsConstraint) {
+ expr.lower(rhs); // sum(coeff * x) >= rhs
+ } else if (con instanceof LinearSmallerThanEqualsConstraint) {
+ expr.upper(rhs); // sum(coeff * x) <= rhs
+ } else {
+ // Fallback: treat as equality
+ expr.level(rhs);
+ }
+ }
+
+ // Solve (minimise or maximise)
+ Optimisation.Result result;
+ try {
+ result = model.minimise();
+ } catch (Exception e) {
+ // Any numerical/solver error: behave like "no solution"
+ e.printStackTrace(System.err);
+ return null;
+ }
+
+ if (result == null) {
+ // No solution returned by ojAlgo
+ return null;
+ }
+
+ // Extract solution vector
+ double[] solution = new double[n];
+ for (int i = 0; i < n; i++) {
+ solution[i] = result.doubleValue(i);
+ }
+
+ return solution;
+ }
+
+ @Override
+ public void addLinearBiggerThanEqualsConstraint(LinearBiggerThanEqualsConstraint c) {
+ throw new UnsupportedOperationException(
+ "Incremental constraint addition is not supported by OjAlgoLinearProgramSolver");
+ }
+
+ @Override
+ public void addLinearSmallerThanEqualsConstraint(LinearSmallerThanEqualsConstraint c) {
+ throw new UnsupportedOperationException(
+ "Incremental constraint addition is not supported by OjAlgoLinearProgramSolver");
+ }
+
+ @Override
+ public void addEqualsConstraint(LinearEqualsConstraint c) {
+ throw new UnsupportedOperationException(
+ "Incremental constraint addition is not supported by OjAlgoLinearProgramSolver");
+ }
+
+ @Override
+ public String getName() {
+ return "ojAlgo";
+ }
+
+ @Override
+ public String[] getLibraryNames() {
+ // No native libraries required
+ return new String[0];
+ }
+
+ @Override
+ public void setTimeconstraint(int seconds) {
+ this.timeConstraintSeconds = seconds;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/sbscl/fba/OjAlgoLinearProgramSolverTest.java b/src/test/java/org/sbscl/fba/OjAlgoLinearProgramSolverTest.java
new file mode 100644
index 00000000..1870f5db
--- /dev/null
+++ b/src/test/java/org/sbscl/fba/OjAlgoLinearProgramSolverTest.java
@@ -0,0 +1,47 @@
+package org.sbscl.fba;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+import org.simulator.fba.OjAlgoLinearProgramSolver;
+
+import scpsolver.constraints.LinearSmallerThanEqualsConstraint;
+import scpsolver.problems.LinearProgram;
+
+/**
+ * Basic tests for the pure Java {@link OjAlgoLinearProgramSolver}.
+ */
+public class OjAlgoLinearProgramSolverTest {
+
+ @Test
+ public void testSimpleMaximisation() {
+ // Maximise 10*x0 + 6*x1 + 4*x2
+ // subject to x0 + x1 + x2 <= 100, x >= 0
+ LinearProgram lp = new LinearProgram(new double[] {10.0, 6.0, 4.0});
+
+ lp.addConstraint(new LinearSmallerThanEqualsConstraint(
+ new double[] {1.0, 1.0, 1.0},
+ 100.0,
+ "c1"));
+
+ lp.setLowerbound(new double[] {0.0, 0.0, 0.0});
+ lp.setUpperbound(new double[] {
+ Double.MAX_VALUE,
+ Double.MAX_VALUE,
+ Double.MAX_VALUE
+ });
+
+ OjAlgoLinearProgramSolver solver = new OjAlgoLinearProgramSolver();
+ double[] solution = solver.solve(lp);
+
+ assertNotNull(solution, "Solver should return a solution array");
+ assertEquals(3, solution.length, "Solution dimension must match number of variables");
+
+ double objective = lp.evaluate(solution);
+
+ // Best is to put all 100 units into x0:
+ // 10*100 = 1000, with x1 = x2 = 0
+ assertEquals(1000.0, objective, 1e-6, "Objective value should be optimal");
+ }
+}
\ No newline at end of file