Skip to content

fix(tuneParam): correct csense sizing, safer arg handling, no global solver swap#2652

Merged
farid-zare merged 2 commits into
opencobra:developfrom
marouenbg:fix/tuneParam
May 7, 2026
Merged

fix(tuneParam): correct csense sizing, safer arg handling, no global solver swap#2652
farid-zare merged 2 commits into
opencobra:developfrom
marouenbg:fix/tuneParam

Conversation

@marouenbg
Copy link
Copy Markdown
Contributor

Summary

Audit of tuneParam (CPLEX parameter tuner) surfaced several correctness and robustness issues. This PR fixes them with minimal changes and preserves the documented public interface.

Fixes

  1. size(LPProblem.A) used as a scalar length. When csense is missing, the code did nMet = size(LPProblem.A); LPProblem.csense(1:nMet,1) = 'E';. Because size returns a 2-vector, 1:nMet collapses to 1:size(A,1) only by accident and the assignment behaves unpredictably. Replaced with size(LPProblem.A, 1).
  2. .tune.* fields written before contFunctName is parsed. The previous code did contFunctName.tune.timelimit = timelimit before checking whether contFunctName was a struct. Passing a function-name string (the documented alternative usage, e.g. 'CPLEXParamSet') therefore crashed. Now we resolve contFunctName to a struct first, then apply the tune overrides.
  3. Permanent global solver side-effect during availability probing. if ~changeCobraSolver('ibm_cplex') was used as an availability check, but it permanently changes CBT_LP_SOLVER. The function now snapshots and restores the previous LP solver after the probe, and uses changeCobraSolver(..., 'LP', 0) to suppress noisy prints.
  4. Pre-allocate b_L/b_U. The original code assigned only into rows whose csense matched 'E'/'G'/'L'; rows with any other character were left as undefined trailing zeros. Now both vectors start as -inf/+inf of length size(A,1).
  5. CPLEX init error swallowed the original exception. Replaced with a typed MException('tuneParam:cplexInit', ...) and addCause(ME) so the underlying CPLEX error is preserved.
  6. eval-based dotted-path access. Reading/writing CPLEX parameters via eval('optimParam.' paramPath{i} '=ILOGcplex.Param.' paramPath{i} '.Cur') is fragile and unsafe. Replaced with setfield/getfield helpers that walk the dotted path explicitly.
  7. Return type consistency. Empty contFunctName previously made optimParam = []; now it is initialised to struct() so the function always returns a struct as documented.
  8. Silent fprintf('Optimisation failed.') turned into a real warning('tuneParam:tuneFailed', ...) with an identifier callers can suppress or trap.

Backward compatibility

  • Public signature tuneParam(LPProblem, contFunctName, timelimit, nrepeat, printLevel) is unchanged.
  • contFunctName still accepts a struct, a function-name string, or empty.
  • The wrapper tuneParamForModel is unaffected.

Files

  • src/base/solvers/cplex/tuneParam.m — all fixes above. No public API changes.

…solver swap

- size(LPProblem.A) returned a 2-vector, then csense(1:nMet,1)='E' used
  it as a length, producing an incorrectly sized csense. Use
  size(LPProblem.A, 1).
- Apply cpxControl.tune.{timelimit,repeat,display} only after
  contFunctName has been resolved into a struct. The previous code
  assigned .tune fields onto contFunctName before checking its type, so
  passing a function-name string (the documented alternative) crashed.
- Permanent global side-effect: changeCobraSolver('ibm_cplex') silently
  switched the user's configured LP solver during availability probing.
  Restore the previous CBT_LP_SOLVER after the check.
- Pre-allocate b_L/b_U as -inf/+inf vectors of the correct length so
  rows whose csense character is missing get well-defined bounds rather
  than undefined trailing zeros.
- Wrap the CPLEX init failure as a typed MException with the original
  exception attached via addCause (was: bare error, original cause lost).
- Replace eval-based dotted-path read/write of CPLEX params with
  helpers using setfield/getfield (no eval, no silent injection risk).
- Treat empty contFunctName as struct() rather than [] so the function
  always returns a struct as documented.
- Convert silent 'Optimisation failed.' fprintf into a proper warning
  with an identifier (tuneParam:tuneFailed).
@github-actions
Copy link
Copy Markdown
Contributor

Summary:

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️
253 217 1 35

Failed Tests:

Failed Tests ❌ Fail Message
❌ testReconMap.m Assertion failed.

Github Test Reporter by CTRF 💚

@farid-zare
Copy link
Copy Markdown
Collaborator

@codex

@farid-zare
Copy link
Copy Markdown
Collaborator

Hi,
One test is failing. Could you please check to see what is causing it to fail?

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f47a8e0735

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +121 to 123
b_L = -inf(nRows, 1);
b_U = inf(nRows, 1);
if ~isempty(csense)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject empty csense before relaxing all constraints

b_L/b_U are initialized to -inf/+inf and only constrained inside if ~isempty(csense). If LPProblem.csense is present but empty (for example '' with a non-empty A), this branch is skipped and every row is effectively free (-inf <= A*x <= inf), so tuning runs on the wrong problem while silently ignoring A/b. The previous implementation failed loudly in this case; this change introduces a silent correctness failure. Please add a guard for empty csense with nonzero rows (or default those rows to equality bounds).

Useful? React with 👍 / 👎.

@marouenbg
Copy link
Copy Markdown
Contributor Author

Hi @farid-zare , yes I am on it!

Address Codex review (PR opencobra#2652): when LPProblem.csense was present but
empty (e.g. ''), the bounds branch was skipped, leaving every row at
[-inf, +inf] and silently tuning a free LP. Treat empty csense the same
as a missing csense (default to equality, matching existing behaviour),
and error out when csense length disagrees with size(A,1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Summary:

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️
257 222 0 35

Failed Tests:

No failed tests ✨

Github Test Reporter by CTRF 💚

@farid-zare farid-zare merged commit a378bea into opencobra:develop May 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants