Reputation: 107
I try to create a simple optimisation script. Here is my code:
# Analysis gives the initial inputs and outputs
$initialinputs
$initialoutputs
# The objective function
$F = ([math]::pow($initialinputs, 2)) * 2 - 3* $initialinputs
# Differentiation of the objective function
$DF = 2 * $initialinputs - 3
$ScaleFactor = 0.2
# If the optimum solution has been obtained, two termination measurements:
# maximum iteration and termination criterion(gradient)
$Maxloop = 100
$Termi = 0.001
# Create arrays
$InputsArr = @() #The array of inputs
$FunctionArr = @() #The array of function values
$DFunctionArr = @() # The array of differentiation values (Gradient)
# Calculations
#$InputsArr[0] = $initialinputs #The first input
#$FunctionArr[0] = $F[$InputsArr[0]]
#$DFunctionArr[0] = $DF[$inputsArr[0]]
for ($Innerloop = 1; $Innerloop -le $Maxloop; $Innerloop++)
{
# Calculate the second input
$InputsArr[$innerloop] = $InputsArr[$Innerloop - 1] - $ScaleFactor * (2 * $InputsArr[$Innerloop - 1] - 3)
$initialinputs = $InputsArr[$Innerloop]
# Calculate the function value
$FunctionArr[$innerloop] = ([math]::pow($initialinputs, 2)) * 2 - 3 * $initialinputs
Return, $FunctionArr
# Calculate the gradient value
$DFunctionArr[$innerloop] = 2 * $initialinputs - 3
return, $DFunctionArr
# If the gradient value less than the termination criterion (gradient),
# break the loop
if ($DFunctionArr[$innerloop] -le $Termi)
{
break
}
}
I created the empty arrays and then use the for
loop to do the optimisation and store the outputs in the arrays. But I got some errors as shown below:
ERROR: + $InputsArr[$Innerloop] = $initialinputs ERROR: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ERROR: + CategoryInfo : OperationStopped: (:) [], IndexOutOfRangeException ERROR: + FullyQualifiedErrorId : System.IndexOutOfRangeException ERROR: + $FunctionArr[$innerloop] = $Functionoutput ERROR: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ERROR: + CategoryInfo : OperationStopped: (:) [], IndexOutOfRangeException ERROR: + FullyQualifiedErrorId : System.IndexOutOfRangeException
I am not quite sure how to fix the errors. How to return the value to the arrays? Is +=
the only way to do so? I get confused about the arrays now.
Upvotes: 0
Views: 460
Reputation: 200193
PowerShell arrays defined by @()
are dynamically sized. You can't index-access things in them unless they actually contain elements. That's why you're getting "index out of bounds" exceptions when you use $array[$index]
in your for
loop. The array simply doesn't have a field $index
yet.
Basically there are two ways of filling PowerShell arrays:
Append to an existing array in a loop:
$array = @()
for (...) {
$array += $foo
}
Echo the elements inside the loop and assign the loop output to a variable:
$array = for (...) {
$foo
}
The former should be avoided for larger numbers of array elements, as each append operation (+=
) will create a new array with increased size, copy the elements, then assign the new array to the variable. The latter approach is far superior performance-wise. The only downside of the latter approach is that the result won't be an array unless the loop produces at least two elements. You can mitigate this by enforcing array output: @(for (...) {...})
.
PowerShell also allows you to create arrays with fixed size:
$size = 100
$array = New-Object Object[] $size
However, there's usually no advantage to doing this.
Also note that the first instruction in your loop assumes that the array already contains an initial value:
$InputsArr[$innerloop] = $InputsArr[$Innerloop - 1] - $ScaleFactor * (2 * $InputsArr[$Innerloop - 1] - 3) #Calculate the second input
thus you need to prefill the array with one element before starting the loop:
$InputsArr = @($initialValue)
With that said, there are more PoSh ways of filling an array with values, e.g. like this:
$i = $initialValue
$array = @($i)
$array += 1..$Maxloop | ForEach-Object {
$i -= $ScaleFactor * (2 * $i - 3)
$i
}
If you want to fill multiple arrays in the same loop (because you can't use one loop per array for some reason) appending is probably your best option. Like this:
$InputsArr = @($initialinputs)
$FunctionArr = @($initialinputs)
$DFunctionArr = @($initialinputs)
for ($i = 1; $i -le $Maxloop -and $DFunctionsArr[-1] -le $Termi; $i++) {
$InputsArr += $InputsArr[-1] - $ScaleFactor * (2 * $InputsArr[-1] - 3)
$FunctionArr += ([Math]::Pow($InputsArr[-1], 2)) * 2 - 3 * $InputsArr[-1]
$DFunctionArr += 2 * $InputsArr[-1] - 3
}
or like this, if you don't need $InputsArr
later on:
$FunctionArr = @($initialinputs)
$DFunctionArr = @($initialinputs)
$v = $initialinputs
for ($i = 1; $i -le $Maxloop -and $DFunctionsArr[-1] -le $Termi; $i++) {
$v -= $ScaleFactor * (2 * $v - 3)
$FunctionArr += [Math]::Pow($v, 2) * 2 - 3 * $v
$DFunctionArr += 2 * $v - 3
}
Upvotes: 0
Reputation: 899
When you initialize an array like this: $InputsArr = @()
, powershell creates an array of length 0. Thus when you try and address this array with $InputsArr[$innerloop]
it throws an error that element $innerloop
does not exist.
Two solutions. You can either explicitly initialize an array of a particular type and length:
$InputsArr = New-Object double[] $MaxLoop
Or, you can use the +=
operator in your code to add new values to an array:
$InputsArr += $InputsArr[$Innerloop - 1] - $ScaleFactor * (2 * $InputsArr[$Innerloop - 1] - 3) #Calculate the second input
+=
creates a new array of length n+1, copies the old array, and then adds the new value, so is very inefficient for large arrays. See here.
Upvotes: 1