1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
|
#!/usr/bin/env python3
# encoding: utf-8
"""
_FieldElement.py
This contains all the pure-python code for FieldElement.tmpl
Created by Graham Dennis on 2007-10-17.
Copyright (c) 2007-2012, Graham Dennis
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from xpdeint.ScriptElement import ScriptElement
from xpdeint.ParserException import ParserException, parserWarning
from xpdeint.Utilities import lazy_property, symbolsInString
class _FieldElement (ScriptElement):
def __init__(self, *args, **KWs):
# The MomentGroup and GeometryElement subclasses define name properties
if not self.hasattr('name'):
self.name = KWs['name']
del KWs['name']
if not 'parent' in KWs: KWs['parent'] = self.simulation
ScriptElement.__init__(self, *args, **KWs)
# Set default variables
self.managedVectors = set()
self.temporaryVectors = set()
self.dimensions = []
self._basisForBasisCache = {}
self.getVar('fields').append(self)
@property
def vectors(self):
returnValue = self.managedVectors.copy()
returnValue.update(self.temporaryVectors)
return returnValue
@property
def children(self):
children = super(_FieldElement, self).children
children.extend(self.dimensions)
# Sort managed vectors by name
children.extend(self.managedVectors)
return children
@lazy_property
def prefix(self):
return '_' + self.name if self.name != 'geometry' else ''
# Do we have the dimension?
def hasDimension(self, dimension):
return self.hasDimensionName(dimension.name)
# Do we have a dimension matching dimensionName?
def hasDimensionName(self, dimensionName):
dimensionList = [x for x in self.dimensions if x.name == dimensionName]
assert len(dimensionList) <= 1
return len(dimensionList) == 1
# Is the field a subset of another field (in terms of dimensions)
def isSubsetOfField(self, field):
"""Return whether this field's dimensions are a subset of the dimensions of field `field`."""
for dimension in self.dimensions:
if not field.hasDimension(dimension):
return False
return True
def isEquivalentToField(self, field):
"""Return whether this field and field `field` have the same dimensions."""
return self.isSubsetOfField(field) and field.isSubsetOfField(self)
# The index of the provided dimension
def indexOfDimension(self, dimension):
"""Return the index (in the `dimensions` list) of the dimension corresponding to `dimension`."""
return self.indexOfDimensionName(dimension.name)
# The index of the dimension with the name dimensionName
def indexOfDimensionName(self, dimensionName):
"""Return the index (in the `dimensions` list) of the dimension that has the name `dimensionName`."""
dimensionList = [x for x in self.dimensions if x.name == dimensionName]
assert len(dimensionList) == 1
return self.dimensions.index(dimensionList[0])
def dimensionWithName(self, dimensionName):
"""Return the dimension that has the name `dimensionName`."""
return self.dimensions[self.indexOfDimensionName(dimensionName)]
def localPointsInDimensionsAfterDimRepInBasis(self, dimRep, basis):
dimReps = self.inBasis(basis)
# Grab everything after dimension
dimReps = dimReps[(dimReps.index(dimRep)+1):]
if not len(dimReps):
return '1'
return ' * '.join([dimRep.localLattice for dimRep in dimReps])
@property
def transverseDimensions(self):
return [x for x in self.dimensions if x.transverse]
# Initialise field
def initialise(self):
return self.implementationsForChildren('initialise')
# Allocate (and initialise active pointers)
def allocate(self):
return self.implementationsForChildren('allocate')
# Free vectors
def free(self):
return self.implementationsForChildren('free')
@lazy_property
def isDistributed(self):
return self._driver.isFieldDistributed(self)
def sizeInBasis(self, basis):
return '(' + (' * '.join([dimRep.localLattice for dimRep in self.inBasis(basis)]) or '1') + ')'
def sortDimensions(self):
"""Sort the dimensions of the field into canonical (geometry element) order."""
geometryTemplate = self.getVar('geometry')
sortFunction = lambda x: geometryTemplate.indexOfDimension(x)
self.dimensions.sort(key = sortFunction)
def basisForBasis(self, basis):
"""
Return a basis that only contains the dimensions in this field.
It also handles the case in which a distributed basis becomes a non-distributed basis
after a dimension has been omitted.
We do not validate that such a transformation is possible.
The results of this method are cached on a per-instance basis for speed.
"""
if not basis in self._basisForBasisCache:
geometry = self.getVar('geometry')
dimRepNames = set([dr.canonicalName for dim in self.dimensions for dr in geometry.dimensionWithName(dim.name).representations])
if not len(dimRepNames.intersection(basis)) == len(self.dimensions):
raise ParserException(
None,
"Internal error: The basis provided (%s) contained insufficient information to generate the appropriate basis for a vector in this field (%s). A specification is required for all dimensions (%s). \n\nPlease report this error to %s." %
(', '.join(basis), self.name, ', '.join(dim.name for dim in self.dimensions), self.getVar('bugReportAddress'))
)
newBasis = tuple(b for b in basis if b in dimRepNames)
maxDistributedIdx = max([idx for idx, dimRepName in enumerate(newBasis) if dimRepName.startswith('distributed ')] + [-1])
orderedDimRepNames = [(idx, dimRep.canonicalName)
for idx, dim in enumerate(self.dimensions)
for dimRep in geometry.dimensionWithName(dim.name).representations
if dimRep.canonicalName in newBasis[maxDistributedIdx+1:]]
orderedDimRepNames.sort()
newBasis = self._driver.canonicalBasisForBasis(newBasis[:maxDistributedIdx+1] + tuple([x[1] for x in orderedDimRepNames]))
self._basisForBasisCache[basis] = newBasis
return self._basisForBasisCache[basis]
def completedBasisForBasis(self, basis, defaultBasis):
# If an incomplete basis is known for this field, it may be desirable to complete the basis using information from a default.
basis = basis or ()
geometry = self.getVar('geometry')
dimRepNameToDimMap = dict((dr.canonicalName, dim) for dim in self.dimensions for dr in geometry.dimensionWithName(dim.name).representations)
missingDimensions = set(self.dimensions)
for dimRepName in basis:
missingDimensions.discard(dimRepNameToDimMap.get(dimRepName))
# We now have the missing dimensions, now to find the corresponding dimRepName from the field's defaultCoordinateBasis
for dimRepName in defaultBasis:
dimension = dimRepNameToDimMap[dimRepName]
if dimension in missingDimensions:
missingDimensions.discard(dimension)
basis += (dimRepName,)
orderedBasis = tuple(dim.inBasis(basis).name for dim in self.dimensions)
canonicalBasis = self._driver.canonicalBasisForBasis(orderedBasis)
return self.basisForBasis(canonicalBasis)
def inBasis(self, basis):
"""
Return a list of dimReps corresponding to the supplied basis. We cannot guarantee that the basis we are passed is directly appropriate
for this field. So we must pass it through basisForBasis.
"""
basis = self.basisForBasis(basis)
dimRepNameMap = dict([(dimRep.canonicalName, dimRep) for dim in self.dimensions for dimRep in dim.representations if dimRep])
return [dimRepNameMap[b] for b in basis]
def basisFromString(self, basisString, xmlElement = None):
"""
Return the basis given `basisString`.
"""
xmlElement = xmlElement or self.xmlElement
basis = set(symbolsInString(basisString, xmlElement = xmlElement))
geometry = self.getVar('geometry')
validNames = set([dimRep.name for dim in self.dimensions for dimRep in geometry.dimensionWithName(dim.name).representations])
if basis.difference(validNames):
raise ParserException(
xmlElement,
"The following names are not valid basis specifiers: %s." % ', '.join(basis.difference(validNames))
)
# Now we know we don't have any specifiers that we can't identify,
# so we just need to check that we don't have two specifiers for the same dimension.
dimToDimRepMap = dict([(dim.name, [dimRep.name for dimRep in geometry.dimensionWithName(dim.name).representations]) for dim in self.dimensions])
for dimName, dimRepNames in list(dimToDimRepMap.items()):
basisNamesInDimRepNames = basis.intersection(dimRepNames)
if len(basisNamesInDimRepNames) > 1:
raise ParserException(
xmlElement,
"There is more than one basis specifier for dimension '%s'. The conflict is between %s." \
% (dimName, ' and '.join(basis.intersection(dimRepNames)))
)
elif len(basisNamesInDimRepNames) == 1:
# Use the map to now list the actual chosen dimRep name
dimToDimRepMap[dimName] = list(basisNamesInDimRepNames)[0]
else:
# This dimension doesn't have a rep specified. Default to the first rep.
dimToDimRepMap[dimName] = dimRepNames[0]
if dimToDimRepMap[dimName] is None:
raise ParserException(
xmlElement,
"Internal Error: When turning string '%s' into a basis, we were unable to determine the correct dimension representation for the %s dimension. "
"Please report this error to %s" \
% (basisString, dimName, self.getVar('bugReportAddress'))
)
# Now we just need to construct the basis
basis = self._driver.canonicalBasisForBasis(tuple(dimToDimRepMap[dim.name] for dim in self.dimensions))
return basis
@lazy_property
def defaultCoordinateBasis(self):
# Grab the first rep for each dim whose tag is a 'coordinate' tag
# i.e. the tag is a subclass of the 'coordinate' tag.
return self._driver.canonicalBasisForBasis(
tuple([dim.firstDimRepWithTagName('coordinate').canonicalName for dim in self.dimensions])
)
@lazy_property
def defaultSpectralBasis(self):
# Grab the first dim rep for each dim whose tag is 'spectral' if one exists
# Failing that, take the first one with a 'coordinate' tag.
reps = []
for dim in self.dimensions:
for tagName in ['spectral', 'coordinate']:
rep = dim.firstDimRepWithTagName(tagName)
if rep: break
assert rep, "We should have found a representation that was either spectral or coordinate but somehow failed"
reps.append(rep.canonicalName)
return self._driver.canonicalBasisForBasis(tuple(reps))
@classmethod
def sortedFieldWithDimensionNames(cls, dimensionNames, xmlElement = None, createIfNeeded = True):
"""
Return a field containing `dimensionNames` as the dimensions in canonical order.
This function will either return an existing field, or create one if necessary.
Although this class method is defined on `_FieldElement`, it must be called as
``FieldElement.sortedFieldWithDimensionNames`` in order to get a FieldElement
instance out.
"""
globalNameSpace = cls.argumentsToTemplateConstructors['searchList'][0]
geometry = globalNameSpace['geometry']
fields = globalNameSpace['fields']
dimensionNames = list(dimensionNames)
# If we have an xmlElement, first check that all of the dimension names provided
# are valid dimension names
if xmlElement:
for dimensionName in dimensionNames:
if not geometry.hasDimensionName(dimensionName):
raise ParserException(xmlElement, "Don't recognise '%(dimensionName)s' as one of "
"the dimensions defined in the geometry element." % locals())
dimensionNames.sort(key = lambda x: geometry.indexOfDimensionName(x))
fieldDimensions = [geometry.dimensionWithName(dimName) for dimName in dimensionNames]
if len(dimensionNames):
fieldName = ''.join(dimensionNames)
else:
fieldName = 'dimensionless'
potentialFields = [x for x in fields if x.dimensions == fieldDimensions and x.name == fieldName]
field = None
if potentialFields:
# If there is a field already in existence that matches our requirements, use it
field = potentialFields[0]
elif createIfNeeded:
# Otherwise we need to construct our own
field = cls(name = fieldName, **cls.argumentsToTemplateConstructors)
# Copy in our dimensions
field.dimensions[:] = [dim.copy(parent = field) for dim in fieldDimensions]
if xmlElement:
field.xmlElement = xmlElement
return field
|