STK组件:探索函数(Exploring Functions)
翻译:原文地址:http://help.agi.com/AGIComponents/html/ExploringFunctions.htm
在许多情况下,确定给定函数何时跨过阈值非常有用。当感兴趣的阈值为0.0时,这被称为求函数的根,任何阈值交叉问题都可以通过从方程的两边减去阈值来重新定义为求根问题。如果函数的数学公式可用,通常可以直接求解根。然而,如果不是这样的话,就必须用数字求根;换句话说,我们在独立变量的离散值处对函数进行采样,并检查采样值的趋势,以便将根的位置缩小到所需的精度水平。
函数探索器
DoubleFunctionExplorer
、DurationFunctionExplorer
和JulianDateFunctionExplorer
类分别用于对double
、Duration
和JulianDate
函数进行数值探索。除了找到阈值交叉点之外,这些函数探索器还报告了局部极小值和极大值,它们是函数具有零导数的位置,或者非正式地说,是函数改变方向的位置。JulianDateFunctionExplorer
在STK组件内部用于计算过境。
函数探索器的一些主要功能包括:
- 同时探索多个函数。
- 确定函数的局部极值。
- 确定阈值的交叉点。每个函数可以指定多个阈值。
- 函数的数学公式不是必需的。
- 函数的导数不需要可用。
- 完全控制函数的采样方式。
- 通过探索极值,即使没有样本落在阈值的另一侧,也可以找到阈值交叉点。
- 即使对于具有平点和不连续性的函数,只要该函数可以在探索区间内进行求值,也可以预测其行为。
以下是使用DoubleFunctionExplorer
查找函数跨越特定阈值的值的基本示例:
var explorer = new DoubleFunctionExplorer
{
FindAllCrossingsPrecisely = true
};
explorer.Functions.Add(x => 2 * x + 5, 10.0);
explorer.SampleSuggestionCallback = (start, stop, lastSample) => lastSample + 1.0;
var thresholdCrossings = new List<double>();
explorer.ThresholdCrossingFound += (sender, e) => thresholdCrossings.Add(e.Finding.CrossingVariable);
explorer.Explore(0.0, 10.0);
// thresholdCrossings列表包含函数跨越阈值时的X值。
首先,我们创建一个函数探索器,并通过设置FindAllCrossingsPrecision
属性指示它应该精确地查找所有阈值交叉点。这将在下一节中进行更详细的讨论。接下来,我们添加我们感兴趣的函数,f(x)=2*x+5,并指出我们感兴趣的阈值为10.0。在这里,函数是用lambda表达式表示的,但是可以使用任何接受double
并返回double
的函数。该函数作为委托提供。
函数探索器通过对函数值进行离散采样来探索函数。因此,我们提供了一个SampleSuggestionCallback
,用于确定函数采样的变量值。回调给出了正在探索的间隔的开始和结束以及上一个变量值,并期望返回下一个要采样的变量值。同样,我们使用lambda表达式指定回调函数,但是可以使用任何兼容的函数。我们将在后面的章节中更深入地讨论采样。
接下来,我们订阅每次发现阈值交叉时由函数探索器引发的事件。事件处理程序(我们表示为匿名委托)只是将阈值交叉的变量值添加到列表中。
最后,我们调用Explore
方法来探索给定间隔内的函数。每当函数探索器发现一些有趣的东西时,它都会引发适当的事件,并调用附加的事件处理程序(如果有的话)。在本例中,在Explore
方法返回后,thresholdCrossings
集合包含单个变量值2.5。
发现和事件
所有三个函数探索器都会根据以下发现引发事件:
- FunctionSampled:每次对函数进行采样时引发。
- ThresholdCrossingIndicated:两个连续的样本位于阈值的两侧,表示函数在两个点之间的间隔内穿过阈值。引发此事件时,尚未确定交叉口的准确位置。
-
ThresholdCrossingFound:已经找到了阈值交叉口的精确位置。只有在
ThresholdCrossingIndicated
事件期间调用FindReciseCrossing
方法,或者FindAllCrossingsPrecility
为true
时,才会精确地找到阈值交叉点。 - LocalExtremumIndicated:三个连续的样本形成两个斜率相反的线段,表明函数在三个点的第一个点和最后一个点之间具有局部极值(最小值或最大值)。当这个事件发生时,还没有确定局部极值的精确位置。
-
LocalExtremumFound:已找到极值(最小值或最大值)的精确位置。 只有在
LocalExtremumIndicated
事件期间调用FindPreciseExtremum
方法或者FindAllExtremaPrecisely
为true
时,才能找到极值。 如果ExploreExtremaToFindCrossings
为true
,并且极值(如果找到)可能位于阈值的另一侧,则会发现极值,从而导致发现其他阈值交叉点。
采样
使用函数探索器时,必须提供一个回调来控制函数的采样方式。良好的采样对于获得准确的结果至关重要。如果函数采样频率不够高,可能会错过阈值交叉点和局部极值。如果采样太频繁,性能就会受到影响。理想采样在很大程度上取决于被采样函数的性质。
我们建议您使用DoubleFunctionSampling
、JulianDateFunctionSampling
或DurationFunctionSampling
,而不是使用上面示例中所示的固定采样步长。函数探索器可配置为使用这些采样类,如下所示。
Function<double, double> function = GetAnyOldFunction();
DoubleFunctionSampling sampling = new DoubleFunctionSampling
{
MinimumStep = 1.0,
MaximumStep = 100.0,
DefaultStep = 10.0,
TrendingStep = 1.0
};
DoubleFunctionExplorer explorer = new DoubleFunctionExplorer();
explorer.SampleSuggestionCallback = sampling.GetFunctionSampler(function).GetNextSample;
explorer.Functions.Add(function.Evaluate, 1.234);
采样类型确定传递给GetFunctionSampler
的Function <TIndependent,TDependent>
实例上的GetNextSampleSuggestion
方法的下一个样本。 因此,函数本身控制着它的采样方式。 此外,采样类型具有MinimumStep
和MaximumStep
属性,这些属性对函数可以建议的步长施加限制。 如果函数未提供有效建议,或者未向GetFunctionSampler
提供任何函数,则使用DefaultStep
。
采样类型还允许您指定TrendingStep
的大小。趋势步长发生在探索间隔的开始和结束时,其目的是确定函数在端点处的趋势方向。
考虑探索上图中显示的函数。探测从最左边的黄点开始,右边的两个黄点是接下来的两个样本。连接三个点的两条线的斜率以红色显示。因为两个线段都显示出减小的斜率,所以函数探索器不知道该函数在这三个样本之间的间隔内具有局部最大值。更糟糕的是,局部最大值超过阈值,因此如果我们不认识到存在这个局部最大值,我们将错过两个阈值交叉。
这是一个问题,因为最左边的黄点是第一个探索过的样本。如果我们之前已经开始探索(例如图中的绿点),则由前三个点形成的斜率将相反,函数探索器将识别该斜率表示局部最大值。找到局部最大值也会导致找到两个交叉点。但是,在我们从第一个黄点开始的情况下,我们可以做些什么来找到交叉点?
您可能已经猜到,答案是使用趋势步长。在间隔开始处的一小步,显示为蓝点,确定该函数在间隔开始时呈趋势正向。然后,第二个黄点给出了两个具有相反斜率的区段,表示局部最大值,我们找到了交叉点。
趋势步长越小,函数可以在阈值的“另一”侧花费的时间越短,而不会错过两个交叉。另一方面,如果趋势步长太小,则在探索间隔开始时和趋势步长中的函数值将是不可区分的。虽然函数探索器会考虑这种配置,其中前两个样本相等而第三个不同,以指示局部极值,在函数确实在间隔内持续增加或减少的情况下,我们将浪费时间搜索最终位于一个端点或另一个端点的极值。
局部极值
在对函数进行采样时,当三个采样函数值形成具有相反斜率的两个线段时,表示有局部极值(最小值或最大值)。此外,如果前两个值或后两个值相等,则表示为极值,但如果所有三个值相等则不表示为极值。当表示有极值时,将引发LocalExtremumIndicated
事件,并保证局部极值存在于三个样本中的第一个和最后一个之间。但是,如果采样不充分,则第一个和最后一个样本之间可能存在多个极值。
如果FindAllExtremaPrecisely
为true
,则立即找到指示的极值的确切位置。如果ExploreExtremaToFindCrossings
为true
并且极值是最大值且所有三个样本都低于阈值,或者最小值并且所有三个样本都高于阈值,则还会找到极值的精确位置。最后,如果用户在LocalExtremumIndicated
事件期间显式调用FindPreciseExtremum
方法,则会精确找到极值。当找到极值的精确位置时,函数探索器会引发LocalExtremumFound
。
使用BrentFindExtremum
可以找到极值的精确位置。 ExtremumConvergenceCriteria
属性控制用于确定何时发现极值达到足够精度的标准。当设置为ConvergenceCriteria.Variable
时,当极值被小于ExtremumVariableTolerance
的范围时,极值搜索会收敛。当设置为ConvergenceCriteria.Function
时,当最后一个采样函数值在通过将曲线拟合到指示极值的三个点而估计的值的ExtremumValueTolerance
范围内时,极值搜索收敛。 ConvergenceCriteria
也可以允许融合。任何一个标准或者可以要求满足ConvergenceCriteria.Both
标准。
如果采样不充分导致三个样本之间存在多个局部极值标明为一个极值,则函数探索器将只发现一个极值,并且它们不能保证找到哪一个极值。
当ReportExtremaAtEndpoints
设置为true
时,还会在探测间隔的开始和结束时报告局部极值。例如,如果函数在间隔开始时减小,则第一个样本是局部最大值。类似地,如果函数在间隔结束时减少,则最后一个样本是局部最小值。如果函数在端点处是平坦的,则不会报告极值。
阈值交叉
在对函数进行采样时,当两个采样函数值位于阈值的相对侧时,表示有阈值交叉。当采样函数值恰好位于阈值上时,基于BehaviorWhenOnThreshold
属性的值确定阈值交叉时,将其视为高于还是低于阈值。
当出现极值时,将引发ThresholdCrossingIndicated
事件,并保证交叉存在于两点之间的某处。但是,如果采样不充分,则两个样本之间可能存在多个交叉点。
如果FindAllCrossingsPrecisely
为true
,则立即找到标明交叉点的确切位置。如果用户在ThresholdCrossingIndicated
事件期间显式调用FindPreciseCrossing
方法,也会找到交叉点的精确位置。当找到交叉点的精确位置时,函数探索器会引发ThresholdCrossingFound
事件。
使用BrentFindRoot
找到交叉点的精确位置。 ConvergenceCriteria
属性控制用于确定何时发现交叉点达到足够精度的标准。当设置为ConvergenceCriteria.Variable
时,当交叉点被小于CrossingVariableTolerance
的间隔范围内时,交叉搜索会收敛。当设置为ConvergenceCriteria.Function
时,当最后一个采样函数值在阈值的ValueTolerance
范围内时,交叉搜索会收敛。 ConvergenceCriteria
也可以允许融合。任何一个标准或者都可以要求满足ConvergenceCriteria.Both
标准。当识别的交叉点不完全等于阈值时,SolutionType
属性控制报告的交叉点是略高于还是略低于阈值。
当函数在阈值处是平坦的时,将平坦区域中的任意数量的样本报告为阈值交叉是合理的。相反,函数探索器根据SolutionType
属性的值保证报告交叉的位置。当设置为ThresholdCrossingSolutionType.OnOrAboveThreshold
,并且函数在增加时越过阈值时,报告的交叉是阈值上最早的采样变量值。如果函数在减小时越过阈值,则报告的交叉是阈值上的最新采样变量值。这与ThresholdCrossingSolutionType.OnOrAboveThreshold
暗示应将阈值上的值视为高于阈值的观点一致;函数探索器首次报告它超过阈值并且最后一次超过阈值。类似地,当设置为ThresholdCrossingSolutionType.OnOrBelowThreshold
,并且函数在增加时越过阈值时,报告的交叉是阈值上的最新采样变量值。如果函数在减小时越过阈值,则报告的交叉是阈值上最早的采样变量值。最后,当设置为ThresholdCrossingSolutionType.OnAboveOrBelowThreshold
时,函数探索器会报告最早的交叉,无论方向如何。
如果采样不充分导致两个样本之间出现多个交叉点表示为一个交叉点,则函数探索器只能找到其中一个交叉点,并且无法保证找到哪个交叉点。