Skip to content

Commit

Permalink
Merge pull request #193 from fitbenchmarking/functionEvaluationMethod
Browse files Browse the repository at this point in the history
Function Evaluation Method
  • Loading branch information
wathen committed Aug 7, 2019
2 parents a8f13ce + 6a1a6d2 commit fc3fda7
Show file tree
Hide file tree
Showing 21 changed files with 366 additions and 51 deletions.
5 changes: 3 additions & 2 deletions example_scripts/example_runScripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@
# "Muon_data" works for mantid minimizers
# problem_sets = ["Neutron_data", "NIST/average_difficulty"]
# problem_sets = ["CUTEst", "Muon_data", "Neutron_data", "NIST/average_difficulty", "NIST/high_difficulty", "NIST/low_difficulty"]
# problem_sets = ["CUTEst", "NIST/average_difficulty", "NIST/high_difficulty", "NIST/low_difficulty"]
problem_sets = ['CUTEst']

problem_sets = ["NIST/low_difficulty"]

for sub_dir in problem_sets:
# generate group label/name used for problem set
label = sub_dir.replace('/', '_')
Expand Down
3 changes: 2 additions & 1 deletion example_scripts/example_runScripts_mantid.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@
# ADD WHICH PROBLEM SETS TO TEST AGAINST HERE
# Do this, in this example file, by selecting sub-folders in benchmark_probs_dir
# "Muon_data" works for mantid minimizers
problem_sets = ["CUTEst", "Muon_data", "Neutron_data", "NIST/average_difficulty", "NIST/high_difficulty", "NIST/low_difficulty"]
# problem_sets = ["CUTEst", "Muon_data", "Neutron_data", "NIST/average_difficulty", "NIST/high_difficulty", "NIST/low_difficulty"]
problem_sets = ['Muon_data', 'CUTEst']

for sub_dir in problem_sets:
# generate group label/name used for problem set
Expand Down
1 change: 1 addition & 0 deletions fitbenchmarking/fitbenchmark_one_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def fitbm_one_prob(user_input, problem):
# scipy does not currently support the GEM problem
if 'GEM' in problem.name and user_input.software == 'scipy':
break

results_problem, best_fit = \
fit_one_function_def(user_input.software, problem, data_struct,
function, user_input.minimizers, cost_function)
Expand Down
9 changes: 9 additions & 0 deletions fitbenchmarking/fitting/mantid/externals.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,16 @@ def gen_func_obj(function_name, params_set):
@returns :: mantid function object that can be called in python
"""
params_set = (params_set.split(', ties'))[0]
params_set_list = params_set.split(',')

# attr_list = ''
# for param in params_set_list:
# if param.startswith('BinWidth'):
# attr_list += param + ','
# attr_list = attr_list[:-1]

exec "function_object = msapi." + function_name + "("+ params_set +")"
# exec "function_object = msapi." + function_name + "("+ attr_list +")"
return function_object


Expand Down
40 changes: 30 additions & 10 deletions fitbenchmarking/fitting/mantid/func_def.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
from __future__ import (absolute_import, division, print_function)

from utils.logging_setup import logger
from mantid.api import *
from mantid.fitfunctions import *
import numpy as np


def function_definitions(problem):
Expand All @@ -41,22 +44,20 @@ def function_definitions(problem):

problem_type = extract_problem_type(problem)

if problem_type == 'NIST':
# NIST data requires prior formatting

if isinstance((problem.get_function())[0][0], FunctionWrapper):
function_defs = [problem.get_function()[0][0]]
elif problem_type == 'NIST':
nb_start_vals = len(problem.starting_values[0][1])
function_defs = parse_nist_function_definitions(problem, nb_start_vals)
elif problem_type == 'FitBenchmark'.upper():
# Native FitBenchmark format does not require any processing
function_defs = []
function_defs.append(problem.equation)
function_defs = parse_function_definitions(problem, nb_start_vals)
else:
raise NameError('Currently data types supported are FitBenchmark'
' and nist, data type supplied was {}'.format(problem_type))

return function_defs


def parse_nist_function_definitions(problem, nb_start_vals):
def parse_function_definitions(problem, nb_start_vals):
"""
Helper function that parses the NIST function definitions and
transforms them into a mantid-readable format.
Expand All @@ -75,8 +76,27 @@ def parse_nist_function_definitions(problem, nb_start_vals):
start_val_str += ('{0}={1},'.format(param[0], param[1][start_idx]))
# Eliminate trailing comma
start_val_str = start_val_str[:-1]
function_defs.append("name=UserFunction,Formula={0},{1}".
format(problem.equation, start_val_str))
function_defs.append("name=fitFunction,{}".
format(start_val_str))

param_names = [row[0] for row in problem.starting_values]

class fitFunction(IFunction1D):
def init(self):

for param in param_names:
self.declareParameter(param)

def function1D(self, xdata):

fit_param = np.zeros(len(param_names))
fit_param.setflags(write=1)
for param in param_names:
fit_param[param_names.index(param)] = self.getParameterValue(param)

return problem.eval_f(xdata, fit_param)

FunctionFactory.subscribe(fitFunction)

return function_defs

Expand Down
3 changes: 2 additions & 1 deletion fitbenchmarking/fitting/mantid/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from fitting import misc
from fitting.plotting import plot_helper


MAX_FLOAT = sys.float_info.max


Expand Down Expand Up @@ -67,7 +68,6 @@ def benchmark(problem, wks_created, function, minimizers, cost_function):

return results_problem, best_fit


def fit(problem, wks_created, function, minimizer,
cost_function='Least squares'):
"""
Expand All @@ -86,6 +86,7 @@ def fit(problem, wks_created, function, minimizer,
and how much time it took for the fit to finish (float)
"""


fit_result, t_start, t_end = None, None, None
try:
ignore_invalid = get_ignore_invalid(problem, cost_function)
Expand Down
13 changes: 7 additions & 6 deletions fitbenchmarking/fitting/mantid/tests/test_func_def.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
sys.path.insert(0, main_dir)

from fitting.mantid.func_def import function_definitions
from fitting.mantid.func_def import parse_nist_function_definitions
from fitting.mantid.func_def import parse_function_definitions

from parsing.parse_nist_data import FittingProblem as NISTFittingProblem
from parsing.parse_fitbenchmark_data import FittingProblem as FBFittingProblem
Expand Down Expand Up @@ -101,8 +101,8 @@ def test_functionDefinitions_return_NIST_functions(self):

function_defs = function_definitions(prob)
function_defs_expected = \
["name=UserFunction,Formula=b1*(1-exp(-b2*x)),b1=500.0,b2=0.0001",
"name=UserFunction,Formula=b1*(1-exp(-b2*x)),b1=250.0,b2=0.0005"]
["name=fitFunction,b1=500.0,b2=0.0001",
"name=fitFunction,b1=250.0,b2=0.0005"]

self.assertListEqual(function_defs_expected, function_defs)

Expand All @@ -111,6 +111,7 @@ def test_functionDefinitions_return_neutron_function(self):
prob = self.Neutron_problem()

function_defs = function_definitions(prob)
function_defs = [str(function_defs[0])]
function_defs_expected = \
[("name=LinearBackground,A0=0,A1=0;name=BackToBackExponential,"
"I=597.076,A=1,B=0.05,X0=24027.5,S=22.9096")]
Expand All @@ -122,10 +123,10 @@ def test_parseNistFunctionDefinitions_get_true_function(self):
prob = self.NIST_problem()
nb_start_vals = 2

function_defs = parse_nist_function_definitions(prob, nb_start_vals)
function_defs = parse_function_definitions(prob, nb_start_vals)
function_defs_expected = \
["name=UserFunction,Formula=b1*(1-exp(-b2*x)),b1=500.0,b2=0.0001",
"name=UserFunction,Formula=b1*(1-exp(-b2*x)),b1=250.0,b2=0.0005"]
["name=fitFunction,b1=500.0,b2=0.0001",
"name=fitFunction,b1=250.0,b2=0.0005"]

self.assertListEqual(function_defs_expected, function_defs)

Expand Down
4 changes: 4 additions & 0 deletions fitbenchmarking/fitting/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ def create_result_entry(problem, status, chi_sq, runtime, minimizer,
@returns :: the result object
"""

if 'fitFunction' in ini_function_def:
ini_function_def = ini_function_def.replace('fitFunction',problem.equation)
fin_function_def = fin_function_def.replace('fitFunction',problem.equation)

# Create empty fitting result object
result = fitbm_result.FittingResult()

Expand Down
4 changes: 4 additions & 0 deletions fitbenchmarking/fitting/prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def prepare_mantid(problem, use_errors):

wks_mtd, cost_function = \
wks_cost_function(problem, use_errors)

# String containing the function name(s) and the starting parameter values for each function
function_definitions = \
function_definitions(problem)

Expand All @@ -85,6 +87,8 @@ def prepare_scipy(problem, use_errors):

data, cost_function = \
prepare_data(problem, use_errors)

# String containing function evaluation methods and the starting values for each method
function_definitions = function_definitions(problem)
# For problems that have no specified boundaries, set -inf and +inf
if problem.start_x == None and problem.end_x == None:
Expand Down
17 changes: 2 additions & 15 deletions fitbenchmarking/fitting/scipy/func_def.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

import numpy as np
import re
from utils.logging_setup import logger

def function_definitions(problem):
"""
Expand All @@ -35,20 +34,8 @@ def function_definitions(problem):
"""
problem_type = extract_problem_type(problem)

if problem_type == 'NIST':
from fitting.scipy.nist_data_functions import nist_func_definitions
return nist_func_definitions(problem.equation,
problem.starting_values)
elif problem_type == 'FitBenchmark'.upper():
"""
The following import is inserted here to allow fitbenchmarking
to run independently of mantid when solving NIST problems.
FitBenchmark problems require mantid to generate problem function objects.
As mantid is imported in fitbenchmark_data_function, the script should be
imported only in the case of solving FitBenchmark problems
"""
from fitting.scipy.fitbenchmark_data_functions import fitbenchmark_func_definitions
return fitbenchmark_func_definitions(problem.equation)
if problem_type == 'NIST' or problem_type == 'FitBenchmark'.upper():
return problem.get_function()
else:
RuntimeError("Your problem type is not supported yet!")

Expand Down
151 changes: 151 additions & 0 deletions fitbenchmarking/fitting/scipy/tests/test_func_def.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
from __future__ import (absolute_import, division, print_function)

import unittest
import os
import numpy as np

import sys
test_dir = os.path.dirname(os.path.realpath(__file__))
parent_dir = os.path.dirname(os.path.normpath(test_dir))
parent_dir = os.path.dirname(os.path.normpath(parent_dir))
main_dir = os.path.dirname(os.path.normpath(parent_dir))
sys.path.insert(0, main_dir)

from fitting.scipy.func_def import function_definitions
from fitting.scipy.func_def import get_fin_function_def
from fitting.scipy.func_def import get_init_function_def

from parsing.parse_nist_data import FittingProblem as NISTFittingProblem
from parsing.parse_fitbenchmark_data import FittingProblem as FBFittingProblem
from mock_problem_files.get_problem_files import get_file

class ScipyTests(unittest.TestCase):

def NIST_problem(self):
"""
Helper function.
Sets up the problem object for the nist problem file Misra1a.dat
"""

data_pattern = np.array([[10.07, 77.6],
[14.73, 114.9],
[17.94, 141.1],
[23.93, 190.8],
[29.61, 239.9],
[35.18, 289.0],
[40.02, 332.8],
[44.82, 378.4],
[50.76, 434.8],
[55.05, 477.3],
[61.01, 536.8],
[66.40, 593.1],
[75.47, 689.1],
[81.78, 760.0]])

fname = get_file('NIST_Misra1a.dat')
prob = NISTFittingProblem(fname)
prob.name = 'Misra1a'
prob.equation = 'b1*(1-exp(-b2*x))'
prob.starting_values = [['b1', [500.0, 250.0]],
['b2', [0.0001, 0.0005]]]
prob.data_x = data_pattern[:, 1]
prob.data_y = data_pattern[:, 0]

return prob

def Neutron_problem(self):
"""
Sets up the problem object for the neutron problem file:
ENGINX193749_calibration_peak19.txt
"""

fname = get_file('FB_ENGINX193749_calibration_peak19.txt')
prob = FBFittingProblem(fname)
prob.name = 'ENGINX 193749 calibration, spectrum 651, peak 19'
prob.equation = ("name=LinearBackground,A0=0,A1=0;"
"name=BackToBackExponential,"
"I=597.076,A=1,B=0.05,X0=24027.5,S=22.9096")
prob.starting_values = None
prob.start_x = 23919.5789114
prob.end_x = 24189.3183142

return prob

def test_functionDefinitions_return_NIST_functions(self):

prob = self.NIST_problem()

function_defs = function_definitions(prob)
function_defs_expected = prob.get_function()

function = function_defs[0][0]
function_expected = function_defs_expected[0][0]

np.testing.assert_array_equal(function(prob.data_x,500.0,250.0), function_expected(prob.data_x,500.0,250.0))
np.testing.assert_array_equal(function(prob.data_x,0.0001,0.0005), function_expected(prob.data_x,0.0001,0.0005))

self.assertListEqual(function_defs_expected[0][1:], function_defs[0][1:])

def test_functionDefinitions_return_neutron_functions(self):

prob = self.Neutron_problem()

function_defs = function_definitions(prob)
function_defs_expected = prob.get_function()

self.assertEqual(str(function_defs_expected[0][0]), str(function_defs[0][0]))

np.testing.assert_array_equal(function_defs_expected[0][1], function_defs[0][1])

def test_get_init_function_def_return_NIST_init_func_def(self):

prob = self.NIST_problem()

init_func_def = get_init_function_def((prob.get_function())[0],prob.equation)

init_func_def_expected = "b1*(1-np.exp(-b2*x)) | b1= 500.0, b2= 0.0001"

self.assertEqual(init_func_def_expected, init_func_def)

def test_get_init_function_def_return_neutron_init_func_def(self):

prob = self.Neutron_problem()

init_func_def = get_init_function_def((prob.get_function())[0],prob.equation)

init_func_def_expected = "name=LinearBackground,A0=0,A1=0;name=BackToBackExponential,I=597.076,A=1,B=0.05,X0=24027.5,S=22.9096"

self.assertEqual(init_func_def_expected, init_func_def)

def test_get_fin_function_def_return_NIST_fin_func_def(self):

prob = self.NIST_problem()

init_func_def = "b1*(1-np.exp(-b2*x)) | b1= 500.0, b2= 0.0001"

popt = np.array([2.4, 250.])

fin_func_def = get_fin_function_def(init_func_def,(prob.get_function())[0][0],popt)

fin_func_def_expected = "b1*(1-np.exp(-b2*x)) | b1= 2.4, b2= 250.0"

self.assertEqual(fin_func_def_expected, fin_func_def)

def test_get_fin_function_def_return_neutron_fin_func_def(self):

prob = self.Neutron_problem()

init_func_def = "name=LinearBackground,A0=0,A1=0;name=BackToBackExponential,I=597.076,A=1,B=0.05,X0=24027.5,S=22.9096"

popt = np.array([-2.28680098e+01, 9.80089245e-04, 7.10042119e+02, 3.58802084e+00,
3.21533386e-02, 2.40053562e+04, 1.65148875e+01])

fin_func_def = get_fin_function_def(init_func_def,(prob.get_function())[0][0],popt)

fin_func_def_expected = "name=LinearBackground,A0=0,A1=0;name=BackToBackExponential,I=597.076,A=1,B=0.05,X0=24027.5,S=22.9096"

self.assertEqual(fin_func_def_expected, fin_func_def)


if __name__ == "__main__":
unittest.main()

0 comments on commit fc3fda7

Please sign in to comment.