Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SimulatedAnnealing Discrete Variables #2312

Merged
merged 26 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a8d8437
moved tests for SimulatedAnnealing to continuos distribution, since a…
alfoa Apr 25, 2024
e22c854
fixed dist
alfoa Apr 25, 2024
2f3961e
updated test names for continous
alfoa Apr 30, 2024
a794170
discrete opt with simulated annealing
alfoa Apr 30, 2024
463c60a
moving
alfoa May 1, 2024
70ca888
Merge branch 'devel' into alfoa/simulatedAnnealingDiscreteVariables
alfoa May 2, 2024
c2dedce
rand of var
alfoa May 3, 2024
0e66168
Merge branch 'devel' into alfoa/simulatedAnnealingDiscreteVariables
alfoa May 3, 2024
ca6beba
changing delta as function of variable bounds
alfoa May 6, 2024
584b9c3
added discrete
alfoa May 7, 2024
b367515
discrete?
alfoa May 7, 2024
f081b8c
fixed tests
alfoa May 7, 2024
755daed
updated xsd schema
alfoa May 7, 2024
cc72e4e
Apply suggestions from code review
alfoa May 7, 2024
8a79cb7
spelling
alfoa May 7, 2024
5229718
Merge branch 'alfoa/simulatedAnnealingDiscreteVariables' of github.co…
alfoa May 7, 2024
f9d0803
isFloats in categorical
alfoa May 8, 2024
57dd190
isFloats is now checked for False entitites
alfoa May 8, 2024
91b4bab
regolded serpent test since it fails in optional machine
alfoa May 8, 2024
28624b8
added denromalization
alfoa May 14, 2024
30b880e
Update ravenframework/Distributions.py
alfoa May 16, 2024
de4209d
Merge branch 'devel' into alfoa/simulatedAnnealingDiscreteVariables
alfoa May 16, 2024
eeee85d
simulated annealing test
alfoa May 16, 2024
debee1e
use utils.isClose instead of np.isClose
alfoa May 16, 2024
de67311
Apply suggestions from code review
alfoa May 16, 2024
44192fa
Update tests/framework/Optimizers/SimulatedAnnealing/discrete/Functio…
alfoa May 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions developer_tools/XSDSchemas/Optimizers.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,14 @@
<xsd:complexType name="boltzmanCoolingType">
<xsd:all>
<xsd:element name="d" type="xsd:float"/>
<xsd:element name="learningRate" type="xsd:float"/>
</xsd:all>
</xsd:complexType>

<xsd:complexType name="cauchyCoolingType">
<xsd:all>
<xsd:element name="d" type="xsd:float"/>
<xsd:element name="learningRate" type="xsd:float"/>
</xsd:all>
</xsd:complexType>

Expand Down
1 change: 1 addition & 0 deletions doc/user_manual/ProbabilityDistributions.tex
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,7 @@ \subsubsection{1-Dimensional Discrete Distributions.}
\vspace{-5mm}
\subnodeIntro
\begin{itemize}
\item \xmlNode{rtol}, \xmlDesc{float, optional parameter}, relative tolerance used to identify close state in case of float or integer states, \default{1E-6}.
\item \xmlNode{state}, \xmlDesc{float, required parameter}, probability for outcome 1
\begin{itemize}
\item \xmlAttr{outcome}, \xmlDesc{float, required parameter}, outcome value.
Expand Down
6 changes: 5 additions & 1 deletion doc/user_manual/generated/optimizer.tex
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ \subsection{SimulatedAnnealing}
T^{k} = \frac{T^0}{log(k + d)}$$ In case of \xmlString{cauchy} is provided,
The cooling process will be governed by: $$ T^{k} = \frac{T^0}{k + d}$$In case of
\xmlString{veryfast} is provided, The cooling process will be governed by: $$ T^{k} = T^0 *
\exp(-ck^{1/D}),$$ where $D$ is the dimentionality of the problem (i.e.,
\exp(-ck^{1/D}),$$ where $D$ is the dimensionality of the problem (i.e.,
number of optimized variables), $k$ is the number of the current iteration
$T^{0} = \max{(0.01,1-\frac{k}{\xmlNode{limit}})}$ is the initial temperature, and $T^{k}$ is
the current temperature according to the specified cooling schedule.
Expand Down Expand Up @@ -758,6 +758,8 @@ \subsection{SimulatedAnnealing}
\begin{itemize}
\item \xmlNode{d}: \xmlDesc{float},
bias, \default{1.0}
\item \xmlNode{learningRate}: \xmlDesc{float},
learning rate: Scale constant for adjusting guesses, \default{0.9}
\end{itemize}

\item \xmlNode{boltzmann}: \xmlDesc{string},
Expand All @@ -767,6 +769,8 @@ \subsection{SimulatedAnnealing}
\begin{itemize}
\item \xmlNode{d}: \xmlDesc{float},
bias, \default{1.0}
\item \xmlNode{learningRate}: \xmlDesc{float},
learning rate: Scale constant for adjusting guesses, \default{0.9}
\end{itemize}
\end{itemize}

Expand Down
110 changes: 68 additions & 42 deletions ravenframework/Distributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import copy
import math as math
import bisect
from collections import namedtuple

from .EntityFactoryBase import EntityFactory
from .BaseClasses import BaseEntity, InputDataUser
Expand Down Expand Up @@ -76,6 +77,12 @@ def factorial(x):
'UniformDiscrete' : 'UniformDiscreteDistribution'
}


# Declaring namedtuple(DistributionTypes)
DistributionTypes = namedtuple('DistributionType', ['discrete', 'continuous'])
# Adding values
distType = DistributionTypes('Discrete', 'Continuous')

class DistributionsCollection(InputData.ParameterInput):
"""
Class for reading in a collection of distributions
Expand Down Expand Up @@ -393,7 +400,7 @@ def __init__(self):
"""
super().__init__()
self.dimensionality = 1
self.distType = 'Continuous'
self.distType = distType.continuous

def cdf(self,x):
"""
Expand Down Expand Up @@ -798,7 +805,7 @@ def __init__(self, low=0.0, alpha=0.0, beta=1.0):
self.alpha = alpha
self.beta = beta
self.type = 'Gamma'
self.distType = 'Continuous'
self.distType = distType.continuous
self.hasInfiniteBound = True
self.compatibleQuadrature.append('Laguerre')
self.compatibleQuadrature.append('CDF')
Expand Down Expand Up @@ -955,7 +962,7 @@ def __init__(self):
self.alpha = 0.0
self.beta = 0.0
self.type = 'Beta'
self.distType = 'Continuous'
self.distType = distType.continuous
self.hasInfiniteBound = True
self.compatibleQuadrature.append('Jacobi')
self.compatibleQuadrature.append('CDF')
Expand Down Expand Up @@ -1129,7 +1136,7 @@ def __init__(self):
self.min = None # domain lower boundary
self.max = None # domain upper boundary
self.type = 'Triangular'
self.distType = 'Continuous'
self.distType = distType.continuous
self.compatibleQuadrature.append('CDF')
self.preferredQuadrature = 'CDF'
self.preferredPolynomials = 'CDF'
Expand Down Expand Up @@ -1244,7 +1251,7 @@ def __init__(self):
self.mu = 0.0
self.type = 'Poisson'
self.hasInfiniteBound = True
self.distType = 'Discrete'
self.distType = distType.discrete
self.compatibleQuadrature.append('CDF')
self.preferredQuadrature = 'CDF'
self.preferredPolynomials = 'CDF'
Expand Down Expand Up @@ -1340,7 +1347,7 @@ def __init__(self):
self.p = 0.0
self.type = 'Binomial'
self.hasInfiniteBound = True
self.distType = 'Discrete'
self.distType = distType.discrete
self.compatibleQuadrature.append('CDF')
self.preferredQuadrature = 'CDF'
self.preferredPolynomials = 'CDF'
Expand Down Expand Up @@ -1439,7 +1446,7 @@ def __init__(self):
super().__init__()
self.p = 0.0
self.type = 'Bernoulli'
self.distType = 'Discrete'
self.distType = distType.discrete
self.lowerBound = 0.0
self.upperBound = 1.0
self.compatibleQuadrature.append('CDF')
Expand Down Expand Up @@ -1532,7 +1539,7 @@ def __init__(self):
super().__init__()
self.p = 0.0
self.type = 'Geometric'
self.distType = 'Discrete'
self.distType = distType.discrete
self.lowerBound = 0.0
self.upperBound = 1.0
self.compatibleQuadrature.append('CDF')
Expand Down Expand Up @@ -1614,6 +1621,11 @@ def getInputSpecification(cls):
StatePartInput = InputData.parameterInputFactory("state", contentType=InputTypes.FloatType)
StatePartInput.addParam("outcome", InputTypes.FloatOrStringType, True)
inputSpecification.addSub(StatePartInput, InputData.Quantity.one_to_infinity)
inputSpecification.addSub(InputData.parameterInputFactory("rtol",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please update user manual to reflect it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

contentType=InputTypes.FloatType,
descr=r"""Relative tolerance used to identify close state in case of"""
r""" float/int states. Not used for string states!""", default=1e-6))


## Because we do not inherit from the base class, we need to manually
## add the name back in.
Expand All @@ -1632,8 +1644,9 @@ def __init__(self):
self.values = set()
self.type = 'Categorical'
self.dimensionality = 1
self.distType = 'Discrete'
self.isFloat = False
self.distType = distType.discrete
self.isFloat = True
self.rtol = 1e-6

def _handleInput(self, paramInput):
"""
Expand All @@ -1642,22 +1655,23 @@ def _handleInput(self, paramInput):
@ Out, None
"""
super()._handleInput(paramInput)
isFloats = []
for child in paramInput.subparts:
if child.getName() == "state":
outcome = child.parameterValues["outcome"]
value = child.value
self.mapping[outcome] = value
try:
float(outcome)
self.isFloat = True
except:
self.isFloat = False
isFloats.append(utils.floatConversion(outcome) is not None)
if outcome in self.values:
self.raiseAnError(IOError,'Categorical distribution has identical outcomes')
else:
self.values.add(float(outcome) if self.isFloat else outcome)
self.values.add(float(outcome) if isFloats[-1] else outcome)
else:
self.raiseAnError(IOError,'Invalid xml node for Categorical distribution; only "state" is allowed')
if False in isFloats:
self.isFloat = False
else:
self.rtol = paramInput.findNodesAndExtractValues(['rtol'])[0]['rtol']
self.initializeDistribution()
self.upperBoundUsed = True
self.lowerBoundUsed = True
Expand Down Expand Up @@ -1690,10 +1704,13 @@ def initializeFromDict(self, inputDict):
@ In, inputDict, dict, dictionary containing the np.arrays for xAxis and pAxis
@ Out, None
"""
isFloats = []
for idx, val in enumerate(inputDict['outcome']):
self.mapping[val] = inputDict['state'][idx]
self.values.add(val)

isFloats.append(utils.floatConversion(val) is not None)
if False in isFloats:
self.isFloat = False
self.checkDistParams()

def initializeDistribution(self):
Expand Down Expand Up @@ -1721,9 +1738,9 @@ def checkDistParams(self):
self.raiseAnError(IOError,'Categorical distribution cannot be initialized with probabilities greater than 1')

localSum = sum(self.mapping.values())
if not mathUtils.compareFloats(localSum,1.0):
self.raiseAnError('Categorical distribution cannot be initialized: sum of probabilities is ',
repr(localSum), ', not 1.0!', 'Please re-normalize it to 1!')
if not mathUtils.compareFloats(localSum,1., self.rtol):
self.raiseAnError(f'Categorical distribution cannot be initialized: sum of probabilities is {localSum},'
' not 1.0! Please re-normalize it to 1!')

# Probability values normalization
for key in self.mapping.keys():
Expand All @@ -1740,10 +1757,19 @@ def pdf(self,x):
else:
if self.isFloat:
vals = sorted(list(self.values))
idx = bisect.bisect(vals, x)
pdfValue = self.mapping[list(vals)[idx]]
idx = [idx for idx in range(len(vals)) if utils.isClose(vals[idx], x, relTolerance=self.rtol)]
if not len(idx):
self.raiseAnError(IOError,f'{self.type} distribution cannot compute pdf for {x} since the closest '
f'state {list(vals)[bisect.bisect(vals, x)]} is outside the acceptance interval given by the provided relative tolerance {self.rtol}!')
idx = idx[0]
val = list(vals)[idx]
pdfValue = self.mapping[val]
if not utils.isClose(val, x, relTolerance=self.rtol):
self.raiseAnError(IOError,f'{self.type} distribution cannot compute pdf for {x} since the closest '
f'state {val} is outside the acceptance interval given by the provided relative tolerance {self.rtol}!')
else:
self.raiseAnError(IOError,'Categorical distribution cannot calculate pdf for ' + str(x))
self.raiseAnError(IOError,f'{self.type} distribution cannot compute pdf for {x} since the states are not floats/integers and the'
f'value {x} is not present in the list of states!!')
return pdfValue

def cdf(self,x):
Expand All @@ -1755,6 +1781,7 @@ def cdf(self,x):
sortedMapping = sorted(self.mapping.items(), key=operator.itemgetter(0))
if x == sortedMapping[-1][0]:
return 1.0

if x in self.values:
cumulative=0.0
for element in sortedMapping:
Expand All @@ -1764,21 +1791,21 @@ def cdf(self,x):
else:
if self.isFloat:
cumulative=0.0
for element in sortedMapping:
cumulative += element[1]
if x >= element[0]:
for idx in range(len(sortedMapping)-1):
cumulative += sortedMapping[idx][1]
if x >= sortedMapping[idx][0] and x <= sortedMapping[idx+1][0]:
return cumulative
# if we reach this point we must error out
self.raiseAnError(IOError,'Categorical distribution cannot calculate cdf for ' + str(x))
self.raiseAnError(IOError,f'{self.type} distribution cannot calculate cdf for ' + str(x))

def ppf(self,x):
"""
Function that calculates the inverse of the cdf given 0 =< x =< 1
@ In, x, float, value to get the ppf at
@ Out, element[0], float/string, requested inverse cdf
"""
if x > 1.0 or x < 0:
self.raiseAnError(IOError,'Categorical distribution cannot calculate ppf for', str(x), '! Valid value should within [0,1]!')
if x > 1. or x < 0.:
self.raiseAnError(IOError,f'{self.type} distribution cannot calculate ppf for', str(x), '! Valid value should within [0,1]!')
sortedMapping = sorted(self.mapping.items(), key=operator.itemgetter(0))
if x == 1.0:
return float(sortedMapping[-1][0]) if self.isFloat else sortedMapping[-1][0]
Expand Down Expand Up @@ -1844,8 +1871,9 @@ def __init__(self):
super().__init__()
self.type = 'UniformDiscrete'
self.dimensionality = 1
self.distType = 'Discrete'
self.distType = distType.discrete
self.memory = True
self.nPoints = None

def _handleInput(self, paramInput):
"""
Expand All @@ -1861,16 +1889,14 @@ def _handleInput(self, paramInput):
self.raiseAnError(IOError,'upperBound value needed for UniformDiscrete distribution')

strategy = paramInput.findFirst('strategy')
if strategy != None:
if strategy is not None:
self.strategy = strategy.value
else:
self.raiseAnError(IOError,'strategy specification needed for UniformDiscrete distribution')

nPoints = paramInput.findFirst('nPoints')
if nPoints != None:
if nPoints is not None:
self.nPoints = nPoints.value
else:
self.nPoints = None

self.initializeDistribution()

Expand Down Expand Up @@ -2036,7 +2062,7 @@ def __init__(self):
"""
super().__init__()
self.dimensionality = 1
self.distType = 'Discrete'
self.distType = distType.discrete
self.type = 'MarkovCategorical'
self.steadyStatePb = None # variable containing the steady state probabilities of the Markov Model
self.transition = None # transition matrix of a continuous time Markov Model
Expand Down Expand Up @@ -2163,7 +2189,7 @@ def __init__(self):
self.location = 0.0
self.scale = 1.0
self.type = 'Logistic'
self.distType = 'Continuous'
self.distType = distType.continuous
self.hasInfiniteBound = True
self.compatibleQuadrature.append('CDF')
self.preferredQuadrature = 'CDF'
Expand Down Expand Up @@ -2273,7 +2299,7 @@ def __init__(self):
self.location = 0.0
self.scale = 1.0
self.type = 'Laplace'
self.distType = 'Continuous'
self.distType = distType.continuous
self.hasInfiniteBound = True
self.compatibleQuadrature.append('CDF')
self.preferredQuadrature = 'CDF'
Expand Down Expand Up @@ -2376,7 +2402,7 @@ def __init__(self):
self.lambdaVar = 1.0
self.low = 0.0
self.type = 'Exponential'
self.distType = 'Continuous'
self.distType = distType.continuous
self.hasInfiniteBound = True
self.compatibleQuadrature.append('CDF')
self.preferredQuadrature = 'CDF'
Expand Down Expand Up @@ -2516,7 +2542,7 @@ def __init__(self):
self.sigma = 1.0
self.low = 0.0
self.type = 'LogNormal'
self.distType = 'Continuous'
self.distType = distType.continuous
self.hasInfiniteBound = True
self.compatibleQuadrature.append('CDF')
self.preferredQuadrature = 'CDF'
Expand Down Expand Up @@ -2632,7 +2658,7 @@ def __init__(self):
self.lambdaVar = 1.0
self.k = 1.0
self.type = 'Weibull'
self.distType = 'Continuous'
self.distType = distType.continuous
self.low = 0.0
self.hasInfiniteBound = True
self.compatibleQuadrature.append('CDF')
Expand Down Expand Up @@ -2754,7 +2780,7 @@ def __init__(self):
self.functionID = None
self.variableID = None
self.dimensionality = 1
self.distType = 'Continuous'
self.distType = distType.continuous
# Scipy.interpolate.UnivariateSpline is used
self.k = 4 # Degree of the smoothing spline, Must be <=5
self.s = 0 # Positive smoothing factor used to choose the number of knots
Expand Down Expand Up @@ -3556,7 +3582,7 @@ def __init__(self):
"""
super().__init__()
self.type = 'MultivariateNormal'
self.distType = 'Continuous'
self.distType = distType.continuous
self.mu = None
self.covariance = None
self.covarianceType = 'abs' # abs: absolute covariance, rel: relative covariance matrix
Expand Down
Loading