Skip to content

Commit 388e21f

Browse files
author
Lachlan Grose
committed
Merge branch 'inequality'
2 parents ae9873c + ae350f9 commit 388e21f

20 files changed

+374
-117
lines changed

.github/workflows/windows_CI.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Installing dependencies
1919
shell: bash -l {0}
2020
run: |
21-
conda install -c conda-forge cython numpy scipy scikit-image scikit-learn pyamg flake8 pytest networkx
21+
conda install -c conda-forge cython numpy scipy scikit-image scikit-learn pyamg flake8 pytest networkx osqp
2222
- name: Checking formatting of code
2323
shell: bash -l {0}
2424
run: |

LoopStructural/interpolators/__init__.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,24 @@
44
"""
55
from enum import IntEnum
66

7+
78
class InterpolatorType(IntEnum):
8-
'''
9+
"""
910
Enum for the different interpolator types
10-
11+
1112
1-9 should cover interpolators with supports
1213
9+ are data supported
13-
'''
14+
"""
15+
1416
BASE = 0
1517
BASE_DISCRETE = 1
1618
FINITE_DIFFERENCE = 2
1719
DISCRETE_FOLD = 3
1820
PIECEWISE_LINEAR = 4
1921
BASE_DATA_SUPPORTED = 10
2022
SURFE = 11
23+
24+
2125
from LoopStructural.interpolators.geological_interpolator import GeologicalInterpolator
2226
from LoopStructural.interpolators.discrete_interpolator import DiscreteInterpolator
2327
from LoopStructural.interpolators.structured_tetra import TetMesh
@@ -32,6 +36,3 @@ class InterpolatorType(IntEnum):
3236
from LoopStructural.interpolators.discrete_fold_interpolator import (
3337
DiscreteFoldInterpolator,
3438
)
35-
36-
37-

LoopStructural/interpolators/base_structured_3d_support.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(
2727
# we use property decorators to update these when different parts of
2828
# the geometry need to change
2929
# inisialise the private attributes
30-
if np.any(step_vector==0):
30+
if np.any(step_vector == 0):
3131
logger.warning(f"Step vector {step_vector} has zero values")
3232
self._nsteps = np.array(nsteps, dtype=int)
3333
self._step_vector = np.array(step_vector)
@@ -74,7 +74,7 @@ def step_vector(self, step_vector):
7474
newsteps = self._nsteps / change_factor
7575
self._nsteps = np.ceil(newsteps).astype(int)
7676
self._step_vector = step_vector
77-
77+
7878
@property
7979
def origin(self):
8080
return self._origin
@@ -143,7 +143,7 @@ def nodes(self):
143143
max = self.origin + self.nsteps_cells * self.step_vector
144144
if np.any(np.isnan(self.nsteps)):
145145
raise ValueError("Cannot resize mesh nsteps is NaN")
146-
if np.any(np.isnan(self.origin)):
146+
if np.any(np.isnan(self.origin)):
147147
raise ValueError("Cannot resize mesh origin is NaN")
148148

149149
x = np.linspace(self.origin[0], max[0], self.nsteps[0])

LoopStructural/interpolators/discrete_fold_interpolator.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
import numpy as np
77
from LoopStructural.interpolators.cython.dsi_helper import fold_cg
88

9-
from LoopStructural.interpolators import (
10-
PiecewiseLinearInterpolator, InterpolatorType
11-
)
9+
from LoopStructural.interpolators import PiecewiseLinearInterpolator, InterpolatorType
1210

1311
from LoopStructural.utils import getLogger
1412

LoopStructural/interpolators/discrete_interpolator.py

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ def reset(self):
138138
139139
"""
140140
logger.debug("Resetting interpolation constraints")
141-
142141

143142
def add_constraints_to_least_squares(self, A, B, idc, w=1.0, name="undefined"):
144143
"""
@@ -255,7 +254,7 @@ def remove_constraints_from_least_squares(
255254

256255
pass
257256

258-
def add_equality_constraints(self, node_idx, values,name="undefined"):
257+
def add_equality_constraints(self, node_idx, values, name="undefined"):
259258
"""
260259
Adds hard constraints to the least squares system. For now this just
261260
sets
@@ -283,8 +282,7 @@ def add_equality_constraints(self, node_idx, values,name="undefined"):
283282
"B": values[outside].tolist(),
284283
"col": idc[outside].tolist(),
285284
# "w": w,
286-
"row": np.arange(self.eq_const_c, self.eq_const_c+idc[outside].shape[0])
287-
285+
"row": np.arange(self.eq_const_c, self.eq_const_c + idc[outside].shape[0]),
288286
}
289287
self.eq_const_c += idc[outside].shape[0]
290288
# ,'C':np.ones(idc[outside].shape[0]).tolist(),}
@@ -317,20 +315,38 @@ def add_inequality_constraints_to_matrix(self, A, l, u, idc, name="undefined"):
317315
318316
"""
319317
# map from mesh node index to region node index
320-
gi = np.zeros(self.support.n_nodes)
318+
gi = np.zeros(self.support.n_nodes,dtype=int)
321319
gi[:] = -1
322-
gi[self.region] = np.arange(0, self.nx)
320+
gi[self.region] = np.arange(0, self.nx,dtype=int)
323321
idc = gi[idc]
324-
rows = np.arange(self.ineq_const_c, self.ineq_const_c+idc.shape[0])
325-
rows = np.tile(rows, (A.shape[-1], 1)).T
326-
self.ineq_constraints[name] = {
327-
"A": A,
328-
"l": l,
329-
"col": idc,
330-
"u": u,
331-
"row": rows
332-
}
333-
self.ineq_const_c+=idc.shape[0]
322+
rows = np.arange(self.ineq_const_c, self.ineq_const_c + idc.shape[0])
323+
rows = np.tile(rows, (A.shape[-1], 1)).T
324+
self.ineq_constraints[name] = {"A": A, "l": l, "col": idc, "u": u, "row": rows}
325+
self.ineq_const_c += idc.shape[0]
326+
327+
def add_inequality_feature(self, feature, lower=True, mask=None):
328+
329+
# add inequality value for the nodes of the mesh
330+
# flag lower determines whether the feature is a lower bound or upper bound
331+
# mask is just a boolean array determining which nodes to apply it to
332+
333+
value = feature(self.support.nodes)
334+
if mask is None:
335+
mask = np.ones(value.shape[0], dtype=bool)
336+
l = np.zeros(value.shape[0]) - np.inf
337+
u = np.zeros(value.shape[0]) + np.inf
338+
mask = np.logical_and(mask, ~np.isnan(value))
339+
if lower:
340+
l[mask] = value[mask]
341+
if lower == False:
342+
u[mask] = value[mask]
343+
344+
self.add_inequality_constraints_to_matrix(
345+
np.ones((value.shape[0], 1)),
346+
l,
347+
u,
348+
np.arange(0, self.nx, dtype=int),
349+
)
334350

335351
def add_tangent_constraints(self, w=1.0):
336352
"""
@@ -426,20 +442,21 @@ def build_matrix(self, square=True, damp=0.0, ie=False):
426442
nc = 0
427443
for c in self.equal_constraints.values():
428444
aa = (c["A"]).flatten()
429-
b.extend((c["B"] ).tolist())
445+
b.extend((c["B"]).tolist())
430446
mask = aa == 0
431447
a.extend(aa[~mask].tolist())
432448
rows.extend(c["row"].flatten()[~mask].tolist())
433449
cols.extend(c["col"].flatten()[~mask].tolist())
434450
C = coo_matrix(
435-
436-
(np.array(a), (np.array(rows), cols)), shape=(self.eq_const_c, self.nx), dtype=float
437-
).tocsr()
438-
451+
(np.array(a), (np.array(rows), cols)),
452+
shape=(self.eq_const_c_, self.nx),
453+
dtype=float,
454+
).tocsr()
455+
439456
d = np.array(b)
440457
ATA = bmat([[ATA, C.T], [C, None]])
441458
ATB = np.hstack([ATB, d])
442-
459+
443460
if isinstance(damp, bool):
444461
if damp == True:
445462
damp = np.finfo("float").eps
@@ -449,7 +466,7 @@ def build_matrix(self, square=True, damp=0.0, ie=False):
449466
logger.info("Adding eps to matrix diagonal")
450467
ATA += eye(ATA.shape[0]) * damp
451468
if len(self.ineq_constraints) > 0 and ie:
452-
print('using inequality constraints')
469+
print("using inequality constraints")
453470
a = []
454471
l = []
455472
u = []
@@ -465,16 +482,19 @@ def build_matrix(self, square=True, damp=0.0, ie=False):
465482
rows.extend(c["row"].flatten()[~mask].tolist())
466483
cols.extend(c["col"].flatten()[~mask].tolist())
467484
Aie = coo_matrix(
468-
(np.array(a), (np.array(rows), cols)), shape=(self.ineq_const_c, self.nx), dtype=float
469-
).tocsc() # .tocsr()
485+
(np.array(a), (np.array(rows), cols)),
486+
shape=(self.ineq_const_c, self.nx),
487+
dtype=float,
488+
).tocsc() # .tocsr()
470489

471490
uie = np.array(u)
472491
lie = np.array(l)
473492

474493
return ATA, ATB, Aie.T.dot(Aie), Aie.T.dot(uie), Aie.T.dot(lie)
475494
return ATA, ATB
476-
def _solve_osqp(self, P, A, q, l, u):
477-
495+
496+
def _solve_osqp(self, P, A, q, l, u,mkl=False):
497+
478498
try:
479499
import osqp
480500
except ImportError:
@@ -502,13 +522,21 @@ def _solve_osqp(self, P, A, q, l, u):
502522
# # Solve problem
503523
# res = prob.solve()
504524

505-
506525
# Create an OSQP object
507526
prob = osqp.OSQP()
508527

509528
# Setup workspace
510529
# osqp likes csc matrices
511-
prob.setup(P.tocsc(), np.array(q), A.tocsc(), np.array(u), np.array(l))
530+
linsys_solver='qdldl'
531+
if mkl:
532+
linsys_solver='mkl pardiso'
533+
534+
try:
535+
prob.setup(P.tocsc(), np.array(q), A.tocsc(), np.array(u), np.array(l),linsys_solver=linsys_solver)
536+
except ValueError:
537+
if mkl:
538+
logger.error('MKL solver library path not correct. Please add to LD_LIBRARY_PATH')
539+
raise LoopImportError("Cannot import MKL pardiso")
512540
res = prob.solve()
513541
return res.x
514542

@@ -676,8 +704,8 @@ def _solve(self, solver="cg", **kwargs):
676704
damp = True
677705
if solver == "lsqr":
678706
A, B = self.build_matrix(False)
679-
elif solver =='osqp':
680-
P, q, A, l, u = self.build_matrix(True,ie=True)
707+
elif solver == "osqp":
708+
P, q, A, l, u = self.build_matrix(True, ie=True)
681709
else:
682710
A, B = self.build_matrix(damp=damp)
683711

@@ -702,8 +730,8 @@ def _solve(self, solver="cg", **kwargs):
702730
if solver == "external":
703731
logger.warning("Using external solver")
704732
self.c[self.region] = kwargs["external"](A, B)[: self.nx]
705-
if solver == 'osqp':
706-
self.c[self.region] = self._solve_osqp(P, A, q, l, u)#, **kwargs)
733+
if solver == "osqp":
734+
self.c[self.region] = self._solve_osqp(P, A, q, l, u,mkl=kwargs.get('mkl',False)) # , **kwargs)
707735
# check solution is not nan
708736
# self.support.properties[self.propertyname] = self.c
709737
if np.all(self.c == np.nan):

LoopStructural/interpolators/finite_difference_interpolator.py

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def _setup_interpolator(self, **kwargs):
132132
self.add_vaue_constraints(self.interpolation_weights["cpw"])
133133
self.add_tangent_constraints(self.interpolation_weights["tpw"])
134134
self.add_interface_constraints(self.interpolation_weights["ipw"])
135+
self.add_inequality_constraints()
135136

136137
def copy(self):
137138
"""
@@ -161,12 +162,11 @@ def add_vaue_constraints(self, w=1.0):
161162
node_idx, inside = self.support.position_to_cell_corners(points[:, :3])
162163
# print(points[inside,:].shape)
163164

164-
gi = np.zeros(self.support.n_nodes)
165+
gi = np.zeros(self.support.n_nodes, dtype=int)
165166
gi[:] = -1
166-
gi[self.region] = np.arange(0, self.nx)
167+
gi[self.region] = np.arange(0, self.nx, dtype=int)
167168
idc = np.zeros(node_idx.shape)
168169
idc[:] = -1
169-
170170
idc[inside, :] = gi[node_idx[inside, :]]
171171
inside = np.logical_and(~np.any(idc == -1, axis=1), inside)
172172
a = self.support.position_to_dof_coefs(points[inside, :3])
@@ -179,8 +179,41 @@ def add_vaue_constraints(self, w=1.0):
179179
w=w * points[inside, 4],
180180
name="value",
181181
)
182-
if np.sum(inside)<=0:
183-
logger.warning(f"{self.propertyname}: {np.sum(~inside)} value constraints not added: outside of model bounding box")
182+
if np.sum(inside) <= 0:
183+
logger.warning(
184+
f"{self.propertyname}: {np.sum(~inside)} value constraints not added: outside of model bounding box"
185+
)
186+
187+
def add_inequality_constraints(self, w=1.0):
188+
points = self.get_inequality_constraints()
189+
# check that we have added some points
190+
if points.shape[0] > 0:
191+
node_idx, inside = self.support.position_to_cell_corners(points[:, :3])
192+
# print(points[inside,:].shape)
193+
194+
gi = np.zeros(self.support.n_nodes, dtype=int)
195+
gi[:] = -1
196+
gi[self.region] = np.arange(0, self.nx, dtype=int)
197+
idc = np.zeros(node_idx.shape, dtype=int)
198+
idc[:] = -1
199+
200+
idc[inside, :] = gi[node_idx[inside, :]]
201+
inside = np.logical_and(~np.any(idc == -1, axis=1), inside)
202+
a = self.support.position_to_dof_coefs(points[inside, :3])
203+
# a*=w
204+
# a/=np.product(self.support.step_vector)
205+
self.add_inequality_constraints_to_matrix(
206+
a.T,
207+
points[inside, 3],
208+
points[inside, 4],
209+
idc[inside, :],
210+
name="value_inequality",
211+
)
212+
if np.sum(inside) <= 0:
213+
logger.warning(
214+
f"{self.propertyname}: {np.sum(~inside)} value constraints not added: outside of model bounding box"
215+
)
216+
184217
def add_interface_constraints(
185218
self, w=1.0
186219
): # for now weight all value points the same
@@ -245,8 +278,6 @@ def add_interface_constraints(
245278
name="interface_{}".format(unique_id),
246279
)
247280

248-
249-
250281
def add_gradient_constraints(self, w=1.0):
251282
"""
252283
@@ -297,8 +328,11 @@ def add_gradient_constraints(self, w=1.0):
297328
self.add_constraints_to_least_squares(
298329
A, B, idc[inside, :], w=w * self.vol, name="gradient"
299330
)
300-
if np.sum(inside)<=0:
301-
logger.warning(f"{self.propertyname}: {np.sum(~inside)} norm constraints not added: outside of model bounding box")
331+
if np.sum(inside) <= 0:
332+
logger.warning(
333+
f"{self.propertyname}: {np.sum(~inside)} norm constraints not added: outside of model bounding box"
334+
)
335+
302336
def add_norm_constraints(self, w=1.0):
303337
"""
304338
Add constraints to control the norm of the gradient of the scalar field
@@ -358,8 +392,10 @@ def add_norm_constraints(self, w=1.0):
358392
w=w * self.vol,
359393
name="norm",
360394
)
361-
if np.sum(inside)<=0:
362-
logger.warning(f"{self.propertyname}: {np.sum(~inside)} norm constraints not added: outside of model bounding box")
395+
if np.sum(inside) <= 0:
396+
logger.warning(
397+
f"{self.propertyname}: {np.sum(~inside)} norm constraints not added: outside of model bounding box"
398+
)
363399

364400
def add_gradient_orthogonal_constraints(self, points, vector, w=1.0, B=0):
365401
"""
@@ -410,8 +446,10 @@ def add_gradient_orthogonal_constraints(self, points, vector, w=1.0, B=0):
410446
self.add_constraints_to_least_squares(
411447
A, B, idc[inside, :], w=w * self.vol, name="gradient orthogonal"
412448
)
413-
if np.sum(inside)<=0:
414-
logger.warning(f"{self.propertyname}: {np.sum(~inside)} gradient constraints not added: outside of model bounding box")
449+
if np.sum(inside) <= 0:
450+
logger.warning(
451+
f"{self.propertyname}: {np.sum(~inside)} gradient constraints not added: outside of model bounding box"
452+
)
415453

416454
def add_regularisation(self, operator, w=0.1):
417455
"""

0 commit comments

Comments
 (0)