Skip to content

Commit 20407e4

Browse files
author
Lachlan Grose
committed
fix: matrix assembly based on constraints dict
1 parent 984402d commit 20407e4

File tree

6 files changed

+145
-105
lines changed

6 files changed

+145
-105
lines changed

LoopStructural/interpolators/cython/dsi_helper.pyx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def cg(double [:,:,:] EG, long long [:,:] neighbours, long long [:,:] elements,d
1616
ncons = 0
1717
cdef int [:] flag = np.zeros(ne,dtype=np.int32)
1818
cdef double [:,:] c = np.zeros((len(neighbours)*4,Nc))
19+
cdef double [:] areas = np.zeros((len(neighbours)*4))
1920
cdef long long [:,:] idc = np.zeros((ne*4,5),dtype=np.int64)
2021
cdef long long [3] common
2122
cdef double [:] norm = np.zeros((3))
@@ -78,7 +79,7 @@ def cg(double [:,:,:] EG, long long [:,:] neighbours, long long [:,:] elements,d
7879
for itr_left in range(Na):
7980
idc[ncons,itr_left] = idl[itr_left]
8081
for i in range(3):
81-
c[ncons,itr_left] += norm[i]*e1[i][itr_left]*area
82+
c[ncons,itr_left] += norm[i]*e1[i][itr_left]
8283
next_available_position = Na
8384
for itr_right in range(Na):
8485
common_index = -1
@@ -94,9 +95,10 @@ def cg(double [:,:,:] EG, long long [:,:] neighbours, long long [:,:] elements,d
9495
next_available_position+=1
9596
idc[ncons,position_to_write] = idr[itr_right]
9697
for i in range(3):
97-
c[ncons,position_to_write] -= norm[i]*e2[i][itr_right]*area
98+
c[ncons,position_to_write] -= norm[i]*e2[i][itr_right]
99+
areas[ncons] = area
98100
ncons+=1
99-
return idc, c, ncons
101+
return idc, c, ncons, areas
100102
def constant_norm(double [:,:,:] EG, long long [:,:] neighbours, long long [:,:] elements,double [:,:] nodes, long long [:] region):
101103
cdef int Nc, Na, i,Ns, j, ne, ncons, e, n, neigh
102104
Nc = 5 #numer of constraints shared nodes + independent

LoopStructural/interpolators/discrete_fold_interpolator.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,9 @@ def add_fold_constraints(self, fold_orientation=10., fold_axis_w=10., fold_regul
133133
logger.info("Adding fold orientation constraint to %s w = %f"%(self.propertyname, fold_orientation))
134134
A = np.einsum('ij,ijk->ik', deformed_orientation[element_idx[::step],:], eg[element_idx[::step],:,:])
135135
A *= vol[element_idx[::step], None]
136-
A *= fold_orientation
137136
B = np.zeros(A.shape[0])
138137
idc = self.support.get_elements()[element_idx[::step],:]
139-
self.add_constraints_to_least_squares(A, B, idc, name='fold orientation')
138+
self.add_constraints_to_least_squares(A, B, idc, w=fold_orientation, name='fold orientation')
140139

141140
if fold_axis_w is not None:
142141
"""
@@ -147,11 +146,10 @@ def add_fold_constraints(self, fold_orientation=10., fold_axis_w=10., fold_regul
147146
logger.info("Adding fold axis constraint to %s w = %f"%(self.propertyname,fold_axis_w))
148147
A = np.einsum('ij,ijk->ik', fold_axis[element_idx[::step],:], eg[element_idx[::step],:,:])
149148
A *= vol[element_idx[::step], None]
150-
A *= fold_axis_w
151149
B = np.zeros(A.shape[0]).tolist()
152150
idc = self.support.get_elements()[element_idx[::step],:]
153151

154-
self.add_constraints_to_least_squares(A, B, idc, name='fold axis')
152+
self.add_constraints_to_least_squares(A, B, idc, w=fold_axis_w, name='fold axis')
155153

156154
if fold_normalisation is not None:
157155
"""
@@ -162,7 +160,7 @@ def add_fold_constraints(self, fold_orientation=10., fold_axis_w=10., fold_regul
162160
logger.info("Adding fold normalisation constraint to %s w = %f"%(self.propertyname,fold_normalisation))
163161
A = np.einsum('ij,ijk->ik', dgz[element_idx[::step],:], eg[element_idx[::step],:,:])
164162
A *= vol[element_idx[::step], None]
165-
A *= fold_normalisation
163+
166164
B = np.ones(A.shape[0])
167165

168166
if fold_norm is not None:
@@ -171,7 +169,7 @@ def add_fold_constraints(self, fold_orientation=10., fold_axis_w=10., fold_regul
171169
B *= vol[element_idx[::step]]
172170
idc = self.support.get_elements()[element_idx[::step],:]
173171

174-
self.add_constraints_to_least_squares(A, B, idc, name='fold normalisation')
172+
self.add_constraints_to_least_squares(A, B, idc, w=fold_normalisation, name='fold normalisation')
175173

176174
if fold_regularisation is not None:
177175
"""
@@ -182,21 +180,18 @@ def add_fold_constraints(self, fold_orientation=10., fold_axis_w=10., fold_regul
182180

183181
idc, c, ncons = fold_cg(eg, dgz, self.support.get_neighbours(), self.support.get_elements(), self.support.nodes)
184182
A = np.array(c[:ncons, :])
185-
A *= fold_regularisation[0]
186183
B = np.zeros(A.shape[0])
187184
idc = np.array(idc[:ncons, :])
188-
self.add_constraints_to_least_squares(A, B, idc, name='fold regularisation 1')
185+
self.add_constraints_to_least_squares(A, B, idc, fold_regularisation[0], name='fold regularisation 1')
189186

190187
idc, c, ncons = fold_cg(eg, deformed_orientation, self.support.get_neighbours(), self.support.get_elements(), self.support.nodes)
191188
A = np.array(c[:ncons, :])
192-
A *= fold_regularisation[1]
193189
B = np.zeros(A.shape[0])
194190
idc = np.array(idc[:ncons, :])
195-
self.add_constraints_to_least_squares(A, B, idc, name='fold regularisation 2')
191+
self.add_constraints_to_least_squares(A, B, idc, fold_regularisation[1], name='fold regularisation 2')
196192

197193
idc, c, ncons = fold_cg(eg, fold_axis, self.support.get_neighbours(), self.support.get_elements(), self.support.nodes)
198194
A = np.array(c[:ncons, :])
199-
A *= fold_regularisation[2]
200195
B = np.zeros(A.shape[0])
201196
idc = np.array(idc[:ncons, :])
202-
self.add_constraints_to_least_squares(A, B, idc, name='fold regularisation 3')
197+
self.add_constraints_to_least_squares(A, B, fold_regularisation[1], idc, name='fold regularisation 3')

LoopStructural/interpolators/discrete_interpolator.py

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(self, support):
4242
self.A = [] # sparse matrix storage coo format
4343
self.col = []
4444
self.row = [] # sparse matrix storage
45+
self.w = []
4546
self.solver = None
4647
self.eq_const_C = []
4748
self.eq_const_row = []
@@ -135,7 +136,7 @@ def reset(self):
135136
self.B = []
136137
self.n_constraints = 0
137138

138-
def add_constraints_to_least_squares(self, A, B, idc, name='undefined'):
139+
def add_constraints_to_least_squares(self, A, B, idc, w = 1., name='undefined'):
139140
"""
140141
Adds constraints to the least squares system. Automatically works
141142
out the row
@@ -166,49 +167,51 @@ def add_constraints_to_least_squares(self, A, B, idc, name='undefined'):
166167

167168
if len(A.shape) > 2:
168169
nr = A.shape[0] * A.shape[1]
170+
w = np.tile(w,(A.shape[1]))
169171
A = A.reshape((A.shape[0]*A.shape[1],A.shape[2]))
170172
idc = idc.reshape((idc.shape[0]*idc.shape[1],idc.shape[2]))
173+
B = B.reshape((A.shape[0]))
174+
# w = w.reshape((A.shape[0]))
171175
# normalise by rows of A
172-
length = norm(A,axis=1)#.getcol(0).norm()
176+
length = np.linalg.norm(A,axis=1)#.getcol(0).norm()
173177
B[length>0]/=length[length>0]
174-
A = normalize(A,axis=1)
175178
# going to assume if any are nan they are all nan
176179
mask = np.any(np.isnan(A),axis=1)
177180
A[mask,:] = 0
181+
A[length>0,:] /= length[length>0,None]
182+
if isinstance(w,float):
183+
w = np.ones(A.shape[0])*w
184+
185+
if isinstance(w,np.ndarray) == False:
186+
raise BaseException('w must be a numpy array')
187+
188+
if w.shape[0] != A.shape[0]:
189+
# # make w the same size as A
190+
# w = np.tile(w,(A.shape[1],1)).T
191+
# else:
192+
raise BaseException('Weight array does not match number of constraints')
178193
if np.any(np.isnan(idc)) or np.any(np.isnan(A)) or np.any(np.isnan(B)):
179194
logger.warning("Constraints contain nan not adding constraints: {}".format(name))
180195
# return
181-
182196
rows = np.arange(0, nr).astype(int)
183197
rows += self.c_
184198
constraint_ids = rows.copy()
185-
186-
if name in self.constraints:
199+
base_name=name
200+
while name in self.constraints:
187201
count = 0
188202
if '_' in name:
189-
count = int(name.split('_')[0])+1
190-
name = name + '_{}'.format(count)
203+
count = int(name.split('_')[1])+1
204+
name = base_name + '_{}'.format(count)
191205

192206
# self.constraints[name]['A'] = A#np.vstack([self.constraints[name]['A'],A])
193207
# self.constraints[name]['B'] = B#np.hstack([self.constraints[name]['B'], B])
194208
# self.constraints[name]['idc'] = idc#np.vstack([self.constraints[name]['idc'],
195209
# idc])
196-
197-
self.constraints[name] = {'node_indexes':constraint_ids,'A':A,'B':B.flatten(),'idc':idc}
198210
rows = np.tile(rows, (A.shape[-1], 1)).T
211+
self.constraints[name] = {'node_indexes':constraint_ids,'A':A,'B':B.flatten(),'col':idc,'w':w,'row':rows}
199212

200213
self.c_ += nr
201-
if self.shape == 'rectangular':
202-
# don't add operator where it is = 0 to the sparse matrix!
203-
A = A.flatten()
204-
rows = rows.flatten()
205-
idc = idc.flatten()
206-
B = B.flatten()
207-
mask = A == 0
208-
self.A.extend(A[~mask].tolist())
209-
self.row.extend(rows[~mask].tolist())
210-
self.col.extend(idc[~mask].tolist())
211-
self.B.extend(B.tolist())
214+
212215

213216
def calculate_residual_for_constraints(self):
214217
residuals = {}
@@ -318,11 +321,41 @@ def build_matrix(self, square=True, damp=True):
318321

319322
logger.info("Interpolation matrix is %i x %i"%(self.c_,self.nx))
320323
cols = np.array(self.col)
321-
A = coo_matrix((np.array(self.A), (np.array(self.row), \
324+
# To keep the solvers consistent for different model scales the range of the constraints should be similar.
325+
# We normalise the row vectors for the interpolation matrix
326+
# Each constraint can then be weighted separately for the least squares problem
327+
# The weights are normalised so that the max weight is 1.0
328+
# This means that the tolerance and other parameters for the solver
329+
# are kept the same between iterations.
330+
# #TODO currently the element size is not incorporated into the weighting.
331+
# For cartesian grids this is probably ok but for tetrahedron could be more problematic if
332+
# the tetras have different volumes. Would expect for the size of the element to influence
333+
# how much it contributes to the system.
334+
# It could be implemented by multiplying the weight array by the element size.
335+
# I am not sure how to integrate regularisation into this framework as my gut feeling is the regularisation
336+
# should be weighted by the area of the element face and not element volume, but this means the weight decreases with model scale
337+
# which is not ideal.
338+
max_weight = 0
339+
for c in self.constraints.values():
340+
if c['w'].max() > max_weight:
341+
max_weight = c['w'].max()
342+
a = []
343+
b = []
344+
rows = []
345+
cols = []
346+
for c in self.constraints.values():
347+
aa = (c['A']*c['w'][:,None]/max_weight).flatten()
348+
b.extend((c['B']*c['w']/max_weight).tolist())
349+
mask = aa == 0
350+
a.extend(aa[~mask].tolist())
351+
rows.extend(c['row'].flatten()[~mask].tolist())
352+
cols.extend(c['col'].flatten()[~mask].tolist())
353+
354+
A = coo_matrix((np.array(a), (np.array(rows), \
322355
cols)), shape=(self.c_, self.nx),
323356
dtype=float) # .tocsr()
324-
B = np.array(self.B)
325-
357+
358+
B = np.array(b)
326359
if not square:
327360
logger.info("Using rectangular matrix, equality constraints are not used")
328361
return A, B

0 commit comments

Comments
 (0)