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
43 changes: 39 additions & 4 deletions LoopStructural/interpolators/_discrete_interpolator.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ def __init__(self, support, data={}, c=None, up_to_date=False):
self.interpolation_weights = {}
logger.info("Creating discrete interpolator with {} degrees of freedom".format(self.dof))
self.type = InterpolatorType.BASE_DISCRETE

self.apply_scaling_matrix = True
self.add_ridge_regulatisation = True
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

Corrected spelling of 'regulatisation' to 'regularisation'.

Suggested change
self.add_ridge_regulatisation = True
self.add_ridge_regularisation = True

Copilot uses AI. Check for mistakes.
self.ridge_factor = 1e-8
def set_nelements(self, nelements: int) -> int:
return self.support.set_nelements(nelements)

Expand Down Expand Up @@ -511,6 +513,25 @@ def build_matrix(self):

B = np.hstack(bs)
return A, B
def compute_column_scaling_matrix(self, A: sparse.csr_matrix) -> sparse.dia_matrix:
"""Compute column scaling matrix S for matrix A so that A @ S has columns with unit norm.

Parameters
----------
A : sparse.csr_matrix
interpolation matrix

Returns
-------
scipy.sparse.dia_matrix
diagonal scaling matrix S
"""
col_norms = sparse.linalg.norm(A, axis=0)
scaling_factors = np.ones(A.shape[1])
mask = col_norms > 0
scaling_factors[mask] = 1.0 / col_norms[mask]
S = sparse.diags(scaling_factors)
return S

def add_equality_block(self, A, B):
if len(self.equal_constraints) > 0:
Expand Down Expand Up @@ -591,6 +612,15 @@ def solve_system(
raise ValueError("Pre solve failed")

A, b = self.build_matrix()
if self.add_ridge_regulatisation:
ridge = sparse.eye(A.shape[1]) * self.ridge_factor
A = sparse.vstack([A, ridge])
b = np.hstack([b, np.zeros(A.shape[1])])
logger.info("Adding ridge regularisation to interpolation matrix")
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

Corrected spelling of 'regularisation' to 'regularization' for consistency with American English spelling used elsewhere in scientific computing libraries.

Suggested change
logger.info("Adding ridge regularisation to interpolation matrix")
logger.info("Adding ridge regularization to interpolation matrix")

Copilot uses AI. Check for mistakes.
if self.apply_scaling_matrix:
S = self.compute_column_scaling_matrix(A)
A = A @ S

Q, bounds = self.build_inequality_matrix()
if callable(solver):
logger.warning('Using custom solver')
Expand Down Expand Up @@ -620,12 +650,14 @@ def solve_system(

elif solver == 'lsmr':
logger.info("Solving using lsmr")
if 'atol' not in solver_kwargs:
if tol is not None:
solver_kwargs['atol'] = tol
# if 'atol' not in solver_kwargs:
# if tol is not None:
# solver_kwargs['atol'] = tol
Comment on lines +653 to +655
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

This comment appears to contain commented-out code.

Suggested change
# if 'atol' not in solver_kwargs:
# if tol is not None:
# solver_kwargs['atol'] = tol

Copilot uses AI. Check for mistakes.
if 'btol' not in solver_kwargs:
if tol is not None:
solver_kwargs['btol'] = tol
solver_kwargs['atol'] = 0.
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

This expression mutates a default value.
This expression mutates a default value.

Copilot uses AI. Check for mistakes.
logger.info(f"Setting lsmr btol to {tol}")
logger.info(f"Solver kwargs: {solver_kwargs}")
res = sparse.linalg.lsmr(A, b, **solver_kwargs)
if res[1] == 1 or res[1] == 4 or res[1] == 2 or res[1] == 5:
Expand Down Expand Up @@ -684,6 +716,9 @@ def solve_system(
logger.error(f"Unknown solver {solver}")
self.up_to_date = False
# self._post_solve()
# apply scaling matrix to solution
if self.apply_scaling_matrix:
self.c = S @ self.c
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

Variable S is only defined when self.apply_scaling_matrix is True (line 621), but this code path can be reached through multiple solver branches (callable, cg, lsmr, admm, unknown solver) where S might not be in scope. If apply_scaling_matrix is False initially but somehow becomes True, or if an error path is taken, S will be undefined. The variable should be initialized to None at the start of the method, or this block should be moved inside the earlier if self.apply_scaling_matrix block.

Suggested change
self.c = S @ self.c
S = locals().get("S", None)
if S is None:
logger.error("Scaling matrix S is not defined; skipping scaling of solution.")
self.up_to_date = False
else:
self.c = S @ self.c

Copilot uses AI. Check for mistakes.
return self.up_to_date

def update(self) -> bool:
Expand Down
Loading
Loading