# -*- tcl -*-
# optimize.test --
#    Test cases for the ::math::optimize package
#
# Note:
#    By evaluating the tests in a different namespace than global,
#    we assure that the namespace issue (Bug #...) is checked.
#

if {[lsearch [namespace children] ::tcltest] == -1} {
    package require tcltest
    namespace import ::tcltest::*
}

source [file join [file dirname [info script]] optimize.tcl]

puts "math::optimize [package present math::optimize]"

namespace eval optimizetest {

#package require math::optimize
namespace import ::math::optimize::*

#
# Simple test functions
#
proc const_func { x } {
   return 1.0
}
proc ffunc { x } {
   expr {$x*(1.0-$x*$x)}
}
proc minfunc { x } {
   expr {-$x*(1.0-$x*$x)}
}
proc absfunc { x } {
   expr {abs($x*(1.0-$x*$x))}
}

proc within_range { result min max } {
   #puts "Within range? $result $min $max"
   #puts "[expr {2.0*abs($result-$min)/abs($max+$min)}]"
   if { $result >= $min && $result <= $max } {
      set ok 1
   } else {
      set ok 0
   }
   return $ok
}

#
# Test the minimum procedure
#
# Note about the uneven and even functions:
# the initial interval is chosen symmetrical, so that the
# three function values are equal.
#
test "Minimum-1.0" "Minimum of constant function" {
   set result [minimum -1.0 1.0 ::optimizetest::const_func]
   within_range $result -1.0 1.0
} 1

#
# To do: find out why this case fails
#
test "Minimum-2.0" "Minimum of odd function, case 1" {
   set result [minimum -1.0 1.0 ::optimizetest::ffunc]
   set xmin   [expr {-sqrt(1.0/3.0)-0.0001}]
   set xmax   [expr {-sqrt(1.0/3.0)+0.0001}]
   within_range $result $xmin $xmax
} 1

test "Minimum-2.1" "Minimum of odd function, asymmetric interval" {
   set result [minimum -0.8 1.2 ::optimizetest::ffunc]
   set xmin   [expr {-sqrt(1.0/3.0)-0.0001}]
   set xmax   [expr {-sqrt(1.0/3.0)+0.0001}]
   within_range $result $xmin $xmax
} 1

test "Minimum-2.2" "Minimum of odd function, case 2" {
   set result [minimum -1.0 1.0 ::optimizetest::minfunc]
   set xmin   [expr {sqrt(1.0/3.0)-0.0001}]
   set xmax   [expr {sqrt(1.0/3.0)+0.0001}]
   within_range $result $xmin $xmax
} 1

test "Minimum-2.3" "Minimum of even function" {
   set result [minimum -1.0 1.0 ::optimizetest::absfunc]
   set xmin   -0.0001
   set xmax    0.0001
   within_range $result $xmin $xmax
} 1

#
# Test the maximum procedure
#
# Note about the uneven and even functions:
# the initial interval is chosen symmetrical, so that the
# three function values are equal.
#
test "Maximum-1.0" "Maximum of constant function" {
   set result [maximum -1.0 1.0 ::optimizetest::const_func]
   within_range $result -1.0 1.0
} 1

test "Maximum-2.0" "Maximum of odd function, case 1" {
   set result [maximum -1.0 1.0 ::optimizetest::ffunc]
   set xmin   [expr {sqrt(1.0/3.0)-0.0001}]
   set xmax   [expr {sqrt(1.0/3.0)+0.0001}]
   within_range $result $xmin $xmax
} 1

test "Maximum-2.1" "Maximum of odd function, case 2" {
   set result [maximum -1.0 1.0 ::optimizetest::minfunc]
   set xmin   [expr {-sqrt(1.0/3.0)-0.0001}]
   set xmax   [expr {-sqrt(1.0/3.0)+0.0001}]
   within_range $result $xmin $xmax
} 1

#
# Either of the two maxima will do
#
test "Maximum-2.2" "Maximum of even function" {
   set result [maximum -1.0 1.0 ::optimizetest::absfunc]
   set xmin   [expr {-sqrt(1.0/3.0)-0.0001}]
   set xmax   [expr {-sqrt(1.0/3.0)+0.0001}]
   set ok     [within_range $result $xmin $xmax]
   set xmin   [expr {sqrt(1.0/3.0)-0.0001}]
   set xmax   [expr {sqrt(1.0/3.0)+0.0001}]
   incr ok    [within_range $result $xmin $xmax]
} 1

#
# Test the solveLinearProgram procedure
# - not implemented yet
#
if { 0 } {
set symm_constraints {
       { 1.0   2.0  1.0 }
       { 2.0   1.0  1.0 } }

test "LinearProg-1.0" "Symmetric constraints, case 1" {
   set result [solveLinearProgram $::symm_constraints {1.0 1.0}]
   set ok 1
   if { ! [within_range [lindex $result 0]  0.333300 0.333360] ||
        ! [within_range [lindex $result 1]  0.333300 0.333360] } {
         set ok 0
   }
   set ok
} 1

test "LinearProg-1.1" "Symmetric constraints, case 2" {
   set result [solveLinearProgram $::symm_constraints {1.0 0.0}]
   set ok 1
   if { ! [within_range [lindex $result 0]  0.999900 1.000100] ||
        ! [within_range [lindex $result 1] -0.000100 0.000100] } {
         set ok 0
   }
   set ok
} 1

test "LinearProg-1.2" "Symmetric constraints, case 3" {
   set result [solveLinearProgram $::symm_constraints {0.0 1.0}]
   set ok 1
   if { ! [within_range [lindex $result 1]  0.999900 1.000100] ||
        ! [within_range [lindex $result 0] -0.000100 0.000100] } {
         set ok 0
   }
   set ok
} 1

test "LinearProg-1.3" "Symmetric constraints, case 4" {
   set result [solveLinearProgram $::symm_constraints {3.0 4.0}]
   set ok 1
   if { ! [within_range [lindex $result 0]  0.333300 0.333360] ||
        ! [within_range [lindex $result 1]  0.333300 0.333360] } {
         set ok 0
   }
   set ok
} 1

test "LinearProg-2.1" "Unbounded program" {
   set result [solveLinearProgram {{1.0 -2.0 1.0} {-2.0 1.0 1.0}} {3.0 4.0}]
} "unbounded"

test "LinearProg-2.2" "Infeasible program" {
   set result [solveLinearProgram {{1.0  0.0 2.0} {1.0 1.0 1.0}} {1.0 1.0}]
} "infeasible"

test "LinearProg-3.1" "Simple 3D program" {
   set result [solveLinearProgram \
      {{1.0  1.0  2.0  1.0}
       {1.0  2.0  1.0  1.0}
       {2.0  1.0  1.0  1.0}} {1.0 1.0 1.0}]
   set ok 1
   if { ! [within_range [lindex $result 0]  0.249900 0.250100] ||
        ! [within_range [lindex $result 1]  0.249900 0.250100] ||
        ! [within_range [lindex $result 2]  0.249900 0.250100] } {
         set ok 0
   }
   set ok
} 1

test "LinearProg-3.2" "Redundant constraints" {
   set result [solveLinearProgram \
      {{1.0  0.0 10.0}
       {0.0  1.0 10.0}
       {-1.0 -2.0 -1.0}
       {-1.0 -1.0 -1.0}} {1.0 1.0}]
   set ok 1
   # Expected answer?
   #if { ! [within_range [lindex $result 0]  0.249900 0.250100] ||
   #     ! [within_range [lindex $result 1]  0.249900 0.250100] } {
   #      set ok 0
   #}
   set ok
} 1

} ;# End of if 0

} ;# End of optimizetest namespace
