def localInputAndChecks(self, xmlNode): """ Local method for additional reading. @ In, xmlNode, xml.etree.ElementTree.Element, Xml element node @ Out, None """ GradientBasedOptimizer.localInputAndChecks(self, xmlNode) self.paramDict['alpha'] = float(self.paramDict.get('alpha', 0.602)) self.paramDict['gamma'] = float(self.paramDict.get('gamma', 0.101)) self.paramDict['A'] = float( self.paramDict.get('A', self.limit['mdlEval'] / 10.)) self.paramDict['a'] = self.paramDict.get('a', None) self.paramDict['c'] = float(self.paramDict.get('c', 0.005)) #FIXME the optimization parameters should probably all operate ONLY on normalized data! # -> perhaps the whole optimizer should only work on optimized data. #FIXME normalizing doesn't seem to have the desired effect, currently; it makes the step size very small (for large scales) #if "a" was defaulted, use the average scale of the input space. #This is the suggested value from the paper, missing a 1/gradient term since we don't know it yet. if self.paramDict['a'] is None: self.paramDict['a'] = mathUtils.hyperdiagonal( np.ones(len( self.getOptVars()))) # the features are always normalized self.raiseAMessage('Defaulting "a" gradient parameter to', self.paramDict['a']) else: self.paramDict['a'] = float(self.paramDict['a']) self.constraintHandlingPara['innerBisectionThreshold'] = float( self.paramDict.get('innerBisectionThreshold', 1e-2)) self.constraintHandlingPara['innerLoopLimit'] = float( self.paramDict.get('innerLoopLimit', 1000)) self.gradDict['pertNeeded'] = self.gradDict['numIterForAve'] * 2 stochDist = self.paramDict.get('stochasticDistribution', 'Hypersphere') if stochDist == 'Bernoulli': self.stochasticDistribution = Distributions.returnInstance( 'Bernoulli', self) self.stochasticDistribution.p = 0.5 self.stochasticDistribution.initializeDistribution() # Initialize bernoulli distribution for random perturbation. Add artificial noise to avoid that specular loss functions get false positive convergence # FIXME there has to be a better way to get two random numbers self.stochasticEngine = lambda: [ (0.5 + randomUtils.random() * (1. + randomUtils.random( ) / 1000. * randomUtils.randomIntegers(-1, 1, self))) if self.stochasticDistribution.rvs() == 1 else -1. * (0.5 + randomUtils.random() * (1. + randomUtils.random( ) / 1000. * randomUtils.randomIntegers(-1, 1, self))) for _ in range(len(self.getOptVars())) ] elif stochDist == 'Hypersphere': self.stochasticEngine = lambda: randomUtils.randPointsOnHypersphere( len(self.getOptVars())) else: self.raiseAnError( IOError, self.paramDict['stochasticEngine'] + 'is currently not supported for SPSA')
def _getSegmentIndexFromClusterIndex(self, cluster, labelMap, clusterIndex=None, chooseRandom=False): """ Given the index of a rom WITHIN a cluster, get the index of that rom's segment in the full history @ In, cluster, int, label of cluster that ROM is within @ In, labelMap, list(int), map of where clusters appear in order of full history @ In, clusterIndex, int, optional, index of ROM within the cluster @ In, chooseRandom, bool, optional, if True then choose randomly from eligible indices @ Out, segmentIndex, int, position of rom's segment in full history @ Out, clusterIndex, int, position of rom within cluster (returned in event of random) """ # Need to either provide the index, or let it be random, but not both ## TODO modify to use internal RNG from randUtils assert not (clusterIndex is None and chooseRandom is False) assert not (clusterIndex is not None and chooseRandom is True) # indices of all segments indices = np.arange(len(labelMap)) # indices who belong to this cluster eligible = indices[labelMap == cluster] # if random, choose now if chooseRandom: i = randomUtils.randomIntegers(0, len(eligible) - 1, self) clusterIndex = eligible[i] # global index segmentIndex = eligible[clusterIndex] return segmentIndex, clusterIndex
def twoPointsCrossover(parents, parentIndexes,**kwargs): """ Method designed to perform a two point crossover on 2 parents: Partition each parents in three sequences (A,B,C): parent1 = A1 B1 C1 parent2 = A2 B2 C2 Then: children1 = A1 B2 C1 children2 = A2 B1 C2 @ In, parents, xr.DataArray, parents involved in the mating process @ In, parentIndexes, list, list containing pairs of parents @ In, kwargs, dict, dictionary of parameters for this mutation method: parents, 2D array, parents in the current mating process. Shape is nParents x len(chromosome) i.e, number of Genes/Vars crossoverProb, float, crossoverProb determines when child takes genes from a specific parent, default is random points, integer, point at which the cross over happens, default is random @ Out, children, xr.DataArray, children resulting from the crossover. Shape is nParents x len(chromosome) i.e, number of Genes/Vars """ nParents,nGenes = np.shape(parents) children = xr.DataArray(np.zeros((int(2*comb(nParents,2)),np.shape(parents)[1])), dims=['chromosome','Gene'], coords={'chromosome': np.arange(int(2*comb(nParents,2))), 'Gene':parents.coords['Gene'].values}) index = 0 for couples in parentIndexes: locRangeList = list(range(0,nGenes)) index1 = randomUtils.randomIntegers(0, len(locRangeList), caller=None, engine=None) loc1 = locRangeList[index1] locRangeList.pop(loc1) index2 = randomUtils.randomIntegers(0, len(locRangeList), caller=None, engine=None) loc2 = locRangeList[index2] if loc1>loc2: locL=loc2 locU=loc1 elif loc1<loc2: locL=loc1 locU=loc2 parent1 = parents[couples[0]].values parent2 = parents[couples[1]].values children1,children2 = twoPointsCrossoverMethod(parent1,parent2,locL,locU) children[index]=copy.deepcopy(children1) children[index+1]=copy.deepcopy(children2) index = index + 2 return children
def inversionMutator(offSprings, **kwargs): """ This method is designed mirror a sequence of genes in each chromosome with probability = mutationProb. The sequence of genes to be mirrored is completely random. E.g. given chromosome C = [0,1,2,3,4,5,6,7,8,9] and sampled locL=2 locU=6; New chromosome C' = [0,1,6,5,4,3,2,7,8,9] @ In, offSprings, xr.DataArray, children resulting from the crossover process @ In, kwargs, dict, dictionary of parameters for this mutation method: mutationProb, float, probability that governs the mutation process, i.e., if prob < random number, then the mutation will occur @ Out, offSprings, xr.DataArray, children resulting from the crossover process """ for child in offSprings: # the mutation is performed for each child independently if randomUtils.random(dim=1, samples=1) < kwargs['mutationProb']: # sample gene locations: i.e., determine loc1 and loc2 locRangeList = list(range(0, child.values.shape[0])) index1 = randomUtils.randomIntegers(0, len(locRangeList), caller=None, engine=None) loc1 = locRangeList[index1] locRangeList.pop(loc1) index2 = randomUtils.randomIntegers(0, len(locRangeList), caller=None, engine=None) loc2 = locRangeList[index2] if loc1 > loc2: locL = loc2 locU = loc1 elif loc1 < loc2: locL = loc1 locU = loc2 ############## # select sequence to be mirrored and mirror it seq = child.values[locL:locU + 1] mirrSeq = seq[::-1] ############## # insert mirrored sequence into child child.values[locL:locU + 1] = mirrSeq return offSprings
def readSamplerInit(self, xmlNode): """ This method is responsible to read only the samplerInit block in the .xml file. This method has been moved from the base sampler class since the samplerInit block is needed only for the MC and stratified (LHS) samplers @ In, xmlNode, xml.etree.ElementTree.Element, Xml element node @ Out, None """ for child in xmlNode: if child.tag == "samplerInit": self.initSeed = randomUtils.randomIntegers(0, 2**31, self) for childChild in child: if childChild.tag == "limit": try: self.limit = int(childChild.text) except ValueError: self.raiseAnError( IOError, 'reading the attribute for the sampler ' + self.name + ' it was not possible to perform the conversion to integer for the attribute limit with value ' + str(childChild.text)) if childChild.tag == "initialSeed": try: self.initSeed = int(childChild.text) except ValueError: self.raiseAnError( IOError, 'reading the attribute for the sampler ' + self.name + ' it was not possible to perform the conversion to integer for the attribute initialSeed with value ' + str(childChild.text)) elif childChild.tag == "reseedEachIteration": if childChild.text.lower( ) in utils.stringsThatMeanTrue(): self.reseedAtEachIteration = True elif childChild.tag == "distInit": for childChildChild in childChild: NDdistData = {} for childChildChildChild in childChildChild: if childChildChildChild.tag == 'initialGridDisc': NDdistData[childChildChildChild.tag] = int( childChildChildChild.text) elif childChildChildChild.tag == 'tolerance': NDdistData[ childChildChildChild.tag] = float( childChildChildChild.text) else: self.raiseAnError( IOError, 'Unknown tag ' + childChildChildChild.tag + ' .Available are: initialGridDisc and tolerance!' ) self.NDSamplingParams[ childChildChild.attrib['name']] = NDdistData
def scrambleMutator(offSprings, **kwargs): """ This method performs the scramble mutator. For each child, a subset of genes is chosen and their values are shuffled randomly. @ In, offSprings, xr.DataArray, offsprings after crossover @ In, kwargs, dict, dictionary of parameters for this mutation method: chromosome, numpy.array, the chromosome that will mutate to the new child locs, list, the locations of the genes to be randomly scrambled mutationProb, float, probability that governs the mutation process, i.e., if prob < random number, then the mutation will occur variables, list, variables names. @ Out, child, np.array, the mutated chromosome, i.e., the child. """ locs = kwargs['locs'] if locs == None: nLocs = randomUtils.randomIntegers(0, offSprings.sizes['Gene'] - 1, None) locs = [] for i in range(nLocs): l = randomUtils.randomIntegers(0, offSprings.sizes['Gene'] - 1, None) locs.append(l) locs = list(set(locs)) nMutable = len(locs) # initializing children children = xr.DataArray(np.zeros((np.shape(offSprings))), dims=['chromosome', 'Gene'], coords={ 'chromosome': np.arange(np.shape(offSprings)[0]), 'Gene': kwargs['variables'] }) for i in range(np.shape(offSprings)[0]): children[i] = copy.deepcopy(offSprings[i]) new = list(itemgetter(*locs)(offSprings[i].values)) for ind, element in enumerate(locs): if randomUtils.random(dim=1, samples=1) < kwargs['mutationProb']: children[i, locs[0]:locs[-1] + 1] = randomUtils.randomPermutation( list(offSprings.data[i, locs[0]:locs[-1] + 1]), None) return children
def __getstate__(self): """ Obtains state of object for pickling. @ In, None @ Out, d, dict, stateful dictionary """ d = copy.copy(self.__dict__) # set up a seed for the next pickled iteration if self.reseedCopies: rand = randomUtils.randomIntegers(1, int(2**20), self) d['random seed'] = rand return d
def bitFlipMutator(offSprings, **kwargs): """ This method is designed to flip a single gene in each chromosome with probability = mutationProb. E.g. gene at location loc is flipped from current value to newValue The gene to be flipped is completely random. The new value of the flipped gene is is completely random. @ In, offSprings, xr.DataArray, children resulting from the crossover process @ In, kwargs, dict, dictionary of parameters for this mutation method: mutationProb, float, probability that governs the mutation process, i.e., if prob < random number, then the mutation will occur @ Out, offSprings, xr.DataArray, children resulting from the crossover process """ for child in offSprings: # the mutation is performed for each child independently if randomUtils.random(dim=1, samples=1) < kwargs['mutationProb']: # sample gene location to be flipped: i.e., determine loc chromosomeSize = child.values.shape[0] loc = randomUtils.randomIntegers(0, chromosomeSize, caller=None, engine=None) ############## # sample value: i.e., determine newValue if kwargs['sampleRange'] == 'local': rangeValues = list(set(offSprings[:, loc].values)) else: #kwargs['sampleRange']=='global' rangeValues = offSprings.values.ravel().tolist() rangeValues.pop(child.values[loc]) newValuePos = randomUtils.randomIntegers(0, len(rangeValues), caller=None, engine=None) newValue = rangeValues[newValuePos] ############## # gene at location loc is flipped from current value to newValue child.values[loc] = newValue return offSprings
def onePointCrossover(parents,**kwargs): """ Method designed to perform crossover by swapping chromosome portions before/after specified or sampled location @ In, parents, xr.DataArray, parents involved in the mating process. @ In, kwargs, dict, dictionary of parameters for this mutation method: crossoverProb, float, crossoverProb determines when child takes genes from a specific parent, default is random points, integer, point at which the cross over happens, default is random variables, list, variables names. @ Out, children, np.array, children resulting from the crossover. Shape is nParents x len(chromosome) i.e, number of Genes/Vars """ nParents,nGenes = np.shape(parents) # Number of children = 2* (nParents choose 2) children = xr.DataArray(np.zeros((int(2*comb(nParents,2)),nGenes)), dims=['chromosome','Gene'], coords={'chromosome': np.arange(int(2*comb(nParents,2))), 'Gene':kwargs['variables']}) # defaults if (kwargs['crossoverProb'] == None) or ('crossoverProb' not in kwargs.keys()): crossoverProb = randomUtils.random(dim=1, samples=1) else: crossoverProb = kwargs['crossoverProb'] # create children parentsPairs = list(combinations(parents,2)) for ind,parent in enumerate(parentsPairs): parent = np.array(parent).reshape(2,-1) # two parents at a time if randomUtils.random(dim=1,samples=1) <= crossoverProb: if (kwargs['points'] == None) or ('points' not in kwargs.keys()): point = list([randomUtils.randomIntegers(1,nGenes-1,None)]) elif (any(i>=nGenes-1 for i in kwargs['points'])): raise ValueError('Crossover point cannot be larger than number of Genes (variables)') else: point = kwargs['points'] for i in range(nGenes): if len(point)>1: raise ValueError('In one Point Crossover a single crossover location should be provided!') children[2*ind:2*ind+2,i] = copy.deepcopy(parent[np.arange(0,2)*(i<point[0])+np.arange(-1,-3,-1)*(i>=point[0]),i]) else: # Each child is just a copy of the parents children[2*ind:2*ind+2,:] = copy.deepcopy(parent) return children
def handleInput(self, paramInput): """ Read input specs @ In, paramInput, InputData.ParameterInput, parameter specs interpreted @ Out, None """ likelihood = paramInput.findFirst('likelihood') if likelihood is not None: self._likelihood = likelihood.value self._logLikelihood = likelihood.parameterValues.get('log', False) else: self.raiseAnError(IOError, "likelihood is required, but not provided!") init = paramInput.findFirst('samplerInit') if init is not None: # limit limit = init.findFirst('limit') if limit is not None: self.limit = limit.value else: self.raiseAnError( IOError, 'MCMC', self.name, 'needs the limit block (number of samples) in the samplerInit block' ) # initialSeed seed = init.findFirst('initialSeed') if seed is not None: self.initSeed = seed.value else: self.initSeed = randomUtils.randomIntegers(0, 2**31, self) burnIn = init.findFirst('burnIn') if burnIn is not None: self._burnIn = burnIn.value else: self.raiseAnError(IOError, 'MCMC', self.name, 'needs the samplerInit block') if self._burnIn >= self.limit: self.raiseAnError( IOError, 'Provided "burnIn" value must be less than "limit" value!') # TargetEvaluation Node (Required) targetEval = paramInput.findFirst('TargetEvaluation') self._targetEvaluation = targetEval.value self._updateValues = copy.copy(self._initialValues)
def readSamplerInit(self,xmlNode): """ This method is responsible to read only the samplerInit block in the .xml file. This method has been moved from the base sampler class since the samplerInit block is needed only for the MC and stratified (LHS) samplers @ In, xmlNode, xml.etree.ElementTree.Element, Xml element node @ Out, None """ #TODO, this is redundant and paramInput should be directly passed in. paramInput = self.getInputSpecification()() paramInput.parseNode(xmlNode) for child in paramInput.subparts: if child.getName() == "samplerInit": self.initSeed = randomUtils.randomIntegers(0,2**31,self) for childChild in child.subparts: if childChild.getName() == "limit": try: self.limit = int(childChild.value) except ValueError: self.raiseAnError(IOError,'reading the attribute for the sampler '+self.name+' it was not possible to perform the conversion to integer for the attribute limit with value ' + str(childChild.value)) if childChild.getName() == "initialSeed": try: self.initSeed = int(childChild.value) except ValueError: self.raiseAnError(IOError,'reading the attribute for the sampler '+self.name+' it was not possible to perform the conversion to integer for the attribute initialSeed with value ' + str(childChild.value)) elif childChild.getName() == "reseedEachIteration": if childChild.value.lower() in utils.stringsThatMeanTrue(): self.reseedAtEachIteration = True elif childChild.getName() == "distInit": for childChildChild in childChild.subparts: NDdistData = {} for childChildChildChild in childChildChild.subparts: if childChildChildChild.getName() == 'initialGridDisc': NDdistData[childChildChildChild.getName()] = int(childChildChildChild.value) elif childChildChildChild.getName() == 'tolerance': NDdistData[childChildChildChild.getName()] = float(childChildChildChild.value) else: self.raiseAnError(IOError,'Unknown tag '+childChildChildChild.getName()+' .Available are: initialGridDisc and tolerance!') self.NDSamplingParams[childChildChild.parameterValues['name']] = NDdistData
def initialize(self, externalSeeding=None, solutionExport=None): """ This function should be called every time a clean sampler is needed. Called before takeAstep in <Step> @ In, externalSeeding, int, optional, external seed @ In, solutionExport, DataObject, optional, in goal oriented sampling (a.k.a. adaptive sampling this is where the space/point satisfying the constrains) @ Out, None """ if self.initSeed == None: self.initSeed = randomUtils.randomIntegers(0, 2**31, self) self.counter = 0 if not externalSeeding: randomUtils.randomSeed( self.initSeed) #use the sampler initialization seed self.auxcnt = self.initSeed elif externalSeeding == 'continue': pass #in this case the random sequence needs to be preserved else: randomUtils.randomSeed( externalSeeding) #the external seeding is used self.auxcnt = externalSeeding #grab restart dataobject if it's available, then in localInitialize the sampler can deal with it. if 'Restart' in self.assemblerDict.keys(): self.raiseADebug('Restart object: ' + str(self.assemblerDict['Restart'])) self.restartData = self.assemblerDict['Restart'][0][3] self.raiseAMessage('Restarting from ' + self.restartData.name) #check consistency of data try: rdata = self.restartData.getAllMetadata()['crowDist'] sdata = self.inputInfo['crowDist'] self.raiseAMessage('sampler inputs:') for sk, sv in sdata.items(): self.raiseAMessage('| ' + str(sk) + ': ' + str(sv)) for i, r in enumerate(rdata): if type(r) != dict: continue if not r == sdata: self.raiseAMessage('restart inputs %i:' % i) for rk, rv in r.items(): self.raiseAMessage('| ' + str(rk) + ': ' + str(rv)) self.raiseAnError( IOError, 'Restart "%s" data[%i] does not have same inputs as sampler!' % (self.restartData.name, i)) except KeyError as e: self.raiseAWarning( "No CROW distribution available in restart -", e) else: self.raiseAMessage('No restart for ' + self.printTag) #load restart data into existing points if self.restartData is not None: if not self.restartData.isItEmpty(): inps = self.restartData.getInpParametersValues() outs = self.restartData.getOutParametersValues() #FIXME there is no guarantee ordering is accurate between restart data and sampler inputs = list(v for v in inps.values()) existingInps = zip(*inputs) outVals = zip(*list(v for v in outs.values())) self.existing = dict(zip(existingInps, outVals)) #specializing the self.localInitialize() to account for adaptive sampling if solutionExport != None: self.localInitialize(solutionExport=solutionExport) else: self.localInitialize() for distrib in self.NDSamplingParams: if distrib in self.distributions2variablesMapping: params = self.NDSamplingParams[distrib] temp = utils.first( self.distributions2variablesMapping[distrib][0].keys()) self.distDict[temp].updateRNGParam(params) else: self.raiseAnError( IOError, 'Distribution "%s" specified in distInit block of sampler "%s" does not exist!' % (distrib, self.name)) # Store the transformation matrix in the metadata if self.variablesTransformationDict: self.entitiesToRemove = [] for variable in self.variables2distributionsMapping.keys(): distName = self.variables2distributionsMapping[variable][ 'name'] dim = self.variables2distributionsMapping[variable]['dim'] totDim = self.variables2distributionsMapping[variable][ 'totDim'] if totDim > 1 and dim == 1: transformDict = {} transformDict['type'] = self.distDict[ variable.strip()].type transformDict['transformationMatrix'] = self.distDict[ variable.strip()].transformationMatrix() self.inputInfo['transformation-' + distName] = transformDict self.entitiesToRemove.append('transformation-' + distName)
def _readMoreXMLbase(self,xmlNode): """ Function to read the portion of the xml input that belongs to the base sampler only and initialize some stuff based on the inputs got The text is supposed to contain the info where and which variable to change. In case of a code the syntax is specified by the code interface itself @ In, xmlNode, xml.etree.ElementTree.Element, Xml element node1 @ Out, paramInput, InputData.ParameterInput the parsed paramInput """ paramInput = self.getInputSpecification()() paramInput.parseNode(xmlNode) for child in paramInput.subparts: prefix = "" if child.getName() == 'Distribution': for childChild in child.subparts: if childChild.getName() =='distribution': prefix = "<distribution>" toBeSampled = childChild.value self.toBeSampled[prefix+child.parameterValues['name']] = toBeSampled elif child.getName() == 'variable': # variable for tracking if distributions or functions have been declared foundDistOrFunc = False # store variable name for re-use varName = child.parameterValues['name'] # set shape if present if 'shape' in child.parameterValues: self.variableShapes[varName] = child.parameterValues['shape'] # read subnodes for childChild in child.subparts: if childChild.getName() =='distribution': # can only have a distribution if doesn't already have a distribution or function if not foundDistOrFunc: foundDistOrFunc = True else: self.raiseAnError(IOError,'A sampled variable cannot have both a distribution and a function, or more than one of either!') # name of the distribution to sample toBeSampled = childChild.value varData={} varData['name']=childChild.value # variable dimensionality if 'dim' not in childChild.parameterValues: dim=1 else: dim=childChild.parameterValues['dim'] varData['dim']=dim # set up mapping for variable to distribution self.variables2distributionsMapping[varName] = varData # flag distribution as needing to be sampled self.toBeSampled[prefix+varName] = toBeSampled elif childChild.getName() == 'function': # can only have a function if doesn't already have a distribution or function if not foundDistOrFunc: foundDistOrFunc = True else: self.raiseAnError(IOError,'A sampled variable cannot have both a distribution and a function!') # function name toBeSampled = childChild.value # track variable as a functional sample self.dependentSample[prefix+varName] = toBeSampled if not foundDistOrFunc: self.raiseAnError(IOError,'Sampled variable',varName,'has neither a <distribution> nor <function> node specified!') elif child.getName() == "variablesTransformation": transformationDict = {} listIndex = None for childChild in child.subparts: if childChild.getName() == "latentVariables": transformationDict[childChild.getName()] = list(childChild.value) elif childChild.getName() == "manifestVariables": transformationDict[childChild.getName()] = list(childChild.value) elif childChild.getName() == "manifestVariablesIndex": # the index provided by the input file starts from 1, but the index used by the code starts from 0. listIndex = list(int(inp) - 1 for inp in childChild.value) elif childChild.getName() == "method": self.transformationMethod[child.parameterValues['distribution']] = childChild.value if listIndex == None: self.raiseAWarning('Index is not provided for manifestVariables, default index will be used instead!') listIndex = range(len(transformationDict["manifestVariables"])) transformationDict["manifestVariablesIndex"] = listIndex self.variablesTransformationDict[child.parameterValues['distribution']] = transformationDict elif child.getName() == "constant": name,value = self._readInConstant(child) self.constants[name] = value elif child.getName() == "restartTolerance": self.restartTolerance = child.value if len(self.constants) > 0: # check if constant variables are also part of the sampled space. In case, error out if not set(self.toBeSampled.keys()).isdisjoint(self.constants.keys()): self.raiseAnError(IOError,"Some constant variables are also in the sampling space:" + ' '.join([i if i in self.toBeSampled.keys() else "" for i in self.constants.keys()]) ) if self.initSeed == None: self.initSeed = randomUtils.randomIntegers(0,2**31,self) # Creation of the self.distributions2variablesMapping dictionary: {'distName': [{'variable_name1': dim1}, {'variable_name2': dim2}]} for variable in self.variables2distributionsMapping.keys(): distName = self.variables2distributionsMapping[variable]['name'] dim = self.variables2distributionsMapping[variable]['dim'] listElement={} listElement[variable] = dim if (distName in self.distributions2variablesMapping.keys()): self.distributions2variablesMapping[distName].append(listElement) else: self.distributions2variablesMapping[distName]=[listElement] # creation of the self.distributions2variablesIndexList dictionary:{'distName':[dim1,dim2,...,dimN]} self.distributions2variablesIndexList = {} for distName in self.distributions2variablesMapping.keys(): positionList = [] for var in self.distributions2variablesMapping[distName]: position = utils.first(var.values()) positionList.append(position) if sum(set(positionList)) > 1 and len(positionList) != len(set(positionList)): dups = set(str(var) for var in positionList if positionList.count(var) > 1) self.raiseAnError(IOError,'Each of the following dimensions are assigned to multiple variables in Samplers: "{}"'.format(', '.join(dups)), ' associated to ND distribution ', distName, '. This is currently not allowed!') positionList = list(set(positionList)) positionList.sort() self.distributions2variablesIndexList[distName] = positionList for key in self.variables2distributionsMapping.keys(): distName = self.variables2distributionsMapping[key]['name'] dim = self.variables2distributionsMapping[key]['dim'] reducedDim = self.distributions2variablesIndexList[distName].index(dim) + 1 self.variables2distributionsMapping[key]['reducedDim'] = reducedDim # the dimension of variable in the transformed space self.variables2distributionsMapping[key]['totDim'] = max(self.distributions2variablesIndexList[distName]) # We will reset the value if the node <variablesTransformation> exist in the raven input file if not self.variablesTransformationDict and self.variables2distributionsMapping[key]['totDim'] > 1: if self.variables2distributionsMapping[key]['totDim'] != len(self.distributions2variablesIndexList[distName]): self.raiseAnError(IOError,'The "dim" assigned to the variables insider Sampler are not correct! the "dim" should start from 1, and end with the full dimension of given distribution') #Checking the variables transformation if self.variablesTransformationDict: for dist,varsDict in self.variablesTransformationDict.items(): maxDim = len(varsDict['manifestVariables']) listLatentElement = varsDict['latentVariables'] if len(set(listLatentElement)) != len(listLatentElement): dups = set(var for var in listLatentElement if listLatentElement.count(var) > 1) self.raiseAnError(IOError,'The following are duplicated variables listed in the latentVariables: ' + str(dups)) if len(set(varsDict['manifestVariables'])) != len(varsDict['manifestVariables']): dups = set(var for var in varsDict['manifestVariables'] if varsDict['manifestVariables'].count(var) > 1) self.raiseAnError(IOError,'The following are duplicated variables listed in the manifestVariables: ' + str(dups)) if len(set(varsDict['manifestVariablesIndex'])) != len(varsDict['manifestVariablesIndex']): dups = set(var+1 for var in varsDict['manifestVariablesIndex'] if varsDict['manifestVariablesIndex'].count(var) > 1) self.raiseAnError(IOError,'The following are duplicated variables indices listed in the manifestVariablesIndex: ' + str(dups)) listElement = self.distributions2variablesMapping[dist] for var in listElement: self.variables2distributionsMapping[utils.first(var.keys())]['totDim'] = maxDim #reset the totDim to reflect the totDim of original input space tempListElement = {k.strip():v for x in listElement for ks,v in x.items() for k in list(ks.strip().split(','))} listIndex = [] for var in listLatentElement: if var not in set(tempListElement.keys()): self.raiseAnError(IOError, 'The variable listed in latentVariables ' + var + ' is not listed in the given distribution: ' + dist) listIndex.append(tempListElement[var]-1) if max(listIndex) > maxDim: self.raiseAnError(IOError,'The maximum dim = ' + str(max(listIndex)) + ' defined for latent variables is exceeded the dimension of the problem ' + str(maxDim)) if len(set(listIndex)) != len(listIndex): dups = set(var+1 for var in listIndex if listIndex.count(var) > 1) self.raiseAnError(IOError,'Each of the following dimensions are assigned to multiple latent variables in Samplers: ' + str(dups)) # update the index for latentVariables according to the 'dim' assigned for given var defined in Sampler self.variablesTransformationDict[dist]['latentVariablesIndex'] = listIndex return paramInput
## test many points vals = randomUtils.randomNormal(3,5,engine=None) checkAnswer('randomNormal number of samples for engine not provided',len(vals),5) checkAnswer('randomNormal size of sample for engine not provided',len(vals[0]),3) vals = randomUtils.randomNormal(3,5,engine=eng) checkAnswer('randomNormal number of samples for local engine provided',len(vals),5) checkAnswer('randomNormal size of sample for local engine provided',len(vals[0]),3) ### randomIntegers(), sampling integers in a range randomUtils.randomSeed(42,engine=None) randomUtils.randomSeed(42,engine=eng) right = [14,18,20,12,17] for i in range(5): n = randomUtils.randomIntegers(10,20,None,engine=None) #no message handler, error handling will error out checkAnswer('random integer, {} sample for engine not provided'.format(i),n,right[i]) for i in range(5): n = randomUtils.randomIntegers(10,20,None,engine=eng) #no message handler, error handling will error out checkAnswer('random integer, {} sample for local engine provided'.format(i),n,right[i]) ### randomPermutation(), rearranging lists randomUtils.randomSeed(42,engine=None) randomUtils.randomSeed(42,engine=eng) l = [1,2,3,4,5] l2 = randomUtils.randomPermutation(l,None,engine=None) checkArray('random permutation for engine not provided',l2,[2,4,5,1,3]) l2 = randomUtils.randomPermutation(l,None,engine=eng) checkArray('random permutation for local engine provided',l2,[2,4,5,1,3]) ### randPointsOnHypersphere(), unit hypersphere surface sampling (aka random direction) randomUtils.randomSeed(42,engine=None)
def _generateVarsUpdateConstrained(self, traj, ak, gradient, varK): """ Method to generate input for model to run, considering also that the input satisfies the constraint @ In, traj, int, trajectory label for whom we are generating variables with constraint consideration @ In, ak, float or array, it is gain for variable update (if array, different gain for each variable) @ In, gradient, dictionary, contains the gradient information for variable update @ In, varK, dictionary, current variable values (normalized) @ Out, varKPlus, dictionary, variable values for next iteration. @ Out, modded, bool, if True the point was modified by the constraint """ varKPlus = {} try: gain = ak[:] except (TypeError, IndexError): gain = [ak] * len( self.getOptVars() ) #technically incorrect, but missing ones will be *0 anyway just below here gain = np.asarray(gain) for index, var in enumerate(self.getOptVars( )): #get full opt vars so all variables carried through varKPlus[var] = varK[var] - gain[index] * gradient.get(var, 0.0) * 1.0 satisfied, activeConstraints = self.checkConstraint( self.denormalizeData(varKPlus)) if satisfied: return varKPlus, False # else if not satisfied ... # check if the active constraints are the boundary ones. In this case, try to project the gradient at an angle modded = False if len(activeConstraints['internal']) > 0: modded = True projectedOnBoundary = {} for activeConstraint in activeConstraints['internal']: projectedOnBoundary[activeConstraint[0]] = activeConstraint[1] gradient[activeConstraint[0]] = 0.0 # remove this component varKPlus.update(self.normalizeData(projectedOnBoundary)) newNormWithoutComponents = LA.norm(gradient.values()) for var in gradient.keys(): gradient[var] = gradient[ var] / newNormWithoutComponents if newNormWithoutComponents != 0.0 else gradient[ var] if len(activeConstraints['external']) == 0: return varKPlus, modded # Try to find varKPlus by shorten the gradient vector self.raiseADebug('Trajectory "{}" hit constraints ...'.format(traj)) self.raiseADebug(' Attempting to shorten step length ...') foundVarsUpdate, varKPlus = self._bisectionForConstrainedInput( traj, varK, ak, gradient) if foundVarsUpdate: self.raiseADebug( ' ... successfully found new point by shortening length.') return varKPlus, True # Try to find varKPlus by rotate the gradient towards its orthogonal, since we consider the gradient as perpendicular # with respect to the constraints hyper-surface self.raiseADebug(' Attempting instead to rotate trajectory ...') innerLoopLimit = self.constraintHandlingPara['innerLoopLimit'] if innerLoopLimit < 0: self.raiseAnError( IOError, 'Limit for internal loop for constraint handling shall be nonnegative' ) loopCounter = 0 foundPendVector = False while not foundPendVector and loopCounter < innerLoopLimit: loopCounter += 1 depVarPos = randomUtils.randomIntegers( 0, len(self.getOptVars(traj=traj)) - 1, self) pendVector = {} npDot = 0 for varID, var in enumerate(self.getOptVars(traj=traj)): pendVector[ var] = self.stochasticEngineForConstraintHandling.rvs( ) if varID != depVarPos else 0.0 npDot += pendVector[var] * gradient[var] for varID, var in enumerate(self.getOptVars(traj=traj)): if varID == depVarPos: pendVector[var] = -npDot / gradient[var] r = LA.norm( np.asarray( [gradient[var] for var in self.getOptVars(traj=traj)])) / LA.norm( np.asarray([ pendVector[var] for var in self.getOptVars(traj=traj) ])) for var in self.getOptVars(traj=traj): pendVector[var] = copy.deepcopy(pendVector[var]) * r varKPlus = {} for index, var in enumerate(self.getOptVars(traj=traj)): varKPlus[var] = copy.copy(varK[var] - gain[index] * pendVector[var] * 1.0) foundPendVector, activeConstraints = self.checkConstraint( self.denormalizeData(varKPlus)) if not foundPendVector: foundPendVector, varKPlus = self._bisectionForConstrainedInput( traj, varK, gain, pendVector) gain = gain / 2. if foundPendVector: lenPendVector = 0 for var in self.getOptVars(traj=traj): lenPendVector += pendVector[var]**2 lenPendVector = np.sqrt(lenPendVector) rotateDegreeUpperLimit = 2 while self.angleBetween(traj, gradient, pendVector) > rotateDegreeUpperLimit: sumVector, lenSumVector = {}, 0 for var in self.getOptVars(traj=traj): sumVector[var] = gradient[var] + pendVector[var] lenSumVector += sumVector[var]**2 tempTempVarKPlus = {} for index, var in enumerate(self.getOptVars(traj=traj)): sumVector[var] = copy.deepcopy( sumVector[var] / np.sqrt(lenSumVector) * lenPendVector) tempTempVarKPlus[var] = copy.copy(varK[var] - gain[index] * sumVector[var] * 1.0) satisfied, activeConstraints = self.checkConstraint( self.denormalizeData(tempTempVarKPlus)) if satisfied: varKPlus = copy.deepcopy(tempTempVarKPlus) pendVector = copy.deepcopy(sumVector) else: gradient = copy.deepcopy(sumVector) self.raiseADebug( ' ... successfully found new point by rotating trajectory.') return varKPlus, True varKPlus = varK self.raiseADebug(' ... did not successfully find new point.') return varKPlus, False
def initialize(self,externalSeeding=None,solutionExport=None): """ This function should be called every time a clean sampler is needed. Called before takeAstep in <Step> @ In, externalSeeding, int, optional, external seed @ In, solutionExport, DataObject, optional, in goal oriented sampling (a.k.a. adaptive sampling this is where the space/point satisfying the constrains) @ Out, None """ if self.initSeed == None: self.initSeed = randomUtils.randomIntegers(0,2**31,self) self.counter = 0 if not externalSeeding: randomUtils.randomSeed(self.initSeed) #use the sampler initialization seed self.auxcnt = self.initSeed elif externalSeeding=='continue': pass #in this case the random sequence needs to be preserved else : randomUtils.randomSeed(externalSeeding) #the external seeding is used self.auxcnt = externalSeeding #grab restart dataobject if it's available, then in localInitialize the sampler can deal with it. if 'Restart' in self.assemblerDict.keys(): self.raiseADebug('Restart object: '+str(self.assemblerDict['Restart'])) self.restartData = self.assemblerDict['Restart'][0][3] # check the right variables are in the restart need = set(self.toBeSampled.keys()+self.dependentSample.keys()) if not need.issubset(set(self.restartData.getVars())): missing = need - set(self.restartData.getVars()) #TODO this could be a warning, instead, but user wouldn't see it until the run was deep in self.raiseAnError(KeyError,'Restart data object "{}" is missing the following variables: "{}". No restart can be performed.'.format(self.restartData.name,', '.join(missing))) else: self.raiseAMessage('Restarting from '+self.restartData.name) # we used to check distribution consistency here, but we want to give more flexibility to using # restart data, so do NOT check distributions of restart data. else: self.raiseAMessage('No restart for '+self.printTag) #load restart data into existing points # TODO do not copy data! Read directly from restart. #if self.restartData is not None: # if len(self.restartData) > 0: # inps = self.restartData.getInpParametersValues() # outs = self.restartData.getOutParametersValues() # #FIXME there is no guarantee ordering is accurate between restart data and sampler # inputs = list(v for v in inps.values()) # existingInps = zip(*inputs) # outVals = zip(*list(v for v in outs.values())) # self.existing = dict(zip(existingInps,outVals)) #specializing the self.localInitialize() to account for adaptive sampling if solutionExport != None: self.localInitialize(solutionExport=solutionExport) else: self.localInitialize() for distrib in self.NDSamplingParams: if distrib in self.distributions2variablesMapping: params = self.NDSamplingParams[distrib] temp = utils.first(self.distributions2variablesMapping[distrib][0].keys()) self.distDict[temp].updateRNGParam(params) else: self.raiseAnError(IOError,'Distribution "%s" specified in distInit block of sampler "%s" does not exist!' %(distrib,self.name)) # Store the transformation matrix in the metadata if self.variablesTransformationDict: self.entitiesToRemove = [] for variable in self.variables2distributionsMapping.keys(): distName = self.variables2distributionsMapping[variable]['name'] dim = self.variables2distributionsMapping[variable]['dim'] totDim = self.variables2distributionsMapping[variable]['totDim'] if totDim > 1 and dim == 1: transformDict = {} transformDict['type'] = self.distDict[variable.strip()].type transformDict['transformationMatrix'] = self.distDict[variable.strip()].transformationMatrix() self.inputInfo['transformation-'+distName] = transformDict self.entitiesToRemove.append('transformation-'+distName) # Register expected metadata meta = ['ProbabilityWeight','prefix','PointProbability'] for var in self.toBeSampled.keys(): meta += ['ProbabilityWeight-'+ key for key in var.split(",")] self.addMetaKeys(*meta)
def _readMoreXMLbase(self, xmlNode): """ Function to read the portion of the xml input that belongs to the base optimizer only and initialize some stuff based on the inputs got @ In, xmlNode, xml.etree.ElementTree.Element, Xml element node1 @ Out, None """ paramInput = self.getInputSpecification()() paramInput.parseNode(xmlNode) # TODO some merging with base sampler XML reading might be possible, but in general requires different entries # first read all XML nodes for child in paramInput.subparts: #FIXME: the common variable reading should be wrapped up in a method to reduce the code redundancy if child.getName() == "variable": if self.fullOptVars is None: self.fullOptVars = [] # store variable name varName = child.parameterValues['name'] self.optVarsInitialized[varName] = False # store varible requested shape, if any if 'shape' in child.parameterValues: self.variableShapes[varName] = child.parameterValues[ 'shape'] self.fullOptVars.append(varName) self.optVarsInit['initial'][varName] = {} for childChild in child.subparts: if childChild.getName() == "upperBound": self.optVarsInit['upperBound'][ varName] = childChild.value elif childChild.getName() == "lowerBound": self.optVarsInit['lowerBound'][ varName] = childChild.value elif childChild.getName() == "initial": self.optVarsInit['initial'][varName] = {} self.optVarsInitialized[varName] = True initPoints = childChild.value for trajInd, initVal in enumerate(initPoints): try: self.optVarsInit['initial'][varName][ trajInd] = float(initVal) except ValueError: self.raiseAnError( ValueError, 'Unable to convert to float the intial value for variable "{}" in trajectory "{}": {}' .format(varName, trajInd, initVal)) if self.optTraj == None: self.optTraj = list( range( len(self.optVarsInit['initial'] [varName].keys()))) elif child.getName() == "constant": name, value = self._readInConstant(child) self.constants[child.parameterValues['name']] = value elif child.getName() == "objectVar": self.objVar = child.value.strip() elif child.getName() == "initialization": self.initSeed = randomUtils.randomIntegers(0, 2**31, self) for childChild in child.subparts: if childChild.getName() == "limit": self.limit['mdlEval'] = childChild.value #the manual once claimed that "A" defaults to iterationLimit/10, but it's actually this number/10. elif childChild.getName() == "type": self.optType = childChild.value if self.optType not in ['min', 'max']: self.raiseAnError( IOError, 'Unknown optimization type "{}". Available: "min" or "max"' .format(childChild.value)) elif childChild.getName() == "initialSeed": self.initSeed = childChild.value elif childChild.getName() == 'thresholdTrajRemoval': self.thresholdTrajRemoval = childChild.value elif childChild.getName() == 'writeSteps': whenToWrite = childChild.value.strip().lower() if whenToWrite == 'every': self.writeSolnExportOn = 'every' elif whenToWrite == 'final': self.writeSolnExportOn = 'final' else: self.raiseAnError( IOError, 'Unexpected frequency for <writeSteps>: "{}". Expected "every" or "final".' .format(whenToWrite)) else: self.raiseAnError( IOError, 'Unknown tag: ' + childChild.getName()) elif child.getName() == "convergence": for childChild in child.subparts: if childChild.getName() == "iterationLimit": self.limit['varsUpdate'] = childChild.value elif childChild.getName() == "absoluteThreshold": self.absConvergenceTol = childChild.value elif childChild.getName() == "relativeThreshold": self.relConvergenceTol = childChild.value elif childChild.getName() == "minStepSize": self.minStepSize = childChild.value elif childChild.getName() == 'persistence': self.convergencePersistence = childChild.value elif child.getName() == "restartTolerance": self.restartTolerance = child.value elif child.getName() == 'parameter': for childChild in child.subparts: self.paramDict[childChild.getName()] = childChild.value # now that XML is read, do some checks and defaults # set defaults if self.writeSolnExportOn is None: self.writeSolnExportOn = 'every' self.raiseAMessage( 'Writing to solution export on "{}" optimizer iteration.'.format( self.writeSolnExportOn)) if self.optType is None: self.optType = 'min' if self.thresholdTrajRemoval is None: self.thresholdTrajRemoval = 0.05 if self.initSeed is None: self.initSeed = randomUtils.randomIntegers(0, 2**31, self) # NOTE: optTraj can be changed in "initialize" if the user provides a sampler for seeding if self.optTraj is None: self.optTraj = [0] # check required settings TODO this can probably be removed thanks to the input checking! if self.objVar is None: self.raiseAnError( IOError, 'Object variable is not specified for optimizer!') if self.fullOptVars is None: self.raiseAnError( IOError, 'Decision variable(s) not specified for optimizer!') for var in self.getOptVars(): if var not in self.variableShapes: self.variableShapes[var] = (1, ) else: if len(self.variableShapes[var]) > 1: self.raiseAnError( NotImplementedError, 'Matrices as inputs are not yet supported in the Optimizer. For variable "{}" received shape "{}"!' .format(var, self.variableShapes[var])) for varName in self.fullOptVars: if varName not in self.optVarsInit['upperBound'].keys(): self.raiseAnError( IOError, 'Upper bound for ' + varName + ' is not provided') if varName not in self.optVarsInit['lowerBound'].keys(): self.raiseAnError( IOError, 'Lower bound for ' + varName + ' is not provided') #store ranges of variables self.optVarsInit['ranges'][ varName] = self.optVarsInit['upperBound'][ varName] - self.optVarsInit['lowerBound'][varName] if len(self.optVarsInit['initial'][varName]) == 0: for traj in self.optTraj: self.optVarsInit['initial'][varName][traj] = None
def _generateVarsUpdateConstrained(self,traj,ak,gradient,varK): """ Method to generate input for model to run, considering also that the input satisfies the constraint @ In, traj, int, trajectory label for whom we are generating variables with constraint consideration @ In, ak, float or array, it is gain for variable update (if array, different gain for each variable) @ In, gradient, dictionary, contains the gradient information for variable update @ In, varK, dictionary, current variable values (normalized) @ Out, varKPlus, dictionary, variable values for next iteration. @ Out, modded, bool, if True the point was modified by the constraint """ varKPlus = {} gain = [ak]*self._numberOfSamples() #technically too many entries, but unneeded ones will be *0 anyway just below here gain = np.asarray(gain) index = 0 for var in self.getOptVars(): numSamples = np.prod(self.variableShapes[var]) if numSamples == 1: new = varK[var]-gain[index]*gradient.get(var,0.0) index += 1 else: new = np.zeros(numSamples) for i in range(numSamples): new[i] = varK[var][i] - gain[index] * gradient.get(var,[0.0]*i)[i] index += 1 varKPlus[var] = new satisfied, activeViolations = self.checkConstraint(self.denormalizeData(varKPlus)) if satisfied: return varKPlus, False # else if not satisfied ... # check if the active constraints are the boundary ones. In this case, try to project the gradient at an angle modded = False if len(activeViolations['internal']) > 0: self.raiseADebug('Attempting to fix constraint violation with gradient projection ...') modded = True projectedOnBoundary= {} for var,under,over in activeViolations['internal']: if np.prod(self.variableShapes[var]) == 1: if np.sum(over) > 0: projectedOnBoundary[var] = self.optVarsInit['upperBound'][var] elif np.sum(under) > 0: projectedOnBoundary[var] = self.optVarsInit['lowerBound'][var] gradient[var] = 0.0 else: projectedOnBoundary[var] = self.denormalizeData({var:varKPlus[var]})[var] projectedOnBoundary[var][under] = self.optVarsInit['lowerBound'][var] projectedOnBoundary[var][over] = self.optVarsInit['upperBound'][var] gradient[var][np.logical_or(under,over)] = 0.0 varKPlus.update(self.normalizeData(projectedOnBoundary)) newNormWithoutComponents = self.calculateMultivectorMagnitude(gradient.values()) for var in gradient.keys(): gradient[var] = gradient[var]/newNormWithoutComponents if newNormWithoutComponents != 0.0 else gradient[var] if len(activeViolations['external']) == 0: return varKPlus, modded self.raiseADebug('Attempting to fix constraint violation by shortening gradient vector ...') # Try to find varKPlus by shorten the gradient vector self.raiseADebug('Trajectory "{}" hit constraints ...'.format(traj)) self.raiseADebug(' Attempting to shorten step length ...') foundVarsUpdate, varKPlus = self._bisectionForConstrainedInput(traj,varK, ak, gradient) if foundVarsUpdate: self.raiseADebug(' ... successfully found new point by shortening length.') return varKPlus, True self.raiseADebug('Attempting to fix constraint violation by rotating towards orthogonal ...') # Try to find varKPlus by rotate the gradient towards its orthogonal, since we consider the gradient as perpendicular # with respect to the constraints hyper-surface self.raiseADebug(' Attempting instead to rotate trajectory ...') innerLoopLimit = self.constraintHandlingPara['innerLoopLimit'] if innerLoopLimit < 0: self.raiseAnError(IOError, 'Limit for internal loop for constraint handling should be nonnegative') loopCounter = 0 foundPendVector = False # search for the perpendicular vector while not foundPendVector and loopCounter < innerLoopLimit: loopCounter += 1 # randomly choose the index of a variable to be the dependent? pivot depVarPos = randomUtils.randomIntegers(0,len(self.getOptVars())-1,self) # if that variable is multidimensional, pick a dimension -> this is not precisely equal probability of picking, but that should be okay. varSize = np.prod(self.variableShapes[var]) if varSize > 1: depVarIdx = randomUtils.randomIntegers(0,varSize-1,self) pendVector = {} npDot = 0 for varID, var in enumerate(self.getOptVars()): varSize = np.prod(self.variableShapes[var]) if varSize == 1: pendVector[var] = self.stochasticEngineForConstraintHandling.rvs() if varID != depVarPos else 0.0 npDot += pendVector[var]*gradient[var] else: for i in range(varSize): pendVector[var][i] = self.stochasticEngineForConstraintHandling.rvs() if (varID != depVarPos and depVarIdx != i) else 0.0 npDot += np.sum(pendVector[var]*gradient[var]) # TODO does this need to be in a separate loop or can it go with above? for varID, var in enumerate(self.getOptVars()): if varID == depVarPos: varSize = np.prod(self.variableShapes[var]) if varSize == 1: pendVector[var] = -npDot/gradient[var] else: pendVector[var][depVarIdx] = -npDot/gradient[var][depVarIdx] r = self.calculateMultivectorMagnitude([ gradient[var] for var in self.getOptVars()]) r /= self.calculateMultivectorMagnitude([pendVector[var] for var in self.getOptVars()]) for var in self.getOptVars(): pendVector[var] = copy.deepcopy(pendVector[var])*r varKPlus = {} index = 0 for var in self.getOptVars(): varSize = np.prod(self.variableShapes[var]) new = np.zeros(varSize) for i in range(varSize): if varSize == 1: new = copy.copy(varK[var]-gain[index]*pendVector[var]*1.0) else: new[i] = copy.copy(varK[var][i]-gain[index]*pendVector[var][i]*1.0) index += 1 varKPlus[var] = new foundPendVector, activeConstraints = self.checkConstraint(self.denormalizeData(varKPlus)) if not foundPendVector: foundPendVector, varKPlus = self._bisectionForConstrainedInput(traj,varK, gain, pendVector) gain = gain/2. if foundPendVector: lenPendVector = 0 for var in self.getOptVars(): lenPendVector += np.sum(pendVector[var]**2) lenPendVector = np.sqrt(lenPendVector) rotateDegreeUpperLimit = 2 while self.angleBetween(traj,gradient, pendVector) > rotateDegreeUpperLimit: sumVector = {} lenSumVector = 0 for var in self.getOptVars(): sumVector[var] = gradient[var] + pendVector[var] lenSumVector += np.sum(sumVector[var]**2) tempTempVarKPlus = {} index = 0 for var in self.getOptVars(): sumVector[var] = copy.deepcopy(sumVector[var]/np.sqrt(lenSumVector)*lenPendVector) varSize = np.prod(self.variableShapes[var]) new = np.zeros(varSize) for i in range(varSize): if varSize == 1: new = copy.copy(varK[var]-gain[index]*sumVector[var]*1.0) else: new[i] = copy.copy(varK[var][i]-gain[index]*sumVector[var][i]*1.0) index += 1 tempTempVarKPlus[var] = new satisfied, activeConstraints = self.checkConstraint(self.denormalizeData(tempTempVarKPlus)) if satisfied: varKPlus = copy.deepcopy(tempTempVarKPlus) pendVector = copy.deepcopy(sumVector) else: gradient = copy.deepcopy(sumVector) self.raiseADebug(' ... successfully found new point by rotating trajectory.') return varKPlus, True varKPlus = varK self.raiseADebug(' ... did not successfully find new point.') return varKPlus, False
def _readMoreXMLbase(self, xmlNode): """ Function to read the portion of the xml input that belongs to the base sampler only and initialize some stuff based on the inputs got The text is supposed to contain the info where and which variable to change. In case of a code the syntax is specified by the code interface itself @ In, xmlNode, xml.etree.ElementTree.Element, Xml element node1 @ Out, None """ for child in xmlNode: prefix = "" if child.tag == 'Distribution': for childChild in child: if childChild.tag == 'distribution': prefix = "<distribution>" tobesampled = childChild.text self.toBeSampled[prefix + child.attrib['name']] = tobesampled #if child.attrib['name'] != tobesampled:self.raiseAnError(IOError,"name of the <Distribution> node and <distribution> mismatches for node named "+ child.attrib['name']) elif child.tag == 'variable': foundDistOrFunc = False for childChild in child: if childChild.tag == 'distribution': if not foundDistOrFunc: foundDistOrFunc = True else: self.raiseAnError( IOError, 'A sampled variable cannot have both a distribution and a function!' ) tobesampled = childChild.text varData = {} varData['name'] = childChild.text if childChild.get('dim') == None: dim = 1 else: dim = childChild.attrib['dim'] varData['dim'] = int(dim) self.variables2distributionsMapping[ child.attrib['name']] = varData self.toBeSampled[prefix + child.attrib['name']] = tobesampled elif childChild.tag == 'function': if not foundDistOrFunc: foundDistOrFunc = True else: self.raiseAnError( IOError, 'A sampled variable cannot have both a distribution and a function!' ) tobesampled = childChild.text self.dependentSample[ prefix + child.attrib['name']] = tobesampled if not foundDistOrFunc: self.raiseAnError( IOError, 'Sampled variable', child.attrib['name'], 'has neither a <distribution> nor <function> node specified!' ) elif child.tag == "variablesTransformation": transformationDict = {} listIndex = None for childChild in child: if childChild.tag == "latentVariables": transformationDict[childChild.tag] = list( inp.strip() for inp in childChild.text.strip().split(',')) elif childChild.tag == "manifestVariables": transformationDict[childChild.tag] = list( inp.strip() for inp in childChild.text.strip().split(',')) elif childChild.tag == "manifestVariablesIndex": # the index provided by the input file starts from 1, but the index used by the code starts from 0. listIndex = list( int(inp.strip()) - 1 for inp in childChild.text.strip().split(',')) elif childChild.tag == "method": self.transformationMethod[ child.attrib['distribution']] = childChild.text if listIndex == None: self.raiseAWarning( 'Index is not provided for manifestVariables, default index will be used instead!' ) listIndex = range( len(transformationDict["manifestVariables"])) transformationDict["manifestVariablesIndex"] = listIndex self.variablesTransformationDict[ child.attrib['distribution']] = transformationDict elif child.tag == "constant": value = utils.partialEval(child.text) if value is None: self.raiseAnError( IOError, 'The body of "constant" XML block should be a number. Got: ' + child.text) try: self.constants[child.attrib['name']] = value except KeyError: self.raiseAnError( KeyError, child.tag + ' must have the attribute "name"!!!') elif child.tag == "restartTolerance": self.restartTolerance = float(child.text) if len(self.constants) > 0: # check if constant variables are also part of the sampled space. In case, error out if not set(self.toBeSampled.keys()).isdisjoint( self.constants.keys()): self.raiseAnError( IOError, "Some constant variables are also in the sampling space:" + ' '.join([ i if i in self.toBeSampled.keys() else "" for i in self.constants.keys() ])) if self.initSeed == None: self.initSeed = randomUtils.randomIntegers(0, 2**31, self) # Creation of the self.distributions2variablesMapping dictionary: {'distName': ({'variable_name1': dim1}, {'variable_name2': dim2})} for variable in self.variables2distributionsMapping.keys(): distName = self.variables2distributionsMapping[variable]['name'] dim = self.variables2distributionsMapping[variable]['dim'] listElement = {} listElement[variable] = dim if (distName in self.distributions2variablesMapping.keys()): self.distributions2variablesMapping[distName].append( listElement) else: self.distributions2variablesMapping[distName] = [listElement] # creation of the self.distributions2variablesIndexList dictionary:{'distName':[dim1,dim2,...,dimN]} self.distributions2variablesIndexList = {} for distName in self.distributions2variablesMapping.keys(): positionList = [] for var in self.distributions2variablesMapping[distName]: position = utils.first(var.values()) positionList.append(position) positionList = list(set(positionList)) positionList.sort() self.distributions2variablesIndexList[distName] = positionList for key in self.variables2distributionsMapping.keys(): distName = self.variables2distributionsMapping[key]['name'] dim = self.variables2distributionsMapping[key]['dim'] reducedDim = self.distributions2variablesIndexList[distName].index( dim) + 1 self.variables2distributionsMapping[key][ 'reducedDim'] = reducedDim # the dimension of variable in the transformed space self.variables2distributionsMapping[key]['totDim'] = max( self.distributions2variablesIndexList[distName] ) # We will reset the value if the node <variablesTransformation> exist in the raven input file if not self.variablesTransformationDict and self.variables2distributionsMapping[ key]['totDim'] > 1: if self.variables2distributionsMapping[key]['totDim'] != len( self.distributions2variablesIndexList[distName]): self.raiseAnError( IOError, 'The "dim" assigned to the variables insider Sampler are not correct! the "dim" should start from 1, and end with the full dimension of given distribution' ) #Checking the variables transformation if self.variablesTransformationDict: for dist, varsDict in self.variablesTransformationDict.items(): maxDim = len(varsDict['manifestVariables']) listLatentElement = varsDict['latentVariables'] if len(set(listLatentElement)) != len(listLatentElement): dups = set(var for var in listLatentElement if listLatentElement.count(var) > 1) self.raiseAnError( IOError, 'The following are duplicated variables listed in the latentVariables: ' + str(dups)) if len(set(varsDict['manifestVariables'])) != len( varsDict['manifestVariables']): dups = set(var for var in varsDict['manifestVariables'] if varsDict['manifestVariables'].count(var) > 1) self.raiseAnError( IOError, 'The following are duplicated variables listed in the manifestVariables: ' + str(dups)) if len(set(varsDict['manifestVariablesIndex'])) != len( varsDict['manifestVariablesIndex']): dups = set( var + 1 for var in varsDict['manifestVariablesIndex'] if varsDict['manifestVariablesIndex'].count(var) > 1) self.raiseAnError( IOError, 'The following are duplicated variables indices listed in the manifestVariablesIndex: ' + str(dups)) listElement = self.distributions2variablesMapping[dist] for var in listElement: self.variables2distributionsMapping[utils.first( var.keys() )]['totDim'] = maxDim #reset the totDim to reflect the totDim of original input space tempListElement = { k.strip(): v for x in listElement for ks, v in x.items() for k in list(ks.strip().split(',')) } listIndex = [] for var in listLatentElement: if var not in set(tempListElement.keys()): self.raiseAnError( IOError, 'The variable listed in latentVariables ' + var + ' is not listed in the given distribution: ' + dist) listIndex.append(tempListElement[var] - 1) if max(listIndex) > maxDim: self.raiseAnError( IOError, 'The maximum dim = ' + str(max(listIndex)) + ' defined for latent variables is exceeded the dimension of the problem ' + str(maxDim)) if len(set(listIndex)) != len(listIndex): dups = set(var + 1 for var in listIndex if listIndex.count(var) > 1) self.raiseAnError( IOError, 'Each of the following dimensions are assigned to multiple latent variables in Samplers: ' + str(dups)) # update the index for latentVariables according to the 'dim' assigned for given var defined in Sampler self.variablesTransformationDict[dist][ 'latentVariablesIndex'] = listIndex
def __init__(self, messageHandler, **kwargs): """ A constructor that will appropriately intialize a supervised learning object @ In, messageHandler: a MessageHandler object in charge of raising errors, and printing messages @ In, kwargs: an arbitrary dictionary of keywords and values """ # general infrastructure supervisedLearning.__init__(self, messageHandler, **kwargs) self.printTag = 'ARMA' self._dynamicHandling = True # This ROM is able to manage the time-series on its own. # training storage self.trainingData = { } # holds normalized ('norm') and original ('raw') training data, by target self.cdfParams = {} # dictionary of fitted CDF parameters, by target self.armaResult = { } # dictionary of assorted useful arma information, by target self.correlations = [] # list of correlated variables self.fourierResults = {} # dictionary of Fourier results, by target # training parameters self.fourierParams = { } # dict of Fourier training params, by target (if requested, otherwise not present) self.Pmax = kwargs.get('Pmax', 3) # bounds for autoregressive lag self.Pmin = kwargs.get('Pmin', 0) self.Qmax = kwargs.get('Qmax', 3) # bounds for moving average lag self.Qmin = kwargs.get('Qmin', 0) self.segments = kwargs.get('segments', 1) # data manipulation self.reseedCopies = kwargs.get('reseedCopies', True) self.outTruncation = { 'positive': set(), 'negative': set() } # store truncation requests self.pivotParameterID = kwargs['pivotParameter'] self.pivotParameterValues = None # In here we store the values of the pivot parameter (e.g. Time) self.seed = kwargs.get('seed', None) self.zeroFilterTarget = None # target for whom zeros should be filtered out self.zeroFilterTol = None # tolerance for zerofiltering to be considered zero, set below self.zeroFilterMask = None # mask of places where zftarget is zero, or None if unused # check zeroFilterTarget is one of the targets given if self.zeroFilterTarget is not None and self.zeroFilterTarget not in self.target: self.raiseAnError( 'Requested ZeroFilter on "{}" but this target was not found among the ROM targets!' .format(self.zeroFilterTarget)) # get seed if provided ## FIXME only applies to VARMA sampling right now, since it has to be sampled through Numpy! ## see note under "check for correlation" below. if self.seed is None: self.seed = randomUtils.randomIntegers(0, 4294967295, self) else: self.seed = int(self.seed) self.normEngine = Distributions.returnInstance('Normal', self) self.normEngine.mean = 0.0 self.normEngine.sigma = 1.0 self.normEngine.upperBoundUsed = False self.normEngine.lowerBoundUsed = False self.normEngine.initializeDistribution() # check for correlation correlated = kwargs.get('correlate', None) if correlated is not None: # FIXME set the numpy seed ## we have to do this because VARMA.simulate does not accept a random number generator, ## but instead uses numpy directly. As a result, for now, we have to seed numpy. ## Because we use our RNG to set the seed, though, it should follow the global seed still. self.raiseADebug('Setting Numpy seed to', self.seed) np.random.seed(self.seed) # store correlated targets corVars = [x.strip() for x in correlated.split(',')] for var in corVars: if var not in self.target: self.raiseAnError( IOError, 'Variable "{}" requested in "correlate" but not found among the targets!' .format(var)) # NOTE: someday, this could be expanded to include multiple sets of correlated variables. self.correlations = corVars # check if the pivotParameter is among the targetValues if self.pivotParameterID not in self.target: self.raiseAnError( IOError, "The pivotParameter " + self.pivotParameterID + " must be part of the Target space!") # can only handle one scaling input currently if len(self.features) != 1: self.raiseAnError( IOError, "The ARMA can only currently handle a single feature, which scales the outputs!" ) # we aren't set up to optimize p and q anymore, so if they're different error out if self.Pmin != self.Pmax or self.Qmax != self.Qmin: self.raiseAnError( IOError, 'ARMA temporarily has optimizing P and Q disabled; please set Pmax and Pmin to ' + 'the same value, and similarly for Q. If optimizing is desired, please contact us so we can expedite ' + 'the fix.') # read off of paramInput for more detailed inputs # TODO someday everything should read off this! paramInput = kwargs['paramInput'] for child in paramInput.subparts: # read truncation requests (really value limits, not truncation) if child.getName() == 'outTruncation': # did the user request positive or negative? domain = child.parameterValues['domain'] # if a recognized request, store it for later if domain in self.outTruncation: self.outTruncation[ domain] = self.outTruncation[domain] | set(child.value) # if unrecognized, error out else: self.raiseAnError(IOError,'Unrecognized "domain" for "outTruncation"! Was expecting "positive" '+\ 'or "negative" but got "{}"'.format(domain)) # additional info for zerofilter elif child.getName() == 'ZeroFilter': self.zeroFilterTarget = child.value if self.zeroFilterTarget not in self.target: self.raiseAnError( IOError, 'Requested zero filtering for "{}" but not found among targets!' .format(self.zeroFilterTarget)) self.zeroFilterTol = child.parameterValues.get('tol', 1e-16) # read SPECIFIC parameters for Fourier detrending elif child.getName() == 'SpecificFourier': # clear old information periods = None orders = None # what variables share this Fourier? variables = child.parameterValues['variables'] # check for variables that aren't targets missing = set(variables) - set(self.target) if len(missing): self.raiseAnError( IOError, 'Requested SpecificFourier for variables {} but not found among targets!' .format(missing)) # record requested Fourier periods, orders for cchild in child.subparts: if cchild.getName() == 'periods': periods = cchild.value elif cchild.getName() == 'orders': orders = cchild.value # sanity check if len(periods) != len(orders): self.raiseADebug(IOError,'"periods" and "orders" need to have the same number of entries' +\ 'for variable group "{}"!'.format(variables)) # set these params for each variable for v in variables: self.raiseADebug( 'recording specific Fourier settings for "{}"'.format( v)) if v in self.fourierParams: self.raiseAWarning( 'Fourier params for "{}" were specified multiple times! Using first values ...' .format(v)) continue self.fourierParams[v] = { 'periods': periods, 'orders': dict(zip(periods, orders)) } # read GENERAL parameters for Fourier detrending ## these apply to everyone without SpecificFourier nodes ## use basePeriods to check if Fourier node present basePeriods = paramInput.findFirst('Fourier') if basePeriods is not None: # read periods basePeriods = basePeriods.value if len(set(basePeriods)) != len(basePeriods): self.raiseAnError( IOError, 'Some <Fourier> periods have been listed multiple times!') # read orders baseOrders = self.initOptionDict.get('FourierOrder', [1] * len(basePeriods)) if len(basePeriods) != len(baseOrders): self.raiseAnError( IOError, '{} Fourier periods were requested, but {} Fourier order expansions were given!' .format(len(basePeriods), len(baseOrders))) # set to any variable that doesn't already have a specific one for v in set(self.target) - set(self.fourierParams.keys()): self.raiseADebug( 'setting general Fourier settings for "{}"'.format(v)) self.fourierParams[v] = { 'periods': basePeriods, 'orders': dict(zip(basePeriods, baseOrders)) }
def localInputAndChecks(self, xmlNode): """ Local method for additional reading. @ In, xmlNode, xml.etree.ElementTree.Element, Xml element node @ Out, None """ GradientBasedOptimizer.localInputAndChecks(self, xmlNode) self.currentDirection = None numValues = self._numberOfSamples() # set the initial step size ## use the hyperdiagonal of a unit hypercube with a side length equal to the user's provided initialStepSize * 1.0 stepPercent = float(self.paramDict.get('initialStepSize', 0.05)) self.paramDict['initialStepSize'] = mathUtils.hyperdiagonal(np.ones(numValues)*stepPercent) self.raiseADebug('Based on initial step size factor of "{:1.5e}", initial step size is "{:1.5e}"' .format(stepPercent, self.paramDict['initialStepSize'])) # set the perturbation distance ## if not given, default to 10% of the step size self.paramDict['pertDist'] = float(self.paramDict.get('perturbationDistance',0.01)) self.raiseADebug('Perturbation distance is "{:1.5e}" percent of the step size' .format(self.paramDict['pertDist'])) self.constraintHandlingPara['innerBisectionThreshold'] = float(self.paramDict.get('innerBisectionThreshold', 1e-2)) if not 0 < self.constraintHandlingPara['innerBisectionThreshold'] < 1: self.raiseAnError(IOError,'innerBisectionThreshold must be between 0 and 1; got',self.constraintHandlingPara['innerBisectionThreshold']) self.constraintHandlingPara['innerLoopLimit'] = float(self.paramDict.get('innerLoopLimit', 1000)) self.gradDict['pertNeeded'] = self.gradDict['numIterForAve'] * (self.paramDict['pertSingleGrad']+1) # determine the number of indpendent variables (scalar and vectors included) stochDist = self.paramDict.get('stochasticDistribution', 'Hypersphere') if stochDist == 'Bernoulli': self.stochasticDistribution = Distributions.returnInstance('Bernoulli',self) self.stochasticDistribution.p = 0.5 self.stochasticDistribution.initializeDistribution() # Initialize bernoulli distribution for random perturbation. Add artificial noise to avoid that specular loss functions get false positive convergence # FIXME there has to be a better way to get two random numbers self.stochasticEngine = lambda: [(0.5+randomUtils.random()*(1.+randomUtils.random()/1000.*randomUtils.randomIntegers(-1, 1, self))) if self.stochasticDistribution.rvs() == 1 else -1.*(0.5+randomUtils.random()*(1.+randomUtils.random()/1000.*randomUtils.randomIntegers(-1, 1, self))) for _ in range(numValues)] elif stochDist == 'Hypersphere': # TODO assure you can't get a "0" along any dimension! Need to be > 1e-15. Right now it's just highly unlikely. self.stochasticEngine = lambda: randomUtils.randPointsOnHypersphere(numValues) if numValues > 1 else [randomUtils.randPointsOnHypersphere(numValues)] else: self.raiseAnError(IOError, self.paramDict['stochasticEngine']+'is currently not supported for SPSA')
def __init__(self, messageHandler, **kwargs): """ A constructor that will appropriately intialize a supervised learning object @ In, messageHandler: a MessageHandler object in charge of raising errors, and printing messages @ In, kwargs: an arbitrary dictionary of keywords and values """ supervisedLearning.__init__(self, messageHandler, **kwargs) self.printTag = 'ARMA' self._dynamicHandling = True # This ROM is able to manage the time-series on its own. self.trainingData = { } # holds normalized ('norm') and original ('raw') training data, by target self.cdfParams = {} # dictionary of fitted CDF parameters, by target self.fourierResults = {} # dictionary of Fourier results, by target self.armaResult = { } # dictionary of assorted useful arma information, by target self.correlations = [] # list of correlated variables self.Pmax = kwargs.get('Pmax', 3) # bounds for autoregressive lag self.Pmin = kwargs.get('Pmin', 0) self.Qmax = kwargs.get('Qmax', 3) # bounds for moving average lag self.Qmin = kwargs.get('Qmin', 0) self.reseedCopies = kwargs.get('reseedCopies', True) self.outTruncation = kwargs.get( 'outTruncation', None ) # Additional parameters to allow user to specify the time series to be all positive or all negative self.pivotParameterID = kwargs['pivotParameter'] self.pivotParameterValues = None # In here we store the values of the pivot parameter (e.g. Time) self.seed = kwargs.get('seed', None) # get seed if provided ## FIXME only applies to VARMA sampling right now, since it has to be sampled through Numpy! ## see note under "check for correlation" below. if self.seed is None: self.seed = randomUtils.randomIntegers(0, 4294967295, self) else: self.seed = int(self.seed) self.normEngine = Distributions.returnInstance('Normal', self) self.normEngine.mean = 0.0 self.normEngine.sigma = 1.0 self.normEngine.upperBoundUsed = False self.normEngine.lowerBoundUsed = False self.normEngine.initializeDistribution() # check for correlation correlated = kwargs.get('correlate', None) if correlated is not None: # FIXME set the numpy seed ## we have to do this because VARMA.simulate does not accept a random number generator, ## but instead uses numpy directly. As a result, for now, we have to seed numpy. ## Because we use our RNG to set the seed, though, it should follow the global seed still. self.raiseADebug('Setting Numpy seed to', self.seed) np.random.seed(self.seed) # store correlated targets corVars = [x.strip() for x in correlated.split(',')] for var in corVars: if var not in self.target: self.raiseAnError( IOError, 'Variable "{}" requested in "correlate" but not found among the targets!' .format(var)) # NOTE: someday, this could be expanded to include multiple sets of correlated variables. self.correlations = corVars # check if the pivotParameter is among the targetValues if self.pivotParameterID not in self.target: self.raiseAnError( IOError, "The pivotParameter " + self.pivotParameterID + " must be part of the Target space!") # can only handle one scaling input currently if len(self.features) != 1: self.raiseAnError( IOError, "The ARMA can only currently handle a single feature, which scales the outputs!" ) # we aren't set up to optimize p and q anymore, so if they're different error out if self.Pmin != self.Pmax or self.Qmax != self.Qmin: self.raiseAnError( IOError, 'ARMA temporarily has optimizing P and Q disabled; please set Pmax and Pmin to ' + 'the same value, and similarly for Q. If optimizing is desired, please contact us so we can expedite ' + 'the fix.') # Initialize parameters for Fourier detrending if 'Fourier' not in self.initOptionDict.keys(): self.hasFourierSeries = False else: self.hasFourierSeries = True self.fourierPara = {} basePeriods = self.initOptionDict['Fourier'] if isinstance(basePeriods, basestring): basePeriods = [float(s) for s in basePeriods.split(',')] else: basePeriods = [float(basePeriods)] self.fourierPara['basePeriod'] = basePeriods if len(set(self.fourierPara['basePeriod'])) != len( self.fourierPara['basePeriod']): self.raiseAnError( IOError, 'The same Fourier value was listed multiple times!') self.fourierPara['FourierOrder'] = {} if 'FourierOrder' not in self.initOptionDict.keys(): self.fourierPara['basePeriod'] = dict( (basePeriod, 4) for basePeriod in self.fourierPara['basePeriod']) else: orders = self.initOptionDict['FourierOrder'] if isinstance(orders, str): orders = [int(x) for x in orders.split(',')] else: orders = [orders] if len(self.fourierPara['basePeriod']) != len(orders): self.raiseAnError( ValueError, 'Number of FourierOrder entries should be "{}"'.format( len(self.fourierPara['basePeriod']))) self.fourierPara['FourierOrder'] = dict( (basePeriod, orders[i]) for i, basePeriod in enumerate( self.fourierPara['basePeriod']))