rock
rock

Reputation: 107

Errors when returning values to the array

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

Answers (2)

Ansgar Wiechers
Ansgar Wiechers

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

antonyoni
antonyoni

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

Related Questions