diff --git a/Tests/FunctionalTests/TestCore/TestCore.py b/Tests/FunctionalTests/TestCore/TestCore.py new file mode 100755 index 0000000000000000000000000000000000000000..248d478a06fb73d6d19051ef965e8d094662e4bc --- /dev/null +++ b/Tests/FunctionalTests/TestCore/TestCore.py @@ -0,0 +1,87 @@ +# Run C++ core tests for libBornAgainCore library +# Usage: python test_all.py +import sys +import os +import subprocess +import time + + +Tests = [ + "IsGISAXS01", + "IsGISAXS02", + "IsGISAXS03", + "IsGISAXS04", + "IsGISAXS06", + "IsGISAXS07", + "IsGISAXS08", + "IsGISAXS09", + "IsGISAXS10", + "IsGISAXS11", + "IsGISAXS15" +] + +test_info = [] + +# run system command and catch multiline stdout and stderr +def run_command(command): + p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p.wait() + return iter(p.stdout.readline, b''), iter(p.stderr.readline, b'') + + +# parse stdout, stderr for test description and test result +def parse_output(testName, stdout, stderr): + # normally the message from test looks like "IsGISAXS01 Mixture of cylinders and prisms [OK]" + # we want to find status (FAILED or OK) and extract description "Mixture of cylinders and prisms" + status="OK" + for line in stderr: + status="FAILED" # test failed, if there are some non empty stderr messages + descr="" + for line in stdout: + if testName in line: + if "FAILED" in line: status="FAILED" + descr=line.strip(testName).strip("\n") + descr=descr.strip("[OK]") + descr=descr.strip("[FAILED]") + + descr = descr[:55] + descr = descr.ljust(55).lstrip() + + return descr, status + + +# run tests one by one +def runTests(): + print ">>> Starting TestCore" + for testName in Tests: + command = testName+"/"+testName # i.e. "path/executable" like "IsGISAXS01/IsGISAXS01" + path = os.path.split(__file__)[0] + if path: command = path + "/" + command + print "Running test ", testName + start_time = time.time() + stdout, stderr = run_command(command) + total_time = time.time() - start_time + descr, status = parse_output(testName, stdout, stderr) + test_info.append((testName, descr, total_time, status)) + return getSummary() + + +# making summary +def getSummary(): + summary = "--------------------------------------------------------------------------------\n" + summary += "Functional Tests of libBornAgainCore (C++) \n" + summary += "--------------------------------------------------------------------------------\n" + n=1 + for x in test_info: + summary += '{0:2d}. {1} {2:.53s} {3:.2f}s [{4}] '.format(n, x[0],x[1],x[2],x[3]) + "\n" + n+=1 + return summary + + +#------------------------------------------------------------- +# main() +#------------------------------------------------------------- +if __name__ == '__main__': + summary = runTests() + print summary + diff --git a/Tests/FunctionalTests/TestCore/test_all.py b/Tests/FunctionalTests/TestFit/TestFit.py similarity index 86% rename from Tests/FunctionalTests/TestCore/test_all.py rename to Tests/FunctionalTests/TestFit/TestFit.py index 415cbe856cf455bfe539f5025c9939abbeb8487c..8d81ba91aa58329c9d35d26c25eef473c6c03b37 100755 --- a/Tests/FunctionalTests/TestCore/test_all.py +++ b/Tests/FunctionalTests/TestFit/TestFit.py @@ -1,23 +1,13 @@ -# Run C++ core tests for libBornAgainCore library +# Run C++ fitting tests for libBornAgainFit library # Usage: python test_all.py + import sys import os import subprocess import time - Tests = [ - "IsGISAXS01", - "IsGISAXS02", - "IsGISAXS03", - "IsGISAXS04", - "IsGISAXS06", - "IsGISAXS07", - "IsGISAXS08", - "IsGISAXS09", - "IsGISAXS10", - "IsGISAXS11", - "IsGISAXS15" + "TestFit01", ] test_info = [] @@ -64,7 +54,7 @@ def runTests(): # print test results def printResults(): print "-------------------------------------------------------------------------------" - print "TestCore Summary " + print "TestFit Summary " print "-------------------------------------------------------------------------------" n=1 for x in test_info: diff --git a/Tests/FunctionalTests/TestPyCore/TestPyCore.py b/Tests/FunctionalTests/TestPyCore/TestPyCore.py new file mode 100644 index 0000000000000000000000000000000000000000..388113aa6e000cf6fab92e7f1718dc9b2ee4de90 --- /dev/null +++ b/Tests/FunctionalTests/TestPyCore/TestPyCore.py @@ -0,0 +1,83 @@ +# Run Python core tests for libBornAgainCore library +# Usage: python test_all.py + +import sys +import os +import subprocess +import time + +import isgisaxs01 +import isgisaxs02 +import isgisaxs03 +import isgisaxs04 +import isgisaxs06 +import isgisaxs07 +import isgisaxs08 +import isgisaxs09 +import isgisaxs10 +import isgisaxs11 +import isgisaxs15 + +Tests = { + "IsGISAXS01": isgisaxs01.runTest, + "IsGISAXS02": isgisaxs02.runTest, + "IsGISAXS03": isgisaxs03.runTest, + "IsGISAXS04": isgisaxs04.runTest, + "IsGISAXS06": isgisaxs06.runTest, + "IsGISAXS07": isgisaxs07.runTest, + "IsGISAXS08": isgisaxs08.runTest, + "IsGISAXS09": isgisaxs09.runTest, + "IsGISAXS10": isgisaxs10.runTest, + "IsGISAXS11": isgisaxs11.runTest, + "IsGISAXS15": isgisaxs15.runTest +} + +test_info = [] + +# parse stdout, stderr for test description and test result +def parse_output(testName, test_result): + # normally the message from test looks like "IsGISAXS01 Mixture of cylinders and prisms [OK]" + # we want to find status (FAILED or OK) and extract description "Mixture of cylinders and prisms" + status="OK" + descr="" + if testName in test_result: + descr = test_result[1] + status = test_result[2] + else: + descr = "Can't parse the description" + descr = descr[:55] + descr = descr.ljust(55) + return descr, status + + +# run tests one by one +def runTests(): + print ">>> Starting TestPyCore" + for testName in sorted(Tests.iterkeys()): + print "Running test ", testName + start_time = time.time() + result = Tests[testName]() + total_time = time.time() - start_time + descr, status = parse_output(testName, result) + test_info.append((testName, descr, total_time, status)) + return getSummary() + + +# compose summary +def getSummary(): + summary = "--------------------------------------------------------------------------------\n" + summary += "Functional Tests of libBornAgainCore (Python) \n" + summary += "--------------------------------------------------------------------------------\n" + n=1 + for x in test_info: + summary += '{0:2d}. {1} {2:.53s} {3:.2f}s [{4}] '.format(n, x[0],x[1],x[2],x[3]) + "\n" + n+=1 + return summary + + +#------------------------------------------------------------- +# main() +#------------------------------------------------------------- +if __name__ == '__main__': + summary = runTests() + print summary diff --git a/Tests/FunctionalTests/TestPyFit/README b/Tests/FunctionalTests/TestPyFit/README new file mode 100644 index 0000000000000000000000000000000000000000..150ef386dcdabbdb70ddb5eebc873da493fbecd9 --- /dev/null +++ b/Tests/FunctionalTests/TestPyFit/README @@ -0,0 +1,12 @@ +Collection of functional tests (Python) + +Collection of fitting tests for libBornAgainFit library. +Different geometries, number of fit parameters, variety of minimizers +and minimization strategies. + +To run tests +python test_all.py + +List of tests +testfit01.py - Two parameter fit using variety of minimizers. Geometry: cylinders in the air. + diff --git a/Tests/FunctionalTests/TestPyFit/TestPyFit.py b/Tests/FunctionalTests/TestPyFit/TestPyFit.py new file mode 100644 index 0000000000000000000000000000000000000000..33c891b3897f587c206a59b5790a43dc2bbfe148 --- /dev/null +++ b/Tests/FunctionalTests/TestPyFit/TestPyFit.py @@ -0,0 +1,60 @@ +# Run Python fitting tests for libBornAgainFit library +# Usage: python test_all.py + +import sys +import os +import subprocess +import time + +import testfit01 + +Tests = { + "TestFit01": testfit01.runTest +} + +test_info = [] + +# parse stdout, stderr for test description and test result +def parse_output(testName, test_result): + # normally the message from test looks like "IsGISAXS01 Mixture of cylinders and prisms [OK]" + # we want to find status (FAILED or OK) and extract description "Mixture of cylinders and prisms" + status="OK" + descr="" + if testName in test_result: + descr = test_result[1] + status = test_result[2] + else: + descr = "Can't parse the description" + descr = descr[:55] + descr = descr.ljust(55) + return descr, status + + +# run tests one by one +def runTests(): + for testName in sorted(Tests.iterkeys()): + print "Running test ", testName + start_time = time.time() + result = Tests[testName]() + total_time = time.time() - start_time + descr, status = parse_output(testName, result) + test_info.append((testName, descr, total_time, status)) + + +# print test results +def printResults(): + print "-------------------------------------------------------------------------------" + print "TestPyFit Summary " + print "-------------------------------------------------------------------------------" + n=1 + for x in test_info: + print '{0:2d}. {1} {2} {3:.3f}sec [{4}] '.format(n, x[0],x[1],x[2],x[3]) + n+=1 + + +#------------------------------------------------------------- +# main() +#------------------------------------------------------------- +if __name__ == '__main__': + runTests() + printResults() diff --git a/Tests/FunctionalTests/TestPyCore/test_all.py b/Tests/FunctionalTests/TestPyFit/test_all.py similarity index 68% rename from Tests/FunctionalTests/TestPyCore/test_all.py rename to Tests/FunctionalTests/TestPyFit/test_all.py index b796aa9f4e42ab3f4ccdcf9bbcd8dbeb9f2dc135..33c891b3897f587c206a59b5790a43dc2bbfe148 100644 --- a/Tests/FunctionalTests/TestPyCore/test_all.py +++ b/Tests/FunctionalTests/TestPyFit/test_all.py @@ -1,4 +1,4 @@ -# Run Python core tests for libBornAgainCore library +# Run Python fitting tests for libBornAgainFit library # Usage: python test_all.py import sys @@ -6,30 +6,10 @@ import os import subprocess import time -import isgisaxs01 -import isgisaxs02 -import isgisaxs03 -import isgisaxs04 -import isgisaxs06 -import isgisaxs07 -import isgisaxs08 -import isgisaxs09 -import isgisaxs10 -import isgisaxs11 -import isgisaxs15 +import testfit01 Tests = { - "IsGISAXS01": isgisaxs01.runTest, - "IsGISAXS02": isgisaxs02.runTest, - "IsGISAXS03": isgisaxs03.runTest, - "IsGISAXS04": isgisaxs04.runTest, - "IsGISAXS06": isgisaxs06.runTest, - "IsGISAXS07": isgisaxs07.runTest, - "IsGISAXS08": isgisaxs08.runTest, - "IsGISAXS09": isgisaxs09.runTest, - "IsGISAXS10": isgisaxs10.runTest, - "IsGISAXS11": isgisaxs11.runTest, - "IsGISAXS15": isgisaxs15.runTest + "TestFit01": testfit01.runTest } test_info = [] @@ -64,7 +44,7 @@ def runTests(): # print test results def printResults(): print "-------------------------------------------------------------------------------" - print "TestPyCore Summary " + print "TestPyFit Summary " print "-------------------------------------------------------------------------------" n=1 for x in test_info: diff --git a/Tests/FunctionalTests/TestPyFit/testfit01.py b/Tests/FunctionalTests/TestPyFit/testfit01.py new file mode 100644 index 0000000000000000000000000000000000000000..6f6e0db94d2d5c32695e57869164899c4282ee67 --- /dev/null +++ b/Tests/FunctionalTests/TestPyFit/testfit01.py @@ -0,0 +1,136 @@ +# functional test: two parameter fit using variety of minimizers +# +# In this test we are using simple geometry: cylinders without interference in +# air layer with two parameters (radius and height of cylinders), describing +# the sample. Our "real" data is 2D intensity map obtained from the simulation of +# the same geometry with fixed values height = 5nm and radius = 5nm. +# Then we run our minimization consequently using different minimization engines, +# with height=4nm, radius=6nm as starting fit parameter values. + + +import sys +import os +import numpy +import time + +sys.path.append(os.path.abspath( + os.path.join(os.path.split(__file__)[0], + '..', '..', '..', 'lib'))) + +from libBornAgainCore import * +from libBornAgainFit import * + +# sample parameters we are going to find +cylinder_height = 5*nanometer +cylinder_radius = 5*nanometer + +# minimizer name and type of minimization algorithm +Minimizers = [ + ("Minuit2","Migrad"), + ("Minuit2","Fumili"), + ("GSLMultiMin","BFGS"), + ("GSLMultiMin","SteepestDescent"), + ("GSLMultiFit",""), + ("GSLSimAn","") +] + + +# ----------------------------------------------------------------------------- +# run several minimization rounds using different minimizers +# ----------------------------------------------------------------------------- +def runTest(): + print "**********************************************************************" + print "* Starting TestFit01 *" + print "**********************************************************************" + nTest=0 + status = "OK" + for m in Minimizers: + minimizer_name = m[0] + minimizer_algorithm = m[1] + print "Test {0:2d} {1:}({2:})".format(nTest, minimizer_name, minimizer_algorithm) + result_ok = run_fitting(minimizer_name, minimizer_algorithm) + nTest+=1 + if not result_ok: status = "FAILED" + + return "TestFit01", "Two parameters fit using variety of minimizers.", status + + +# ----------------------------------------------------------------------------- +# run fitting specified minimizer +# ----------------------------------------------------------------------------- +def run_fitting(minimizer_name, minimizer_algorithm): + sample = buildSample() + simulation = createSimulation() + simulation.setSample(sample) + + # creating real data, which is simply results of our simulation with default values + simulation.runSimulation() + real_data = simulation.getOutputDataClone() + + # setting fit suite + fitSuite = FitSuite() + fitSuite.setMinimizer( MinimizerFactory.createMinimizer(minimizer_name, minimizer_algorithm) ) + fitSuite.addFitParameter("*height", 4.*nanometer, 0.04*nanometer, AttLimits.lowerLimited(0.01) ) + fitSuite.addFitParameter("*radius", 6.*nanometer, 0.06*nanometer, AttLimits.lowerLimited(0.01) ) + fitSuite.addSimulationAndRealData(simulation, real_data) + + # run fit + start_time = time.time() + fitSuite.runFit() + real_time = time.time() - start_time + + height_found = fitSuite.getMinimizer().getValueOfVariableAtMinimum(0) + height_diff = abs(height_found - cylinder_height)/cylinder_height + radius_found = fitSuite.getMinimizer().getValueOfVariableAtMinimum(1) + radius_diff = abs(radius_found - cylinder_radius)/cylinder_radius + + print " RealTime : {0:.3f} sec".format(real_time) + print " NCalls : {0:<5d}".format(fitSuite.getNCalls()) + print ' par1 : {0:.4f} ({1:.3g}) '.format(height_found, height_diff) + print ' par2 : {0:.4f} ({1:.3g}) '.format(radius_found, radius_diff) + + diff = 1.0e-02 + isSuccess = True + if( (height_diff > diff) or (radius_diff > diff) ) : isSuccess=False + return isSuccess + + + +# ----------------------------------------------------------------------------- +# create cylinders in the air +# ----------------------------------------------------------------------------- +def buildSample(): + cylinder_ff = FormFactorCylinder(cylinder_height, cylinder_radius) + n_particle = complex(1.0-6e-4, 2e-8) + cylinder = Particle(n_particle, cylinder_ff) + interference = InterferenceFunctionNone() + + particle_decoration = ParticleDecoration() + particle_decoration.addParticle(cylinder) + particle_decoration.addInterferenceFunction(interference) + + mAmbience = MaterialManager.getHomogeneousMaterial("Air", 1.0, 0.0 ) + air_layer = Layer(mAmbience) + air_layer_decorator = LayerDecorator(air_layer, particle_decoration) + multi_layer = MultiLayer() + multi_layer.addLayer(air_layer_decorator) + + return multi_layer + + +def createSimulation(): + simulation = Simulation(); + simulation.setDetectorParameters(100, 0.0*degree, 2.0*degree,100 , 0.0*degree, 2.0*degree); + simulation.setBeamParameters(1.0*angstrom, -0.2*degree, 0.0*degree); + simulation.setBeamIntensity(1e10); + return simulation + + +#------------------------------------------------------------- +# main() +#------------------------------------------------------------- +if __name__ == '__main__': + name,description,status = runTest() + print name,description,status + + diff --git a/Tests/FunctionalTests/test_all.py b/Tests/FunctionalTests/test_all.py new file mode 100644 index 0000000000000000000000000000000000000000..be9cd09c0dfa990c12f7822263f359421a2267f7 --- /dev/null +++ b/Tests/FunctionalTests/test_all.py @@ -0,0 +1,65 @@ +# run C++/Python functional tests for BornAgain libraries +# +# Usage: +# 'python test_all.py' - to run all tests +# 'python test_all.py C++' - to run C++ tests only +# 'python test_all.py Python' - to run Python tests only + +import os +import sys +import glob + + +sys.path.insert(0, './TestPyCore') +import TestPyCore +sys.path.insert(0, './TestCore') +import TestCore + + +#------------------------------------------------------------- +# run python functional tests +#------------------------------------------------------------- +def runPythonTests(): + summary = TestPyCore.runTests() + return summary + + +#------------------------------------------------------------- +# run C++ functional tests +#------------------------------------------------------------- +def runCppTests(): + summary = TestCore.runTests() + return summary + + + +#------------------------------------------------------------- +# main() +#------------------------------------------------------------- +def main(): + run_python_tests = True + run_cpp_tests = True + if len(sys.argv) >2: + print "Usage:" + print "'python test_all.py' - to run all tests" + print "'python test_all.py C++' - to run C++ tests only" + print "'python test_all.py Python' - to run Python tests only" + exit() + elif len(sys.argv) == 2: + if "C++" in sys.argv[1]: run_python_tests = False + if "Python" in sys.argv[1]: run_cpp_tests = False + print run_python_tests, run_cpp_tests + + summary = "\n" + summary += "Functional Tests Summary -->\n" + if run_python_tests: summary += runPythonTests() + if run_cpp_tests: summary += runCppTests() + print summary + + +#------------------------------------------------------------- +# main() +#------------------------------------------------------------- +if __name__ == '__main__': + main() +