diff --git a/config/diffcalc/test1.json b/config/diffcalc/test1.json index ab7f00c..0b0df76 100644 --- a/config/diffcalc/test1.json +++ b/config/diffcalc/test1.json @@ -3,25 +3,25 @@ "crystal": "['cubic', 7.723, 7.707, 7.723, 90.0, 89.265, 90.0]", "reflist": { "1": { - "tag": null, + "tag": "None", "hkl": "[0.0, 1.0, 2.0]", "pos": "[5.0, 9.379, 19.7895, 102.6162, 0, 0]", "energy": 9.5007, - "time": "2019-07-31T14:14:59.939000" + "time": "2019-08-15T11:11:28.003000" }, "2": { - "tag": null, + "tag": "None", "hkl": "[2.0, 0.0, 2.0]", "pos": "[5.0, 19.0754, 20.1865, 11.9693, 0, 0]", "energy": 9.5007, - "time": "2019-07-31T14:14:59.973000" + "time": "2019-08-15T11:11:28.025000" }, "3": { - "tag": null, + "tag": "None", "hkl": "[2.0, 2.0, 2.0]", "pos": "[5.0, 27.1234, 21.2368, 60.0354, 0, 0]", "energy": 9.5007, - "time": "2019-07-31T14:15:00.093000" + "time": "2019-08-15T11:11:28.082000" } }, "orientlist": {}, diff --git a/config/settings.properties b/config/settings.properties index 64f5b7a..4048598 100644 --- a/config/settings.properties +++ b/config/settings.properties @@ -1,4 +1,4 @@ -#Wed Jul 31 17:19:31 CEST 2019 +#Fri Aug 16 14:54:41 CEST 2019 count_time=1.0 geometry=fourcv roi=173 88 285 136 diff --git a/script/cpython/image_functions.py b/script/cpython/image_functions.py index dab5206..eb6f4e9 100644 --- a/script/cpython/image_functions.py +++ b/script/cpython/image_functions.py @@ -1,9 +1,15 @@ import numpy +from scipy import signal, ndimage def img_get_int(fname, thres1, thres2, thres3, thres4, header, width, height, depth, x1,y1,x2,y2, bx1,by1,bx2,by2 , filter_median = False, filter_nsigma = 0): # read actual image file img = numpy.fromfile(fname, dtype=numpy.uint32) img.shape = height, width + if filter_nsigma>0: + img = ndimage.gaussian_filter(img, filter_nsigma) + elif filter_median: + #img = signal.medfilt2d(img.astype('d'), kernel_size=3) + img = ndimage.median_filter(img, size=3) # signal roi area_I = ( x2 - x1 + 1) * ( y2 - y1 + 1) I_sum = img[y1:y2, x1:x2].sum() @@ -11,10 +17,10 @@ def img_get_int(fname, thres1, thres2, thres3, thres4, header, width, height, d thresh2_count = len(numpy.where(img>thres2)[0]) thresh3_count = len(numpy.where(img>thres3)[0]) thresh4_count = len(numpy.where(img>thres4)[0]) - # background roi I_sum_bgr = img[by1:by2, bx1:bx2].sum() area_bgr= (bx2 - bx1 + 1) * (by2 - by1 + 1) + return (I_sum, area_I, thresh1_count, thresh2_count, thresh3_count, thresh4_count, I_sum_bgr, area_bgr) diff --git a/script/daq/daq4-setup.py b/script/daq/daq4-setup.py index 89ca13c..f48b93d 100644 --- a/script/daq/daq4-setup.py +++ b/script/daq/daq4-setup.py @@ -6,19 +6,19 @@ set_geometry("fourcv", True) ###################################################################################################\ #Constraints ################################################################################################### -#hkl.con('a_eq_b') +#con('a_eq_b') #equivalent to 'setmode 0' + 'freeze 5' -hkl.con( 'alpha', 5) #alpha constant +con( 'alpha', 5) #alpha constant #equivalent to 'setmode 1' + 'freeze 5' -#hkl.con( 'beta', 5) #alpha constant +#con( 'beta', 5) #alpha constant #equivalent to 'setmode 2'' -#hkl.con( 'a_eq_b', 0) #alpha constant +#con( 'a_eq_b', 0) #alpha constant -#hkl.con( 'eta', 0) +#con( 'eta', 0) setup_axis(alpha, alpha.getMinValue(), alpha.getMaxValue()) diff --git a/script/device/Image.py b/script/device/Image.py index 6ae1a52..f0ef8bc 100644 --- a/script/device/Image.py +++ b/script/device/Image.py @@ -92,7 +92,7 @@ class Image(DeviceBase, Readable): try: ret = img_get_int(tmp_file, threshold1, threshold2, threshold3, threshold4, \ self.pixel.image_header_length, self.pixel.PIX_XDIM, self.pixel.PIX_YDIM,self.pixel.PIX_COLOR_DEPTH, \ - roi[0], roi[1], roi[2], roi[3], bkroi[0], bkroi[1], bkroi[2], bkroi[3]) + roi[0], roi[1], roi[2], roi[3], bkroi[0], bkroi[1], bkroi[2], bkroi[3], filter_median = True, filter_nsigma = 30) self.I_sum, self.area_I, self.thresh1_count, self.thresh2_count, self.thresh3_count, self.thresh4_count, self.I_sum_bgr, self.area_bgr = ret except: log("Error calculating intensity: " + str(filename) + " - " + str(sys.exc_info()[1]), False) diff --git a/script/geometry/fourcv.py b/script/geometry/fourcv.py index 2ac2ed2..ec2a7b2 100644 --- a/script/geometry/fourcv.py +++ b/script/geometry/fourcv.py @@ -1,86 +1,8 @@ run("diffutils") -###################################################################################################\ -#Setup -################################################################################################### - #alpha, delta, gamma, omegaV setup_diff(fourcv, energy, ("mu", "delta", "gam", "eta"), simultaneous_move=True) -#setup_axis(alpha, alpha.getMinValue(), alpha.getMaxValue()) -#setup_axis(delta, delta.getMinValue(), 90) #delta.getMaxValue()) -#setup_axis(gamma, 0, gamma.getMaxValue()) -#setup_axis(omegaV, omegaV.getMinValue(), omegaV.getMaxValue()) - - if energy.isSimulated(): wavelength.write(1.305) - - -""" -###################################################################################################\ -#Orientation -################################################################################################### -help(ub.ub) -ub.listub() - -#alpha delta gamma omegaV - -# Create a new ub calculation and set lattice parameters -ub.newub('test') - -#ub.setlat('cubic', 5.114, 5.8361, 11.058, 90, 90, 90) -#en = 8 -##ub.c2th([0, 0, 4], en) -##ub.addref([0, 0, 4]) #From current position and ekergy -#ub.addref([0, 0, 4], [16.2785, 0.0, 32.5568, 0.0], en) -##ub.c2th([2, 0, 12], en) -#ub.addref([2, 0, 12], [71.8285, 37.3082, 138.7440, 0.0], en) -##ub.c2th([1, -4, 10], en) -#ub.addref([1, -4, 10], [27.7185, 17.6409 , 128.4220, 0.0], en) - -#ub.setlat('cubic', 1.0, 1.0, 1.0, 90, 90, 90) -ub.setlat('cubic', 1.305, 1.305, 1.305, 90, 90, 90) - -#en = 12.4 -en = 9.5 -#ub.c2th([0, 0, 4], en) -#ub.addref([0, 0, 4]) #From current position and ekergy -ub.addref([0, 0, 1], [30.0, 0.0, 60.0, 0.0], en) -#ub.c2th([2, 0, 12], en) -ub.addref([1, 0, 1], [20.0, 45.5564,90.000, 44.4437], en) -#ub.c2th([1, -4, 10], en) -ub.addref([0, 1, 1], [20.0, 45.5564,90.000, 134.4437], en) - -ub.ub() - -#ub.setub([[1.22862,0.00000,0.00000], [-0.00000,1.07663,0.00000], [-0.00000,-0.00000,0.56820]]) - - -# check the state - -ub.checkub() - - - -###################################################################################################\ -#Constraints -################################################################################################### -help(hkl.con) -#hkl.con('a_eq_b') -#hkl.con('eta:0') - - -hkl.con( 'eta', 0) #OmegaV constant -#hkl.con( 'mu', 20) #Alpha constant - - -###################################################################################################\ -#Motion -################################################################################################### - -#print angles_to_hkl((16.278, 0.0000, 32.5568, 0.0)) -#print angles_to_hkl((44.3400, 0.0000, 123.7322 , 0.0)) -#print hkl_to_angles(2, -2, 10) -""" diff --git a/script/local.py b/script/local.py index 05fb0ed..82a06b0 100644 --- a/script/local.py +++ b/script/local.py @@ -3,22 +3,26 @@ ################################################################################################### import os import os.path +from shutil import copyfile +import json ################################################################################################### # Interlocks ################################################################################################### -class MyInterlock1 (Interlock): - def __init__(self): - Interlock.__init__(self, (alpha, gamma)) +class InterlockFourcv (Interlock): + def __init__(self): + Interlock.__init__(self, (fourcv, alpha, delta, gamma, omegaV)) - def check(self, (a, g)): - if a>=g: + def check(self, (p, a, d, g, o)): + if fourcv.isStartingSimultaneousMove(): + a, d, g, o = p + if a>g: return False return True - -#interlock1 = MyInterlock1() + +#interlock1 = InterlockFourcv() ################################################################################################### # Hardware @@ -99,37 +103,6 @@ def set_count_time(value): """ set_setting(COUNT_TIME_PREFERENCE, value ) - - -def get_geometry(): - """ - """ - setting = get_setting(GEOMETRY_PREFERENCE) - if setting is None or (len(setting.strip()) == 0): - return None - return setting - -def set_geometry(value, apply = None): - """ - """ - if value is None or (len(value.strip()) == 0): - set_setting(GEOMETRY_PREFERENCE, "" ) - for name in "wavelength", "hkl_group", "h", "k", "l": - dev = get_device(name) - if dev is not None: - remove_device(dev) - return - filename = get_context().setup.expandPath("{script}/geometry/"+ str(value)+".py") - if not os.path.isfile(filename): - raise Exception("Invalid geometry file: " + value) - former = get_geometry() - if ((apply is None) and former != value) or (apply==True) : - set_setting(GEOMETRY_PREFERENCE, value ) - run(filename) - -def is_geometry_set(): - return get_device("wavelength") is not None - def get_roi(): """ """ @@ -162,6 +135,41 @@ def set_bg_roi(x1, y1, x2, y2): set_setting(BG_ROI_PREFERENCE, str(int(x1)) + " " + str(int(y1)) + " " + str(int(x2)) + " " + str(int(y2)) ) +################################################################################################### +# Context +################################################################################################### + + +def get_geometry(): + """ + """ + setting = get_setting(GEOMETRY_PREFERENCE) + if setting is None or (len(setting.strip()) == 0): + return None + return setting + +def set_geometry(value, apply = None): + """ + """ + if value is None or (len(value.strip()) == 0): + set_setting(GEOMETRY_PREFERENCE, "" ) + for name in "wavelength", "hkl_group", "h", "k", "l": + dev = get_device(name) + if dev is not None: + remove_device(dev) + return + filename = get_context().setup.expandPath("{script}/geometry/"+ str(value)+".py") + if not os.path.isfile(filename): + raise Exception("Invalid geometry file: " + value) + former = get_geometry() + if ((apply is None) and former != value) or (apply==True) : + set_setting(GEOMETRY_PREFERENCE, value ) + run(filename) + +def is_geometry_set(): + return get_device("wavelength") is not None + + ################################################################################################### # Scan callbacks ################################################################################################### @@ -377,12 +385,34 @@ def wait_for_file_size(filepath, size, timeout = None): def set_data_path(path): + """Changes data root path. + """ get_context().setDataPath(path) def set_script_path(path): + """Changes script root path. + """ get_context().setScriptPath(path) +def backup_ub(name=None, destination = "{data}"): + """Copies ub matrix (default= current) to user space. + """ + if not name: + name = ub.ubcalc._state.name + name = name + ".json" + f = settings.persistence_path + "/" + name + if not os.path.isfile(f): + raise Exception("Invalid UB name: " + str(name)) + copyfile(f, get_context().setup.expandPath(destination + "/" +name)) + +def restore_ub(name, origin = "{data}"): + """Restores ub matrix from user space and loads it. + """ + f = settings.persistence_path + "/" + name + ".json" + copyfile(get_context().setup.expandPath(origin + "/" +name + ".json"), f) + loadub(name) + ################################################################################################### # HKL commands ################################################################################################### @@ -496,6 +526,7 @@ def hkllinscan(hstart, hfinish, kstart, kfinish, lstart, lfinish, number_of_step ################################################################################################### set_geometry(get_geometry(),True) +load_exp_context() diff --git a/script/test/Retrieving context.py b/script/test/Retrieving context.py new file mode 100644 index 0000000..651d894 --- /dev/null +++ b/script/test/Retrieving context.py @@ -0,0 +1,5 @@ +print getub() + + +print hklci((44.3400, 0.0000, 123.7322 , 0.0))[0] +print hklca((0, 1, 2))[0] diff --git a/script/test/diffcalc (copy)/.cache/v/cache/lastfailed b/script/test/diffcalc (copy)/.cache/v/cache/lastfailed new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/script/test/diffcalc (copy)/.cache/v/cache/lastfailed @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/script/test/diffcalc (copy)/.gitignore b/script/test/diffcalc (copy)/.gitignore new file mode 100644 index 0000000..edda307 --- /dev/null +++ b/script/test/diffcalc (copy)/.gitignore @@ -0,0 +1,9 @@ +*.class +*~ +*.pyc +doc/build/ +.pydevproject +.project +.tox +.DS_Store +.idea diff --git a/script/test/diffcalc (copy)/.settings/com.wdev91.eclipse.copyright.xml b/script/test/diffcalc (copy)/.settings/com.wdev91.eclipse.copyright.xml new file mode 100644 index 0000000..eb2e1ed --- /dev/null +++ b/script/test/diffcalc (copy)/.settings/com.wdev91.eclipse.copyright.xml @@ -0,0 +1,29 @@ + + + Diamond Light Source Ltd. + .]]> +
+ + + +
+
+ + + +
+
diff --git a/script/test/diffcalc (copy)/.settings/org.eclipse.core.resources.prefs b/script/test/diffcalc (copy)/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..d0a9e4e --- /dev/null +++ b/script/test/diffcalc (copy)/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding//doc/source/conf.py=utf-8 +encoding/=UTF-8 diff --git a/script/test/diffcalc (copy)/.settings/org.eclipse.core.runtime.prefs b/script/test/diffcalc (copy)/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000..5a0ad22 --- /dev/null +++ b/script/test/diffcalc (copy)/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n diff --git a/script/test/diffcalc (copy)/.travis.yml b/script/test/diffcalc (copy)/.travis.yml new file mode 100644 index 0000000..2ee17ea --- /dev/null +++ b/script/test/diffcalc (copy)/.travis.yml @@ -0,0 +1,21 @@ +# based on https://www.topbug.net/blog/2012/05/27/use-travis-ci-with-jython/ + +language: python + +python: + - "2.7" + +env: + - JYTHON=false + - JYTHON=true + +install: + - if [ "$JYTHON" == "true" ]; then . install-jython-environment.sh; fi + - if [ "$JYTHON" == "false" ]; then pip install --upgrade pytest; pip install pytest-xdist; fi + +before_script: + - if [ "$JYTHON" == "true" ]; then export PYTEST=$HOME/jython/bin/pytest; else export PYTEST=pytest; fi + - echo PYTEST:- $PYTEST + +script: $PYTEST + diff --git a/script/test/diffcalc (copy)/COPYING b/script/test/diffcalc (copy)/COPYING new file mode 100644 index 0000000..20d40b6 --- /dev/null +++ b/script/test/diffcalc (copy)/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/script/test/diffcalc (copy)/Makefile b/script/test/diffcalc (copy)/Makefile new file mode 100644 index 0000000..221a775 --- /dev/null +++ b/script/test/diffcalc (copy)/Makefile @@ -0,0 +1,52 @@ + +test-all: test-python test-jython test-integration test-launcher + +test-python: + py.test + +test-jython: + export CLASSPATH=$(HOME)/lib/Jama-1.0.3.jar:$(CLASSPATH); echo $$CLASSPATH; $(HOME)/jython/bin/py.test + +test-integration: + py.test --boxed integration_checks.py + +test-launcher: + ./diffcalc.py --help + ./diffcalc.py --modules + ./diffcalc.py --non-interactive --python sixcircle + +install-jython: + ./install-jython-environment.sh + +doc-source: + ./diffcalc.py --non-interactive --make-manuals-source + +doc-html: + cd doc; make html + +doc-pdf: + cd doc; make pdf + +doc-all: + cd doc; make all + +doc-clean: + cd doc; make clean + +help: + @echo + @echo "Please use \`make ' where is one of" + @echo + @echo " test-all" + @echo " test-python" + @echo " test-jython" + @echo " test-integration" + @echo " test-launcher" + @echo " install-jython" + @echo + @echo " doc-source : to expand *_template.rst to *.rst" + @echo " doc-html" + @echo " doc-pdf" + @echo " doc-all" + @echo " doc-clean" + @echo diff --git a/script/test/diffcalc (copy)/README.rst b/script/test/diffcalc (copy)/README.rst new file mode 100644 index 0000000..95f0ad3 --- /dev/null +++ b/script/test/diffcalc (copy)/README.rst @@ -0,0 +1,513 @@ +Diffcalc - A Diffraction Condition Calculator for Diffractometer Control +======================================================================== + +Diffcalc is a python/jython based diffraction condition calculator used for +controlling diffractometers within reciprocal lattice space. It performs the +same task as the fourc, sixc, twoc, kappa, psic and surf macros from SPEC. + +There is a `user guide `_ and `developer guide `_, both at `diffcalc.readthedocs.io `_ + +|Travis| |Read the docs| + +.. |Travis| image:: https://travis-ci.org/DiamondLightSource/diffcalc.svg?branch=master + :target: https://travis-ci.org/DiamondLightSource/diffcalc + :alt: Build Status + +.. |Read the docs| image:: https://readthedocs.org/projects/diffcalc/badge/?version=latest + :target: http://diffcalc.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +.. contents:: + +.. section-numbering:: + +Software compatibility +---------------------- + +- Written in Python using numpy +- Works in Jython using Jama +- Runs directly in `OpenGDA` +- Runs in in Python or IPython using minimal OpenGda emulation (included) +- Contact us for help running in your environment + +Diffractometer compatibility +---------------------------- + +Diffcalc’s standard calculation engine is an implementation of [You1999]_ and +[Busing1967]_. Diffcalc works with any diffractometer which is a subset of: + + .. image:: https://raw.githubusercontent.com/DiamondLightSource/diffcalc/master/doc/source/youmanual_images/4s_2d_diffractometer.png + :alt: 4s + 2d six-circle diffractometer, from H.You (1999) + :width: 50% + :align: center + +Diffcalc can be configured to work with any diffractometer geometry which is a +subset of this. For example, a five-circle diffractometer might be missing the +nu circle above. + +Note that the first versions of Diffcalc were based on [Vlieg1993]_ and +[Vlieg1998]_ and a ‘Vlieg’ engine is still available. There is also an engine +based on [Willmott2011]_. The ‘You’ engine is more generic and the plan is to +remove the old ‘Vlieg’ engine once beamlines have been migrated. + +Installation +------------ + +Check it out:: + + $ git clone https://github.com/DiamondLightSource/diffcalc.git + Cloning into 'diffcalc'... + +At Diamond Diffcalc may be installed within an OpenGDA deployment and is +available via the 'module' system from bash. + +Starting +-------- + +Start diffcalc in ipython using a sixcircle dummy diffractometer:: + + $ cd diffcalc + $ ./diffcalc.py --help + ... + + $ ./diffcalc.py sixcircle + + Running: "ipython --no-banner --HistoryManager.hist_file=/tmp/ipython_hist_zrb13439.sqlite -i -m diffcmd.start sixcircle False" + + ---------------------------------- DIFFCALC ----------------------------------- + Startup script: '/Users/zrb13439/git/diffcalc/startup/sixcircle.py' + Loading ub calculation: 'test' + ------------------------------------ Help ------------------------------------- + Quick: https://github.com/DiamondLightSource/diffcalc/blob/master/README.rst + Manual: https://diffcalc.readthedocs.io + Type: > help ub + > help hkl + ------------------------------------------------------------------------------- + In [1]: + +Within Diamond use:: + + $ module load diffcalc + $ diffcalc --help + ... + $ diffcalc sixcircle + +Trying it out +------------- + +Type ``demo.all()`` to see it working and then move try the following quick +start guide:: + + >>> demo.all() + ... + +Getting help +------------ + +To view help with orientation and then moving in hkl space:: + + >>> help ub + ... + >>> help hkl + ... + +Configuring a UB calculation +---------------------------- +See the full `user manual for many more +options and an explanation of what this all means. + +To load the last used UB-calculation:: + + >>> lastub + Loading ub calculation: 'mono-Si' + +To load a previous UB-calculation:: + + >>> listub + UB calculations in: /Users/walton/.diffcalc/i16 + + 0) mono-Si 15 Feb 2017 (22:32) + 1) i16-32 13 Feb 2017 (18:32) + + >>> loadub 0 + +To create a new UB-calculation:: + + >>> newub 'example' + >>> setlat '1Acube' 1 1 1 90 90 90 + +Find U matrix from two reflections:: + + >>> pos wl 1 + wl: 1.0000 + >>> c2th [0 0 1] + 59.99999999999999 + + >>> pos sixc [0 60 0 30 90 0] + sixc: mu: 0.0000 delta: 60.0000 gam: 0.0000 eta: 30.0000 chi: 90.0000 phi: 0.0000 + >>> addref [0 0 1] + + >>> pos sixc [0 90 0 45 45 90] + sixc: mu: 0.0000 delta: 90.0000 gam: 0.0000 eta: 45.0000 chi: 45.0000 phi: 90.0000 + >>> addref [0 1 1] + Calculating UB matrix. + + +Check that it looks good:: + + >>> checkub + + ENERGY H K L H_COMP K_COMP L_COMP TAG + 1 12.3984 0.00 0.00 1.00 0.0000 0.0000 1.0000 + 2 12.3984 0.00 1.00 1.00 0.0000 1.0000 1.0000 + +To see the resulting UB-calculation:: + + >>> ub + UBCALC + + name: example + + n_phi: 0.00000 0.00000 1.00000 <- set + n_hkl: -0.00000 0.00000 1.00000 + miscut: None + + CRYSTAL + + name: 1Acube + + a, b, c: 1.00000 1.00000 1.00000 + 90.00000 90.00000 90.00000 + + B matrix: 6.28319 0.00000 0.00000 + 0.00000 6.28319 0.00000 + 0.00000 0.00000 6.28319 + + UB MATRIX + + U matrix: 1.00000 0.00000 0.00000 + 0.00000 1.00000 0.00000 + 0.00000 0.00000 1.00000 + + U angle: 0 + + UB matrix: 6.28319 0.00000 0.00000 + 0.00000 6.28319 0.00000 + 0.00000 0.00000 6.28319 + + REFLECTIONS + + ENERGY H K L MU DELTA GAM ETA CHI PHI TAG + 1 12.398 0.00 0.00 1.00 0.0000 60.0000 0.0000 30.0000 90.0000 0.0000 + 2 12.398 0.00 1.00 1.00 0.0000 90.0000 0.0000 45.0000 45.0000 90.0000 + +Setting the reference vector +---------------------------- +See the full `user manual for many more +options and an explanation of what this all means. + +By default the reference vector is set parallel to the phi axis. That is, +along the z-axis of the phi coordinate frame. + +The `ub` command shows the current reference vector, along with any inferred +miscut, at the top its report (or it can be shown by calling ``setnphi`` or +``setnhkl'`` with no args):: + + >>> ub + ... + n_phi: 0.00000 0.00000 1.00000 <- set + n_hkl: -0.00000 0.00000 1.00000 + miscut: None + ... + +Constraining solutions for moving in hkl space +---------------------------------------------- +See the full `user manual for many more +options and an explanation of what this all means. + +To get help and see current constraints:: + + >>> help con + ... + + >>> con + DET REF SAMP + ------ ------ ------ + delta --> a_eq_b --> mu + --> gam alpha eta + qaz beta chi + naz psi phi + mu_is_gam + + gam : 0.0000 + a_eq_b + mu : 0.0000 + + Type 'help con' for instructions + +Three constraints can be given: zero or one from the DET and REF columns and the +remainder from the SAMP column. Not all combinations are currently available. +Use ``help con`` to see a summary if you run into troubles. + +To configure four-circle vertical scattering:: + + >>> con gam 0 mu 0 a_eq_b + gam : 0.0000 + a_eq_b + mu : 0.0000 + +Moving in hkl space +------------------- + +Simulate moving to a reflection:: + + >>> sim hkl [0 1 1] + sixc would move to: + mu : 0.0000 + delta : 90.0000 + gam : 0.0000 + eta : 45.0000 + chi : 45.0000 + phi : 90.0000 + + alpha : 30.0000 + beta : 30.0000 + naz : 35.2644 + psi : 90.0000 + qaz : 90.0000 + tau : 45.0000 + theta : 45.0000 + +Move to reflection:: + + >>> pos hkl [0 1 1] + hkl: h: 0.00000 k: 1.00000 l: 1.00000 + + >>> pos sixc + sixc: mu: 0.0000 delta: 90.0000 gam: 0.0000 eta: 45.0000 chi: 45.0000 phi: 90.0000 + + +Scanning in hkl space +--------------------- + +Scan an hkl axis (and read back settings):: + + >>> scan l 0 1 .2 sixc + l mu delta gam eta chi phi + ------- ------- -------- ------- -------- ------- -------- + 0.00000 0.0000 60.0000 0.0000 30.0000 0.0000 90.0000 + 0.20000 0.0000 61.3146 0.0000 30.6573 11.3099 90.0000 + 0.40000 0.0000 65.1654 0.0000 32.5827 21.8014 90.0000 + 0.60000 0.0000 71.3371 0.0000 35.6685 30.9638 90.0000 + 0.80000 0.0000 79.6302 0.0000 39.8151 38.6598 90.0000 + 1.00000 0.0000 90.0000 0.0000 45.0000 45.0000 90.0000 + +Scan a constraint (and read back virtual angles and eta):: + + >>> con psi + gam : 0.0000 + ! psi : --- + mu : 0.0000 + >>> scan psi 70 110 10 hklverbose [0 1 1] eta + psi eta h k l theta qaz alpha naz tau psi beta + -------- -------- ------- ------- ------- -------- -------- -------- -------- -------- -------- -------- + 70.00000 26.1183 0.00000 1.00000 1.00000 45.00000 90.00000 19.20748 45.28089 45.00000 70.00000 42.14507 + 80.00000 35.1489 -0.00000 1.00000 1.00000 45.00000 90.00000 24.40450 40.12074 45.00000 80.00000 35.93196 + 90.00000 45.0000 0.00000 1.00000 1.00000 45.00000 90.00000 30.00000 35.26439 45.00000 90.00000 30.00000 + 100.00000 54.8511 -0.00000 1.00000 1.00000 45.00000 90.00000 35.93196 30.68206 45.00000 100.00000 24.40450 + 110.00000 63.8817 -0.00000 1.00000 1.00000 45.00000 90.00000 42.14507 26.34100 45.00000 110.00000 19.20748 + + +Orientation Commands +-------------------- + ++-----------------------------+---------------------------------------------------+ +| **STATE** | ++-----------------------------+---------------------------------------------------+ +| **-- newub** {'name'} | start a new ub calculation name | ++-----------------------------+---------------------------------------------------+ +| **-- loadub** 'name' | num | load an existing ub calculation | ++-----------------------------+---------------------------------------------------+ +| **-- lastub** | load the last used ub calculation | ++-----------------------------+---------------------------------------------------+ +| **-- listub** | list the ub calculations available to load | ++-----------------------------+---------------------------------------------------+ +| **-- rmub** 'name'|num | remove existing ub calculation | ++-----------------------------+---------------------------------------------------+ +| **-- saveubas** 'name' | save the ub calculation with a new name | ++-----------------------------+---------------------------------------------------+ +| **LATTICE** | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** | interactively enter lattice parameters (Angstroms | +| | and Deg) | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** name a | assumes cubic | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** name a b | assumes tetragonal | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** name a b c | assumes ortho | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** name a b c | assumes mon/hex with gam not equal to 90 | +| gamma | | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** name a b c | arbitrary | +| alpha beta gamma | | ++-----------------------------+---------------------------------------------------+ +| **-- c2th** [h k l] | calculate two-theta angle for reflection | ++-----------------------------+---------------------------------------------------+ +| **-- hklangle** [h1 k1 l1] | calculate angle between [h1 k1 l1] and [h2 k2 l2] | +| [h2 k2 l2] | crystal planes | ++-----------------------------+---------------------------------------------------+ +| **REFERENCE (SURFACE)** | ++-----------------------------+---------------------------------------------------+ +| **-- setnphi** {[x y z]} | sets or displays n_phi reference | ++-----------------------------+---------------------------------------------------+ +| **-- setnhkl** {[h k l]} | sets or displays n_hkl reference | ++-----------------------------+---------------------------------------------------+ +| **REFLECTIONS** | ++-----------------------------+---------------------------------------------------+ +| **-- showref** | shows full reflection list | ++-----------------------------+---------------------------------------------------+ +| **-- addref** | add reflection interactively | ++-----------------------------+---------------------------------------------------+ +| **-- addref** [h k l] | add reflection with current position and energy | +| {'tag'} | | ++-----------------------------+---------------------------------------------------+ +| **-- addref** [h k l] (p1, | add arbitrary reflection | +| .., pN) energy {'tag'} | | ++-----------------------------+---------------------------------------------------+ +| **-- editref** num | interactively edit a reflection | ++-----------------------------+---------------------------------------------------+ +| **-- delref** num | deletes a reflection (numbered from 1) | ++-----------------------------+---------------------------------------------------+ +| **-- clearref** | deletes all the reflections | ++-----------------------------+---------------------------------------------------+ +| **-- swapref** | swaps first two reflections used for calculating | +| | U matrix | ++-----------------------------+---------------------------------------------------+ +| **-- swapref** num1 num2 | swaps two reflections (numbered from 1) | ++-----------------------------+---------------------------------------------------+ +| **CRYSTAL ORIENTATIONS** | ++-----------------------------+---------------------------------------------------+ +| **-- showorient** | shows full list of crystal orientations | ++-----------------------------+---------------------------------------------------+ +| **-- addorient** | add crystal orientation interactively | ++-----------------------------+---------------------------------------------------+ +| **-- addorient** [h k l] | add crystal orientation in laboratory frame | +| [x y z] {'tag'} | | ++-----------------------------+---------------------------------------------------+ +| **-- editorient** num | interactively edit a crystal orientation | ++-----------------------------+---------------------------------------------------+ +| **-- delorient** num | deletes a crystal orientation (numbered from 1) | ++-----------------------------+---------------------------------------------------+ +| **-- clearorient** | deletes all the crystal orientations | ++-----------------------------+---------------------------------------------------+ +| **-- swaporient** | swaps first two crystal orientations used for | +| | calculating U matrix | ++-----------------------------+---------------------------------------------------+ +| **-- swaporient** num1 num2 | swaps two crystal orientations (numbered from 1) | ++-----------------------------+---------------------------------------------------+ +| **UB MATRIX** | ++-----------------------------+---------------------------------------------------+ +| **-- checkub** | show calculated and entered hkl values for | +| | reflections | ++-----------------------------+---------------------------------------------------+ +| **-- setu** | manually set u matrix | +| {[[..][..][..]]} | | ++-----------------------------+---------------------------------------------------+ +| **-- setub** | manually set ub matrix | +| {[[..][..][..]]} | | ++-----------------------------+---------------------------------------------------+ +| **-- calcub** | (re)calculate u matrix from ref1 and ref2 | ++-----------------------------+---------------------------------------------------+ +| **-- trialub** | (re)calculate u matrix from ref1 only (check | +| | carefully) | ++-----------------------------+---------------------------------------------------+ +| **-- refineub** {[h k l]} | refine unit cell dimensions and U matrix to match | +| {pos} | diffractometer angles for a given hkl value | ++-----------------------------+---------------------------------------------------+ +| **-- addmiscut** angle | apply miscut to U matrix using a specified miscut | +| {[x y z]} | angle in degrees and a rotation axis | +| | (default: [0 1 0]) | ++-----------------------------+---------------------------------------------------+ +| **-- setmiscut** angle | manually set U matrix using a specified miscut | +| {[x y z]} | angle in degrees and a rotation axis | +| | (default: [0 1 0]) | ++-----------------------------+---------------------------------------------------+ + +Motion Commands +--------------- + ++-----------------------------+---------------------------------------------------+ +| **CONSTRAINTS** | ++-----------------------------+---------------------------------------------------+ +| **-- con** | list available constraints and values | ++-----------------------------+---------------------------------------------------+ +| **-- con** {val} | constrains and optionally sets one constraint | ++-----------------------------+---------------------------------------------------+ +| **-- con** {val} | clears and then fully constrains | +| {val} {val} | | ++-----------------------------+---------------------------------------------------+ +| **-- uncon** | remove constraint | ++-----------------------------+---------------------------------------------------+ +| **HKL** | ++-----------------------------+---------------------------------------------------+ +| **-- allhkl** [h k l] | print all hkl solutions ignoring limits | ++-----------------------------+---------------------------------------------------+ +| **HARDWARE** | ++-----------------------------+---------------------------------------------------+ +| **-- hardware** | show diffcalc limits and cuts | ++-----------------------------+---------------------------------------------------+ +| **-- setcut** {name {val}} | sets cut angle | ++-----------------------------+---------------------------------------------------+ +| **-- setmin** {axis {val}} | set lower limits used by auto sector code (None | +| | to clear) | ++-----------------------------+---------------------------------------------------+ +| **-- setmax** {name {val}} | sets upper limits used by auto sector code (None | +| | to clear) | ++-----------------------------+---------------------------------------------------+ +| **MOTION** | ++-----------------------------+---------------------------------------------------+ +| **-- sim** hkl scn | simulates moving scannable (not all) | ++-----------------------------+---------------------------------------------------+ +| **-- sixc** | show Eularian position | ++-----------------------------+---------------------------------------------------+ +| **-- pos** sixc [mu, delta, | move to Eularian position(None holds an axis | +| gam, eta, chi, phi] | still) | ++-----------------------------+---------------------------------------------------+ +| **-- sim** sixc [mu, delta, | simulate move to Eulerian positionsixc | +| gam, eta, chi, phi] | | ++-----------------------------+---------------------------------------------------+ +| **-- hkl** | show hkl position | ++-----------------------------+---------------------------------------------------+ +| **-- pos** hkl [h k l] | move to hkl position | ++-----------------------------+---------------------------------------------------+ +| **-- pos** {h | k | l} val | move h, k or l to val | ++-----------------------------+---------------------------------------------------+ +| **-- sim** hkl [h k l] | simulate move to hkl position | ++-----------------------------+---------------------------------------------------+ + + +References +---------- + +.. [You1999] H. You. *Angle calculations for a '4S+2D' six-circle diffractometer.* + J. Appl. Cryst. (1999). **32**, 614-623. `(pdf link) + `__. + +.. [Busing1967] W. R. Busing and H. A. Levy. *Angle calculations for 3- and 4-circle X-ray + and neutron diffractometers.* Acta Cryst. (1967). **22**, 457-464. `(pdf link) + `__. + +.. [Vlieg1993] Martin Lohmeier and Elias Vlieg. *Angle calculations for a six-circle + surface x-ray diffractometer.* J. Appl. Cryst. (1993). **26**, 706-716. `(pdf link) + `__. + +.. [Vlieg1998] Elias Vlieg. *A (2+3)-type surface diffractometer: mergence of the z-axis and + (2+2)-type geometries.* J. Appl. Cryst. (1998). **31**, 198-203. `(pdf link) + `__. + +.. [Willmott2011] C. M. Schlepütz, S. O. Mariager, S. A. Pauli, R. Feidenhans'l and + P. R. Willmott. *Angle calculations for a (2+3)-type diffractometer: focus + on area detectors.* J. Appl. Cryst. (2011). **44**, 73-83. `(pdf link) + `__. diff --git a/script/test/diffcalc (copy)/README_template.rst b/script/test/diffcalc (copy)/README_template.rst new file mode 100644 index 0000000..f62cc5f --- /dev/null +++ b/script/test/diffcalc (copy)/README_template.rst @@ -0,0 +1,267 @@ +Diffcalc - A Diffraction Condition Calculator for Diffractometer Control +======================================================================== + +Diffcalc is a python/jython based diffraction condition calculator used for +controlling diffractometers within reciprocal lattice space. It performs the +same task as the fourc, sixc, twoc, kappa, psic and surf macros from SPEC. + +There is a `user guide `_ and `developer guide `_, both at `diffcalc.readthedocs.io `_ + +|Travis| |Read the docs| + +.. |Travis| image:: https://travis-ci.org/DiamondLightSource/diffcalc.svg?branch=master + :target: https://travis-ci.org/DiamondLightSource/diffcalc + :alt: Build Status + +.. |Read the docs| image:: https://readthedocs.org/projects/diffcalc/badge/?version=latest + :target: http://diffcalc.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +.. contents:: + +.. section-numbering:: + +Software compatibility +---------------------- + +- Written in Python using numpy +- Works in Jython using Jama +- Runs directly in `OpenGDA` +- Runs in in Python or IPython using minimal OpenGda emulation (included) +- Contact us for help running in your environment + +Diffractometer compatibility +---------------------------- + +Diffcalc’s standard calculation engine is an implementation of [You1999]_ and +[Busing1967]_. Diffcalc works with any diffractometer which is a subset of: + + .. image:: https://raw.githubusercontent.com/DiamondLightSource/diffcalc/master/doc/source/youmanual_images/4s_2d_diffractometer.png + :alt: 4s + 2d six-circle diffractometer, from H.You (1999) + :width: 50% + :align: center + +Diffcalc can be configured to work with any diffractometer geometry which is a +subset of this. For example, a five-circle diffractometer might be missing the +nu circle above. + +Note that the first versions of Diffcalc were based on [Vlieg1993]_ and +[Vlieg1998]_ and a ‘Vlieg’ engine is still available. There is also an engine +based on [Willmott2011]_. The ‘You’ engine is more generic and the plan is to +remove the old ‘Vlieg’ engine once beamlines have been migrated. + +If we choose the x axis parallel to b, the yaxis intheplaneofblandb2,andthezaxis perpendicular to that plane, + +Installation +------------ + +Check it out:: + + $ git clone https://github.com/DiamondLightSource/diffcalc.git + Cloning into 'diffcalc'... + +At Diamond Diffcalc may be installed within an OpenGDA deployment and is +available via the 'module' system from bash. + +Starting +-------- + +Start diffcalc in ipython using a sixcircle dummy diffractometer:: + + $ cd diffcalc + $ ./diffcalc.py --help + ... + + $ ./diffcalc.py sixcircle + + Running: "ipython --no-banner --HistoryManager.hist_file=/tmp/ipython_hist_zrb13439.sqlite -i -m diffcmd.start sixcircle False" + + ---------------------------------- DIFFCALC ----------------------------------- + Startup script: '/Users/zrb13439/git/diffcalc/startup/sixcircle.py' + Loading ub calculation: 'test' + ------------------------------------ Help ------------------------------------- + Quick: https://github.com/DiamondLightSource/diffcalc/blob/master/README.rst + Manual: https://diffcalc.readthedocs.io + Type: > help ub + > help hkl + ------------------------------------------------------------------------------- + In [1]: + +Within Diamond use:: + + $ module load diffcalc + $ diffcalc --help + ... + $ diffcalc sixcircle + +Trying it out +------------- + +Type ``demo.all()`` to see it working and then move try the following quick +start guide:: + + >>> demo.all() + ... + +Getting help +------------ + +To view help with orientation and then moving in hkl space:: + + >>> help ub + ... + >>> help hkl + ... + +Configuring a UB calculation +---------------------------- +See the full `user manual for many more +options and an explanation of what this all means. + +To load the last used UB-calculation:: + + >>> lastub + Loading ub calculation: 'mono-Si' + +To load a previous UB-calculation:: + + >>> listub + UB calculations in: /Users/walton/.diffcalc/i16 + + 0) mono-Si 15 Feb 2017 (22:32) + 1) i16-32 13 Feb 2017 (18:32) + + >>> loadub 0 + +To create a new UB-calculation:: + + ==> newub 'example' + ==> setlat '1Acube' 1 1 1 90 90 90 + +where the basis is defined by Busing & Levy: + + "...we choose the x axis parallel to b, the y axis in the plane of bl + and b2, and the zaxis perpendicular to that plane." + + +Find U matrix from two reflections:: + + ==> pos wl 1 + ==> c2th [0 0 1] + 59.99999999999999 + + ==> pos sixc [0 60 0 30 90 0] + ==> addref [0 0 1] + + ==> pos sixc [0 90 0 45 45 90] + ==> addref [0 1 1] + + +Check that it looks good:: + + ==> checkub + +To see the resulting UB-calculation:: + + ==> ub + +Setting the reference vector +---------------------------- +See the full `user manual for many more +options and an explanation of what this all means. + +By default the reference vector is set parallel to the phi axis. That is, +along the z-axis of the phi coordinate frame. + +The `ub` command shows the current reference vector, along with any inferred +miscut, at the top its report (or it can be shown by calling ``setnphi`` or +``setnhkl'`` with no args):: + + >>> ub + ... + n_phi: 0.00000 0.00000 1.00000 <- set + n_hkl: -0.00000 0.00000 1.00000 + miscut: None + ... + +Constraining solutions for moving in hkl space +---------------------------------------------- +See the full `user manual for many more +options and an explanation of what this all means. + +To get help and see current constraints:: + + >>> help con + ... + + ==> con + +Three constraints can be given: zero or one from the DET and REF columns and the +remainder from the SAMP column. Not all combinations are currently available. +Use ``help con`` to see a summary if you run into troubles. + +To configure four-circle vertical scattering:: + + ==> con gam 0 mu 0 a_eq_b + +Moving in hkl space +------------------- + +Simulate moving to a reflection:: + + ==> sim hkl [0 1 1] + +Move to reflection:: + + ==> pos hkl [0 1 1] + + ==> pos sixc + + +Scanning in hkl space +--------------------- + +Scan an hkl axis (and read back settings):: + + ==> scan l 0 1 .2 sixc + +Scan a constraint (and read back virtual angles and eta):: + + ==> con psi + ==> scan psi 70 110 10 hklverbose [0 1 1] eta + + +Orientation Commands +-------------------- + +==> UB_HELP_TABLE + +Motion Commands +--------------- + +==> HKL_HELP_TABLE + + +References +---------- + +.. [You1999] H. You. *Angle calculations for a '4S+2D' six-circle diffractometer.* + J. Appl. Cryst. (1999). **32**, 614-623. `(pdf link) + `__. + +.. [Busing1967] W. R. Busing and H. A. Levy. *Angle calculations for 3- and 4-circle X-ray + and neutron diffractometers.* Acta Cryst. (1967). **22**, 457-464. `(pdf link) + `__. + +.. [Vlieg1993] Martin Lohmeier and Elias Vlieg. *Angle calculations for a six-circle + surface x-ray diffractometer.* J. Appl. Cryst. (1993). **26**, 706-716. `(pdf link) + `__. + +.. [Vlieg1998] Elias Vlieg. *A (2+3)-type surface diffractometer: mergence of the z-axis and + (2+2)-type geometries.* J. Appl. Cryst. (1998). **31**, 198-203. `(pdf link) + `__. + +.. [Willmott2011] C. M. Schlepütz, S. O. Mariager, S. A. Pauli, R. Feidenhans'l and + P. R. Willmott. *Angle calculations for a (2+3)-type diffractometer: focus + on area detectors.* J. Appl. Cryst. (2011). **44**, 73-83. `(pdf link) + `__. diff --git a/script/test/diffcalc (copy)/buckminster.cspec b/script/test/diffcalc (copy)/buckminster.cspec new file mode 100644 index 0000000..745b4c3 --- /dev/null +++ b/script/test/diffcalc (copy)/buckminster.cspec @@ -0,0 +1,5 @@ + + + + + diff --git a/script/test/diffcalc (copy)/diffcalc.py b/script/test/diffcalc (copy)/diffcalc.py new file mode 100755 index 0000000..66be493 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc.py @@ -0,0 +1,9 @@ +#!/usr/bin/python + +import sys + +from diffcmd.diffcalc_launcher import main + + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/__init__.py b/script/test/diffcalc (copy)/diffcalc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcalc/dc/__init__.py b/script/test/diffcalc (copy)/diffcalc/dc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcalc/dc/common.py b/script/test/diffcalc (copy)/diffcalc/dc/common.py new file mode 100644 index 0000000..9793f71 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/dc/common.py @@ -0,0 +1,24 @@ +from diffcalc.util import allnum, command, DiffcalcException + + +def sim(scn, hkl): + """sim hkl scn -- simulates moving scannable (not all) + """ + if not isinstance(hkl, (tuple, list)): + raise TypeError() + + if not allnum(hkl): + raise TypeError() + + try: + print scn.simulateMoveTo(hkl) + except AttributeError: + raise TypeError( + "The first argument does not support simulated moves") + +def energy_to_wavelength(energy): + try: + return 12.39842 / energy + except ZeroDivisionError: + raise DiffcalcException( + "Cannot calculate hkl position as Energy is set to 0") \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/dc/dcvlieg.py b/script/test/diffcalc (copy)/diffcalc/dc/dcvlieg.py new file mode 100644 index 0000000..b1fb5da --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/dc/dcvlieg.py @@ -0,0 +1,76 @@ +from diffcalc.dc.common import energy_to_wavelength + +from diffcalc import settings +from diffcalc.hkl.vlieg.transform import VliegTransformSelector,\ + TransformCommands, VliegPositionTransformer +from diffcalc.dc.help import compile_extra_motion_commands_for_help +import diffcalc.hkl.vlieg.calc + + +# reload to aid testing only +import diffcalc.ub.ub as _ub +reload(_ub) +from diffcalc import hardware as _hardware +#reload(_hardware) +import diffcalc.hkl.vlieg.hkl as _hkl +reload(_hkl) + +from diffcalc.ub.ub import * # @UnusedWildImport +from diffcalc.hardware import * # @UnusedWildImport +from diffcalc.hkl.vlieg.hkl import * # @UnusedWildImport +from diffcalc.gdasupport.scannable.sim import sim + +_transform_selector = VliegTransformSelector() +_transform_commands = TransformCommands(_transform_selector) +_transformer = VliegPositionTransformer(settings.geometry, settings.hardware, + _transform_selector) + +transform = _transform_commands.transform +transforma = _transform_commands.transforma +transformb = _transform_commands.transformb +transformc = _transform_commands.transformc + + +on = 'on' +off = 'off' +auto = 'auto' +manual = 'manual' + +def hkl_to_angles(h, k, l, energy=None): + """Convert a given hkl vector to a set of diffractometer angles""" + if energy is None: + energy = settings.hardware.get_energy() # @UndefinedVariable + + position, params = hklcalc.hklToAngles(h, k, l, energy_to_wavelength(energy)) + position = _transformer.transform(position) + angle_tuple = settings.geometry.internal_position_to_physical_angles(position) # @UndefinedVariable + angle_tuple = settings.hardware.cut_angles(angle_tuple) # @UndefinedVariable + + return angle_tuple, params + + +def angles_to_hkl(angleTuple, energy=None): + """Converts a set of diffractometer angles to an hkl position + ((h, k, l), paramDict)=angles_to_hkl(self, (a1, a2,aN), energy=None)""" + if energy is None: + energy = settings.hardware.get_energy() # @UndefinedVariable + + i_pos = settings.geometry.physical_angles_to_internal_position(angleTuple) # @UndefinedVariable + return hklcalc.anglesToHkl(i_pos, energy_to_wavelength(energy)) + + +settings.ubcalc_strategy = diffcalc.hkl.vlieg.calc.VliegUbCalcStrategy() +settings.angles_to_hkl_function = diffcalc.hkl.vlieg.calc.vliegAnglesToHkl +settings.include_sigtau = True + +ub_commands_for_help = _ub.commands_for_help + +hkl_commands_for_help = (_hkl.commands_for_help + + _hardware.commands_for_help + + ['Transform', + transform, + transforma, + transformb, + transformc] + + compile_extra_motion_commands_for_help()) + diff --git a/script/test/diffcalc (copy)/diffcalc/dc/dcwillmot.py b/script/test/diffcalc (copy)/diffcalc/dc/dcwillmot.py new file mode 100644 index 0000000..7bd8a87 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/dc/dcwillmot.py @@ -0,0 +1,47 @@ +# This file differs from dcyou in only two places + +from diffcalc import settings +from diffcalc.dc.common import energy_to_wavelength +from diffcalc.dc.help import compile_extra_motion_commands_for_help +import diffcalc.hkl.willmott.calc + + +# reload to aid testing only +from diffcalc.ub import ub as _ub +reload(_ub) +from diffcalc import hardware as _hardware +#reload(_hardware) +from diffcalc.hkl.you import hkl as _hkl +reload(_hkl) + +from diffcalc.ub.ub import * # @UnusedWildImport +from diffcalc.hardware import * # @UnusedWildImport +from diffcalc.hkl.willmot.hkl import * # @UnusedWildImport +from diffcalc.gdasupport.scannable.sim import sim + +def hkl_to_angles(h, k, l, energy=None): + """Convert a given hkl vector to a set of diffractometer angles""" + if energy is None: + energy = settings.hardware.get_energy() # @UndefinedVariable + + (pos, params) = hklcalc.hklToAngles(h, k, l, energy_to_wavelength(energy)) + angle_tuple = settings.geometry.internal_position_to_physical_angles(pos) # @UndefinedVariable + angle_tuple = settings.hardware.cut_angles(angle_tuple) # @UndefinedVariable + + return angle_tuple, params + +def angles_to_hkl(angleTuple, energy=None): + """Converts a set of diffractometer angles to an hkl position + ((h, k, l), paramDict)=angles_to_hkl(self, (a1, a2,aN), energy=None)""" + if energy is None: + energy = settings.hardware.get_energy() # @UndefinedVariable + i_pos = settings.geometry.physical_angles_to_internal_position(angleTuple) # @UndefinedVariable + return hklcalc.anglesToHkl(i_pos, energy_to_wavelength(energy)) + +settings.ubcalc_strategy = diffcalc.hkl.willmott.calc.WillmottHorizontalUbCalcStrategy() +settings.angles_to_hkl_function = diffcalc.hkl.willmott.calc.angles_to_hkl + + +ub_commands_for_help = _ub.commands_for_help + +hkl_commands_for_help = _hkl.commands_for_help + _hardware.commands_for_help + compile_extra_motion_commands_for_help() diff --git a/script/test/diffcalc (copy)/diffcalc/dc/dcyou.py b/script/test/diffcalc (copy)/diffcalc/dc/dcyou.py new file mode 100644 index 0000000..651c2fe --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/dc/dcyou.py @@ -0,0 +1,56 @@ +from diffcalc import settings +from diffcalc.dc.common import energy_to_wavelength +from diffcalc.dc.help import compile_extra_motion_commands_for_help + +import diffcalc.hkl.you.calc +settings.ubcalc_strategy = diffcalc.hkl.you.calc.YouUbCalcStrategy() +settings.angles_to_hkl_function = diffcalc.hkl.you.calc.youAnglesToHkl +settings.include_reference = True + +# reload to aid testing only +from diffcalc.ub import ub as _ub + +reload(_ub) +from diffcalc import hardware as _hardware +#reload(_hardware) +from diffcalc.hkl.you import hkl as _hkl +reload(_hkl) + +from diffcalc.ub.ub import * # @UnusedWildImport +from diffcalc.hardware import * # @UnusedWildImport +from diffcalc.hkl.you.hkl import * # @UnusedWildImport + + +def hkl_to_angles(h, k, l, energy=None): + """Convert a given hkl vector to a set of diffractometer angles + + return angle tuple and params dictionary + + """ + if energy is None: + energy = settings.hardware.get_energy() # @UndefinedVariable + + (pos, params) = hklcalc.hklToAngles(h, k, l, energy_to_wavelength(energy)) + angle_tuple = settings.geometry.internal_position_to_physical_angles(pos) # @UndefinedVariable + angle_tuple = settings.hardware.cut_angles(angle_tuple) # @UndefinedVariable + + return angle_tuple, params + + +def angles_to_hkl(angleTuple, energy=None): + """Converts a set of diffractometer angles to an hkl position + + Return hkl tuple and params dictionary + + """ + if energy is None: + energy = settings.hardware.get_energy() # @UndefinedVariable + i_pos = settings.geometry.physical_angles_to_internal_position(angleTuple) # @UndefinedVariable + return hklcalc.anglesToHkl(i_pos, energy_to_wavelength(energy)) + + + + + +ub_commands_for_help = _ub.commands_for_help +hkl_commands_for_help = _hkl.commands_for_help + _hardware.commands_for_help + compile_extra_motion_commands_for_help() diff --git a/script/test/diffcalc (copy)/diffcalc/dc/help.py b/script/test/diffcalc (copy)/diffcalc/dc/help.py new file mode 100644 index 0000000..5d4afe6 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/dc/help.py @@ -0,0 +1,161 @@ +''' +Created on 6 May 2016 + +@author: walton +''' +from diffcalc import settings +from diffcalc.gdasupport.scannable.sim import sim +import textwrap +from diffcalc.util import bold + + +class ExternalCommand(object): + """Instances found in a command_list by format_command_help will + result in documentation for a command without there actually being one. + """ + def __init__(self, docstring): + """Set the docstring that will be pulled off by format_command_help. + """ + self.__doc__ = docstring + self.__name__ = '' + + +WIDTH = 27 +INDENT = 3 + + +def format_command_help(command_list): + + row_list = _command_list_to_table_cells(command_list) + lines = [] + for row_cells in row_list: + if len(row_cells) == 1: + heading = row_cells[0] + lines.append('') + lines.append(bold(heading)) + lines.append('') + elif len(row_cells) == 2: + cell1, cell2 = row_cells + + cell1_lines = textwrap.wrap(cell1, WIDTH, subsequent_indent=' ') + cell2_lines = textwrap.wrap(cell2, 79 - INDENT - 3 - WIDTH) + + first_line = True + while cell1_lines or cell2_lines: + line = ' ' * INDENT + if cell1_lines: + line += cell1_lines.pop(0).ljust(WIDTH) + else: + line += ' ' * (WIDTH) + line += ' : ' if first_line else ' ' + if cell2_lines: + line += cell2_lines.pop(0) + lines.append(line) + first_line = False + + return '\n'.join(lines) + + +def format_commands_for_rst_table(title, command_list): + W1 = WIDTH # internal width + W2 = 79 - W1 - 3 # internal width + HORIZ_LINE = '+-' + '-' * W1 + '-+-' + '-' * W2 + '-+' + + row_list = _command_list_to_table_cells(command_list) + + lines = [] + + lines.append(HORIZ_LINE) # Top line + for row_cells in row_list: + if len(row_cells) == 1: + lines.append('| ' + ('**' + row_cells[0] + '**').ljust(W1 + W2 + 3) + ' |') + + elif len(row_cells) == 2: + cmd_and_args = row_cells[0].split(' ', 1) + cmd = cmd_and_args[0] + args = cmd_and_args[1] if len(cmd_and_args) == 2 else '' + cell1 = '**-- %s** %s' % (cmd, args) + cell1_lines = textwrap.wrap(cell1, W1) #, subsequent_indent=' ') + cell2_lines = textwrap.wrap(row_cells[1], W2) + + while cell1_lines or cell2_lines: + line = '| ' + line += (cell1_lines.pop(0) if cell1_lines else '').ljust(W1) + line += ' | ' + line += (cell2_lines.pop(0) if cell2_lines else '').ljust(W2) + line += ' |' + lines.append(line) + + else: + assert False + + lines.append(HORIZ_LINE) + return lines + + + + + +def _command_list_to_table_cells(command_list): + row_list = [] + for obj in command_list: + + if isinstance(obj, basestring): # group heading + row_list.append([obj.upper()]) + + else: # individual command + doc_before_empty_line = obj.__doc__.split('\n\n')[0] + doc_lines = [s.strip() for s in doc_before_empty_line.split('\n')] + for doc_line in doc_lines: + if doc_line == '': + continue + if obj.__name__ in ('ub', 'hkl'): + continue + name, args, desc = _split_doc_line(doc_line) + desc = desc.strip() + args = args.strip() + if desc and desc[-1] == '.': + desc = desc[:-1] + + row_list.append([name + (' ' if args else '') + args, desc]) + + return row_list + + +def _split_doc_line(docLine): + name, _, right = docLine.partition(' ') + args, _, desc = right.partition('-- ') + return name, args, desc + + +def compile_extra_motion_commands_for_help(): + + _hwname = settings.hardware.name # @UndefinedVariable + _angles = ', '.join(settings.hardware.get_axes_names()) # @UndefinedVariable + + commands = [] + + commands.append('Motion') + commands.append(sim) + commands.append(ExternalCommand( + '%(_hwname)s -- show Eularian position' % vars())) + commands.append(ExternalCommand( + 'pos %(_hwname)s [%(_angles)s] -- move to Eularian position' + '(None holds an axis still)' % vars())) + commands.append(ExternalCommand( + 'sim %(_hwname)s [%(_angles)s] -- simulate move to Eulerian position' + '%(_hwname)s' % vars())) + + commands.append(ExternalCommand( + 'hkl -- show hkl position')) + commands.append(ExternalCommand( + 'pos hkl [h k l] -- move to hkl position')) + commands.append(ExternalCommand( + 'pos {h | k | l} val -- move h, k or l to val')) + commands.append(ExternalCommand( + 'sim hkl [h k l] -- simulate move to hkl position')) + +# if engine_name != 'vlieg': +# pass +# # TODO: remove sigtau command and 'Surface' string + return commands \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/__init__.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/minigda/__init__.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/minigda/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/minigda/command.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/minigda/command.py new file mode 100644 index 0000000..d6e7fc5 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/minigda/command.py @@ -0,0 +1,322 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +#try: +# from gda.device import Scannable +#except ImportError: +# from diffcalc.gdasupport.minigda.scannable import Scannable +from diffcalc.gdasupport.minigda.scannable import Scannable +from diffcalc.util import getMessageFromException, allnum, bold +import math + + +ROOT_NAMESPACE_DICT = {} + +class Pos(object): + + def __init__(self): + self.__name__ = 'pos' + + def __call__(self, *posargs): + if len(posargs) == 0: + + keys = dict(ROOT_NAMESPACE_DICT).keys() + keys.sort() + for key in keys: + val = ROOT_NAMESPACE_DICT[key] + if isinstance(val, Scannable): + print self.posReturningReport(val) + else: + print self.posReturningReport(*posargs) + + def posReturningReport(self, *posargs): + # report position of this scannable + if len(posargs) == 1: + scannable = posargs[0] + self._assert_scannable(scannable) + return self._generatePositionReport(scannable) + + # Move the scannable and report + elif len(posargs) == 2: + scannable = posargs[0] + self._assert_scannable(scannable) + # Move it + scannable.asynchronousMoveTo(posargs[1]) + # TODO: minigda assumes all moves complete instantly, so no need + # yet to check the move is complete + return self._generatePositionReport(scannable) + + else: + raise ValueError( + "Invlaid arguements: 'pos [ scannable [ value ] ]'") + + def _assert_scannable(self, obj): + if not isinstance(obj, Scannable): + raise TypeError( + "The first argument to the pos command must be scannable. " + "Not: " + str(type(obj))) + + def _generatePositionReport(self, scannable): + fieldNames = (tuple(scannable.getInputNames()) + + tuple(scannable.getExtraNames())) + # All scannables + result = "%s:" % scannable.getName() + result = result.ljust(10) + try: + pos = scannable.getPosition() + except Exception, e: + return result + "Error: %s" % getMessageFromException(e) + if pos is None: + return result + "---" + # Single field scannable: + if len(fieldNames) == 1: + try: + result += "%s" % scannable.formatPositionFields(pos)[0] + except AttributeError: + result += str(scannable()) + # Multi field scannable: + else: + try: + formatted = scannable.formatPositionFields(pos) + for name, formattedValue in zip(fieldNames, formatted): + result += "%s: %s " % (name, formattedValue) + except AttributeError: + result += str(scannable()) + + return result + + +class ScanDataHandler: + def __init__(self): + self.scannables = None + + def callAtScanStart(self, scannables): + pass + + def callWithScanPoint(self, PositionDictIndexedByScannable): + pass + + def callAtScanEnd(self): + pass + + +class ScanDataPrinter(ScanDataHandler): + + def __init__(self): + self.first_point_printed = False + self.widths = [] + self.scannables = [] + + def callAtScanStart(self, scannables): + self.first_point_printed = False + self.scannables = scannables + + def print_first_point(self, position_dict): + # also sets self.widths + header_strings = [] + for scn in self.scannables: + field_names = list(scn.getInputNames()) + list(scn.getExtraNames()) + if len(field_names) == 1: + header_strings.append(scn.getName()) + else: + for field_name in field_names: + header_strings.append(field_name) + + first_row_strings = [] + for scn in self.scannables: + pos = position_dict[scn] + first_row_strings.extend(scn.formatPositionFields(pos)) + + self.widths = [] + for header, pos_string in zip(header_strings, first_row_strings): + self.widths.append(max(len(header), len(pos_string))) + + header_cells = [] + for heading, width in zip(header_strings, self.widths): + header_cells.append(heading.rjust(width)) + + underline_cells = ['-' * w for w in self.widths] + + first_row_cells = [] + for pos, width in zip(first_row_strings, self.widths): + first_row_cells.append(pos.rjust(width)) + + #table_width = sum(self.widths) + len(self.widths * 2) - 2 + lines = [] + #lines.append('=' * table_width) + lines.append(bold(' '.join(header_cells))) + lines.append(' '.join(underline_cells)) + lines.append(' '.join(first_row_cells)) + print '\n'.join(lines) + + def callWithScanPoint(self, position_dict): + if not self.first_point_printed: + self.print_first_point(position_dict) + self.first_point_printed = True + else: + row_strings = [] + for scn in self.scannables: + pos = position_dict[scn] + row_strings.extend(scn.formatPositionFields(pos)) + + row_cells = [] + for pos, width in zip(row_strings, self.widths): + row_cells.append(pos.rjust(width)) + + print ' '.join(row_cells) + + def callAtScanEnd(self): + #table_width = sum(self.widths) + len(self.widths * 2) - 2 + #print '=' * table_width + pass + + +class Scan(object): + class Group: + def __init__(self, scannable): + self.scannable = scannable + self.args = [] + + def __cmp__(self, other): + return(self.scannable.getLevel() - other.scannable.getLevel()) + + def __repr__(self): + return "Group(%s, %s)" % (self.scannable.getName(), str(self.args)) + + def shouldTriggerLoop(self): + return len(self.args) == 3 + + def __init__(self, scanDataHandlers): + # scanDataHandlers should be list + if type(scanDataHandlers) not in (tuple, list): + scanDataHandlers = (scanDataHandlers,) + self.dataHandlers = scanDataHandlers + + def __call__(self, *scanargs): + groups = self._parseScanArgsIntoScannableArgGroups(scanargs) + groups = self._reorderInnerGroupsAccordingToLevel(groups) + # Configure data handlers for a new scan + for handler in self.dataHandlers: handler.callAtScanStart( + [grp.scannable for grp in groups]) + # Perform the scan + self._performScan(groups, currentRecursionLevel=0) + # Inform data handlers of scan completion + for handler in self.dataHandlers: handler.callAtScanEnd() + + def _parseScanArgsIntoScannableArgGroups(self, scanargs): + """ + -> [ Group(scnA, (a1, a2, a2)), Group((scnB), (b1)), ... + ... Group((scnC),()), Group((scnD),(d1))] + """ + result = [] + if not isinstance(scanargs[0], Scannable): + raise TypeError("First scan argument must be a scannable") + + # Parse out scannables followed by non-scannable args + for arg in scanargs: + if isinstance(arg, Scannable): + result.append(Scan.Group(arg)) + else: + result[-1].args.append(arg) + return result + + def _reorderInnerGroupsAccordingToLevel(self, groups): + # Find the first group not to trigger a loop + for idx, group in enumerate(groups): + if not group.shouldTriggerLoop(): + break + latter = groups[idx:]; latter.sort() # Horrible hack not needed in python 3! + return groups[:idx] + latter + + def _performScan(self, groups, currentRecursionLevel): + # groups[currentRecursionLevel:] will start with either: + # a) A loop triggering group + # b) A number (possibly 0) of non-loop triggering groups + unprocessedGroups = groups[currentRecursionLevel:] + + # 1) If first remaining group should trigger a loop, perform this loop, + # recursively calling this method on the remaining groups + if len(unprocessedGroups) > 0: + first = unprocessedGroups[0] + # If groups starts with a request to loop: + if first.shouldTriggerLoop(): + posList = self._frange(first.args[0], first.args[1], first.args[2]) + for pos in posList: + first.scannable.asynchronousMoveTo(pos) + # TODO: Should wait. minigda assumes all moves complete immediately + self._performScan(groups, currentRecursionLevel + 1) + return + + # 2) Move all non-loop triggering groups (may be zero) + self._moveNonLoopTriggeringGroups(unprocessedGroups) + + # 3) Sample position of all scannables + posDict = self._samplePositionsOfAllScannables(groups) + + # 4) Inform the data handlers that this point has been recorded + for handler in self.dataHandlers: handler.callWithScanPoint(posDict) + + def _moveNonLoopTriggeringGroups(self, groups): + # TODO: Should wait. minigda assumes all moves complete immediately. groups could be zero lengthed. + for grp in groups: + if len(grp.args) == 0: + pass + elif len(grp.args) == 1: + grp.scannable.asynchronousMoveTo(grp.args[0]) + elif len(grp.args) == 2: + raise Exception("Scannables followed by two args not supported by minigda's scan command ") + else: + raise Exception("Scannable: %s args%s" % (grp.scannable, str(grp.args))) + + def _samplePositionsOfAllScannables(self, groups): + posDict = {} + for grp in groups: + posDict[grp.scannable] = grp.scannable.getPosition() + return posDict + + def _frange(self, limit1, limit2, increment): + """Range function that accepts floats (and integers). + """ +# limit1 = float(limit1) +# limit2 = float(limit2) + try: + increment = float(increment) + except TypeError: + raise TypeError( + "Only scaler values are supported, not GDA format vectors.") + count = int(math.ceil(((limit2 - limit1) + increment / 100.) / increment)) + result = [] + for n in range(count): + result.append(limit1 + n * increment) + return result + + +def sim(scn, hkl): + """sim hkl scn -- simulates moving scannable (not all) + """ + if not isinstance(hkl, (tuple, list)): + raise TypeError() + + if not allnum(hkl): + raise TypeError() + + try: + print scn.simulateMoveTo(hkl) + except AttributeError: + raise TypeError( + "The first argument does not support simulated moves") \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/minigda/scannable.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/minigda/scannable.py new file mode 100644 index 0000000..f6f9926 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/minigda/scannable.py @@ -0,0 +1,511 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +import time + +try: + from gda.device.scannable import ScannableBase +except ImportError: + class Scannable(object): + pass + + class ScannableBase(Scannable): + """Implemtation of a subset of OpenGDA's Scannable interface + """ + + level = 5 + inputNames = [] + extraNames = [] + outputFormat = [] + + def isBusy(self): + raise NotImplementedError() + + def rawGetPosition(self): + raise NotImplementedError() + + def rawAsynchronousMoveTo(self, newpos): + raise NotImplementedError() + + def waitWhileBusy(self): + while self.isBusy(): + time.sleep(.1) + + def getPosition(self): + return self.rawGetPosition() + + def asynchronousMoveTo(self, newpos): + self.rawAsynchronousMoveTo(newpos) + + def atScanStart(self): + pass + + def atScanEnd(self): + pass + + def atCommandFailure(self): + pass + + ### + + def __repr__(self): + pos = self.getPosition() + formattedValues = self.formatPositionFields(pos) + if len(tuple(self.getInputNames()) + tuple(self.getExtraNames())) > 1: + result = self.getName() + ': ' + else: + result = '' + + names = tuple(self.getInputNames()) + tuple(self.getExtraNames()) + for name, val in zip(names, formattedValues): + result += ' ' + name + ': ' + val + return result + ### + + def formatPositionFields(self, pos): + """Returns position as array of formatted strings""" + # Make sure pos is a tuple or list + if type(pos) not in (tuple, list): + pos = tuple([pos]) + + # Sanity check + if len(pos) != len(self.getOutputFormat()): + raise Exception( + "In scannable '%s':number of position fields differs from " + "number format strings specified" % self.getName()) + + result = [] + for field, format in zip(pos, self.getOutputFormat()): + if field is None: + result.append('???') + else: + s = (format % field) + ## if width!=None: + ## s = s.ljust(width) + result.append(s) + + return result + + def getName(self): + return self.name + + def setName(self, value): + self.name = value + + def getLevel(self): + return self.level + + def setLevel(self, value): + self.level = value + + def getInputNames(self): + return self.inputNames + + def setInputNames(self, value): + self.inputNames = value + + def getExtraNames(self): + return self.extraNames + + def setExtraNames(self, value): + self.extraNames = value + + def getOutputFormat(self): + return self.outputFormat + + def setOutputFormat(self, value): + if type(value) not in (tuple, list): + raise TypeError( + "%s.setOutputFormat() expects tuple or list; not %s" % + (self.getName(), str(type(value)))) + self.outputFormat = value + + def __call__(self, newpos=None): + if newpos is None: + return self.getPosition() + self.asynchronousMoveTo(newpos) + + class ScannableAdapter(Scannable): + '''Wrap up a Scannable and give it a new name and optionally an offset + (added to the delegate when reading up and subtracting when setting down + ''' + + def __init__(self, delegate_scn, name, offset=0): + assert len(delegate_scn.getInputNames()) == 1 + assert len(delegate_scn.getExtraNames()) == 0 + self.delegate_scn = delegate_scn + self.name = name + self.offset = offset + + def __getattr__(self, name): + return getattr(self.delegate_scn, name) + + def getName(self): + return self.name + + def getInputNames(self): + return [self.name] + + def getPosition(self): + return self.delegate_scn.getPosition() + self.offset + + def asynchronousMoveTo(self, newpos): + self.delegate_scn.asynchronousMoveTo(newpos - self.offset) + + def __repr__(self): + pos = self.getPosition() + formatted_values = self.delegate_scn.formatPositionFields(pos) + return self.name + ': ' + formatted_values[0] + ' ' + self.get_hint() + + def get_hint(self): + if self.offset: + offset_hint = ' + ' if self.offset >= 0 else ' - ' + offset_hint += str(self.offset) + else: + offset_hint = '' + return '(%s%s)' % (self.delegate_scn.name, offset_hint) + + def __call__(self, newpos=None): + if newpos is None: + return self.getPosition() + self.asynchronousMoveTo(newpos) + +class SingleFieldDummyScannable(ScannableBase): + + def __init__(self, name, initial_position=0.): + self.name = name + self.inputNames = [name] + self.outputFormat = ['% 6.4f'] + self.level = 3 + self._current_position = float(initial_position) + + def isBusy(self): + return False + + def waitWhileBusy(self): + return + + def asynchronousMoveTo(self, new_position): + self._current_position = float(new_position) + + def getPosition(self): + return self._current_position + + +class DummyPD(SingleFieldDummyScannable): + """For compatability with the gda's dummy_pd module""" + pass + + +class MultiInputExtraFieldsDummyScannable(ScannableBase): + '''Multi input Dummy PD Class supporting input and extra fields''' + def __init__(self, name, inputNames, extraNames): + self.setName(name) + self.setInputNames(inputNames) + self.setExtraNames(extraNames) + self.setOutputFormat(['%6.4f'] * (len(inputNames) + len(extraNames))) + self.setLevel(3) + self.currentposition = [0.0] * len(inputNames) + + def isBusy(self): + return 0 + + def asynchronousMoveTo(self, new_position): + if type(new_position) == type(1) or type(new_position) == type(1.0): + new_position = [new_position] + msg = "Wrong new_position size" + assert len(new_position) == len(self.currentposition), msg + for i in range(len(new_position)): + if new_position[i] != None: + self.currentposition[i] = float(new_position[i]) + + def getPosition(self): + extraValues = range(100, 100 + (len(self.getExtraNames()))) + return self.currentposition + map(float, extraValues) + + +class ZeroInputExtraFieldsDummyScannable(ScannableBase): + '''Zero input/extra field dummy pd + ''' + def __init__(self, name): + self.setName(name) + self.setInputNames([]) + self.setOutputFormat([]) + + def isBusy(self): + return 0 + + def asynchronousMoveTo(self, new_position): + pass + + def getPosition(self): + pass + + +class ScannableGroup(ScannableBase): + """wraps up motors. Simulates motors if non given.""" + + def __init__(self, name, motorList): + + self.setName(name) + # Set input format + motorNames = [] + for scn in motorList: + motorNames.append(scn.getName()) + self.setInputNames(motorNames) + # Set output format + format = [] + for motor in motorList: + format.append(motor.getOutputFormat()[0]) + self.setOutputFormat(format) + self.__motors = motorList + + def asynchronousMoveTo(self, position): + # if input has any Nones, then replace these with the current positions + if None in position: + position = list(position) + current = self.getPosition() + for idx, val in enumerate(position): + if val is None: + position[idx] = current[idx] + + for scn, pos in zip(self.__motors, position): + scn.asynchronousMoveTo(pos) + + def getPosition(self): + return [scn.getPosition() for scn in self.__motors] + + def isBusy(self): + for scn in self.__motors: + if scn.isBusy(): + return True + return False + + def configure(self): + pass + + +class ScannableMotionWithScannableFieldsBase(ScannableBase): + ''' + This extended version of ScannableMotionBase contains a + completeInstantiation() method which adds a dictionary of + MotionScannableParts to an instance. Each part allows one of the + instances fields to be interacted with like it itself is a scannable. + Fields are dynamically added to the instance linking to these parts + allowing dotted access from Jython. They may also be accessed using + Jython container access methods (via the __getitem__() method). To acess + them from Jave use the getComponent(name) method. + + When moving a part (via either a pos or scan command), the part calls + the parent to perform the actual task. The parts asynchronousMoveto + command will call the parent with a list of None values except for the + field it represents which will be passed the desired position value. + + The asynchronousMoveTo method in class that inherats from this base + class then must handle these Nones. In some cases the method may + actually be able to move the underlying system assoiciated with one + field individually from others. If this is not possible the best + behaviour may be to simply not support this beahviour and exception or + alternatively to substitute the None values with actual current position + of parent's scannables associated fields. + + ScannableMotionBaseWithMemory() inherats from this calss and provides a + solution useful for some scenarious: it keeps track of the last position + moved to, and replaces the Nones in an asynchronousMoveTo request with + these values. There are a number of dangers associated with this which + are addressed in that class's documentation, but it provides a way to + move one axis within a group of non-orthogonal axis while keeping the + others still. + ''' + childrenDict = {} + numInputFields = None + numExtraFields = None + + def completeInstantiation(self): + '''This method should be called at the end of all user defined + consructors''' + # self.validate() + self.numInputFields = len(self.getInputNames()) + self.numExtraFields = len(self.getExtraNames()) + self.addScannableParts() + self.autoCompletePartialMoveToTargets = False + self.positionAtScanStart = None + + def setAutoCompletePartialMoveToTargets(self, b): + self.autoCompletePartialMoveToTargets = b + + def atScanStart(self): + self.positionAtScanStart = self.getPosition() + + def atCommandFailure(self): + self.positionAtScanStart = None + + def atScanEnd(self): + self.positionAtScanStart = None + +### + + def __repr__(self): + pos = self.getPosition() + formattedValues = self.formatPositionFields(pos) + if len(tuple(self.getInputNames()) + tuple(self.getExtraNames())) > 1: + result = self.getName() + ': ' + else: + result = '' + + names = tuple(self.getInputNames()) + tuple(self.getExtraNames()) + for name, val in zip(names, formattedValues): + result += ' ' + name + ': ' + val + return result +### + + def formatPositionFields(self, pos): + """Returns position as array of formatted strings""" + # Make sure pos is a tuple or list + if type(pos) not in (tuple, list): + pos = tuple([pos]) + + # Sanity check + if len(pos) != len(self.getOutputFormat()): + raise Exception( + "In scannable '%s':number of position fields differs from " + "number format strings specified" % self.getName()) + + result = [] + for field, format in zip(pos, self.getOutputFormat()): + if field is None: + result.append('???') + else: + s = (format % field) +## if width!=None: +## s = s.ljust(width) + result.append(s) + + return result + +### + + def addScannableParts(self): + ''' + Creates an array of MotionScannableParts each of which allows access to + the scannable's fields. See this class's documentation for more info. + ''' + self.childrenDict = {} + # Add parts to access the input fields + for index in range(len(self.getInputNames())): + scannableName = self.getInputNames()[index] + self.childrenDict[scannableName] = self.MotionScannablePart( + scannableName, index, self, isInputField=1) + + # Add parts to access the extra fields + for index in range(len(self.getExtraNames())): + scannableName = self.getExtraNames()[index] + self.childrenDict[scannableName] = self.MotionScannablePart( + scannableName, index + len(self.getInputNames()), + self, isInputField=0) + + def asynchronousMoveTo(self, newpos): + if self.autoCompletePartialMoveToTargets: + newpos = self.completePosition(newpos) + ScannableBase.asynchronousMoveTo(self, newpos) + + def completePosition(self, position): + ''' + If position contains any null or None values, these are replaced with + the corresponding fields from the scannables current position and then + returned.''' + # Just return position if it does not need padding + if None not in position: + return position + if self.positionAtScanStart is not None: + basePosition = self.positionAtScanStart + else: + basePosition = self.getPosition()[:self.numInputFields] + for i in range(self.numInputFields): + if position[i] is None: + position[i] = basePosition[i] + return position + + def __getattr__(self, name): + try: + return self.childrenDict[name] + except: + raise AttributeError("No child named:" + name) + + def __getitem__(self, key): + '''Provides container like access from Jython''' + return self.childrenDict[key] + + def getPart(self, name): + '''Returns the a compnent scannable''' + return self.childrenDict[name] + + class MotionScannablePart(ScannableBase): + ''' + A scannable to be placed in the parent's childrenDict that allows + access to the parent's individual fields.''' + + def __init__(self, scannableName, index, parentScannable, + isInputField): + self.setName(scannableName) + if isInputField: + self.setInputNames([scannableName]) + else: + self.setExtraNames([scannableName]) + self.index = index + self.parentScannable = parentScannable + self.setOutputFormat( + [self.parentScannable.getOutputFormat()[index]]) + + def isBusy(self): + return self.parentScannable.isBusy() + + def asynchronousMoveTo(self, new_position): + if self.parentScannable.isBusy(): + raise Exception( + self.parentScannable.getName() + "." + self.getName() + + " cannot be moved because " + + self.parentScannable.getName() + " is already moving") + + toMoveTo = [None] * len(self.parentScannable.getInputNames()) + toMoveTo[self.index] = new_position + self.parentScannable.asynchronousMoveTo(toMoveTo) + + def moveTo(self, new_position): + self.asynchronousMoveTo(new_position) + self.waitWhileBusy() + + def getPosition(self): + return self.parentScannable.getPosition()[self.index] + + def __str__(self): + return self.__repr__() + + def __repr__(self): + # Get the name of this field + # (assume its an input field first and correct if wrong) + name = self.getInputNames()[0] + + if name == 'value': + name = self.getExtraNames()[0] + parentName = self.parentScannable.getName() + return parentName + "." + name + " : " + str(self.getPosition()) + + + + \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/__init__.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/base.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/base.py new file mode 100644 index 0000000..dfa3182 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/base.py @@ -0,0 +1,62 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +try: + from gda.device.scannable import PseudoDevice +except ImportError: + from diffcalc.gdasupport.minigda.scannable import \ + ScannableBase as PseudoDevice + + +class ScannableGroup(PseudoDevice): + + def __init__(self, name, motorList): + + self.setName(name) + # Set input format + motorNames = [] + for scn in motorList: + motorNames.append(scn.getName()) + self.setInputNames(motorNames) + # Set output format + format = [] + for motor in motorList: + format.append(motor.getOutputFormat()[0]) + self.setOutputFormat(format) + self.__motors = motorList + + def asynchronousMoveTo(self, position): + # if input has any Nones, then replace these with the current positions + if None in position: + position = list(position) + current = self.getPosition() + for idx, val in enumerate(position): + if val is None: + position[idx] = current[idx] + + for scn, pos in zip(self.__motors, position): + scn.asynchronousMoveTo(pos) + + def getPosition(self): + return [scn.getPosition() for scn in self.__motors] + + def isBusy(self): + for scn in self.__motors: + if scn.isBusy(): + return True + return False diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/diffractometer.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/diffractometer.py new file mode 100644 index 0000000..1d1b993 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/diffractometer.py @@ -0,0 +1,126 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +try: + from gda.device.scannable import ScannableMotionBase +except ImportError: + from diffcalc.gdasupport.minigda.scannable import \ + ScannableBase as ScannableMotionBase + +from diffcalc.util import getMessageFromException + +# TODO: Split into a base class when making other scannables + + +class DiffractometerScannableGroup(ScannableMotionBase): + """" + Wraps up a scannableGroup of axis to tweak the way the resulting + object is displayed and to add a simulate move to method. + + The scannable group should have the same geometry as that expected + by the diffractometer hardware geometry used in the diffraction + calculator. + + The optional parameter slaveDriver can be used to provide a + slave_driver. This is useful for triggering a move of an incidental + axis whose position depends on that of the diffractometer, but whose + position need not be included in the DiffractometerScannableGroup + itself. This parameter is exposed as a field and can be set or + cleared to null at will without effecting the core calculation code. + """ + + def __init__(self, name, diffcalc_module, scannableGroup, + slave_driver=None, hint_generator=None): + # if motorList is None, will create a dummy __group + self.diffcalc_module = diffcalc_module + self.__group = scannableGroup + self.slave_driver = slave_driver + self.setName(name) + self.hint_generator = hint_generator + + def getInputNames(self): + return self.__group.getInputNames() + + def getExtraNames(self): + if self.slave_driver is None: + return [] + else: + return self.slave_driver.getScannableNames() + + def getOutputFormat(self): + if self.slave_driver is None: + slave_formats = [] + else: + slave_formats = self.slave_driver.getScannableNames() + return list(self.__group.getOutputFormat()) + slave_formats + + def asynchronousMoveTo(self, position): + self.__group.asynchronousMoveTo(position) + if self.slave_driver is not None: + self.slave_driver.triggerAsynchronousMove(position) + + def getPosition(self): + if self.slave_driver is None: + slave_positions = [] + else: + slave_positions = self.slave_driver.getPositions() + return list(self.__group.getPosition()) + list(slave_positions) + + def isBusy(self): + if self.slave_driver is None: + return self.__group.isBusy() + else: + return self.__group.isBusy() or self.slave_driver.isBusy() + + def waitWhileBusy(self): + self.__group.waitWhileBusy() + if self.slave_driver is not None: + self.slave_driver.waitWhileBusy() + + def simulateMoveTo(self, pos): + if len(pos) != len(self.getInputNames()): + raise ValueError('Wrong number of inputs') + try: + (hkl, params) = self.diffcalc_module.angles_to_hkl(pos) + except Exception, e: + return "Error: %s" % getMessageFromException(e) + width = max(len(k) for k in params) + + lines = ([' ' + 'hkl'.rjust(width) + ' : % 9.4f %.4f %.4f' % + (hkl[0], hkl[1], hkl[2])]) + lines[-1] = lines[-1] + '\n' + fmt = ' %' + str(width) + 's : % 9.4f' + for k in sorted(params): + lines.append(fmt % (k, params[k])) + return '\n'.join(lines) + + def __repr__(self): + position = self.getPosition() + names = list(self.getInputNames()) + list(self.getExtraNames()) + if self.hint_generator is None: + hint_list = [''] * len(self.getInputNames()) + else: + hint_list = self.hint_generator() + + lines = [self.name + ':'] + width = max(len(k) for k in names) + fmt = ' %' + str(width) + 's : % 9.4f %s' + for name, pos, hint in zip(names, position, hint_list): + lines.append(fmt % (name, pos, hint)) + lines[len(self.getInputNames())] += '\n' + return '\n'.join(lines) diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/hkl.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/hkl.py new file mode 100644 index 0000000..12376d2 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/hkl.py @@ -0,0 +1,135 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### +import platform + +DEBUG = False + +try: + from gda.device.scannable.scannablegroup import \ + ScannableMotionWithScannableFieldsBase +except ImportError: + from diffcalc.gdasupport.minigda.scannable import \ + ScannableMotionWithScannableFieldsBase + +from diffcalc.util import getMessageFromException, DiffcalcException + + +class _DynamicDocstringMetaclass(type): + + def _get_doc(self): + return Hkl.dynamic_docstring + + __doc__ = property(_get_doc) # @ReservedAssignment + + +class Hkl(ScannableMotionWithScannableFieldsBase): + + if platform.system() != 'Java': + __metaclass__ = _DynamicDocstringMetaclass # TODO: Removed to fix Jython + + dynamic_docstring = 'Hkl Scannable' + + def _get_doc(self): + return Hkl.dynamic_docstring + + __doc__ = property(_get_doc) # @ReservedAssignment + + def __init__(self, name, diffractometerObject, diffcalcObject, + virtualAnglesToReport=None): + self.diffhw = diffractometerObject + self._diffcalc = diffcalcObject + if type(virtualAnglesToReport) is str: + virtualAnglesToReport = (virtualAnglesToReport,) + self.vAngleNames = virtualAnglesToReport + + self.setName(name) + self.setInputNames(['h', 'k', 'l']) + self.setOutputFormat(['%7.5f'] * 3) + if self.vAngleNames: + self.setExtraNames(self.vAngleNames) + self.setOutputFormat(['%7.5f'] * (3 + len(self.vAngleNames))) + + self.completeInstantiation() + self.setAutoCompletePartialMoveToTargets(True) + self.dynamic_class_doc = 'Hkl Scannable xyz' + + def rawAsynchronousMoveTo(self, hkl): + if len(hkl) != 3: raise ValueError('Hkl device expects three inputs') + try: + (pos, _) = self._diffcalc.hkl_to_angles(hkl[0], hkl[1], hkl[2]) + except DiffcalcException, e: + if DEBUG: + raise + else: + raise DiffcalcException(e.message) + self.diffhw.asynchronousMoveTo(pos) + + def rawGetPosition(self): + pos = self.diffhw.getPosition() # a tuple + (hkl , params) = self._diffcalc.angles_to_hkl(pos) + result = list(hkl) + if self.vAngleNames: + for vAngleName in self.vAngleNames: + result.append(params[vAngleName]) + return result + + def getFieldPosition(self, i): + return self.getPosition()[i] + + def isBusy(self): + return self.diffhw.isBusy() + + def waitWhileBusy(self): + return self.diffhw.waitWhileBusy() + + def simulateMoveTo(self, hkl): + if type(hkl) not in (list, tuple): + raise ValueError('Hkl device expects three inputs') + if len(hkl) != 3: + raise ValueError('Hkl device expects three inputs') + (pos, params) = self._diffcalc.hkl_to_angles(hkl[0], hkl[1], hkl[2]) + + width = max(len(k) for k in (params.keys() + list(self.diffhw.getInputNames()))) + fmt = ' %' + str(width) + 's : % 9.4f' + + lines = [self.diffhw.getName() + ' would move to:'] + for idx, name in enumerate(self.diffhw.getInputNames()): + lines.append(fmt % (name, pos[idx])) + lines[-1] = lines[-1] + '\n' + for k in sorted(params): + lines.append(fmt % (k, params[k])) + return '\n'.join(lines) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + lines = ['hkl:'] + pos = self.diffhw.getPosition() + try: + (hkl, params) = self._diffcalc.angles_to_hkl(pos) + except Exception, e: + return "" % getMessageFromException(e) + + width = max(len(k) for k in params) + lines.append(' ' + 'hkl'.rjust(width) + ' : %9.4f %.4f %.4f' % (hkl[0], hkl[1], hkl[2])) + lines[-1] = lines[-1] + '\n' + fmt = ' %' + str(width) + 's : % 9.4f' + for k in sorted(params): + lines.append(fmt % (k, params[k])) + return '\n'.join(lines) diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/mock.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/mock.py new file mode 100644 index 0000000..13871da --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/mock.py @@ -0,0 +1,47 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +try: + from gda.device.scannable import ScannableMotionBase +except ImportError: + from diffcalc.gdasupport.minigda.scannable import \ + ScannableBase as ScannableMotionBase + + +class MockMotor(ScannableMotionBase): + + def __init__(self, name='mock'): + self.pos = 0.0 + self._busy = False + self.name = name + + def asynchronousMoveTo(self, pos): + self._busy = True + self.pos = float(pos) + + def getPosition(self): + return self.pos + + def isBusy(self): + return self._busy + + def makeNotBusy(self): + self._busy = False + + def getOutputFormat(self): + return ['%f'] diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/parameter.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/parameter.py new file mode 100644 index 0000000..a08e95c --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/parameter.py @@ -0,0 +1,45 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +try: + from gda.device.scannable import ScannableMotionBase +except ImportError: + from diffcalc.gdasupport.minigda.scannable import \ + ScannableBase as ScannableMotionBase + + +class DiffractionCalculatorParameter(ScannableMotionBase): + + def __init__(self, name, parameterName, parameter_manager): + + self.parameter_manager = parameter_manager + self.parameterName = parameterName + + self.setName(name) + self.setInputNames([parameterName]) + self.setOutputFormat(['%5.5f']) + self.setLevel(3) + + def asynchronousMoveTo(self, value): + self.parameter_manager.set_constraint(self.parameterName, value) + + def getPosition(self): + return self.parameter_manager.get_constraint(self.parameterName) + + def isBusy(self): + return False diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/sim.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/sim.py new file mode 100644 index 0000000..44b2108 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/sim.py @@ -0,0 +1,21 @@ +''' +Created on 7 May 2016 + +@author: walton +''' +from diffcalc.util import allnum + +def sim(scn, hkl): + """sim hkl scn -- simulates moving scannable (not all) + """ + if not isinstance(hkl, (tuple, list)): + raise TypeError + + if not allnum(hkl): + raise TypeError() + + try: + print scn.simulateMoveTo(hkl) + except AttributeError: + raise TypeError( + "The first argument does not support simulated moves") \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/simulation.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/simulation.py new file mode 100644 index 0000000..0255a5f --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/simulation.py @@ -0,0 +1,139 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +import time +from math import sqrt, pi, exp + +try: + from gda.device.scannable import PseudoDevice +except ImportError: + from diffcalc.gdasupport.minigda.scannable import \ + ScannableBase as PseudoDevice + +from diffcalc.ub.crystal import CrystalUnderTest +from diffcalc.hkl.you.calc import youAnglesToHkl +from diffcalc.hkl.vlieg.calc import vliegAnglesToHkl +from diffcalc.hkl.you.geometry import calcCHI, calcPHI + +TORAD = pi / 180 +TODEG = 180 / pi + + +class Equation(object): + + def __call__(self, dh, dk, dl): + raise Exception('Abstract') + + def __str__(self): + "Abstract equation" + + +class Gaussian(Equation): + + def __init__(self, variance): + self.variance = float(variance) + + def __call__(self, dh, dk, dl): + dr_squared = dh * dh + dk * dk + dl * dl + return (1 / sqrt(2 * pi * self.variance) * + exp(-dr_squared / (2 * self.variance))) + + +class SimulatedCrystalCounter(PseudoDevice): + + def __init__(self, name, diffractometerScannable, geometryPlugin, + wavelengthScannable, equation=Gaussian(.01), engine='you'): + self.setName(name) + self.setInputNames([name + '_count']) + self.setOutputFormat(['%7.5f']) + self.exposureTime = 1 + self.pause = True + self.diffractometerScannable = diffractometerScannable + self.geometry = geometryPlugin + self.wavelengthScannable = wavelengthScannable + self.equation = equation + self.engine = engine + + self.cut = None + self.UB = None + self.chiMissmount = 0. + self.phiMissmount = 0. + self.setCrystal('cubic', 1, 1, 1, 90, 90, 90) + + def setCrystal(self, name, a, b, c, alpha, beta, gamma): + self.cut = CrystalUnderTest(name, a, b, c, alpha, beta, gamma) + self.calcUB() + + def setChiMissmount(self, chi): + self.chiMissmount = chi + self.calcUB() + + def setPhiMissmount(self, phi): + self.phiMissmount = phi + self.calcUB() + + def calcUB(self): + CHI = calcCHI(self.chiMissmount * TORAD) + PHI = calcPHI(self.phiMissmount * TORAD) + self.UB = CHI * PHI * self.cut.B + + def asynchronousMoveTo(self, exposureTime): + self.exposureTime = exposureTime + if self.pause: + time.sleep(exposureTime) # Should not technically block! + + def getPosition(self): + h, k, l = self.getHkl() + dh, dk, dl = h - round(h), k - round(k), l - round(l) + count = self.equation(dh, dk, dl) + #return self.exposureTime, count*self.exposureTime + return count * self.exposureTime + + def getHkl(self): + pos = self.geometry.physical_angles_to_internal_position( + self.diffractometerScannable.getPosition()) + pos.changeToRadians() + wavelength = self.wavelengthScannable.getPosition() + if self.engine.lower() == 'vlieg': + return vliegAnglesToHkl(pos, wavelength, self.UB) + elif self.engine.lower() == 'you': + return youAnglesToHkl(pos, wavelength, self.UB) + else: + raise ValueError(self.engine) + + def isBusy(self): + return False + + def __str__(self): + return self.__repr__() + + def __repr__(self): + s = 'simulated crystal detector: %s\n' % self.getName() + h, k, l = self.getHkl() + s += ' h : %f\n' % h + s += ' k : %f\n' % k + s += ' l : %f\n' % l + s += self.cut.__str__() + '\n' + s += "chi orientation: %s\n" % self.chiMissmount + s += "phi orientation: %s\n" % self.phiMissmount + ub = self.UB.tolist() + s += "UB:\n" + s += " % 18.13f% 18.13f% 18.12f\n" % (ub[0][0], ub[0][1], ub[0][2]) + s += " % 18.13f% 18.13f% 18.12f\n" % (ub[1][0], ub[1][1], ub[1][2]) + s += " % 18.13f% 18.13f% 18.12f\n" % (ub[2][0], ub[2][1], ub[2][2]) + return s diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/slave_driver.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/slave_driver.py new file mode 100644 index 0000000..cd5ce24 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/slave_driver.py @@ -0,0 +1,109 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from math import pi, tan, sin, atan, cos, atan2 + +TORAD = pi / 180 +TODEG = 180 / pi + + +class SlaveScannableDriver(object): + + def __init__(self, scannables): + self.scannables = scannables + + def isBusy(self): + for scn in self.scannables: + if scn.isBusy(): + return True + return False + + def waitWhileBusy(self): + for scn in self.scannables: + scn.waitWhileBusy() + + def triggerAsynchronousMove(self, triggerPos): + nu = self.slaveFromTriggerPos(triggerPos) + for scn in self.scannables: + scn.asynchronousMoveTo(nu) + + def getPosition(self): + return self.scannables[0].getPosition() + + def slaveFromTriggerPos(self, triggerPos): + raise Exception("Abstract") + + def getScannableNames(self): + return [scn.name for scn in self.scannables] + + def getOutputFormat(self): + return [list(scn.outputFormat)[0] for scn in self.scannables] + + def getPositions(self): + return [float(scn.getPosition()) for scn in self.scannables] + + +""" +Based on: Elias Vlieg, "A (2+3)-Type Surface Diffractometer: Mergence of the +z-axis and (2+2)-Type Geometries", J. Appl. Cryst. (1998). 31. 198-203 +""" + + +class NuDriverForSixCirclePlugin(SlaveScannableDriver): + + def slaveFromTriggerPos(self, triggerPos): + + alpha, delta, gamma, _, _, _ = triggerPos + alpha = alpha * TORAD + delta = delta * TORAD + gamma = gamma * TORAD + + ### Equation16 RHS ### + rhs = -1 * tan(gamma - alpha) * sin(delta) + nu = atan(rhs) # -pi/2 <= nu <= pi/2 + return nu * TODEG + + +class NuDriverForWillmottHorizontalGeometry(SlaveScannableDriver): + + """ + Based on: Phillip Willmott, "Angle calculations for a (2+3)-type + diffractometer: focus on area detectors", J. Appl. Cryst. (2011). 44. + 73-83 + """ + + def __init__(self, scannables, area_detector=False): + SlaveScannableDriver.__init__(self, scannables) + self.area_detector = area_detector + + def slaveFromTriggerPos(self, triggerPos): + + delta, gamma, omegah, _ = triggerPos + delta *= TORAD + gamma *= TORAD + omegah *= TORAD + if self.area_detector: + nu = atan2(sin(delta - omegah), tan(gamma)) # (66) + else: + top = -sin(gamma) * sin(omegah) + bot = (sin(omegah) * cos(gamma) * sin(delta) + + cos(omegah) * cos(delta)) + nu = atan2(top, bot) # (61) + + print 'nu:', nu * TODEG + return nu * TODEG diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/vrmlanimator.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/vrmlanimator.py new file mode 100644 index 0000000..9f40441 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/vrmlanimator.py @@ -0,0 +1,184 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +import time +import threading +import socket +PORT = 4567 + +from gda.device.scannable import ScannableMotionWithScannableFieldsBaseTest + +#import scannable.vrmlModelDriver +#reload(scannable.vrmlModelDriver);from scannable.vrmlModelDriver import \ +# VrmlModelDriver, LinearProfile, MoveThread +#fc=VrmlModelDriver( +# 'fc',['alpha','delta','omega', 'chi','phi'], speed=30, host='diamrl5104') +#alpha = fc.alpha +#delta = fc.delta +#omega = fc.omega +#chi = fc.chi +#phi = fc.phi + + +def connect_to_socket(host, port): + print "Connecting to %s on port %d" % (host, port) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.connect((host, port)) + print "Connected" + socketfile = sock.makefile('rw', 0) + return socketfile + + +class LinearProfile(object): + + def __init__(self, v, t_accel, startList, endList): + assert len(startList) == len(endList) + self.v = float(v) + self.start = startList + self.end = endList + self.t_accel = t_accel + + distances = [e - s for e, s in zip(self.end, self.start)] + max_distance = max([abs(d) for d in distances]) + if max_distance == 0: + self.delta_time = 0 + else: + self.delta_time = abs(max_distance / self.v) + self.speeds = [d / self.delta_time for d in distances] + self.start_time = time.time() + + def getPosition(self): + if self.start_time is None: + return self.start + if not self.isMoving(): + return self.end + t = abs(float(time.time() - self.start_time)) + if t > self.delta_time: + # we are in the deceleration phase (i.e paused for now) + return self.end + return [s + v * t for s, v in zip(self.start, self.speeds)] + + def isMoving(self): + return time.time() < self.start_time + self.delta_time + self.t_accel + + +class MoveThread(threading.Thread): + + def __init__(self, profile, socketfile, axisNames): + threading.Thread.__init__(self) + self.profile = profile + self.socketfile = socketfile + self.axisNames = axisNames + + def run(self): + while self.profile.isMoving(): + self.update() + time.sleep(.1) + self.update() + + def update(self): + pos = self.profile.getPosition() + d = dict(zip(map(str, self.axisNames), pos)) + if self.socketfile: + self.socketfile.write(repr(d) + '\n') + + +class VrmlModelDriver(ScannableMotionWithScannableFieldsBaseTest): + + def __init__(self, name, axes_names, host=None, speed=60, t_accel=.1, + format='%.3f'): + self.name = name + self.inputNames = list(axes_names) + self.extraNames = [] + self.outputFormat = [format] * len(self.inputNames) + self.completeInstantiation() + self.__last_target = [0.] * len(self.inputNames) + self.verbose = False + self.move_thread = None + self.speed = speed + self.host = host + self.t_accel = t_accel + self.socketfile = None + if self.host: + try: + self.connect() + except socket.error: + print "Failed to connect to %s:%r" % (self.host, PORT) + print "Connect with: %s.connect()" % self.name + + def connect(self): + self.socketfile = connect_to_socket(self.host, PORT) + self.rawAsynchronousMoveTo(self.__last_target) + + def isBusy(self): + if self.move_thread is None: + return False + return self.move_thread.profile.isMoving() + + def rawGetPosition(self): + if self.move_thread is None: + return self.__last_target + else: + return self.move_thread.profile.getPosition() + + def rawAsynchronousMoveTo(self, targetList): + if self.isBusy(): + raise Exception(self.name + ' is already moving') + if self.verbose: + print self.name + ".rawAsynchronousMoveTo(%r)" % targetList + + for i, target in enumerate(targetList): + if target is None: + targetList[i] = self.__last_target[i] + profile = LinearProfile( + self.speed, self.t_accel, self.__last_target, targetList) + self.move_thread = MoveThread( + profile, self.socketfile, self.inputNames) + self.move_thread.start() + self.__last_target = targetList + + def getFieldPosition(self, index): + return self.getPosition()[index] + + def __del__(self): + self.socketfile.close() + +#class TrapezoidProfile(object): +# +# def __init__(self, t_accel, v_max, delta_x): +# self.t_a = t_accel +# self.v_m = v_max +# self.delta_x = delta_x +# +# self.t_c = (self.X - self.v_m*self.t_a) / self.v_m +# +# def x(self, t): +# if self.t_c <=0: +# return self.__xshort(t) +# else: +# return self.__xlong(t) +# +# def __xshort(self, t): +# delta_t = 2 * sqrt(self.delta_x*self.t_a/self.v_m) +# if t <= .5*delta_t: +# return (.5*self.v_m/self.t_a) * t**2 +# else: +# v_peak = (self.v_m/self.t_a) * .5*delta_t +# return (t-.5*delta_t)*v_peak - (t-.5*delta_t)**2 ####HERE, bugged +# self.delta_x/2 diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/wavelength.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/wavelength.py new file mode 100644 index 0000000..52fd925 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/scannable/wavelength.py @@ -0,0 +1,50 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +try: + from gdascripts.pd.dummy_pds import DummyPD +except ImportError: + from diffcalc.gdasupport.minigda.scannable import DummyPD + + +class Wavelength(DummyPD): + + def __init__(self, name, energyScannable, + energyScannableMultiplierToGetKeV=1): + self.energyScannable = energyScannable + self.energyScannableMultiplierToGetKeV = \ + energyScannableMultiplierToGetKeV + + DummyPD.__init__(self, name) + + def asynchronousMoveTo(self, pos): + self.energyScannable.asynchronousMoveTo( + (12.39842 / pos) / self.energyScannableMultiplierToGetKeV) + + def getPosition(self): + energy = self.energyScannable.getPosition() + if energy == 0: + raise Exception( + "The energy is 0, so no wavelength could be calculated.run_All()") + return 12.39842 / (energy * self.energyScannableMultiplierToGetKeV) + + def isBusy(self): + return self.energyScannable.isBusy() + + def waitWhileBusy(self): + return self.energyScannable.waitWhileBusy() diff --git a/script/test/diffcalc (copy)/diffcalc/gdasupport/you.py b/script/test/diffcalc (copy)/diffcalc/gdasupport/you.py new file mode 100644 index 0000000..609a344 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/gdasupport/you.py @@ -0,0 +1,113 @@ +from diffcalc.gdasupport.scannable.diffractometer import DiffractometerScannableGroup +from diffcalc.gdasupport.scannable.hkl import Hkl +from diffcalc.gdasupport.scannable.simulation import SimulatedCrystalCounter +from diffcalc.gdasupport.scannable.wavelength import Wavelength +from diffcalc.gdasupport.scannable.parameter import DiffractionCalculatorParameter + + +from diffcalc.dc import dcyou as _dc +from diffcalc.dc.help import format_command_help +reload(_dc) +from diffcalc.dc.dcyou import * # @UnusedWildImport +from diffcalc import settings + +try: + import gda # @UnusedImport @UnresolvedImport + GDA = True +except: + GDA = False + +if not GDA: + from diffcalc.gdasupport.minigda import command + _pos = command.Pos() + _scan = command.Scan(command.ScanDataPrinter()) + + def pos(*args): + """ + pos show position of all Scannables + pos scn show position of scn + pos scn targetmove scn to target (a number) + """ + return _pos(*args) + + def scan(*args): + """ + scan scn start stop step {scn {target}} {det t} + """ + return _scan(*args) + + +from diffcalc.gdasupport.scannable.sim import sim # @UnusedImport + +_scn_group = settings.axes_scannable_group +_diff_scn_name = settings.geometry.name # @UndefinedVariable +_energy_scannable = settings.energy_scannable + + +# Create diffractometer scannable +_diff_scn = DiffractometerScannableGroup(_diff_scn_name, _dc, _scn_group) +globals()[_diff_scn_name] = _diff_scn + +# Create hkl scannables +hkl = Hkl('hkl', _scn_group, _dc) +h = hkl.h +k = hkl.k +l = hkl.l + +Hkl.dynamic_docstring = format_command_help(hkl_commands_for_help) # must be on the class +ub.__doc__ = format_command_help(ub_commands_for_help) + +_virtual_angles = ('theta', 'qaz', 'alpha', 'naz', 'tau', 'psi', 'beta') +hklverbose = Hkl('hklverbose', _scn_group, _dc, _virtual_angles) + + +# Create wavelength scannable +wl = Wavelength( + 'wl', _energy_scannable, settings.energy_scannable_multiplier_to_get_KeV) +if not GDA: + wl.asynchronousMoveTo(1) # Angstrom +_energy_scannable.level = 3 +wl.level = 3 + + +# Create simulated counter timer +ct = SimulatedCrystalCounter('ct', _scn_group, settings.geometry, wl) +ct.level = 10 + + +# Create constraint scannables +def _create_constraint_scannable(con_name, scn_name=None): + if not scn_name: + scn_name = con_name + return DiffractionCalculatorParameter( + scn_name, con_name, _dc.constraint_manager) + +# Detector constraints +def isconstrainable(name): + return not constraint_manager.is_constraint_fixed(name) + +if isconstrainable('delta'): delta_con = _create_constraint_scannable('delta', 'delta_con') +if isconstrainable('gam'): gam_con = _create_constraint_scannable('gam', 'gam_con') +if isconstrainable('qaz'): qaz = _create_constraint_scannable('qaz') +if isconstrainable('naz'): naz = _create_constraint_scannable('naz') + +# Reference constraints +alpha = _create_constraint_scannable('alpha') +beta = _create_constraint_scannable('beta') +psi = _create_constraint_scannable('psi') +a_eq_b = 'a_eq_b' + +# Sample constraints +if isconstrainable('mu'): mu_con = _create_constraint_scannable('mu', 'mu_con') +if isconstrainable('eta'): eta_con = _create_constraint_scannable('eta', 'eta_con') +if isconstrainable('chi'): chi_con = _create_constraint_scannable('chi', 'chi_con') +if isconstrainable('phi'): phi_con = _create_constraint_scannable('phi', 'phi_con') +if isconstrainable('mu') and isconstrainable('gam'): mu_is_gam = 'mu_is_gam' + + +# Cleanup to allow "from gdasupport.you import *" +del DiffractometerScannableGroup, Hkl, SimulatedCrystalCounter +del Wavelength, DiffractionCalculatorParameter + +# Cleanup other cruft +del format_command_help \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/hardware.py b/script/test/diffcalc (copy)/diffcalc/hardware.py new file mode 100644 index 0000000..274feaf --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hardware.py @@ -0,0 +1,382 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from __future__ import absolute_import + +from diffcalc.util import DiffcalcException +from diffcalc import settings + +SMALL = 1e-8 + +from diffcalc.util import command + +__all__ = ['hardware', 'setcut', 'setmin', 'setmax'] + + +def getNameFromScannableOrString(o): + try: # it may be a scannable + return o.getName() + except AttributeError: + return str(o) + + + +@command +def hardware(): + """hardware -- show diffcalc limits and cuts""" + print settings.hardware.repr_sector_limits_and_cuts() # @UndefinedVariable + +@command +def setcut(scannable_or_string=None, val=None): + """setcut {name {val}} -- sets cut angle + """ + if scannable_or_string is None and val is None: + print settings.hardware.repr_sector_limits_and_cuts() # @UndefinedVariable + else: + name = getNameFromScannableOrString(scannable_or_string) + if val is None: + print '%s: %f' % (name, settings.hardware.get_cuts()[name]) # @UndefinedVariable + else: + oldcut = settings.hardware.get_cuts()[name] # @UndefinedVariable + settings.hardware.set_cut(name, float(val)) # @UndefinedVariable + newcut = settings.hardware.get_cuts()[name] # @UndefinedVariable + +@command +def setmin(name=None, val=None): + """setmin {axis {val}} -- set lower limits used by auto sector code (None to clear)""" #@IgnorePep8 + _setMinOrMax(name, val, settings.hardware.set_lower_limit) # @UndefinedVariable + +@command +def setmax(name=None, val=None): + """setmax {name {val}} -- sets upper limits used by auto sector code (None to clear)""" #@IgnorePep8 + _setMinOrMax(name, val, settings.hardware.set_upper_limit) # @UndefinedVariable + +@command +def setrange(name=None, lower=None, upper=None): + """setrange {axis {min} {max}} -- set lower and upper limits used by auto sector code (None to clear)""" #@IgnorePep8 + _setMinOrMax(name, lower, settings.hardware.set_lower_limit) # @UndefinedVariable + _setMinOrMax(name, upper, settings.hardware.set_upper_limit) # @UndefinedVariable + +def _setMinOrMax(name, val, setMethod): + if name is None: + print settings.hardware.repr_sector_limits_and_cuts() # @UndefinedVariable + else: + name = getNameFromScannableOrString(name) + if val is None: + print settings.hardware.repr_sector_limits_and_cuts(name) # @UndefinedVariable + else: + setMethod(name, float(val)) + + +commands_for_help = ['Hardware', + hardware, + setcut, + setmin, + setmax] + + +class HardwareAdapter(object): + + def __init__(self, diffractometerAngleNames, defaultCuts={}, + energyScannableMultiplierToGetKeV=1): + + self._diffractometerAngleNames = diffractometerAngleNames + self._upperLimitDict = {} + self._lowerLimitDict = {} + self._cut_angles = {} + self._configure_cuts(defaultCuts) + self.energyScannableMultiplierToGetKeV = \ + energyScannableMultiplierToGetKeV + self._name = 'base' + + @property + def name(self): + return self._name + + def get_axes_names(self): + return tuple(self._diffractometerAngleNames) + + def get_position(self): + """pos = get_position() -- returns the current physical diffractometer + position as a diffcalc.util object in degrees + """ + raise NotImplementedError() + + def get_wavelength(self): + """wavelength = get_wavelength() -- returns wavelength in Angstroms + """ + return 12.39842 / self.get_energy() + + def get_energy(self): + """energy = get_energy() -- returns energy in kEv """ + raise NotImplementedError() + + def __str__(self): + s = self.name + ":\n" + s += " energy : " + str(self.get_energy()) + " keV\n" + s += " wavelength : " + str(self.get_wavelength()) + " Angstrom\n" + names = self._diffractometerAngleNames + for name, pos in zip(names, self.get_position()): + s += " %s : %r deg\n" % (name, pos) + return s + + def __repr__(self): + return self.__str__() + + def get_position_by_name(self, angleName): + names = list(self._diffractometerAngleNames) + return self.get_position()[names.index(angleName)] + +### Limits ### + + def get_lower_limit(self, name): + '''returns lower limits by axis name. Limit may be None if not set + ''' + if name not in self._diffractometerAngleNames: + raise ValueError("No angle called %s. Try one of: %s" % + (name, self._diffractometerAngleNames)) + return self._lowerLimitDict.get(name) + + def get_upper_limit(self, name): + '''returns upper limit by axis name. Limit may be None if not set + ''' + if name not in self._diffractometerAngleNames: + raise ValueError("No angle called %s. Try one of: %s" % + name, self._diffractometerAngleNames) + return self._upperLimitDict.get(name) + + def set_lower_limit(self, name, value): + """value may be None to remove limit""" + if name not in self._diffractometerAngleNames: + raise ValueError( + "Cannot set lower Diffcalc limit: No angle called %s. Try one " + "of: %s" % (name, self._diffractometerAngleNames)) + if value is None: + try: + del self._lowerLimitDict[name] + except KeyError: + print ("WARNING: There was no lower Diffcalc limit %s set to " + "clear" % name) + else: + self._lowerLimitDict[name] = value + + def set_upper_limit(self, name, value): + """value may be None to remove limit""" + if name not in self._diffractometerAngleNames: + raise ValueError( + "Cannot set upper Diffcalc limit: No angle called %s. Try one " + "of: %s" % (name, self._diffractometerAngleNames)) + if value is None: + try: + del self._upperLimitDict[name] + except KeyError: + print ("WARNING: There was no upper Diffcalc limit %s set to " + "clear" % name) + else: + self._upperLimitDict[name] = value + + def is_position_within_limits(self, positionArray): + """ + where position array is in degrees and cut to be between -180 and 180 + """ + names = self._diffractometerAngleNames + for axis_name, value in zip(names, positionArray): + if not self.is_axis_value_within_limits(axis_name, value): + return False + return True + + def is_axis_value_within_limits(self, axis_name, value): + if axis_name in self._upperLimitDict: + if value > self._upperLimitDict[axis_name]: + return False + if axis_name in self._lowerLimitDict: + if value < self._lowerLimitDict[axis_name]: + return False + return True + + def repr_sector_limits_and_cuts(self, name=None): + if name is None: + s = '' + for name in self.get_axes_names(): + s += self.repr_sector_limits_and_cuts(name) + '\n' + s += "Note: When auto sector/transforms are used,\n " + s += " cuts are applied before checking limits." + return s + # limits: + low = self.get_lower_limit(name) + high = self.get_upper_limit(name) + s = ' ' + if low is not None: + s += "% 6.1f <= " % low + else: + s += ' ' * 10 + s += '%5s' % name + if high is not None: + s += " <= % 6.1f" % high + else: + s += ' ' * 10 + # cuts: + try: + if self.get_cuts()[name] is not None: + s += " (cut: % 6.1f)" % self.get_cuts()[name] + except KeyError: + pass + + return s + +### Cutting Stuff ### + + def _configure_cuts(self, defaultCutsDict): + # 1. Set default cut angles + self._cut_angles = dict.fromkeys(self._diffractometerAngleNames, -180.) + if 'phi' in self._cut_angles: + self._cut_angles['phi'] = 0. + # 2. Overide with user-specified cuts + for name, val in defaultCutsDict.iteritems(): + self.set_cut(name, val) + + def set_cut(self, name, value): + if name in self._cut_angles: + self._cut_angles[name] = value + else: + raise KeyError("Diffractometer has no angle %s. Try: %s." % + (name, self._diffractometerAngleNames)) + + def get_cuts(self): + return self._cut_angles + + def cut_angles(self, positionArray): + '''Assumes each angle in positionArray is between -360 and 360 + ''' + cutArray = [] + names = self._diffractometerAngleNames + for axis_name, value in zip(names, positionArray): + cutArray.append(self.cut_angle(axis_name, value)) + return tuple(cutArray) + + def cut_angle(self, axis_name, value): + cut_angle = self._cut_angles[axis_name] + if cut_angle is None: + return value + return cut_angle_at(cut_angle, value) + + +def cut_angle_at(cut_angle, value): + if (cut_angle == 0 and (abs(value - 360) < SMALL) or + (abs(value + 360) < SMALL) or + (abs(value) < SMALL)): + value = 0. + if value < (cut_angle - SMALL): + return value + 360. + elif value >= cut_angle + 360. + SMALL: + return value - 360. + else: + return value + + +class DummyHardwareAdapter(HardwareAdapter): + + def __init__(self, diffractometerAngleNames): + super(self.__class__, self).__init__(diffractometerAngleNames) +# HardwareAdapter.__init__(self, diffractometerAngleNames) + + self._position = [0.] * len(diffractometerAngleNames) + self._wavelength = 1. + self.energyScannableMultiplierToGetKeV = 1 + self._name = "Dummy" + +# Required methods + + def get_position(self): + """ + pos = getDiffractometerPosition() -- returns the current physical + diffractometer position as a list in degrees + """ + return self._position + + def _set_position(self, pos): + assert len(pos) == len(self.get_axes_names()), \ + "Wrong length of input list" + self._position = pos + + position = property(get_position, _set_position) + + def get_energy(self): + """energy = get_energy() -- returns energy in kEv """ + if self._wavelength is None: + raise DiffcalcException( + "Energy or wavelength have not been set") + return (12.39842 / + (self._wavelength * self.energyScannableMultiplierToGetKeV)) + + def _set_energy(self, energy): + self._wavelength = 12.39842 / energy + + energy = property(get_energy, _set_energy) + + def get_wavelength(self): + """wavelength = get_wavelength() -- returns wavelength in Angstroms""" + if self._wavelength is None: + raise DiffcalcException( + "Energy or wavelength have not been set") + return self._wavelength + + def _set_wavelength(self, wavelength): + self._wavelength = wavelength + + wavelength = property(get_wavelength, _set_wavelength) + + +class ScannableHardwareAdapter(HardwareAdapter): + + def __init__(self, diffractometerScannable, energyScannable, + energyScannableMultiplierToGetKeV=1): + input_names = diffractometerScannable.getInputNames() + super(self.__class__, self).__init__(input_names) +# HardwareAdapter.__init__(self, input_names) + self.diffhw = diffractometerScannable + self.energyhw = energyScannable + self.energyScannableMultiplierToGetKeV = \ + energyScannableMultiplierToGetKeV + self._name = "ScannableHarwdareMonitor" + +# Required methods + + def get_position(self): + """ + pos = getDiffractometerPosition() -- returns the current physical + diffractometer position as a list in degrees + """ + return self.diffhw.getPosition() + + def get_energy(self): + """energy = get_energy() -- returns energy in kEv (NOT eV!) """ + multiplier = self.energyScannableMultiplierToGetKeV + energy = self.energyhw.getPosition() * multiplier + if energy is None: + raise DiffcalcException("Energy has not been set") + return energy + + def get_wavelength(self): + """wavelength = get_wavelength() -- returns wavelength in Angstroms""" + energy = self.get_energy() + return 12.39842 / energy + + @property + def name(self): + return self.diffhw.getName() diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/__init__.py b/script/test/diffcalc (copy)/diffcalc/hkl/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/calcbase.py b/script/test/diffcalc (copy)/diffcalc/hkl/calcbase.py new file mode 100644 index 0000000..c4db5f3 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/calcbase.py @@ -0,0 +1,155 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from math import pi + +from diffcalc.util import DiffcalcException, differ + +TORAD = pi / 180 +TODEG = 180 / pi + + +class HklCalculatorBase(object): + + def __init__(self, ubcalc, geometry, hardware, + raiseExceptionsIfAnglesDoNotMapBackToHkl=False): + + self._ubcalc = ubcalc # to get the UBMatrix, tau and sigma + self._geometry = geometry # to access information about the + # diffractometer geometry and mode_selector + self._hardware = hardware # Used for tracking parameters only + self.raiseExceptionsIfAnglesDoNotMapBackToHkl = \ + raiseExceptionsIfAnglesDoNotMapBackToHkl + + def anglesToHkl(self, pos, wavelength): + """ + Return hkl tuple and dictionary of all virtual angles in degrees from + Position in degrees and wavelength in Angstroms. + """ + + h, k, l = self._anglesToHkl(pos.inRadians(), wavelength) + paramDict = self.anglesToVirtualAngles(pos, wavelength) + return ((h, k, l), paramDict) + + def anglesToVirtualAngles(self, pos, wavelength): + """ + Return dictionary of all virtual angles in degrees from Position object + in degrees and wavelength in Angstroms. + """ + anglesDict = self._anglesToVirtualAngles(pos.inRadians(), wavelength) + for name in anglesDict: + anglesDict[name] = anglesDict[name] * TODEG + return anglesDict + + def hklToAngles(self, h, k, l, wavelength): + """ + Return verified Position and all virtual angles in degrees from + h, k & l and wavelength in Angstroms. + + The calculated Position is verified by checking that it maps back using + anglesToHkl() to the requested hkl value. + + Those virtual angles fixed or generated while calculating the position + are verified by by checking that they map back using + anglesToVirtualAngles to the virtual angles for the given position. + + Throws a DiffcalcException if either check fails and + raiseExceptionsIfAnglesDoNotMapBackToHkl is True, otherwise displays a + warning. + """ + + # Update tracked parameters. During this calculation parameter values + # will be read directly from self._parameters instead of via + # self.getParameter which would trigger another potentially time-costly + # position update. + self.parameter_manager.update_tracked() + + pos, virtualAngles = self._hklToAngles(h, k, l, wavelength) # in rad + + # to degrees: + pos.changeToDegrees() + + for key, val in virtualAngles.items(): + if val is not None: + virtualAngles[key] = val * TODEG + + self._verify_pos_map_to_hkl(h, k, l, wavelength, pos) + + virtualAnglesReadback = self._verify_virtual_angles(h, k, l, wavelength, pos, virtualAngles) + + return pos, virtualAnglesReadback + + def _verify_pos_map_to_hkl(self, h, k, l, wavelength, pos): + hkl, _ = self.anglesToHkl(pos, wavelength) + e = 0.001 + if ((abs(hkl[0] - h) > e) or (abs(hkl[1] - k) > e) or + (abs(hkl[2] - l) > e)): + s = "ERROR: The angles calculated for hkl=(%f,%f,%f) were %s.\n" % (h, k, l, str(pos)) + s += "Converting these angles back to hkl resulted in hkl="\ + "(%f,%f,%f)" % (hkl[0], hkl[1], hkl[2]) + if self.raiseExceptionsIfAnglesDoNotMapBackToHkl: + raise DiffcalcException(s) + else: + print s + + def _verify_virtual_angles(self, h, k, l, wavelength, pos, virtualAngles): + # Check that the virtual angles calculated/fixed during the hklToAngles + # those read back from pos using anglesToVirtualAngles + virtualAnglesReadback = self.anglesToVirtualAngles(pos, wavelength) + for key, val in virtualAngles.items(): + if val != None: # Some values calculated in some mode_selector + r = virtualAnglesReadback[key] + if ((differ(val, r, .00001) and differ(val, r + 360, .00001) and differ(val, r - 360, .00001))): + s = "ERROR: The angles calculated for hkl=(%f,%f,%f) with"\ + " mode=%s were %s.\n" % (h, k, l, self.repr_mode(), str(pos)) + s += "During verification the virtual angle %s resulting "\ + "from (or set for) this calculation of %f" % (key, val) + s += "did not match that calculated by "\ + "anglesToVirtualAngles of %f" % virtualAnglesReadback[key] + if self.raiseExceptionsIfAnglesDoNotMapBackToHkl: + raise DiffcalcException(s) + else: + print s + + return virtualAnglesReadback + + def repr_mode(self): + pass + +### Collect all math access to context here + + def _getUBMatrix(self): + return self._ubcalc.UB + + def _getMode(self): + return self.mode_selector.getMode() + + def _getSigma(self): + return self._ubcalc.sigma + + def _getTau(self): + return self._ubcalc.tau + + def _getParameter(self, name): + # Does not use context.getParameter as this will trigger a costly + # parameter collection + pm = self.parameter_manager + return pm.getParameterWithoutUpdatingTrackedParemeters(name) + + def _getGammaParameterName(self): + return self._gammaParameterName diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/common.py b/script/test/diffcalc (copy)/diffcalc/hkl/common.py new file mode 100644 index 0000000..0b3c13a --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/common.py @@ -0,0 +1,55 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### +from diffcalc.util import allnum + +def getNameFromScannableOrString(o): + try: # it may be a scannable + return o.getName() + except AttributeError: + return str(o) + raise TypeError() + + +class DummyParameterManager(object): + + def getParameterDict(self): + return {} + + def _setParameter(self, name, value): + raise KeyError(name) + + def _getParameter(self, name): + raise KeyError(name) + + def update_tracked(self): + pass + + +def sim(self, scn, hkl): + """sim hkl scn -- simulates moving scannable (not all) + """ + if not isinstance(hkl, (tuple, list)): + raise TypeError + + if not allnum(hkl): + raise TypeError() + + try: + print scn.simulateMoveTo(hkl) + except AttributeError: + raise TypeError("The first argument does not support simulated moves") diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/__init__.py b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/calc.py b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/calc.py new file mode 100644 index 0000000..215e31a --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/calc.py @@ -0,0 +1,846 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from math import pi, asin, acos, sin, cos, sqrt, atan2, fabs, atan + +try: + from numpy import matrix + from numpy.linalg import norm +except ImportError: + from numjy import matrix + from numjy.linalg import norm + +from diffcalc.hkl.calcbase import HklCalculatorBase +from diffcalc.hkl.vlieg.transform import TransformCInRadians +from diffcalc.util import dot3, cross3, bound, differ +from diffcalc.hkl.vlieg.geometry import createVliegMatrices, \ + createVliegsPsiTransformationMatrix, \ + createVliegsSurfaceTransformationMatrices, calcPHI +from diffcalc.hkl.vlieg.geometry import VliegPosition +from diffcalc.hkl.vlieg.constraints import VliegParameterManager +from diffcalc.hkl.vlieg.constraints import ModeSelector +from diffcalc.ub.calc import PaperSpecificUbCalcStrategy + + +TORAD = pi / 180 +TODEG = 180 / pi +transformC = TransformCInRadians() + + +PREFER_POSITIVE_CHI_SOLUTIONS = True + +I = matrix('1 0 0; 0 1 0; 0 0 1') +y = matrix('0; 1; 0') + + +def check(condition, ErrorOrStringOrCallable, *args): + """ + fail = check(condition, ErrorOrString) -- if condition is false raises the + Exception passed in, or creates one from a string. If a callable function + is passed in this is called with any args specified and the thing returns + false. + """ + # TODO: Remove (really nasty) check function + if condition == False: + if callable(ErrorOrStringOrCallable): + ErrorOrStringOrCallable(*args) + return False + elif isinstance(ErrorOrStringOrCallable, str): + raise Exception(ErrorOrStringOrCallable) + else: # assume input is an exception + raise ErrorOrStringOrCallable + return True + + +def sign(x): + if x < 0: + return -1 + else: + return 1 + + +def vliegAnglesToHkl(pos, wavelength, UBMatrix): + """ + Returns hkl indices from pos object in radians. + """ + wavevector = 2 * pi / wavelength + + # Create transformation matrices + [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices( + pos.alpha, pos.delta, pos.gamma, pos.omega, pos.chi, pos.phi) + + # Create the plane normal vector in the alpha axis coordinate frame + qa = ((DELTA * GAMMA) - ALPHA.I) * matrix([[0], [wavevector], [0]]) + + # Transform the plane normal vector from the alpha frame to reciprical + # lattice frame. + hkl = UBMatrix.I * PHI.I * CHI.I * OMEGA.I * qa + + return hkl[0, 0], hkl[1, 0], hkl[2, 0] + + +class VliegUbCalcStrategy(PaperSpecificUbCalcStrategy): + + def calculate_q_phi(self, pos): + + [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices( + pos.alpha, pos.delta, pos.gamma, pos.omega, pos.chi, pos.phi) + + u1a = (DELTA * GAMMA - ALPHA.I) * y + u1p = PHI.I * CHI.I * OMEGA.I * u1a + return u1p + + +class VliegHklCalculator(HklCalculatorBase): + + def __init__(self, ubcalc, geometry, hardware, + raiseExceptionsIfAnglesDoNotMapBackToHkl=True): + r = raiseExceptionsIfAnglesDoNotMapBackToHkl + HklCalculatorBase.__init__(self, ubcalc, geometry, hardware, + raiseExceptionsIfAnglesDoNotMapBackToHkl=r) + self._gammaParameterName = ({'arm': 'gamma', 'base': 'oopgamma'} + [self._geometry.gamma_location]) + self.mode_selector = ModeSelector(self._geometry, None, + self._gammaParameterName) + self.parameter_manager = VliegParameterManager( + self._geometry, self._hardware, self.mode_selector, + self._gammaParameterName) + self.mode_selector.setParameterManager(self.parameter_manager) + + def __str__(self): + # should list paramemeters and indicate which are used in selected mode + result = "Available mode_selector:\n" + result += self.mode_selector.reportAvailableModes() + result += '\nCurrent mode:\n' + result += self.mode_selector.reportCurrentMode() + result += '\n\nParameters:\n' + result += self.parameter_manager.reportAllParameters() + return result + + def _anglesToHkl(self, pos, wavelength): + """ + Return hkl tuple from VliegPosition in radians and wavelength in + Angstroms. + """ + return vliegAnglesToHkl(pos, wavelength, self._getUBMatrix()) + + def _anglesToVirtualAngles(self, pos, wavelength): + """ + Return dictionary of all virtual angles in radians from VliegPosition + object win radians and wavelength in Angstroms. The virtual angles are: + Bin, Bout, azimuth and 2theta. + """ + + # Create transformation matrices + [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices( + pos.alpha, pos.delta, pos.gamma, pos.omega, pos.chi, pos.phi) + [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices( + self._getSigma() * TORAD, self._getTau() * TORAD) + + S = TAU * SIGMA + y_vector = matrix([[0], [1], [0]]) + + # Calculate Bin from equation 15: + surfacenormal_alpha = OMEGA * CHI * PHI * S * matrix([[0], [0], [1]]) + incoming_alpha = ALPHA.I * y_vector + minusSinBetaIn = dot3(surfacenormal_alpha, incoming_alpha) + Bin = asin(bound(-minusSinBetaIn)) + + # Calculate Bout from equation 16: + # surfacenormal_alpha has just ben calculated + outgoing_alpha = DELTA * GAMMA * y_vector + sinBetaOut = dot3(surfacenormal_alpha, outgoing_alpha) + Bout = asin(bound(sinBetaOut)) + + # Calculate 2theta from equation 25: + + cosTwoTheta = dot3(ALPHA * DELTA * GAMMA * y_vector, y_vector) + twotheta = acos(bound(cosTwoTheta)) + psi = self._anglesToPsi(pos, wavelength) + + return {'Bin': Bin, 'Bout': Bout, 'azimuth': psi, '2theta': twotheta} + + def _hklToAngles(self, h, k, l, wavelength): + """ + Return VliegPosition and virtual angles in radians from h, k & l and + wavelength in Angstroms. The virtual angles are those fixed or + generated while calculating the position: Bin, Bout and 2theta; and + azimuth in four and five circle modes. + """ + + if self._getMode().group in ("fourc", "fivecFixedGamma", + "fivecFixedAlpha"): + return self._hklToAnglesFourAndFiveCirclesModes(h, k, l, + wavelength) + elif self._getMode().group == "zaxis": + return self._hklToAnglesZaxisModes(h, k, l, wavelength) + else: + raise RuntimeError( + 'The current mode (%s) has an unrecognised group: %s.' + % (self._getMode().name, self._getMode().group)) + + def _hklToAnglesFourAndFiveCirclesModes(self, h, k, l, wavelength): + """ + Return VliegPosition and virtual angles in radians from h, k & l and + wavelength in Angstrom for four and five circle modes. The virtual + angles are those fixed or generated while calculating the position: + Bin, Bout, 2theta and azimuth. + """ + + # Results in radians during calculations, returned in degreess + pos = VliegPosition(None, None, None, None, None, None) + + # Normalise hkl + wavevector = 2 * pi / wavelength + hklNorm = matrix([[h], [k], [l]]) / wavevector + + # Compute hkl in phi axis coordinate frame + hklPhiNorm = self._getUBMatrix() * hklNorm + + # Determine Bin and Bout + if self._getMode().name == '4cPhi': + Bin = Bout = None + else: + Bin, Bout = self._determineBinAndBoutInFourAndFiveCirclesModes( + hklNorm) + + # Determine alpha and gamma + if self._getMode().group == 'fourc': + pos.alpha, pos.gamma = \ + self._determineAlphaAndGammaForFourCircleModes(hklPhiNorm) + else: + pos.alpha, pos.gamma = \ + self._determineAlphaAndGammaForFiveCircleModes(Bin, hklPhiNorm) + if pos.alpha < -pi: + pos.alpha += 2 * pi + if pos.alpha > pi: + pos.alpha -= 2 * pi + + # Determine delta + (pos.delta, twotheta) = self._determineDelta(hklPhiNorm, pos.alpha, + pos.gamma) + + # Determine omega, chi & phi + pos.omega, pos.chi, pos.phi, psi = \ + self._determineSampleAnglesInFourAndFiveCircleModes( + hklPhiNorm, pos.alpha, pos.delta, pos.gamma, Bin) + # (psi will be None in fixed phi mode) + + # Ensure that by default omega is between -90 and 90, by possibly + # transforming the sample angles + if self._getMode().name != '4cPhi': # not in fixed-phi mode + if pos.omega < -pi / 2 or pos.omega > pi / 2: + pos = transformC.transform(pos) + + # Gather up the virtual angles calculated along the way... + # -pi pi: + psi -= 2 * pi + if psi < (-1 * pi): + psi += 2 * pi + + v = {'2theta': twotheta, 'Bin': Bin, 'Bout': Bout, 'azimuth': psi} + return pos, v + + def _hklToAnglesZaxisModes(self, h, k, l, wavelength): + """ + Return VliegPosition and virtual angles in radians from h, k & l and + wavelength in Angstroms for z-axis modes. The virtual angles are those + fixed or generated while calculating the position: Bin, Bout, and + 2theta. + """ + # Section 6: + + # Results in radians during calculations, returned in degreess + pos = VliegPosition(None, None, None, None, None, None) + + # Normalise hkl + wavevector = 2 * pi / wavelength + hkl = matrix([[h], [k], [l]]) + hklNorm = hkl * (1.0 / wavevector) + + # Compute hkl in phi axis coordinate frame + hklPhi = self._getUBMatrix() * hkl + hklPhiNorm = self._getUBMatrix() * hklNorm + + # Determine Chi and Phi (Equation 29): + pos.phi = -self._getTau() * TORAD + pos.chi = -self._getSigma() * TORAD + + # Equation 30: + [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices( + None, None, None, None, pos.chi, pos.phi) + del ALPHA, DELTA, GAMMA, OMEGA + Hw = CHI * PHI * hklPhi + + # Determine Bin and Bout: + (Bin, Bout) = self._determineBinAndBoutInZaxisModes( + Hw[2, 0] / wavevector) + + # Determine Alpha and Gamma (Equation 32): + pos.alpha = Bin + pos.gamma = Bout + + # Determine Delta: + (pos.delta, twotheta) = self._determineDelta(hklPhiNorm, pos.alpha, + pos.gamma) + + # Determine Omega: + delta = pos.delta + gamma = pos.gamma + d1 = (Hw[1, 0] * sin(delta) * cos(gamma) - Hw[0, 0] * + (cos(delta) * cos(gamma) - cos(pos.alpha))) + d2 = (Hw[0, 0] * sin(delta) * cos(gamma) + Hw[1, 0] * + (cos(delta) * cos(gamma) - cos(pos.alpha))) + + if fabs(d2) < 1e-30: + pos.omega = sign(d1) * sign(d2) * pi / 2.0 + else: + pos.omega = atan2(d1, d2) + + # Gather up the virtual angles calculated along the way + return pos, {'2theta': twotheta, 'Bin': Bin, 'Bout': Bout} + +### + + def _determineBinAndBoutInFourAndFiveCirclesModes(self, hklNorm): + """(Bin, Bout) = _determineBinAndBoutInFourAndFiveCirclesModes()""" + BinModes = ('4cBin', '5cgBin', '5caBin') + BoutModes = ('4cBout', '5cgBout', '5caBout') + BeqModes = ('4cBeq', '5cgBeq', '5caBeq') + azimuthModes = ('4cAzimuth') + fixedBusingAndLeviWmodes = ('4cFixedw') + + # Calculate RHS of equation 20 + # RHS (1/K)(S^-1*U*B*H)_3 where H/K = hklNorm + UB = self._getUBMatrix() + [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices( + self._getSigma() * TORAD, self._getTau() * TORAD) + #S = SIGMA * TAU + S = TAU * SIGMA + RHS = (S.I * UB * hklNorm)[2, 0] + + if self._getMode().name in BinModes: + Bin = self._getParameter('betain') + check(Bin != None, "The parameter betain must be set for mode %s" % + self._getMode().name) + Bin = Bin * TORAD + sinBout = RHS - sin(Bin) + check(fabs(sinBout) <= 1, "Could not compute Bout") + Bout = asin(sinBout) + + elif self._getMode().name in BoutModes: + Bout = self._getParameter('betaout') + check(Bout != None, "The parameter Bout must be set for mode %s" % + self._getMode().name) + Bout = Bout * TORAD + sinBin = RHS - sin(Bout) + check(fabs(sinBin) <= 1, "Could not compute Bin") + Bin = asin(sinBin) + + elif self._getMode().name in BeqModes: + sinBeq = RHS / 2 + check(fabs(sinBeq) <= 1, "Could not compute Bin=Bout") + Bin = Bout = asin(sinBeq) + + elif self._getMode().name in azimuthModes: + azimuth = self._getParameter('azimuth') + check(azimuth != None, "The parameter azimuth must be set for " + "mode %s" % self._getMode().name) + del azimuth + # TODO: codeit + raise NotImplementedError() + + elif self._getMode().name in fixedBusingAndLeviWmodes: + bandlomega = self._getParameter('blw') + check(bandlomega != None, "The parameter abandlomega must be set " + "for mode %s" % self._getMode().name) + del bandlomega + # TODO: codeit + raise NotImplementedError() + else: + raise RuntimeError("AngleCalculator does not know how to handle " + "mode %s" % self._getMode().name) + + return (Bin, Bout) + + def _determineBinAndBoutInZaxisModes(self, Hw3OverK): + """(Bin, Bout) = _determineBinAndBoutInZaxisModes(HwOverK)""" + BinModes = ('6czBin') + BoutModes = ('6czBout') + BeqModes = ('6czBeq') + + if self._getMode().name in BinModes: + Bin = self._getParameter('betain') + check(Bin != None, "The parameter betain must be set for mode %s" % + self._getMode().name) + Bin = Bin * TORAD + # Equation 32a: + Bout = asin(Hw3OverK - sin(Bin)) + + elif self._getMode().name in BoutModes: + Bout = self._getParameter('betaout') + check(Bout != None, "The parameter Bout must be set for mode %s" % + self._getMode().name) + Bout = Bout * TORAD + # Equation 32b: + Bin = asin(Hw3OverK - sin(Bout)) + + elif self._getMode().name in BeqModes: + # Equation 32c: + Bin = Bout = asin(Hw3OverK / 2) + + return (Bin, Bout) + +### + + def _determineAlphaAndGammaForFourCircleModes(self, hklPhiNorm): + + if self._getMode().group == 'fourc': + alpha = self._getParameter('alpha') * TORAD + gamma = self._getParameter(self._getGammaParameterName()) * TORAD + check(alpha != None, "alpha parameter must be set in fourc modes") + check(gamma != None, "gamma parameter must be set in fourc modes") + return alpha, gamma + else: + raise RuntimeError( + "determineAlphaAndGammaForFourCirclesModes() " + "is not appropriate for %s modes" % self._getMode().group) + + def _determineAlphaAndGammaForFiveCircleModes(self, Bin, hklPhiNorm): + + ## Solve equation 34 for one possible Y, Yo + # Calculate surface normal in phi frame + [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices( + self._getSigma() * TORAD, self._getTau() * TORAD) + S = TAU * SIGMA + surfaceNormalPhi = S * matrix([[0], [0], [1]]) + # Compute beta in vector + BetaVector = matrix([[0], [-sin(Bin)], [cos(Bin)]]) + # Find Yo + Yo = self._findMatrixToTransformAIntoB(surfaceNormalPhi, BetaVector) + + ## Calculate Hv from equation 39 + Z = matrix([[1, 0, 0], + [0, cos(Bin), sin(Bin)], + [0, -sin(Bin), cos(Bin)]]) + Hv = Z * Yo * hklPhiNorm + # Fixed gamma: + if self._getMode().group == 'fivecFixedGamma': + gamma = self._getParameter(self._getGammaParameterName()) + check(gamma != None, + "gamma parameter must be set in fivecFixedGamma modes") + gamma = gamma * TORAD + H2 = (hklPhiNorm[0, 0] ** 2 + hklPhiNorm[1, 0] ** 2 + + hklPhiNorm[2, 0] ** 2) + a = -(0.5 * H2 * sin(Bin) - Hv[2, 0]) + b = -(1.0 - 0.5 * H2) * cos(Bin) + c = cos(Bin) * sin(gamma) + check((b * b + a * a - c * c) >= 0, 'Could not solve for alpha') + alpha = 2 * atan2(-(b + sqrt(b * b + a * a - c * c)), -(a + c)) + + # Fixed Alpha: + elif self._getMode().group == 'fivecFixedAlpha': + alpha = self._getParameter('alpha') + check(alpha != None, + "alpha parameter must be set in fivecFixedAlpha modes") + alpha = alpha * TORAD + H2 = (hklPhiNorm[0, 0] ** 2 + hklPhiNorm[1, 0] ** 2 + + hklPhiNorm[2, 0] ** 2) + t0 = ((2 * cos(alpha) * Hv[2, 0] - sin(Bin) * cos(alpha) * H2 + + cos(Bin) * sin(alpha) * H2 - 2 * cos(Bin) * sin(alpha)) / + (cos(Bin) * 2.0)) + check(abs(t0) <= 1, "Cannot compute gamma: sin(gamma)>1") + gamma = asin(t0) + else: + raise RuntimeError( + "determineAlphaAndGammaInFiveCirclesModes() is not " + "appropriate for %s modes" % self._getMode().group) + + return (alpha, gamma) + +### + + def _determineDelta(self, hklPhiNorm, alpha, gamma): + """ + (delta, twotheta) = _determineDelta(hklPhiNorm, alpha, gamma) -- + computes delta for all modes. Also returns twotheta for sanity + checking. hklPhiNorm is a 3X1 matrix. + + alpha, gamma & delta - in radians. + h k & l normalised to wavevector and in phi axis coordinates + """ + h = hklPhiNorm[0, 0] + k = hklPhiNorm[1, 0] + l = hklPhiNorm[2, 0] + # See Vlieg section 5 (with K=1) + cosdelta = ((1 + sin(gamma) * sin(alpha) - (h * h + k * k + l * l) / 2) + / (cos(gamma) * cos(alpha))) + costwotheta = (cos(alpha) * cos(gamma) * bound(cosdelta) - + sin(alpha) * sin(gamma)) + return (acos(bound(cosdelta)), acos(bound(costwotheta))) + + def _determineSampleAnglesInFourAndFiveCircleModes(self, hklPhiNorm, alpha, + delta, gamma, Bin): + """ + (omega, chi, phi, psi)=determineNonZAxisSampleAngles(hklPhiNorm, alpha, + delta, gamma, sigma, tau) where hkl has been normalised by the + wavevector and is in the phi Axis coordinate frame. All angles in + radians. hklPhiNorm is a 3X1 matrix + """ + + def equation49through59(psi): + # equation 49 R = (D^-1)*PI*D*Ro + PSI = createVliegsPsiTransformationMatrix(psi) + R = D.I * PSI * D * Ro + + # eq 57: extract omega from R + if abs(R[0, 2]) < 1e-20: + omega = -sign(R[1, 2]) * sign(R[0, 2]) * pi / 2 + else: + omega = -atan2(R[1, 2], R[0, 2]) + + # eq 58: extract chi from R + sinchi = sqrt(pow(R[0, 2], 2) + pow(R[1, 2], 2)) + sinchi = bound(sinchi) + check(abs(sinchi) <= 1, 'could not compute chi') + # (there are two roots to this equation, but only the first is also + # a solution to R33=cos(chi)) + chi = asin(sinchi) + + # eq 59: extract phi from R + if abs(R[2, 0]) < 1e-20: + phi = sign(R[2, 1]) * sign(R[2, 1]) * pi / 2 + else: + phi = atan2(-R[2, 1], -R[2, 0]) + return omega, chi, phi + + def checkSolution(omega, chi, phi): + _, _, _, OMEGA, CHI, PHI = createVliegMatrices( + None, None, None, omega, chi, phi) + R = OMEGA * CHI * PHI + RtimesH_phi = R * H_phi + print ("R*H_phi=%s, Q_alpha=%s" % + (R * H_phi.tolist(), Q_alpha.tolist())) + return not differ(RtimesH_phi, Q_alpha, .0001) + + # Using Vlieg section 7.2 + + # Needed througout: + [ALPHA, DELTA, GAMMA, _, _, _] = createVliegMatrices( + alpha, delta, gamma, None, None, None) + + ## Find Ro, one possible solution to equation 46: R*H_phi=Q_alpha + + # Normalise hklPhiNorm (As it is currently normalised only to the + # wavevector) + normh = norm(hklPhiNorm) + check(normh >= 1e-10, "reciprical lattice vector too close to zero") + H_phi = hklPhiNorm * (1 / normh) + + # Create Q_alpha from equation 47, (it comes normalised) + Q_alpha = ((DELTA * GAMMA) - ALPHA.I) * matrix([[0], [1], [0]]) + Q_alpha = Q_alpha * (1 / norm(Q_alpha)) + + if self._getMode().name == '4cPhi': + ### Use the fixed value of phi as the final constraint ### + phi = self._getParameter('phi') * TORAD + PHI = calcPHI(phi) + H_chi = PHI * H_phi + omega, chi = _findOmegaAndChiToRotateHchiIntoQalpha(H_chi, Q_alpha) + return (omega, chi, phi, None) # psi = None as not calculated + else: + ### Use Bin as the final constraint ### + + # Find a solution Ro to Ro*H_phi=Q_alpha + Ro = self._findMatrixToTransformAIntoB(H_phi, Q_alpha) + + ## equation 50: Find a solution D to D*Q=norm(Q)*[[1],[0],[0]]) + D = self._findMatrixToTransformAIntoB( + Q_alpha, matrix([[1], [0], [0]])) + + ## Find psi and create PSI + + # eq 54: compute u=D*Ro*S*[[0],[0],[1]], the surface normal in + # psi frame + [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices( + self._getSigma() * TORAD, self._getTau() * TORAD) + S = TAU * SIGMA + [u1], [u2], [u3] = (D * Ro * S * matrix([[0], [0], [1]])).tolist() + # TODO: If u points along 100, then any psi is a solution. Choose 0 + if not differ([u1, u2, u3], [1, 0, 0], 1e-9): + psi = 0 + omega, chi, phi = equation49through59(psi) + else: + # equation 53: V=A*(D^-1) + V = ALPHA * D.I + v21 = V[1, 0] + v22 = V[1, 1] + v23 = V[1, 2] + # equation 55 + a = v22 * u2 + v23 * u3 + b = v22 * u3 - v23 * u2 + c = -sin(Bin) - v21 * u1 # TODO: changed sign from paper + + # equation 44 + # Try first root: + def myatan2(y, x): + if abs(x) < 1e-20 and abs(y) < 1e-20: + return pi / 2 + else: + return atan2(y, x) + psi = 2 * myatan2(-(b - sqrt(b * b + a * a - c * c)), -(a + c)) + #psi = -acos(c/sqrt(a*a+b*b))+atan2(b,a)# -2*pi + omega, chi, phi = equation49through59(psi) + + # if u points along z axis, the psi could have been either 0 or 180 + if (not differ([u1, u2, u3], [0, 0, 1], 1e-9) and + abs(psi - pi) < 1e-10): + # Choose 0 to match that read up by angles-to-virtual-angles + psi = 0. + # if u points a long + return (omega, chi, phi, psi) + + def _anglesToPsi(self, pos, wavelength): + """ + pos assumed in radians. -180<= psi <= 180 + """ + # Using Vlieg section 7.2 + + # Needed througout: + [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices( + pos.alpha, pos.delta, pos.gamma, pos.omega, pos.chi, pos.phi) + + # Solve equation 49 for psi, the rotation of the a reference solution + # about Qalpha or H_phi## + + # Find Ro, the reference solution to equation 46: R*H_phi=Q_alpha + + # Create Q_alpha from equation 47, (it comes normalised) + Q_alpha = ((DELTA * GAMMA) - ALPHA.I) * matrix([[0], [1], [0]]) + Q_alpha = Q_alpha * (1 / norm(Q_alpha)) + + # Finh H_phi + h, k, l = self._anglesToHkl(pos, wavelength) + H_phi = self._getUBMatrix() * matrix([[h], [k], [l]]) + normh = norm(H_phi) + check(normh >= 1e-10, "reciprical lattice vector too close to zero") + H_phi = H_phi * (1 / normh) + + # Find a solution Ro to Ro*H_phi=Q_alpha + # This the reference solution with zero azimuth (psi) + Ro = self._findMatrixToTransformAIntoB(H_phi, Q_alpha) + + # equation 48: + R = OMEGA * CHI * PHI + + ## equation 50: Find a solution D to D*Q=norm(Q)*[[1],[0],[0]]) + D = self._findMatrixToTransformAIntoB(Q_alpha, matrix([[1], [0], [0]])) + + # solve equation 49 for psi + # D*R = PSI*D*Ro + # D*R*(D*Ro)^-1 = PSI + PSI = D * R * ((D * Ro).I) + + # Find psi within PSI as defined in equation 51 + PSI_23 = PSI[1, 2] + PSI_33 = PSI[2, 2] + psi = atan2(PSI_23, PSI_33) + + #print "PSI: ", PSI.tolist() + return psi + + def _findMatrixToTransformAIntoB(self, a, b): + """ + Finds a particular matrix Mo that transforms the unit vector a into the + unit vector b. Thats is it finds Mo Mo*a=b. a and b 3x1 matrixes and Mo + is a 3x3 matrix. + + Throws an exception if this is not possible. + """ + # Maths from the appendix of "Angle caluculations + # for a 5-circle diffractometer used for surface X-ray diffraction", + # E. Vlieg, J.F. van der Veen, J.E. Macdonald and M. Miller, J. of + # Applied Cryst. 20 (1987) 330. + # - courtesy of Elias Vlieg again + + # equation A2: compute angle xi between vectors a and b + cosxi = dot3(a, b) + try: + cosxi = bound(cosxi) + except ValueError: + raise Exception("Could not compute cos(xi), vectors a=%f and b=%f " + "must be of unit length" % (norm(a), norm(b))) + xi = acos(cosxi) + + # Mo is identity matrix if xi zero (math below would blow up) + if abs(xi) < 1e-10: + return I + + # equation A3: c=cross(a,b)/sin(xi) + c = cross3(a, b) * (1 / sin(xi)) + + # equation A4: find D matrix that transforms a into the frame + # x = a; y = c x a; z = c. */ + a1 = a[0, 0] + a2 = a[1, 0] + a3 = a[2, 0] + c1 = c[0, 0] + c2 = c[1, 0] + c3 = c[2, 0] + D = matrix([[a1, a2, a3], + [c2 * a3 - c3 * a2, c3 * a1 - c1 * a3, c1 * a2 - c2 * a1], + [c1, c2, c3]]) + + # equation A5: create Xi to rotate by xi about z-axis + XI = matrix([[cos(xi), -sin(xi), 0], + [sin(xi), cos(xi), 0], + [0, 0, 1]]) + + # eq A6: compute Mo + return D.I * XI * D + + +def _findOmegaAndChiToRotateHchiIntoQalpha(h_chi, q_alpha): + """ + (omega, chi) = _findOmegaAndChiToRotateHchiIntoQalpha(H_chi, Q_alpha) + + Solves for omega and chi in OMEGA*CHI*h_chi = q_alpha where h_chi and + q_alpha are 3x1 matrices with unit length. Omega and chi are returned in + radians. + + Throws an exception if this is not possible. + """ + + def solve(a, b, c): + """ + x1,x2 = solve(a , b, c) + solves for the two solutions to x in equations of the form + a*sin(x) + b*cos(x) = c + by using the trigonometric identity + a*sin(x) + b*cos(x) = a*sin(x)+b*cos(x)=sqrt(a**2+b**2)-sin(x+p) + where + p = atan(b/a) + {0 if a>=0 + {pi if a<0 + """ + if a == 0: + p = pi / 2 if b >= 0 else - pi / 2 + else: + p = atan(b / a) + if a < 0: + p = p + pi + guts = c / sqrt(a ** 2 + b ** 2) + if guts < -1: + guts = -1 + elif guts > 1: + guts = 1 + left1 = asin(guts) + left2 = pi - left1 + return (left1 - p, left2 - p) + + def ne(a, b): + """ + shifts a and b in between -pi and pi and tests for near equality + """ + def shift(a): + if a > pi: + return a - 2 * pi + elif a <= -pi: + return a + 2 * pi + else: + return a + return abs(shift(a) - shift(b)) < .0000001 + + # 1. Compute some solutions + h_chi1 = h_chi[0, 0] + h_chi2 = h_chi[1, 0] + h_chi3 = h_chi[2, 0] + q_alpha1 = q_alpha[0, 0] + q_alpha2 = q_alpha[1, 0] + q_alpha3 = q_alpha[2, 0] + + try: + # a) Solve for chi using Equation 3 + chi1, chi2 = solve(-h_chi1, h_chi3, q_alpha3) + + # b) Solve for omega Equation 1 and each chi + B = h_chi1 * cos(chi1) + h_chi3 * sin(chi1) + eq1omega11, eq1omega12 = solve(h_chi2, B, q_alpha1) + B = h_chi1 * cos(chi2) + h_chi3 * sin(chi2) + eq1omega21, eq1omega22 = solve(h_chi2, B, q_alpha1) + + # c) Solve for omega Equation 2 and each chi + A = -h_chi1 * cos(chi1) - h_chi3 * sin(chi1) + eq2omega11, eq2omega12 = solve(A, h_chi2, q_alpha2) + A = -h_chi1 * cos(chi2) - h_chi3 * sin(chi2) + eq2omega21, eq2omega22 = solve(A, h_chi2, q_alpha2) + + except ValueError, e: + raise ValueError( + str(e) + ":\nProblem in fixed-phi calculation for:\nh_chi: " + + str(h_chi.tolist()) + " q_alpha: " + str(q_alpha.tolist())) + + # 2. Choose values of chi and omega that are solutions to equations 1 and 2 + solutions = [] + # a) Check the chi1 solutions + print "_findOmegaAndChiToRotateHchiIntoQalpha:" + if ne(eq1omega11, eq2omega11) or ne(eq1omega11, eq2omega12): +# print "1: eq1omega11, chi1 = ", eq1omega11, chi1 + solutions.append((eq1omega11, chi1)) + if ne(eq1omega12, eq2omega11) or ne(eq1omega12, eq2omega12): +# print "2: eq1omega12, chi1 = ", eq1omega12, chi1 + solutions.append((eq1omega12, chi1)) + # b) Check the chi2 solutions + if ne(eq1omega21, eq2omega21) or ne(eq1omega21, eq2omega22): +# print "3: eq1omega21, chi2 = ", eq1omega21, chi2 + solutions.append((eq1omega21, chi2)) + if ne(eq1omega22, eq2omega21) or ne(eq1omega22, eq2omega22): +# print "4: eq1omega22, chi2 = ", eq1omega22, chi2 + solutions.append((eq1omega22, chi2)) +# print solutions +# print "*" + + if len(solutions) == 0: + e = "h_chi: " + str(h_chi.tolist()) + e += " q_alpha: " + str(q_alpha.tolist()) + e += ("\nchi1:%4f eq1omega11:%4f eq1omega12:%4f eq2omega11:%4f " + "eq2omega12:%4f" % (chi1 * TODEG, eq1omega11 * TODEG, + eq1omega12 * TODEG, eq2omega11 * TODEG, eq2omega12 * TODEG)) + e += ("\nchi2:%4f eq1omega21:%4f eq1omega22:%4f eq2omega21:%4f " + "eq2omega22:%4f" % (chi2 * TODEG, eq1omega21 * TODEG, + eq1omega22 * TODEG, eq2omega21 * TODEG, eq2omega22 * TODEG)) + raise Exception("Could not find simultaneous solution for this fixed " + "phi mode problem\n" + e) + + if not PREFER_POSITIVE_CHI_SOLUTIONS: + return solutions[0] + + positive_chi_solutions = [sol for sol in solutions if sol[1] > 0] + + if len(positive_chi_solutions) == 0: + print "WARNING: A +ve chi solution was requested, but none were found." + print " Returning a -ve one. Try the mapper" + return solutions[0] + + if len(positive_chi_solutions) > 1: + print ("INFO: Multiple +ve chi solutions were found [(omega, chi) ...]" + " = " + str(positive_chi_solutions)) + print " Returning the first" + + return positive_chi_solutions[0] diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/constraints.py b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/constraints.py new file mode 100644 index 0000000..4751bef --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/constraints.py @@ -0,0 +1,336 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from copy import copy + +from diffcalc.util import DiffcalcException + + +class Mode(object): + + def __init__(self, index, name, group, description, parameterNames, + implemented=True): + self.index = index + self.group = group + self.name = name + self.description = description + self.parameterNames = parameterNames + self.implemented = implemented + + def __repr__(self): + return "%i) %s" % (self.index, self.description) + + def __str__(self): + return self.__repr__() + + def usesParameter(self, name): + return name in self.parameterNames + + +class ModeSelector(object): + + def __init__(self, geometry, parameterManager=None, + gammaParameterName='gamma'): + self.parameter_manager = parameterManager + self._geometry = geometry + self._gammaParameterName = gammaParameterName + self._modelist = {} # indexed by non-contiguous mode number + self._configureAvailableModes() + self._selectedIndex = 1 + + def setParameterManager(self, manager): + """ + Required as a ParameterManager and ModelSelector are mutually tied + together in practice + """ + self.parameter_manager = manager + + def _configureAvailableModes(self): + + gammaName = self._gammaParameterName + + ml = self._modelist + + ml[0] = Mode(0, '4cFixedw', 'fourc', 'fourc fixed-bandlw', + ['alpha', gammaName, 'blw'], False) + + ml[1] = Mode(1, '4cBeq', 'fourc', 'fourc bisecting', + ['alpha', gammaName]) + + ml[2] = Mode(2, '4cBin', 'fourc', 'fourc incoming', + ['alpha', gammaName, 'betain']) + + ml[3] = Mode(3, '4cBout', 'fourc', 'fourc outgoing', + ['alpha', gammaName, 'betaout']) + + ml[4] = Mode(4, '4cAzimuth', 'fourc', 'fourc azimuth', + ['alpha', gammaName, 'azimuth'], False) + + ml[5] = Mode(5, '4cPhi', 'fourc', 'fourc fixed-phi', + ['alpha', gammaName, 'phi']) + + ml[10] = Mode(10, '5cgBeq', 'fivecFixedGamma', 'fivec bisecting', + [gammaName]) + + ml[11] = Mode(11, '5cgBin', 'fivecFixedGamma', 'fivec incoming', + [gammaName, 'betain']) + + ml[12] = Mode(12, '5cgBout', 'fivecFixedGamma', 'fivec outgoing', + [gammaName, 'betaout']) + + ml[13] = Mode(13, '5caBeq', 'fivecFixedAlpha', 'fivec bisecting', + ['alpha']) + + ml[14] = Mode(14, '5caBin', 'fivecFixedAlpha', 'fivec incoming', + ['alpha', 'betain']) + + ml[15] = Mode(15, '5caBout', 'fivecFixedAlpha', 'fivec outgoing', + ['alpha', 'betaout']) + + ml[20] = Mode(20, '6czBeq', 'zaxis', 'zaxis bisecting', + []) + + ml[21] = Mode(21, '6czBin', 'zaxis', 'zaxis incoming', + ['betain']) + + ml[22] = Mode(22, '6czBout', 'zaxis', 'zaxiz outgoing', + ['betaout']) + + def setModeByIndex(self, index): + if index in self._modelist: + self._selectedIndex = index + else: + raise DiffcalcException("mode %r is not defined" % index) + + def setModeByName(self, name): + def findModeWithName(name): + for index, mode in self._modelist.items(): + if mode.name == name: + return index, mode + raise ValueError + + try: + index, mode = findModeWithName(name) + except ValueError: + raise DiffcalcException( + 'Unknown mode. The diffraction calculator supports these ' + 'modeSelector: %s' % self._supportedModes.keys()) + if self._geometry.supports_mode_group(mode.group): + self._selectedIndex = index + else: + raise DiffcalcException( + "Mode %s not supported for this diffractometer (%s)." % + (name, self._geometry.name)) + + def getMode(self): + return self._modelist[self._selectedIndex] + + def reportCurrentMode(self): + return self.getMode().__str__() + + def reportAvailableModes(self): + result = '' + indecis = self._modelist.keys() + indecis.sort() + for index in indecis: + mode = self._modelist[index] + if self._geometry.supports_mode_group(mode.group): + paramString = '' + flags = '' + pm = self.parameter_manager + for paramName in pm.getUserChangableParametersForMode(mode): + paramString += paramName + ", " + if paramString: + paramString = paramString[:-2] # remove trailing commas + if not mode.implemented: + flags += "(Not impl.)" + result += ('%2i) %-15s (%s) %s\n' % (mode.index, + mode.description, paramString, flags)) + return result + + +class VliegParameterManager(object): + + def __init__(self, geometry, hardware, modeSelector, + gammaParameterName='gamma'): + self._geometry = geometry + self._hardware = hardware + self._modeSelector = modeSelector + self._gammaParameterName = gammaParameterName + self._parameters = {} + self._defineParameters() + + def _defineParameters(self): + # Set default fixed values (In degrees if angles) + self._parameters = {} + self._parameters['alpha'] = 0 + self._parameters[self._gammaParameterName] = 0 + self._parameters['blw'] = None # Busing and Levi omega! + self._parameters['betain'] = None + self._parameters['betaout'] = None + self._parameters['azimuth'] = None + self._parameters['phi'] = None + + self._parameterDisplayOrder = ( + 'alpha', self._gammaParameterName, 'betain', 'betaout', 'azimuth', + 'phi', 'blw') + self._trackableParameters = ('alpha', self._gammaParameterName, 'phi') + self._trackedParameters = [] + + # Overide parameters that are unchangable for this diffractometer + for (name, value) in self._geometry.fixed_parameters.items(): + if name not in self._parameters: + raise RuntimeError( + "The %s diffractometer geometry specifies a fixed " + "parameter %s that is not used by the diffractometer " + "calculator" % (self._geometry.getName, name)) + self._parameters[name] = value + + def reportAllParameters(self): + self.update_tracked() + result = '' + for name in self._parameterDisplayOrder: + flags = "" + if not self._modeSelector.getMode().usesParameter(name): + flags += '(not relevant in this mode)' + if self._geometry.parameter_fixed(name): + flags += ' (fixed by this diffractometer)' + if self.isParameterTracked(name): + flags += ' (tracking hardware)' + value = self._parameters[name] + if value is None: + value = '---' + else: + value = float(value) + result += '%s: %s %s\n' % (name.rjust(8), value, flags) + return result + + def reportParametersUsedInCurrentMode(self): + self.update_tracked() + result = '' + for name in self.getUserChangableParametersForMode( + self._modeSelector.getMode()): + flags = "" + value = self._parameters[name] + if value is None: + value = '---' + else: + value = float(value) + if self.isParameterTracked(name): + flags += ' (tracking hardware)' + result += '%s: %s %s\n' % (name.rjust(8), value, flags) + return result + + def getUserChangableParametersForMode(self, mode=None): + """ + (p1,p2...p3) = getUserChangableParametersForMode(mode) returns a list + of parameters names used in this mode for this diffractometer geometry. + Checks current mode if no mode specified. + """ + if mode is None: + mode = self._mode + result = [] + for name in self._parameterDisplayOrder: + if self._isParameterChangeable(name, mode): + result += [name] + return result + +### Fixed parameters stuff ### + + def set_constraint(self, name, value): + if not name in self._parameters: + raise DiffcalcException("No fixed parameter %s is used by the " + "diffraction calculator" % name) + if self._geometry.parameter_fixed(name): + raise DiffcalcException( + "The parameter %s cannot be changed: It has been fixed by the " + "%s diffractometer geometry" + % (name, self._geometry.name)) + if self.isParameterTracked(name): + # for safety and to avoid confusion: + raise DiffcalcException( + "Cannot change parameter %s as it is set to track an axis.\n" + "To turn this off use a command like 'trackalpha 0'." % name) + + if not self.isParameterUsedInSelectedMode(name): + print ("WARNING: The parameter %s is not used in mode %i" % + (name, self._modeSelector.getMode().index)) + self._parameters[name] = value + + def isParameterUsedInSelectedMode(self, name): + return self._modeSelector.getMode().usesParameter(name) + + def getParameterWithoutUpdatingTrackedParemeters(self, name): + try: + return self._parameters[name] + except KeyError: + raise DiffcalcException("No fixed parameter %s is used by the " + "diffraction calculator" % name) + + def get_constraint(self, name): + self.update_tracked() + return self.getParameterWithoutUpdatingTrackedParemeters(name) + + def getParameterDict(self): + self.update_tracked() + return copy(self._parameters) + + @property + def settable_constraint_names(self): + """list of all available constraints that have settable values""" + return sorted(self.getParameterDict().keys()) + + def setTrackParameter(self, name, switch): + if not name in self._parameters.keys(): + raise DiffcalcException("No fixed parameter %s is used by the " + "diffraction calculator" % name) + if not name in self._trackableParameters: + raise DiffcalcException("Parameter %s is not trackable" % name) + if not self._isParameterChangeable(name): + print ("WARNING: Parameter %s is not used in mode %i" % + (name, self._mode.index)) + if switch: + if name not in self._trackedParameters: + self._trackedParameters.append(name) + else: + if name in self._trackedParameters: + self._trackedParameters.remove(name) + + def isParameterTracked(self, name): + return (name in self._trackedParameters) + + def update_tracked(self): + """Note that the name of a tracked parameter MUST map into the name of + an external diffractometer angle + """ + if self._trackedParameters: + externalAnglePositionArray = self._hardware.get_position() + externalAngleNames = list(self._hardware.get_axes_names()) + for name in self._trackedParameters: + self._parameters[name] = \ + externalAnglePositionArray[externalAngleNames.index(name)] + + def _isParameterChangeable(self, name, mode=None): + """ + Returns true if parameter is used in a mode (current mode if none + specified), AND if it is not locked by the diffractometer geometry + """ + if mode is None: + mode = self._modeSelector.getMode() + return (mode.usesParameter(name) and + not self._geometry.parameter_fixed(name)) diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/geometry.py b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/geometry.py new file mode 100644 index 0000000..fc26a83 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/geometry.py @@ -0,0 +1,523 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from math import tan, cos, sin, asin, atan, pi, fabs + +try: + from numpy import matrix +except ImportError: + from numjy import matrix + +from diffcalc.util import x_rotation, z_rotation, y_rotation +from diffcalc.util import AbstractPosition +from diffcalc.util import bound, nearlyEqual + + +TORAD = pi / 180 +TODEG = 180 / pi + + +def calcALPHA(alpha): + return x_rotation(alpha) + + +def calcDELTA(delta): + return z_rotation(-delta) + + +def calcGAMMA(gamma): + return x_rotation(gamma) + + +def calcOMEGA(omega): + return z_rotation(-omega) + + +def calcCHI(chi): + return y_rotation(chi) + + +def calcPHI(phi): + return z_rotation(-phi) + + +def createVliegMatrices(alpha=None, delta=None, gamma=None, omega=None, + chi=None, phi=None): + + ALPHA = None if alpha is None else calcALPHA(alpha) + DELTA = None if delta is None else calcDELTA(delta) + GAMMA = None if gamma is None else calcGAMMA(gamma) + OMEGA = None if omega is None else calcOMEGA(omega) + CHI = None if chi is None else calcCHI(chi) + PHI = None if phi is None else calcPHI(phi) + return ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI + + +def createVliegsSurfaceTransformationMatrices(sigma, tau): + """[SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(sigma, tau) + angles in radians + """ + SIGMA = matrix([[cos(sigma), 0, sin(sigma)], + [0, 1, 0], \ + [-sin(sigma), 0, cos(sigma)]]) + + TAU = matrix([[cos(tau), sin(tau), 0], + [-sin(tau), cos(tau), 0], + [0, 0, 1]]) + return(SIGMA, TAU) + + +def createVliegsPsiTransformationMatrix(psi): + """PSI = createPsiTransformationMatrices(psi) + angles in radians + """ + return matrix([[1, 0, 0], + [0, cos(psi), sin(psi)], + [0, -sin(psi), cos(psi)]]) + + +class VliegPosition(AbstractPosition): + """The position of all six diffractometer axis""" + def __init__(self, alpha=None, delta=None, gamma=None, omega=None, + chi=None, phi=None): + self.alpha = alpha + self.delta = delta + self.gamma = gamma + self.omega = omega + self.chi = chi + self.phi = phi + + def clone(self): + return VliegPosition(self.alpha, self.delta, self.gamma, self.omega, + self.chi, self.phi) + + def changeToRadians(self): + self.alpha *= TORAD + self.delta *= TORAD + self.gamma *= TORAD + self.omega *= TORAD + self.chi *= TORAD + self.phi *= TORAD + + def changeToDegrees(self): + self.alpha *= TODEG + self.delta *= TODEG + self.gamma *= TODEG + self.omega *= TODEG + self.chi *= TODEG + self.phi *= TODEG + + def inRadians(self): + pos = self.clone() + pos.changeToRadians() + return pos + + def inDegrees(self): + pos = self.clone() + pos.changeToDegrees() + return pos + + def nearlyEquals(self, pos2, maxnorm): + for a, b in zip(self.totuple(), pos2.totuple()): + if abs(a - b) > maxnorm: + return False + return True + + def totuple(self): + return (self.alpha, self.delta, self.gamma, self.omega, + self.chi, self.phi) + + def __str__(self): + return ("VliegPosition(alpha %r delta: %r gamma: %r omega: %r chi: %r" + " phi: %r)" % self.totuple()) + + def __repr__(self): + return self.__str__() + + def __eq__(self, b): + return self.nearlyEquals(b, .001) + + +class VliegGeometry(object): + +# Required methods + + def __init__(self, name, supported_mode_groups, fixed_parameters, + gamma_location): + """ + Set geometry name (String), list of supported mode groups (list of + strings), list of axis names (list of strings). Define the parameters + e.g. alpha and gamma for a four circle (dictionary). Define wether the + gamma angle is on the 'arm' or the 'base'; used only by AngleCalculator + to interpret the gamma parameter in fixed gamma mode: for instruments + with gamma on the base, rather than on the arm as the code assume + internally, the two methods physical_angles_to_internal_position and + internal_position_to_physical_angles must still be used. + """ + if gamma_location not in ('arm', 'base', None): + raise RuntimeError( + "Gamma must be on either 'arm' or 'base' or None") + + self.name = name + self.supported_mode_groups = supported_mode_groups + self.fixed_parameters = fixed_parameters + self.gamma_location = gamma_location + + def physical_angles_to_internal_position(self, physicalAngles): + raise NotImplementedError() + + def internal_position_to_physical_angles(self, physicalAngles): + raise NotImplementedError() + +### Do not overide these these ### + + def supports_mode_group(self, name): + return name in self.supported_mode_groups + + def parameter_fixed(self, name): # parameter_fixed + return name in self.fixed_parameters.keys() + + +class SixCircleGammaOnArmGeometry(VliegGeometry): + """ + This six-circle diffractometer geometry simply passes through the + angles from a six circle diffractometer with the same geometry and + angle names as those defined in Vliegs's paper defined internally. + """ + + def __init__(self): + VliegGeometry.__init__( + self, + name='sixc_gamma_on_arm', + supported_mode_groups=('fourc', 'fivecFixedGamma', + 'fivecFixedAlpha', 'zaxis'), + fixed_parameters={}, + gamma_location='arm') + + def physical_angles_to_internal_position(self, physicalAngles): + """ (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p) + """ + assert (len(physicalAngles) == 6), "Wrong length of input list" + return VliegPosition(*physicalAngles) + + def internal_position_to_physical_angles(self, internalPosition): + """ (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p) + """ + return internalPosition.totuple() + + +class SixCircleGeometry(VliegGeometry): + """ + This six-circle diffractometer geometry simply passes through the + angles from a six circle diffractometer with the same geometry and + angle names as those defined in Vliegs's paper defined internally. + """ + + def __init__(self): + VliegGeometry.__init__( + self, + name='sixc', + supported_mode_groups=('fourc', 'fivecFixedGamma', + 'fivecFixedAlpha', 'zaxis'), + fixed_parameters={}, + gamma_location='base') + self.hardwareMonitor = None +#(deltaA, gammaA) = gammaOnBaseToArm(deltaB, gammaB, alpha) (all in radians) +#(deltaB, gammaB) = gammaOnArmToBase(deltaA, gammaA, alpha) (all in radians) + + def physical_angles_to_internal_position(self, physicalAngles): + """ (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p) + """ + assert (len(physicalAngles) == 6), "Wrong length of input list" + alpha, deltaB, gammaB, omega, chi, phi = physicalAngles + (deltaA, gammaA) = gammaOnBaseToArm( + deltaB * TORAD, gammaB * TORAD, alpha * TORAD) + return VliegPosition( + alpha, deltaA * TODEG, gammaA * TODEG, omega, chi, phi) + + def internal_position_to_physical_angles(self, internalPosition): + """ (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p) + """ + alpha, deltaA, gammaA, omega, chi, phi = internalPosition.totuple() + deltaB, gammaB = gammaOnArmToBase( + deltaA * TORAD, gammaA * TORAD, alpha * TORAD) + deltaB, gammaB = deltaB * TODEG, gammaB * TODEG + + if self.hardwareMonitor is not None: + gammaName = self.hardwareMonitor.get_axes_names()[2] + minGamma = self.hardwareMonitor.get_lower_limit(gammaName) + maxGamma = self.hardwareMonitor.get_upper_limit(gammaName) + + if maxGamma is not None: + if gammaB > maxGamma: + gammaB = gammaB - 180 + deltaB = 180 - deltaB + if minGamma is not None: + if gammaB < minGamma: + gammaB = gammaB + 180 + deltaB = 180 - deltaB + + return alpha, deltaB, gammaB, omega, chi, phi + + +class FivecWithGammaOnBase(SixCircleGeometry): + + def __init__(self): + VliegGeometry.__init__( + self, + name='fivec_with_gamma', + supported_mode_groups=('fourc', 'fivecFixedGamma'), + fixed_parameters={'alpha': 0.0}, + gamma_location='base') + self.hardwareMonitor = None + + def physical_angles_to_internal_position(self, physicalAngles): + """ (a,d,g,o,c,p) = physicalAnglesToInternal(d,g,o,c,p) + """ + assert (len(physicalAngles) == 5), "Wrong length of input list" + return SixCircleGeometry.physical_angles_to_internal_position( + self, (0,) + tuple(physicalAngles)) + + def internal_position_to_physical_angles(self, internalPosition): + """ (d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p) + """ + return SixCircleGeometry.internal_position_to_physical_angles( + self, internalPosition)[1:] + + +class Fivec(VliegGeometry): + """ + This five-circle diffractometer geometry is for diffractometers with the + same geometry and angle names as those defined in Vliegs's paper defined + internally, but with no out plane detector arm gamma.""" + + def __init__(self): + VliegGeometry.__init__(self, + name='fivec', + supported_mode_groups=('fourc', 'fivecFixedGamma'), + fixed_parameters={'gamma': 0.0}, + gamma_location='arm' + ) + + def physical_angles_to_internal_position(self, physicalAngles): + """ (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p) + """ + assert (len(physicalAngles) == 5), "Wrong length of input list" + physicalAngles = tuple(physicalAngles) + angles = physicalAngles[0:2] + (0.0,) + physicalAngles[2:] + return VliegPosition(*angles) + + def internal_position_to_physical_angles(self, internalPosition): + """ (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p) + """ + sixAngles = internalPosition.totuple() + return sixAngles[0:2] + sixAngles[3:] + + +class Fourc(VliegGeometry): + """ + This five-circle diffractometer geometry is for diffractometers with the + same geometry and angle names as those defined in Vliegs's paper defined + internally, but with no out plane detector arm gamma.""" + + def __init__(self): + VliegGeometry.__init__(self, + name='fourc', + supported_mode_groups=('fourc'), + fixed_parameters={'gamma': 0.0, 'alpha': 0.0}, + gamma_location='arm' + ) + + def physical_angles_to_internal_position(self, physicalAngles): + """ (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p) + """ + assert (len(physicalAngles) == 4), "Wrong length of input list" + physicalAngles = tuple(physicalAngles) + angles = (0.0, physicalAngles[0], 0.0) + physicalAngles[1:] + return VliegPosition(*angles) + + def internal_position_to_physical_angles(self, internalPosition): + """ (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p) + """ + sixAngles = internalPosition.totuple() + return sixAngles[1:2] + sixAngles[3:] + + +def sign(x): + if x < 0: + return -1 + else: + return 1 + +""" +Based on: Elias Vlieg, "A (2+3)-Type Surface Diffractometer: Mergence of +the z-axis and (2+2)-Type Geometries", J. Appl. Cryst. (1998). 31. +198-203 +""" + + +def solvesEq8(alpha, deltaA, gammaA, deltaB, gammaB): + tol = 1e-6 + return (nearlyEqual(sin(deltaA) * cos(gammaA), sin(deltaB), tol) and + nearlyEqual(cos(deltaA) * cos(gammaA), + cos(gammaB - alpha) * cos(deltaB), tol) and + nearlyEqual(sin(gammaA), sin(gammaB - alpha) * cos(deltaB), tol)) + + +GAMMAONBASETOARM_WARNING = ''' +WARNING: This diffractometer has the gamma circle attached to the + base rather than the end of + the delta arm as Vlieg's paper defines. A conversion has + been made from the physical angles to their internal + representation (gamma-on-base-to-arm). This conversion has + forced gamma to be positive by applying the mapping: + + delta --> 180+delta + gamma --> 180+gamma. + + This should have no adverse effect. +''' + + +def gammaOnBaseToArm(deltaB, gammaB, alpha): + """ + (deltaA, gammaA) = gammaOnBaseToArm(deltaB, gammaB, alpha) (all in + radians) + + Maps delta and gamma for an instrument where the gamma circle rests on + the base to the case where it is on the delta arm. + + There are always two possible solutions. To get the second apply the + transform: + + delta --> 180+delta (flip to opposite side of circle) + gamma --> 180+gamma (flip to opposite side of circle) + + This code will return the solution where gamma is between 0 and 180. + """ + + ### Equation11 ### + if fabs(cos(gammaB - alpha)) < 1e-20: + deltaA1 = sign(tan(deltaB)) * sign(cos(gammaB - alpha)) * pi / 2 + else: + deltaA1 = atan(tan(deltaB) / cos(gammaB - alpha)) + # ...second root + if deltaA1 <= 0: + deltaA2 = deltaA1 + pi + else: + deltaA2 = deltaA1 - pi + + ### Equation 12 ### + gammaA1 = asin(bound(cos(deltaB) * sin(gammaB - alpha))) + # ...second root + if gammaA1 >= 0: + gammaA2 = pi - gammaA1 + else: + gammaA2 = -pi - gammaA1 + + # Choose the delta solution that fits equations 8 + if solvesEq8(alpha, deltaA1, gammaA1, deltaB, gammaB): + deltaA, gammaA = deltaA1, gammaA1 + elif solvesEq8(alpha, deltaA2, gammaA1, deltaB, gammaB): + deltaA, gammaA = deltaA2, gammaA1 + print "gammaOnBaseToArm choosing 2nd delta root (to internal)" + elif solvesEq8(alpha, deltaA1, gammaA2, deltaB, gammaB): + print "gammaOnBaseToArm choosing 2nd gamma root (to internal)" + deltaA, gammaA = deltaA1, gammaA2 + elif solvesEq8(alpha, deltaA2, gammaA2, deltaB, gammaB): + print "gammaOnBaseToArm choosing 2nd delta root and 2nd gamma root" + deltaA, gammaA = deltaA2, gammaA2 + else: + raise RuntimeError( + "No valid solutions found mapping from gamma-on-base to gamma-on-arm") + + return deltaA, gammaA + +GAMMAONARMTOBASE_WARNING = ''' + WARNING: This diffractometer has the gamma circle attached to the base + rather than the end of the delta arm as Vlieg's paper defines. + A conversion has been made from the internal representation of + angles to physical angles (gamma-on-arm-to-base). This + conversion has forced gamma to be positive by applying the + mapping: + + delta --> 180-delta + gamma --> 180+gamma. + + This should have no adverse effect. +''' + + +def gammaOnArmToBase(deltaA, gammaA, alpha): + """ + (deltaB, gammaB) = gammaOnArmToBase(deltaA, gammaA, alpha) (all in + radians) + + Maps delta and gamma for an instrument where the gamma circle is on + the delta arm to the case where it rests on the base. + + There are always two possible solutions. To get the second apply the + transform: + + delta --> 180-delta (reflect and flip to opposite side) + gamma --> 180+gamma (flip to opposite side) + + This code will return the solution where gamma is positive, but will + warn if a sign change was made. + """ + + ### Equation 9 ### + deltaB1 = asin(bound(sin(deltaA) * cos(gammaA))) + # ...second root: + if deltaB1 >= 0: + deltaB2 = pi - deltaB1 + else: + deltaB2 = -pi - deltaB1 + + ### Equation 10 ###: + if fabs(cos(deltaA)) < 1e-20: + gammaB1 = sign(tan(gammaA)) * sign(cos(deltaA)) * pi / 2 + alpha + else: + gammaB1 = atan(tan(gammaA) / cos(deltaA)) + alpha + #... second root: + if gammaB1 <= 0: + gammaB2 = gammaB1 + pi + else: + gammaB2 = gammaB1 - pi + + ### Choose the solution that fits equation 8 ### + if (solvesEq8(alpha, deltaA, gammaA, deltaB1, gammaB1) and + 0 <= gammaB1 <= pi): + deltaB, gammaB = deltaB1, gammaB1 + elif (solvesEq8(alpha, deltaA, gammaA, deltaB2, gammaB1) and + 0 <= gammaB1 <= pi): + deltaB, gammaB = deltaB2, gammaB1 + print "gammaOnArmToBase choosing 2nd delta root (to physical)" + elif (solvesEq8(alpha, deltaA, gammaA, deltaB1, gammaB2) and + 0 <= gammaB2 <= pi): + print "gammaOnArmToBase choosing 2nd gamma root (to physical)" + deltaB, gammaB = deltaB1, gammaB2 + elif (solvesEq8(alpha, deltaA, gammaA, deltaB2, gammaB2) + and 0 <= gammaB2 <= pi): + print "gammaOnArmToBase choosing 2nd delta root and 2nd gamma root" + deltaB, gammaB = deltaB2, gammaB2 + else: + raise RuntimeError( + "No valid solutions found mapping gamma-on-arm to gamma-on-base") + + return deltaB, gammaB diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/hkl.py b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/hkl.py new file mode 100644 index 0000000..ae05018 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/hkl.py @@ -0,0 +1,139 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from diffcalc.hkl.common import getNameFromScannableOrString +from diffcalc.util import command +from diffcalc import settings + + +from diffcalc.ub import ub +from diffcalc.hkl.vlieg.calc import VliegHklCalculator + + +__all__ = ['hklmode', 'setpar', 'trackalpha', 'trackgamma', 'trackphi', + 'parameter_manager', 'hklcalc'] + + +hklcalc = VliegHklCalculator(ub.ubcalc, settings.geometry, settings.hardware) + +parameter_manager = hklcalc.parameter_manager + +def __str__(self): + return hklcalc.__str__() + +@command +def hklmode(num=None): + """hklmode {num} -- changes mode or shows current and available modes and all settings""" #@IgnorePep8 + + if num is None: + print hklcalc.__str__() + else: + hklcalc.mode_selector.setModeByIndex(int(num)) + pm = hklcalc.parameter_manager + print (hklcalc.mode_selector.reportCurrentMode() + "\n" + + pm.reportParametersUsedInCurrentMode()) + +def _setParameter(name, value): + hklcalc.parameter_manager.set_constraint(name, value) + +def _getParameter(name): + return hklcalc.parameter_manager.get_constraint(name) + +@command +def setpar(scannable_or_string=None, val=None): + """setpar {parameter_scannable {{val}} -- sets or shows a parameter' + setpar {parameter_name {val}} -- sets or shows a parameter' + """ + + if scannable_or_string is None: + #show all + parameterDict = hklcalc.parameter_manager.getParameterDict() + names = parameterDict.keys() + names.sort() + for name in names: + print _representParameter(name) + else: + name = getNameFromScannableOrString(scannable_or_string) + if val is None: + _representParameter(name) + else: + oldval = _getParameter(name) + _setParameter(name, float(val)) + print _representParameter(name, oldval, float(val)) + +def _representParameter(name, oldval=None, newval=None): + flags = '' + if hklcalc.parameter_manager.isParameterTracked(name): + flags += '(tracking hardware) ' + if settings.geometry.parameter_fixed(name): # @UndefinedVariable + flags += '(fixed by geometry) ' + pm = hklcalc.parameter_manager + if not pm.isParameterUsedInSelectedMode(name): + flags += '(not relevant in this mode) ' + if oldval is None: + val = _getParameter(name) + if val is None: + val = "---" + else: + val = str(val) + return "%s: %s %s" % (name, val, flags) + else: + return "%s: %s --> %f %s" % (name, oldval, newval, flags) + +def _checkInputAndSetOrShowParameterTracking(name, b=None): + """ + for track-parameter commands: If no args displays parameter settings, + otherwise sets the tracking switch for the given parameter and displays + settings. + """ + # set if arg given + if b is not None: + hklcalc.parameter_manager.setTrackParameter(name, b) + # Display: + lastValue = _getParameter(name) + if lastValue is None: + lastValue = "---" + else: + lastValue = str(lastValue) + flags = '' + if hklcalc.parameter_manager.isParameterTracked(name): + flags += '(tracking hardware)' + print "%s: %s %s" % (name, lastValue, flags) + +@command +def trackalpha(b=None): + """trackalpha {boolean} -- determines wether alpha parameter will track alpha axis""" #@IgnorePep8 + _checkInputAndSetOrShowParameterTracking('alpha', b) + +@command +def trackgamma(b=None): + """trackgamma {boolean} -- determines wether gamma parameter will track alpha axis""" #@IgnorePep8 + _checkInputAndSetOrShowParameterTracking('gamma', b) + +@command +def trackphi(b=None): + """trackphi {boolean} -- determines wether phi parameter will track phi axis""" #@IgnorePep8 + _checkInputAndSetOrShowParameterTracking('phi', b) + + +commands_for_help = ['Mode', + hklmode, + setpar, + trackalpha, + trackgamma, + trackphi] diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/transform.py b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/transform.py new file mode 100644 index 0000000..a41e8b9 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/vlieg/transform.py @@ -0,0 +1,480 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from diffcalc.util import command + +from copy import copy +from math import pi + +from diffcalc.hkl.vlieg.geometry import VliegPosition as P + +SMALL = 1e-10 + + +class Transform(object): + + def transform(self, pos): + raise RuntimeError('Not implemented') + + +### Transforms, currently for definition and testing the theory only + +class TransformC(Transform): + '''Flip omega, invert chi and flip phi + ''' + def transform(self, pos): + pos = pos.clone() + pos.omega -= 180 + pos.chi *= -1 + pos.phi -= 180 + return pos + + +class TransformB(Transform): + '''Flip chi, and invert and flip omega + ''' + def transform(self, pos): + pos = pos.clone() + pos.chi -= 180 + pos.omega = 180 - pos.omega + return pos + + +class TransformA(Transform): + '''Invert scattering plane: invert delta and omega and flip chi''' + def transform(self, pos): + pos = pos.clone() + pos.delta *= -1 + pos.omega *= -1 + pos.chi -= 180 + return pos + + +class TransformCInRadians(Transform): + ''' + Flip omega, invert chi and flip phi. Using radians and keeping + -pi 0: + pos.omega -= pi + else: + pos.omega += pi + pos.chi *= -1 + pos.phi += pi + return pos + + +### + +transformsFromSector = { + 0: (), + 1: ('c',), + 2: ('a',), + 3: ('a', 'c'), + 4: ('b', 'c'), + 5: ('b',), + 6: ('a', 'b', 'c'), + 7: ('a', 'b') +} + +sectorFromTransforms = {} +for k, v in transformsFromSector.iteritems(): + sectorFromTransforms[v] = k + + +class VliegPositionTransformer(object): + + def __init__(self, geometry, hardware, solution_transformer): + self._geometry = geometry + self._hardware = hardware + self._solution_transformer = solution_transformer + solution_transformer.limitCheckerFunction = self.is_position_within_limits + + def transform(self, pos): + # 1. Choose the correct sector/transforms + return self._solution_transformer.transformPosition(pos) + + def is_position_within_limits(self, position): + '''where position is Position object in degrees''' + angleTuple = self._geometry.internal_position_to_physical_angles(position) + angleTuple = self._hardware.cut_angles(angleTuple) + return self._hardware.is_position_within_limits(angleTuple) + + +class VliegTransformSelector(object): + '''All returned angles are between -180. and 180. -180.<=angle<180. + ''' +### basic sector selection + + def __init__(self): + self.transforms = [] + self.autotransforms = [] + self.autosectors = [] + self.limitCheckerFunction = None # inject + self.sector = None + self.setSector(0) + + def setSector(self, sector): + if not 0 <= sector <= 7: + raise ValueError('%i must between 0 and 7.' % sector) + self.sector = sector + self.transforms = list(transformsFromSector[sector]) + + def setTransforms(self, transformList): + transformList = list(transformList) + transformList.sort() + self.sector = sectorFromTransforms[tuple(transformList)] + self.transforms = transformList + + def addTransorm(self, transformName): + if transformName not in ('a', 'b', 'c'): + raise ValueError('%s is not a recognised transform. Try a, b or c' + % transformName) + if transformName in self.transforms: + print "WARNING, transform %s is already selected" + else: + self.setTransforms(self.transforms + [transformName]) + + def removeTransorm(self, transformName): + if transformName not in ('a', 'b', 'c'): + raise ValueError('%s is not a recognised transform. Try a, b or c' + % transformName) + if transformName in self.transforms: + new = copy(self.transforms) + new.remove(transformName) + self.setTransforms(new) + else: + print "WARNING, transform %s was not selected" % transformName + + def addAutoTransorm(self, transformOrSector): + ''' + If input is a string (letter), tags one of the transofrms as being a + candidate for auto application. If a number, tags a sector as being a + candidate for auto application, and removes similar tags for any + transforms (as the two are incompatable). + ''' + if type(transformOrSector) == str: + transform = transformOrSector + if transform not in ('a', 'b', 'c'): + raise ValueError( + '%s is not a recognised transform. Try a, b or c' % + transform) + if transform not in self.autotransforms: + self.autosectors = [] + self.autotransforms.append(transform) + else: + print "WARNING: %s is already set to auto apply" % transform + elif type(transformOrSector) == int: + sector = transformOrSector + if not 0 <= sector <= 7: + raise ValueError('%i must between 0 and 7.' % sector) + if sector not in self.autosectors: + self.autotransforms = [] + self.autosectors.append(sector) + else: + print "WARNING: %i is already set to auto apply" % sector + else: + raise ValueError("Input must be 'a', 'b' or 'c', " + "or 1,2,3,4,5,6 or 7.") + + def removeAutoTransform(self, transformOrSector): + if type(transformOrSector) == str: + transform = transformOrSector + if transform not in ('a', 'b', 'c'): + raise ValueError("%s is not a recognised transform. " + "Try a, b or c" % transform) + if transform in self.autotransforms: + self.autotransforms.remove(transform) + else: + print "WARNING: %s is not set to auto apply" % transform + elif type(transformOrSector) == int: + sector = transformOrSector + if not 0 <= sector <= 7: + raise ValueError('%i must between 0 and 7.' % sector) + if sector in self.autosectors: + self.autosectors.remove(sector) + else: + print "WARNING: %s is not set to auto apply" % sector + else: + raise ValueError("Input must be 'a', 'b' or 'c', " + "or 1,2,3,4,5,6 or 7.") + + def setAutoSectors(self, sectorList): + for sector in sectorList: + if not 0 <= sector <= 7: + raise ValueError('%i must between 0 and 7.' % sector) + self.autosectors = list(sectorList) + + def transformPosition(self, pos): + pos = self.transformNWithoutCut(self.sector, pos) + cutpos = self.cutPosition(pos) + # -180 <= cutpos < 180, NOT the externally applied cuts + if len(self.autosectors) > 0: + if self.is_position_within_limits(cutpos): + return cutpos + else: + return self.autoTransformPositionBySector(cutpos) + if len(self.autotransforms) > 0: + if self.is_position_within_limits(cutpos): + return cutpos + else: + return self.autoTransformPositionByTransforms(pos) + #else + return cutpos + + def transformNWithoutCut(self, n, pos): + + if n == 0: + return P(pos.alpha, pos.delta, pos.gamma, + pos.omega, pos.chi, pos.phi) + if n == 1: + return P(pos.alpha, pos.delta, pos.gamma, + pos.omega - 180., -pos.chi, pos.phi - 180.) + if n == 2: + return P(pos.alpha, -pos.delta, pos.gamma, + -pos.omega, pos.chi - 180., pos.phi) + if n == 3: + return P(pos.alpha, -pos.delta, pos.gamma, + 180. - pos.omega, 180. - pos.chi, pos.phi - 180.) + if n == 4: + return P(pos.alpha, pos.delta, pos.gamma, + -pos.omega, 180. - pos.chi, pos.phi - 180.) + if n == 5: + return P(pos.alpha, pos.delta, pos.gamma, + 180. - pos.omega, pos.chi - 180., pos.phi) + if n == 6: + return P(pos.alpha, -pos.delta, pos.gamma, + pos.omega, -pos.chi, pos.phi - 180.) + if n == 7: + return P(pos.alpha, -pos.delta, pos.gamma, + pos.omega - 180., pos.chi, pos.phi) + else: + raise Exception("sector must be between 0 and 7") + +### autosector + + def hasAutoSectorsOrTransformsToApply(self): + return len(self.autosectors) > 0 or len(self.autotransforms) > 0 + + def autoTransformPositionBySector(self, pos): + okaysectors = [] + okaypositions = [] + for sector in self.autosectors: + newpos = self.transformNWithoutCut(sector, pos) + if self.is_position_within_limits(newpos): + okaysectors.append(sector) + okaypositions.append(newpos) + if len(okaysectors) == 0: + raise Exception( + "Autosector could not find a sector (from %s) to move %s into " + "limits." % (self.autosectors, str(pos))) + if len(okaysectors) > 1: + print ("WARNING: Autosector found multiple sectors that would " + "move %s to move into limits: %s" % (str(pos), okaysectors)) + + print ("INFO: Autosector changed sector from %i to %i" % + (self.sector, okaysectors[0])) + self.sector = okaysectors[0] + return okaypositions[0] + + def autoTransformPositionByTransforms(self, pos): + possibleTransforms = self.createListOfPossibleTransforms() + okaytransforms = [] + okaypositions = [] + for transforms in possibleTransforms: + sector = sectorFromTransforms[tuple(transforms)] + newpos = self.cutPosition(self.transformNWithoutCut(sector, pos)) + if self.is_position_within_limits(newpos): + okaytransforms.append(transforms) + okaypositions.append(newpos) + if len(okaytransforms) == 0: + raise Exception( + "Autosector could not find a sector (from %r) to move %r into " + "limits." % (self.autosectors, pos)) + if len(okaytransforms) > 1: + print ("WARNING: Autosector found multiple sectors that would " + "move %s to move into limits: %s" % + (repr(pos), repr(okaytransforms))) + + print ("INFO: Autosector changed selected transforms from %r to %r" % + (self.transforms, okaytransforms[0])) + self.setTransforms(okaytransforms[0]) + return okaypositions[0] + + def createListOfPossibleTransforms(self): + def vary(possibleTransforms, name): + result = [] + for transforms in possibleTransforms: + # add the original. + result.append(transforms) + # add a modified one + toadd = list(copy(transforms)) + if name in transforms: + toadd.remove(name) + else: + toadd.append(name) + toadd.sort() + result.append(toadd) + return result + # start with the currently selected list of transforms + if len(self.transforms) == 0: + possibleTransforms = [()] + else: + possibleTransforms = copy(self.transforms) + + for name in self.autotransforms: + possibleTransforms = vary(possibleTransforms, name) + + return possibleTransforms + + def is_position_within_limits(self, pos): + '''where pos os a poistion object in degrees''' + return self.limitCheckerFunction(pos) + + def __repr__(self): + def createPrefix(transform): + if transform in self.transforms: + s = '*on* ' + else: + s = 'off ' + if len(self.autotransforms) > 0: + if transform in self.autotransforms: + s += '*auto*' + else: + s += ' ' + return s + s = 'Transforms/sector:\n' + s += (' %s (a transform) Invert scattering plane: invert delta and ' + 'omega and flip chi\n' % createPrefix('a')) + s += (' %s (b transform) Flip chi, and invert and flip omega\n' % + createPrefix('b')) + s += (' %s (c transform) Flip omega, invert chi and flip phi\n' % + createPrefix('c')) + s += ' Current sector: %i (Spec fourc equivalent)\n' % self.sector + if len(self.autosectors) > 0: + s += ' Auto sectors: %s\n' % self.autosectors + return s + + def cutPosition(self, position): + '''Cuts angles at -180.; moves each argument between -180. and 180. + ''' + def cut(a): + if a is None: + return None + else: + if a < (-180. - SMALL): + return a + 360. + if a > (180. + SMALL): + return a - 360. + return a + return P(cut(position.alpha), cut(position.delta), cut(position.gamma), + cut(position.omega), cut(position.chi), cut(position.phi)) + + +def getNameFromScannableOrString(o): + try: # it may be a scannable + return o.getName() + except AttributeError: + return str(o) + + +class TransformCommands(object): + + def __init__(self, sector_selector): + self._sectorSelector = sector_selector + + @command + def transform(self): + """transform -- show transform configuration""" + print self._sectorSelector.__repr__() + + @command + def transforma(self, *args): + """transforma {on|off|auto|manual} -- configure transform A application + """ + self._transform('transforma', 'a', args) + + @command + def transformb(self, *args): + """transformb {on|off|auto|manual} -- configure transform B application + """ + self._transform('transformb', 'b', args) + + @command + def transformc(self, *args): + """transformc {on|off|auto|manual} -- configure transform C application + """ + + self._transform('transformc', 'c', args) + + def _transform(self, commandName, transformName, args): + if len(args) == 0: + print self._sectorSelector.__repr__() + return + # get name + if len(args) != 1: + raise TypeError() + if type(args[0]) is not str: + raise TypeError() + + ss = self._sectorSelector + if args[0] == 'on': + ss.addTransorm(transformName) + elif args[0] == 'off': + ss.removeTransorm(transformName) + elif args[0] == 'auto': + ss.addAutoTransorm(transformName) + elif args[0] == 'manual': + ss.removeAutoTransform(transformName) + else: + raise TypeError() + print self._sectorSelector.__repr__() + + @command + def sector(self, sector=None): + """sector {0-7} -- Select or display sector (a la Spec) + """ + if sector is None: + print self._sectorSelector.__repr__() + else: + if type(sector) is not int and not (0 <= sector <= 7): + raise TypeError() + self._sectorSelector.setSector(sector) + print self._sectorSelector.__repr__() + + @command + def autosector(self, *args): + """autosector [None] [0-7] [0-7]... -- Set sectors that might be automatically applied""" #@IgnorePep8 + if len(args) == 0: + print self._sectorSelector.__repr__() + elif len(args) == 1 and args[0] is None: + self._sectorSelector.setAutoSectors([]) + print self._sectorSelector.__repr__() + else: + sectorList = [] + for arg in args: + if type(arg) is not int: + raise TypeError() + sectorList.append(arg) + self._sectorSelector.setAutoSectors(sectorList) + print self._sectorSelector.__repr__() + + + diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/willmott/__init__.py b/script/test/diffcalc (copy)/diffcalc/hkl/willmott/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/willmott/calc.py b/script/test/diffcalc (copy)/diffcalc/hkl/willmott/calc.py new file mode 100644 index 0000000..a8e94ba --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/willmott/calc.py @@ -0,0 +1,292 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from math import pi, asin, acos, atan2, sin, cos, sqrt + +try: + from numpy import matrix +except ImportError: + from numjy import matrix + +from diffcalc.log import logging +from diffcalc.util import bound, AbstractPosition, DiffcalcException,\ + x_rotation, z_rotation +from diffcalc.hkl.vlieg.geometry import VliegGeometry +from diffcalc.ub.calc import PaperSpecificUbCalcStrategy +from diffcalc.hkl.calcbase import HklCalculatorBase +from diffcalc.hkl.common import DummyParameterManager + +logger = logging.getLogger("diffcalc.hkl.willmot.calcwill") + +CHOOSE_POSITIVE_GAMMA = True + +TORAD = pi / 180 +TODEG = 180 / pi +I = matrix('1 0 0; 0 1 0; 0 0 1') +SMALL = 1e-10 + +TEMPORARY_CONSTRAINTS_DICT_RAD = {'betain': 2 * TORAD} + + +def create_matrices(delta, gamma, omegah, phi): + return (calc_DELTA(delta), calc_GAMMA(gamma), calc_OMEGAH(omegah), + calc_PHI(phi)) + + +def calc_DELTA(delta): + return x_rotation(delta) # (39) + + +def calc_GAMMA(gamma): + return z_rotation(gamma) # (40) + + +def calc_OMEGAH(omegah): + return x_rotation(omegah) # (41) + + +def calc_PHI(phi): + return z_rotation(phi) # (42) + + +def angles_to_hkl_phi(delta, gamma, omegah, phi): + """Calculate hkl matrix in phi frame in units of 2*pi/lambda + """ + DELTA, GAMMA, OMEGAH, PHI = create_matrices(delta, gamma, omegah, phi) + H_lab = (GAMMA * DELTA - I) * matrix([[0], [1], [0]]) # (43) + H_phi = PHI.I * OMEGAH.I * H_lab # (44) + return H_phi + + +def angles_to_hkl(delta, gamma, omegah, phi, wavelength, UB): + """Calculate hkl matrix in reprical lattice space in units of 1/Angstrom + """ + H_phi = angles_to_hkl_phi(delta, gamma, omegah, phi) * 2 * pi / wavelength + hkl = UB.I * H_phi # (5) + return hkl + + +class WillmottHorizontalPosition(AbstractPosition): + + def __init__(self, delta=None, gamma=None, omegah=None, phi=None): + self.delta = delta + self.gamma = gamma + self.omegah = omegah + self.phi = phi + + def clone(self): + return WillmottHorizontalPosition(self.delta, self.gamma, self.omegah, + self.phi) + + def changeToRadians(self): + self.delta *= TORAD + self.gamma *= TORAD + self.omegah *= TORAD + self.phi *= TORAD + + def changeToDegrees(self): + self.delta *= TODEG + self.gamma *= TODEG + self.omegah *= TODEG + self.phi *= TODEG + + def totuple(self): + return (self.delta, self.gamma, self.omegah, self.phi) + + def __str__(self): + return ('WillmottHorizontalPosition(' + 'delta: %.4f gamma: %.4f omegah: %.4f phi: %.4f)' % + (self.delta, self.gamma, self.omegah, self.phi)) + + +class WillmottHorizontalGeometry(object): + + def __init__(self): + self.name = 'willmott_horizontal' + + def physical_angles_to_internal_position(self, physicalAngles): + return WillmottHorizontalPosition(*physicalAngles) + + def internal_position_to_physical_angles(self, internalPosition): + return internalPosition.totuple() + + def create_position(self, delta, gamma, omegah, phi): + return WillmottHorizontalPosition(delta, gamma, omegah, phi) + + +class WillmottHorizontalUbCalcStrategy(PaperSpecificUbCalcStrategy): + + def calculate_q_phi(self, pos): + H_phi = angles_to_hkl_phi(*pos.totuple()) + return matrix(H_phi.tolist()) + + +class DummyConstraints(object): + + @property + def reference(self): + """dictionary of constrained reference circles""" + return TEMPORARY_CONSTRAINTS_DICT_RAD + + +class ConstraintAdapter(object): + + def __init__(self, constraints): + self._constraints = constraints + + def getParameterDict(self): + names = self._constraints.available + return dict(zip(names, [None] * len(names))) + + def setParameter(self, name, value): + self._constraints.set_constraint(name, value) + + def get(self, name): + if name in self._constraints.all: + val = self._constraints.get_value(name) + return 999 if val is None else val + else: + return 999 + + def update_tracked(self): + pass + + +class WillmottHorizontalCalculator(HklCalculatorBase): + + def __init__(self, ubcalc, geometry, hardware, constraints, + raiseExceptionsIfAnglesDoNotMapBackToHkl=True): + """" + Where constraints.reference is a one element dict with the key either + ('betain', 'betaout' or 'equal') and the value a number or None for + 'betain_eq_betaout' + """ + + HklCalculatorBase.__init__(self, ubcalc, geometry, hardware, + raiseExceptionsIfAnglesDoNotMapBackToHkl) + + if constraints is not None: + self.constraints = constraints + self.parameter_manager = ConstraintAdapter(constraints) + else: + self.constraints = DummyConstraints() + self.parameter_manager = DummyParameterManager() + + @property + def _UB(self): + return self._ubcalc.UB + + def _anglesToHkl(self, pos, wavelength): + """ + Calculate miller indices from position in radians. + """ + hkl_matrix = angles_to_hkl(pos.delta, pos.gamma, pos.omegah, pos.phi, + wavelength, self._UB) + return hkl_matrix[0, 0], hkl_matrix[1, 0], hkl_matrix[2, 0], + + def _anglesToVirtualAngles(self, pos, wavelength): + """ + Calculate virtual-angles in radians from position in radians. + + Return theta, alpha, and beta in a dictionary. + """ + + betain = pos.omegah # (52) + + hkl = angles_to_hkl(pos.delta, pos.gamma, pos.omegah, pos.phi, + wavelength, self._UB) + H_phi = self._UB * hkl + H_phi = H_phi / (2 * pi / wavelength) + l_phi = H_phi[2, 0] + sin_betaout = l_phi - sin(betain) + betaout = asin(bound(sin_betaout)) # (54) + + cos_2theta = cos(pos.delta) * cos(pos.gamma) + theta = acos(bound(cos_2theta)) / 2. + + return {'theta': theta, 'betain': betain, 'betaout': betaout} + + def _hklToAngles(self, h, k, l, wavelength): + """ + Calculate position and virtual angles in radians for a given hkl. + """ + + H_phi = self._UB * matrix([[h], [k], [l]]) # units: 1/Angstrom + H_phi = H_phi / (2 * pi / wavelength) # units: 2*pi/wavelength + h_phi = H_phi[0, 0] + k_phi = H_phi[1, 0] + l_phi = H_phi[2, 0] # (5) + + ### determine betain (omegah) and betaout ### + + if not self.constraints.reference: + raise ValueError("No reference constraint has been constrained.") + + ref_name, ref_value = self.constraints.reference.items()[0] + if ref_value is not None: + ref_value *= TORAD + if ref_name == 'betain': + betain = ref_value + betaout = asin(bound(l_phi - sin(betain))) # (53) + elif ref_name == 'betaout': + betaout = ref_value + betain = asin(bound(l_phi - sin(betaout))) # (54) + elif ref_name == 'bin_eq_bout': + betain = betaout = asin(bound(l_phi / 2)) # (55) + else: + raise ValueError("Unexpected constraint name'%s'." % ref_name) + + if abs(betain) < SMALL: + raise DiffcalcException('required betain was 0 degrees (requested ' + 'q is perpendicular to surface normal)') + if betain < -SMALL: + raise DiffcalcException("betain was -ve (%.4f)" % betain) +# logger.info('betain = %.4f, betaout = %.4f', +# betain * TODEG, betaout * TODEG) + omegah = betain # (52) + + ### determine H_lab (X, Y and Z) ### + + Y = -(h_phi ** 2 + k_phi ** 2 + l_phi ** 2) / 2 # (45) + + Z = (sin(betaout) + sin(betain) * (Y + 1)) / cos(omegah) # (47) + + X_squared = (h_phi ** 2 + k_phi ** 2 - + ((cos(betain) * Y + sin(betain) * Z) ** 2)) # (48) + if (X_squared < 0) and (abs(X_squared) < SMALL): + X_squared = 0 + Xpositive = sqrt(X_squared) + if CHOOSE_POSITIVE_GAMMA: + X = -Xpositive + else: + X = Xpositive +# logger.info('H_lab (X,Y,Z) = [%.4f, %.4f, %.4f]', X, Y, Z) + ### determine diffractometer angles ### + + gamma = atan2(-X, Y + 1) # (49) + if (abs(gamma) < SMALL): + # degenerate case, only occurs when q || z + delta = 2 * omegah + else: + delta = atan2(Z * sin(gamma), -X) # (50) + M = cos(betain) * Y + sin(betain) * Z + phi = atan2(h_phi * M - k_phi * X, h_phi * X + k_phi * M) # (51) + + pos = WillmottHorizontalPosition(delta, gamma, omegah, phi) + virtual_angles = {'betain': betain, 'betaout': betaout} + return pos, virtual_angles diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/willmott/commands.py b/script/test/diffcalc (copy)/diffcalc/hkl/willmott/commands.py new file mode 100644 index 0000000..1d6cadd --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/willmott/commands.py @@ -0,0 +1,58 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from diffcalc.hkl.common import getNameFromScannableOrString +from diffcalc.util import command + + +class WillmottHklCommands(object): + + def __init__(self, hklcalc): + self._hklcalc = hklcalc + self.commands = [self.con, + self.uncon, + self.cons] + + def __str__(self): + return self._hklcalc.__str__() + + @command + def con(self, scn_or_string): + """con -- constrains constraint + """ + name = getNameFromScannableOrString(scn_or_string) + self._hklcalc.constraints.constrain(name) + print self._report_constraints() + + @command + def uncon(self, scn_or_string): + """uncon -- unconstrains constraint + """ + name = getNameFromScannableOrString(scn_or_string) + self._hklcalc.constraints.unconstrain(name) + print self._report_constraints() + + @command + def cons(self): + """cons -- list available constraints and values + """ + print self._report_constraints() + + def _report_constraints(self): + return (self._hklcalc.constraints.build_display_table_lines() + '\n\n' + + self._hklcalc.constraints._report_constraints()) diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/willmott/constraints.py b/script/test/diffcalc (copy)/diffcalc/hkl/willmott/constraints.py new file mode 100644 index 0000000..9ab5abd --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/willmott/constraints.py @@ -0,0 +1,156 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from diffcalc.util import DiffcalcException + + +def filter_dict(d, keys): + """Return a copy of d containing only keys that are in keys""" + ##return {k: d[k] for k in keys} # requires Python 2.6 + return dict((k, d[k]) for k in keys if k in d.keys()) + + +ref_constraints = ('betain', 'betaout', 'bin_eq_bout') +valueless_constraints = ('bin_eq_bout') +all_constraints = ref_constraints + + +class WillmottConstraintManager(object): + """Constraints in degrees. + """ + + def __init__(self): + self._constrained = {'bin_eq_bout': None} + + @property + def available_constraint_names(self): + """list of all available constraints""" + return all_constraints + + @property + def all(self): # @ReservedAssignment + """dictionary of all constrained values""" + return self._constrained.copy() + + @property + def reference(self): + """dictionary of constrained reference circles""" + return filter_dict(self.all, ref_constraints) + + @property + def constrained_names(self): + """ordered tuple of constained circles""" + names = self.all.keys() + names.sort(key=lambda name: list(all_constraints).index(name)) + return tuple(names) + + def is_constrained(self, name): + return name in self._constrained + + def get_value(self, name): + return self._constrained[name] + + def _build_display_table(self): + constraint_types = (ref_constraints,) + num_rows = max([len(col) for col in constraint_types]) + max_name_width = max( + [len(name) for name in sum(constraint_types[:2], ())]) + # headings + lines = [' ' + 'REF'.ljust(max_name_width)] + lines.append(' ' + '=' * max_name_width + ' ') + + # constraint rows + for n_row in range(num_rows): + cells = [] + for col in constraint_types: + name = col[n_row] if n_row < len(col) else '' + cells.append(self._label_constraint(name)) + cells.append(('%-' + str(max_name_width) + 's ') % name) + lines.append(''.join(cells)) + lines.append + return '\n'.join(lines) + + def _report_constraints(self): + if not self.reference: + return "!!! No reference constraint set" + name, val = self.reference.items()[0] + if name in valueless_constraints: + return " %s" % name + else: + if val is None: + return "!!! %s: ---" % name + else: + return " %s: %.4f" % (name, val) + + def _label_constraint(self, name): + if name == '': + label = ' ' + elif (self.is_constrained(name) and (self.get_value(name) is None) and + name not in valueless_constraints): + label = 'o-> ' + elif self.is_constrained(name): + label = '--> ' + else: + label = ' ' + return label + + def constrain(self, name): + if name in self.all: + return "%s is already constrained." % name.capitalize() + elif name in ref_constraints: + return self._constrain_reference(name) + else: + raise DiffcalcException('%s is not a valid constraint name') + + def _constrain_reference(self, name): + if self.reference: + constrained_name = self.reference.keys()[0] + del self._constrained[constrained_name] + self._constrained[name] = None + return '%s constraint replaced.' % constrained_name.capitalize() + else: + self._constrained[name] = None + + def unconstrain(self, name): + if name in self._constrained: + del self._constrained[name] + else: + return "%s was not already constrained." % name.capitalize() + +### + def _check_constraint_settable(self, name, verb): + if name not in all_constraints: + raise DiffcalcException( + 'Could not %(verb)s %(name)s as this is not an available ' + 'constraint.' % locals()) + elif name not in self.all.keys(): + raise DiffcalcException( + 'Could not %(verb)s %(name)s as this is not currently ' + 'constrained.' % locals()) + elif name in valueless_constraints: + raise DiffcalcException( + 'Could not %(verb)s %(name)s as this constraint takes no ' + 'value.' % locals()) + + def set_constraint(self, name, value): # @ReservedAssignment + self._check_constraint_settable(name, 'set') + old_value = self.all[name] + old = str(old_value) if old_value is not None else '---' + self._constrained[name] = float(value) + new = str(value) + return "%(name)s : %(old)s --> %(new)s" % locals() diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/you/__init__.py b/script/test/diffcalc (copy)/diffcalc/hkl/you/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/you/calc.py b/script/test/diffcalc (copy)/diffcalc/hkl/you/calc.py new file mode 100644 index 0000000..1e9ac96 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/you/calc.py @@ -0,0 +1,1167 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from math import pi, sin, cos, acos, asin, atan2, sqrt +from itertools import product + +try: + from numpy import matrix + from numpy.linalg import norm +except ImportError: + from numjy import matrix + from numjy.linalg import norm + +from diffcalc.log import logging +from diffcalc.hkl.calcbase import HklCalculatorBase +from diffcalc.hkl.you.geometry import create_you_matrices, calcMU, calcPHI, \ + calcCHI, calcETA +from diffcalc.hkl.you.geometry import YouPosition +from diffcalc.util import DiffcalcException, bound, angle_between_vectors,\ + y_rotation +from diffcalc.util import cross3, z_rotation, x_rotation +from diffcalc.ub.calc import PaperSpecificUbCalcStrategy + +from diffcalc.hkl.you.constraints import NUNAME +logger = logging.getLogger("diffcalc.hkl.you.calc") +I = matrix('1 0 0; 0 1 0; 0 0 1') + +SMALL = 1e-6 +TORAD = pi / 180 +TODEG = 180 / pi + +PRINT_DEGENERATE = False +PRINT_WARNINGS = False + + +def is_small(x): + return abs(x) < SMALL + + +def sign(x): + if is_small(x): + return 0 + if x > 0: + return 1 + # x < 0 + return -1 + + +def normalised(vector): + return vector * (1 / norm(vector)) + + +def cut_at_minus_pi(value): + if value < (-pi - SMALL): + return value + 2 * pi + if value >= pi + SMALL: + return value - 2 * pi + return value + + +def _calc_N(Q, n): + """Return N as described by Equation 31""" + Q = normalised(Q) + n = normalised(n) + if is_small(angle_between_vectors(Q, n)): + # Replace the reference vector with an alternative vector from Eq.(78) + idx_min, _ = min(enumerate([abs(Q[0, 0]), abs(Q[1, 0]), abs(Q[2, 0])]), key=lambda v: v[1]) + idx_1, idx_2 = [idx for idx in range(3) if idx != idx_min] + qval = sqrt(Q[idx_1, 0] * Q[idx_1, 0] + Q[idx_2, 0] * Q[idx_2, 0]) + n[idx_min, 0] = qval + n[idx_1, 0] = -Q[idx_min, 0] * Q[idx_1, 0] / qval + n[idx_2, 0] = -Q[idx_min, 0] * Q[idx_2, 0] / qval + if is_small(norm(n)): + n[idx_min, 0] = 0 + n[idx_1, 0] = Q[idx_2, 0] / qval + n[idx_2, 0] = -Q[idx_1, 0] / qval + Qxn = cross3(Q, n) + QxnxQ = cross3(Qxn, Q) + QxnxQ = normalised(QxnxQ) + Qxn = normalised(Qxn) + return matrix([[Q[0, 0], QxnxQ[0, 0], Qxn[0, 0]], + [Q[1, 0], QxnxQ[1, 0], Qxn[1, 0]], + [Q[2, 0], QxnxQ[2, 0], Qxn[2, 0]]]) + + +def _calc_angle_between_naz_and_qaz(theta, alpha, tau): + # Equation 30: + top = cos(tau) - sin(alpha) * sin(theta) + bottom = cos(alpha) * cos(theta) + if is_small(bottom): + if is_small(cos(alpha)): + raise ValueError('cos(alpha) is too small') + if is_small(cos(theta)): + raise ValueError('cos(theta) is too small') + if is_small(sin(tau)): + return 0. + return acos(bound(top / bottom)) + + +def youAnglesToHkl(pos, wavelength, UBmatrix): + """Calculate miller indices from position in radians. + """ + + [MU, DELTA, NU, ETA, CHI, PHI] = create_you_matrices(*pos.totuple()) + + q_lab = (NU * DELTA - I) * matrix([[0], [2 * pi / wavelength], [0]]) # 12 + + hkl = UBmatrix.I * PHI.I * CHI.I * ETA.I * MU.I * q_lab + + return hkl[0, 0], hkl[1, 0], hkl[2, 0] + + +def _tidy_degenerate_solutions(pos, constraints): + + original = pos.inDegrees() + detector_like_constraint = constraints.detector or constraints.naz + nu_constrained_to_0 = is_small(pos.nu) and detector_like_constraint + mu_constrained_to_0 = is_small(pos.mu) and 'mu' in constraints.sample + delta_constrained_to_0 = is_small(pos.delta) and detector_like_constraint + eta_constrained_to_0 = is_small(pos.eta) and 'eta' in constraints.sample + phi_not_constrained = not 'phi' in constraints.sample + + if nu_constrained_to_0 and mu_constrained_to_0 and phi_not_constrained: + # constrained to vertical 4-circle like mode + if is_small(pos.chi): # phi || eta + desired_eta = pos.delta / 2. + eta_diff = desired_eta - pos.eta + pos.eta = desired_eta + pos.phi -= eta_diff + if PRINT_DEGENERATE: + print ('DEGENERATE: with chi=0, phi and eta are colinear:' + 'choosing eta = delta/2 by adding % 7.3f to eta and ' + 'removing it from phi. (mu=%s=0 only)' % (eta_diff * TODEG, NUNAME)) + print ' original:', original + + elif delta_constrained_to_0 and eta_constrained_to_0 and phi_not_constrained: + # constrained to horizontal 4-circle like mode + if is_small(pos.chi - pi / 2): # phi || mu + desired_mu = pos.nu / 2. + mu_diff = desired_mu - pos.mu + pos.mu = desired_mu + pos.phi += mu_diff + if PRINT_DEGENERATE: + print ('DEGENERATE: with chi=90, phi and mu are colinear: choosing' + ' mu = %s/2 by adding % 7.3f to mu and to phi. ' + '(delta=eta=0 only)' % (NUNAME, mu_diff * TODEG)) + print ' original:', original + + return pos + + +def _theta_and_qaz_from_detector_angles(delta, nu): + # Equation 19: + cos_2theta = cos(delta) * cos(nu) + theta = acos(cos_2theta) / 2. + sgn = sign(sin(2. * theta)) + qaz = atan2(sgn * sin(delta), sgn * cos(delta) * sin(nu)) + return theta, qaz + + +class YouUbCalcStrategy(PaperSpecificUbCalcStrategy): + + def calculate_q_phi(self, pos): + + [MU, DELTA, NU, ETA, CHI, PHI] = create_you_matrices(*pos.totuple()) + # Equation 12: Compute the momentum transfer vector in the lab frame + y = matrix('0; 1; 0') + q_lab = (NU * DELTA - I) * y + # Transform this into the phi frame. + return PHI.I * CHI.I * ETA.I * MU.I * q_lab + + +UNREACHABLE_MSG = ( + 'The current combination of constraints with %s = %.4f\n' + 'prohibits a solution for the specified reflection.') + + +class YouHklCalculator(HklCalculatorBase): + + def __init__(self, ubcalc, geometry, hardware, constraints, + raiseExceptionsIfAnglesDoNotMapBackToHkl=True): + HklCalculatorBase.__init__(self, ubcalc, geometry, hardware, + raiseExceptionsIfAnglesDoNotMapBackToHkl) + self._hardware = hardware # for checking limits only + self.constraints = constraints + self.parameter_manager = constraints # TODO: remove need for this attr + + def __str__(self): + return self.constraints.__str__() + + def _get_n_phi(self): + return self._ubcalc.n_phi + + def _get_ubmatrix(self): + return self._getUBMatrix() # for consistency + + def repr_mode(self): + return repr(self.constraints.all) + + def _anglesToHkl(self, pos, wavelength): + """Calculate miller indices from position in radians. + """ + return youAnglesToHkl(pos, wavelength, self._get_ubmatrix()) + + def _anglesToVirtualAngles(self, pos, _wavelength): + """Calculate pseudo-angles in radians from position in radians. + + Return theta, qaz, alpha, naz, tau, psi and beta in a dictionary. + + """ + + # depends on surface normal n_lab. + mu, delta, nu, eta, chi, phi = pos.totuple() + + theta, qaz = _theta_and_qaz_from_detector_angles(delta, nu) # (19) + + [MU, _, _, ETA, CHI, PHI] = create_you_matrices(mu, + delta, nu, eta, chi, phi) + Z = MU * ETA * CHI * PHI + n_lab = Z * self._get_n_phi() + alpha = asin(bound((-n_lab[1, 0]))) + naz = atan2(n_lab[0, 0], n_lab[2, 0]) # (20) + + cos_tau = cos(alpha) * cos(theta) * cos(naz - qaz) + \ + sin(alpha) * sin(theta) + tau = acos(bound(cos_tau)) # (23) + + # Compute Tau using the dot product directly (THIS ALSO WORKS) +# q_lab = ( (NU * DELTA - I ) * matrix([[0],[1],[0]]) +# norm = norm(q_lab) +# q_lab = matrix([[1],[0],[0]]) if norm == 0 else q_lab * (1/norm) +# tau_from_dot_product = acos(bound(dot3(q_lab, n_lab))) + + sin_beta = 2 * sin(theta) * cos(tau) - sin(alpha) + beta = asin(bound(sin_beta)) # (24) + + psi = next(self._calc_psi(alpha, theta, tau, qaz, naz)) + + return {'theta': theta, 'qaz': qaz, 'alpha': alpha, + 'naz': naz, 'tau': tau, 'psi': psi, 'beta': beta} + + + def _choose_single_solution(self, pos_virtual_angles_pairs_in_degrees): + + if len(pos_virtual_angles_pairs_in_degrees) == 1: + return pos_virtual_angles_pairs_in_degrees[0] + + absolute_distances = [] + for pos_, _ in pos_virtual_angles_pairs_in_degrees: + absolute_distances.append(sum([abs(pos_.totuple()[i]) for i in (0, 3, 4, 5)])) + + shortest_solution_index = absolute_distances.index( + min(absolute_distances)) + pos, virtual_angles = pos_virtual_angles_pairs_in_degrees[shortest_solution_index] + + if logger.isEnabledFor(logging.INFO): + msg = ('Multiple sample solutions found (choosing solution with ' + 'shortest distance to all-zeros position):\n') + i = 0 + for (pos_, _), distance in zip(pos_virtual_angles_pairs_in_degrees, + absolute_distances): + msg += '*' if i == shortest_solution_index else '.' + + msg += ('mu=% 7.3f, delta=% 7.3f, nu=% 7.3f, eta=% 7.3f, chi=% 7.3f, phi=% 7.3f' % + pos_.totuple()) + msg += ' (distance=% 4.3f)\n' % (distance * TODEG) + i += 1 + msg += ':\n' + logger.info(msg) + + return pos, virtual_angles + + def hklToAngles(self, h, k, l, wavelength, return_all_solutions=False): + """ + Return verified Position and all virtual angles in degrees from + h, k & l and wavelength in Angstroms. + + The calculated Position is verified by checking that it maps back using + anglesToHkl() to the requested hkl value. + + Those virtual angles fixed or generated while calculating the position + are verified by by checking that they map back using + anglesToVirtualAngles to the virtual angles for the given position. + + Throws a DiffcalcException if either check fails and + raiseExceptionsIfAnglesDoNotMapBackToHkl is True, otherwise displays a + warning. + """ + + pos_virtual_angles_pairs = self._hklToAngles(h, k, l, wavelength, return_all_solutions) # in rad + assert pos_virtual_angles_pairs + pos_virtual_angles_pairs_in_degrees = [] + for pos, virtual_angles in pos_virtual_angles_pairs: + + # to degrees: + pos.changeToDegrees() + for key, val in virtual_angles.items(): + if val is not None: + virtual_angles[key] = val * TODEG + + self._verify_pos_map_to_hkl(h, k, l, wavelength, pos) + + pos_virtual_angles_pairs_in_degrees.append((pos, virtual_angles)) + + if return_all_solutions: + return pos_virtual_angles_pairs_in_degrees + else: + pos, virtual_angles = self._choose_single_solution(pos_virtual_angles_pairs_in_degrees) + return pos, virtual_angles + + + def hkl_to_all_angles(self, h, k, l, wavelength): + return self.hklToAngles(h, k, l, wavelength, True) + + + def _hklToAngles(self, h, k, l, wavelength, return_all_solutions=False): + """(pos, virtualAngles) = hklToAngles(h, k, l, wavelength) --- with + Position object pos and the virtual angles returned in degrees. Some + modes may not calculate all virtual angles. + """ + + if not self.constraints.is_fully_constrained(): + raise DiffcalcException( + "Diffcalc is not fully constrained.\n" + "Type 'help con' for instructions") + + if not self.constraints.is_current_mode_implemented(): + raise DiffcalcException( + "Sorry, the selected constraint combination is valid but " + "is not implemented. Type 'help con' for implemented combinations") + + # constraints are dictionaries + ref_constraint = self.constraints.reference + if ref_constraint: + ref_constraint_name, ref_constraint_value = ref_constraint.items()[0] + det_constraint = self.constraints.detector + naz_constraint = self.constraints.naz + samp_constraints = self.constraints.sample + + assert not (det_constraint and naz_constraint), ( + "Two 'detector' constraints given") + + + h_phi = self._get_ubmatrix() * matrix([[h], [k], [l]]) + theta = self._calc_theta(h_phi, wavelength) + tau = angle_between_vectors(h_phi, self._get_n_phi()) + + if is_small(sin(tau)) and ref_constraint: + if ref_constraint_name == 'psi': + raise DiffcalcException("Azimuthal angle 'psi' is undefined as reference and scattering vectors parallel.\n" + "Please constrain one of the sample angles or choose different reference vector orientation.") + elif ref_constraint_name == 'a_eq_b': + raise DiffcalcException("Reference constraint 'a_eq_b' is redundant as reference and scattering vectors are parallel.\n" + "Please constrain one of the sample angles or choose different reference vector orientation.") + + ### Reference constraint column ### + + if ref_constraint: + # An angle for the reference vector (n) is given (Section 5.2) + alpha, _ = self._calc_remaining_reference_angles( + ref_constraint_name, ref_constraint_value, theta, tau) + + solution_tuples = [] + if det_constraint or naz_constraint: + + if len(samp_constraints) == 1: + for qaz, naz, delta, nu in self._calc_det_angles_given_det_or_naz_constraint( + det_constraint, naz_constraint, theta, tau, alpha): + for mu, eta, chi, phi in self._calc_sample_angles_from_one_sample_constraint( + samp_constraints, h_phi, theta, alpha, qaz, naz): + solution_tuples.append((mu, delta, nu, eta, chi, phi)) + + elif len(samp_constraints) == 2: + if det_constraint: + det_constraint_name, det_constraint_val = det_constraint.items()[0] + for delta, nu, qaz in self._calc_remaining_detector_angles(det_constraint_name, det_constraint_val, theta): + for mu, eta, chi, phi in self._calc_sample_angles_given_two_sample_and_detector( + samp_constraints, qaz, theta, h_phi, self._get_n_phi()): + solution_tuples.append((mu, delta, nu, eta, chi, phi)) + + else: + raise DiffcalcException( + 'No code yet to handle this combination of detector and sample constraints!') + + elif len(samp_constraints) == 2: + if ref_constraint_name == 'psi': + psi_vals = [ref_constraint_value,] + else: + psi_vals = self._calc_psi(alpha, theta, tau) + for psi in psi_vals: + angles = list(self._calc_sample_given_two_sample_and_reference( + samp_constraints, h_phi, theta, psi)) + solution_tuples.extend(angles) + + elif len(samp_constraints) == 3: + for angles in self._calc_angles_given_three_sample_constraints( + h, k, l, wavelength, return_all_solutions, samp_constraints, + h_phi, theta): + solution_tuples.append(angles) + + if not solution_tuples: + raise DiffcalcException('No solutions were found. ' + 'Please consider using an alternative set of constraints.') + + tidy_solutions = [_tidy_degenerate_solutions(YouPosition(*pos, unit='RAD'), + self.constraints).totuple() for pos in solution_tuples] + merged_solution_tuples = set(self._filter_angle_limits(tidy_solutions, + not return_all_solutions)) + if not merged_solution_tuples: + raise DiffcalcException('No solutions were found matching existing hardware limits. ' + 'Please consider using an alternative set of constraints.') + + #def _find_duplicate_angles(el): + # idx, tpl = el + # for tmp_tpl in filtered_solutions[idx:]: + # if False not in [abs(x-y) < SMALL for x,y in zip(tmp_tpl, tpl)]: + # return False + # return True + #merged_solution_tuples = filter(_find_duplicate_angles, enumerate(filtered_solutions, 1)) + position_pseudo_angles_pairs = self._create_position_pseudo_angles_pairs(wavelength, merged_solution_tuples) + if not position_pseudo_angles_pairs: + raise DiffcalcException('No solutions were found. Please check hardware limits and ' + 'consider using an alternative pseudo-angle constraints.') + + return position_pseudo_angles_pairs + + + def _create_position_pseudo_angles_pairs(self, wavelength, merged_solution_tuples): + + position_pseudo_angles_pairs = [] + for pos in merged_solution_tuples: + # Create position + position = YouPosition(*pos, unit='RAD') + #position = _tidy_degenerate_solutions(position, self.constraints) + #if position.phi <= -pi + SMALL: + # position.phi += 2 * pi + # pseudo angles calculated along the way were for the initial solution + # and may be invalid for the chosen solution TODO: anglesToHkl need no + # longer check the pseudo_angles as they will be generated with the + # same function and it will prove nothing + pseudo_angles = self._anglesToVirtualAngles(position, wavelength) + is_sol = True + for constraint in [self.constraints.reference, + self.constraints.detector, + self.constraints.naz]: + try: + constraint_name, constraint_value = constraint.items()[0] + if constraint_name == 'a_eq_b': + if not is_small(pseudo_angles['alpha'] - pseudo_angles['beta']): + is_sol = False + break + else: + if not is_small(constraint_value - pseudo_angles[constraint_name]): + is_sol = False + break + except: + continue + if is_sol: + position_pseudo_angles_pairs.append((position, pseudo_angles)) + return position_pseudo_angles_pairs + + + def _calc_theta(self, h_phi, wavelength): + """Calculate theta using Equation1 + """ + q_length = norm(h_phi) + if is_small(q_length): + raise DiffcalcException('Reflection is unreachable as |Q| is too small') + wavevector = 2 * pi / wavelength + try: + theta = asin(bound(q_length / (2 * wavevector))) + except AssertionError: + raise DiffcalcException( + 'Reflection is unreachable as |Q| is too long') + if is_small(cos(theta)): + raise DiffcalcException( + 'Reflection is unreachable as theta angle is too close to 90 deg') + return theta + + def _calc_psi(self, alpha, theta, tau, qaz=None, naz=None): + """Calculate psi from Eq. (18), (25) and (28) + """ + sin_tau = sin(tau) + cos_theta = cos(theta) + if is_small(sin_tau): + # The reference vector is parallel to the scattering vector + yield float('nan') + elif is_small(cos_theta): + # Reflection is unreachable as theta angle is too close to 90 deg + yield float('nan') + elif is_small(sin(theta)): + # Reflection is unreachable as |Q| is too small + yield float('nan') + else: + cos_psi = ((cos(tau) * sin(theta) - sin(alpha)) / cos_theta) # (28) + if qaz is None or naz is None : + try: + acos_psi = acos(bound(cos_psi / sin_tau)) + if is_small(acos_psi): + yield 0. + else: + for psi in [acos_psi, -acos_psi]: + yield psi + except AssertionError: + if PRINT_WARNINGS: + print ('WARNING: Diffcalc could not calculate an azimuth (psi)') + yield float('nan') + else: + sin_psi = cos(alpha) * sin(qaz - naz) + sgn = sign(sin_tau) + eps = sin_psi**2 + cos_psi**2 + sigma_ = eps/sin_tau**2 - 1 + if not is_small(sigma_): + if PRINT_WARNINGS: + print ('WARNING: Diffcalc could not calculate a unique azimuth ' + '(psi) because of loss of accuracy in numerical calculation') + yield float('nan') + else: + psi = atan2(sgn * sin_psi, sgn * cos_psi) + yield psi + + + def _calc_remaining_reference_angles(self, name, value, theta, tau): + """Return alpha and beta given one of a_eq_b, alpha, beta or psi + """ + if name == 'psi': + psi = value + # Equation 26 for alpha + sin_alpha = (cos(tau) * sin(theta) - + cos(theta) * sin(tau) * cos(psi)) + if abs(sin_alpha) > 1 + SMALL: + raise DiffcalcException(UNREACHABLE_MSG % (name, value * TODEG)) + alpha = asin(bound(sin_alpha)) + # Equation 27 for beta + sin_beta = cos(tau) * sin(theta) + cos(theta) * sin(tau) * cos(psi) + if abs(sin_beta) > 1 + SMALL: + raise DiffcalcException(UNREACHABLE_MSG % (name, value * TODEG)) + + beta = asin(bound(sin_beta)) + + elif name == 'a_eq_b': + alpha = beta = asin(cos(tau) * sin(theta)) # (24) + + elif name == 'alpha': + alpha = value # (24) + sin_beta = 2 * sin(theta) * cos(tau) - sin(alpha) + if abs(sin_beta) > 1 + SMALL: + raise DiffcalcException(UNREACHABLE_MSG % (name, value * TODEG)) + beta = asin(sin_beta) + + elif name == 'beta': + beta = value + sin_alpha = 2 * sin(theta) * cos(tau) - sin(beta) # (24) + if abs(sin_alpha) > 1 + SMALL: + raise DiffcalcException(UNREACHABLE_MSG % (name, value * TODEG)) + + alpha = asin(sin_alpha) + + return alpha, beta + + def _calc_det_angles_given_det_or_naz_constraint( + self, det_constraint, naz_constraint, theta, tau, alpha): + + assert det_constraint or naz_constraint + try: + naz_qaz_angle = _calc_angle_between_naz_and_qaz(theta, alpha, tau) + except AssertionError: + return + if det_constraint: + # One of the detector angles is given (Section 5.1) + det_constraint_name, det_constraint = det_constraint.items()[0] + for delta, nu, qaz in self._calc_remaining_detector_angles( + det_constraint_name, det_constraint, theta): + if is_small(naz_qaz_angle): + naz_angles = [qaz,] + else: + naz_angles = [qaz - naz_qaz_angle, qaz + naz_qaz_angle] + for naz in naz_angles: + yield qaz, naz, delta, nu + elif naz_constraint: # The 'detector' angle naz is given: + det_constraint_name, det_constraint = naz_constraint.items()[0] + naz_name, naz = det_constraint_name, det_constraint + assert naz_name == 'naz' + if is_small(naz_qaz_angle): + qaz_angles = [naz,] + else: + qaz_angles = [naz - naz_qaz_angle, naz + naz_qaz_angle] + for qaz in qaz_angles: + for delta, nu, _ in self._calc_remaining_detector_angles( + 'qaz', qaz, theta): + yield qaz, naz, delta, nu + + def _calc_remaining_detector_angles(self, constraint_name, + constraint_value, theta): + """Return delta, nu and qaz given one detector angle + """ + # (section 5.1) + # Find qaz using various derivations of 17 and 18 + sin_2theta = sin(2 * theta) + cos_2theta = cos(2 * theta) + if is_small(sin_2theta): + raise DiffcalcException( + 'No meaningful scattering vector (Q) can be found when ' + 'theta is so small (%.4f).' % theta * TODEG) + + if constraint_name == 'delta': + delta = constraint_value + try: + asin_qaz = asin(bound(sin(delta) / sin_2theta)) # (17 & 18) + except AssertionError: + return + cos_delta = cos(delta) + if is_small(cos_delta): + #raise DiffcalcException( + # 'The %s and %s circles are redundant when delta is constrained to %.0f degrees.' + # 'Please change delta constraint or use 4-circle mode.' % (NUNAME, 'mu', delta * TODEG)) + if PRINT_DEGENERATE: + print (('DEGENERATE: with delta=90, %s is degenerate: choosing ' + '%s = 0 (allowed because %s is unconstrained)') % + (NUNAME, NUNAME, NUNAME)) + acos_nu = 1. + else: + try: + acos_nu = acos(bound(cos_2theta / cos_delta)) + except AssertionError: + return + if is_small(cos(asin_qaz)): + qaz_angles = [sign(asin_qaz) * pi / 2.,] + else: + qaz_angles = [asin_qaz, pi - asin_qaz] + if is_small(acos_nu): + nu_angles = [0.,] + else: + nu_angles = [acos_nu, -acos_nu] + for qaz, nu in product(qaz_angles, nu_angles): + sgn_ref = sign(sin_2theta) * sign(cos(qaz)) + sgn_ratio = sign(sin(nu)) * sign(cos_delta) + if sgn_ref == sgn_ratio: + yield delta, nu, qaz + + elif constraint_name == NUNAME: + nu = constraint_value + cos_nu = cos(nu) + if is_small(cos_nu): + raise DiffcalcException( + 'The %s circle constraint to %.0f degrees is redundant.' + 'Please change this constraint or use 4-circle mode.' % (NUNAME, nu * TODEG)) + cos_delta = cos_2theta / cos(nu) + cos_qaz = cos_delta * sin(nu) / sin_2theta + try: + acos_delta = acos(bound(cos_delta)) + acos_qaz = acos(bound(cos_qaz)) + except AssertionError: + return + if is_small(acos_qaz): + qaz_angles = [0.,] + else: + qaz_angles = [acos_qaz, -acos_qaz] + if is_small(acos_delta): + delta_angles = [0.,] + else: + delta_angles = [acos_delta, -acos_delta] + for qaz, delta in product(qaz_angles, delta_angles): + sgn_ref = sign(sin(delta)) + sgn_ratio = sign(sin(qaz)) * sign(sin_2theta) + if sgn_ref == sgn_ratio: + yield delta, nu, qaz + + elif constraint_name == 'qaz': + qaz = constraint_value + asin_delta = asin(sin(qaz) * sin_2theta) + if is_small(cos(asin_delta)): + delta_angles = [sign(asin_delta) * pi / 2.,] + else: + delta_angles = [asin_delta, pi - asin_delta] + for delta in delta_angles: + cos_delta = cos(delta) + if is_small(cos_delta): + if PRINT_DEGENERATE: + print (('DEGENERATE: with delta=90, %s is degenerate: choosing ' + '%s = 0 (allowed because %s is unconstrained)') % + (NUNAME, NUNAME, NUNAME)) + #raise DiffcalcException( + # 'The %s circle is redundant when delta is at %.0f degrees.' + # 'Please change detector constraint or use 4-circle mode.' % (NUNAME, delta * TODEG)) + nu = 0. + else: + sgn_delta = sign(cos_delta) + nu = atan2(sgn_delta * sin_2theta * cos(qaz), sgn_delta * cos_2theta) + yield delta, nu, qaz + else: + raise DiffcalcException( + constraint_name + ' is not an explicit detector angle ' + '(naz cannot be handled here)') + + + def _calc_sample_angles_from_one_sample_constraint( + self, samp_constraints, h_phi, theta, alpha, qaz, naz): + + sample_constraint_name, sample_value = samp_constraints.items()[0] + q_lab = matrix([[cos(theta) * sin(qaz)], + [-sin(theta)], + [cos(theta) * cos(qaz)]]) # (18) + n_lab = matrix([[cos(alpha) * sin(naz)], + [-sin(alpha)], + [cos(alpha) * cos(naz)]]) # (20) + mu_eta_chi_phi_tuples = list(self._calc_remaining_sample_angles( + sample_constraint_name, sample_value, q_lab, n_lab, h_phi, + self._get_n_phi())) + return mu_eta_chi_phi_tuples + + def _calc_sample_given_two_sample_and_reference( + self, samp_constraints, h_phi, theta, psi): + + for angles in self._calc_sample_angles_given_two_sample_and_reference( + samp_constraints, psi, theta, h_phi, self._get_n_phi()): + qaz, psi, mu, eta, chi, phi = angles + values_in_deg = tuple(v * TODEG for v in angles) + logger.info('Initial angles: xi=%.3f, psi=%.3f, mu=%.3f, ' + 'eta=%.3f, chi=%.3f, phi=%.3f' % + values_in_deg) # Try to find a solution for each possible transformed xi + + logger.info("") + msg = "---Trying psi=%.3f, qaz=%.3f" % (psi * TODEG, qaz * TODEG) + logger.info(msg) + + for delta, nu, _ in self._calc_remaining_detector_angles('qaz', qaz, theta): + logger.info("delta=%.3f, %s=%.3f", delta * TODEG, NUNAME, nu * TODEG) + #for mu, eta, chi, phi in self._generate_sample_solutions( + # mu, eta, chi, phi, samp_constraints.keys(), delta, + # nu, wavelength, (h, k, l), ref_constraint_name, + # ref_constraint_value): + yield mu, delta, nu, eta, chi, phi + + def _calc_remaining_sample_angles(self, constraint_name, constraint_value, + q_lab, n_lab, q_phi, n_phi): + """Return phi, chi, eta and mu, given one of these""" + # (section 5.3) + + N_lab = _calc_N(q_lab, n_lab) + N_phi = _calc_N(q_phi, n_phi) + Z = N_lab * N_phi.T + + if constraint_name == 'mu': # (35) + mu = constraint_value + V = calcMU(mu).I * N_lab * N_phi.T + try: + acos_chi = acos(bound(V[2, 2])) + except AssertionError: + return + if is_small(sin(acos_chi)): + # chi ~= 0 or 180 and therefor phi || eta The solutions for phi + # and eta here will be valid but will be chosen unpredictably. + # Choose eta=0: + # + # tan(phi+eta)=v12/v11 from docs/extensions_to_yous_paper.wxm + chi = acos_chi + eta = 0. + phi = atan2(-V[1, 0], V[1, 1]) + logger.debug( + 'Eta and phi cannot be chosen uniquely with chi so close ' + 'to 0 or 180. Returning phi=%.3f and eta=%.3f', + phi * TODEG, eta * TODEG) + yield mu, eta, chi, phi + else: + for chi in [acos_chi, -acos_chi]: + sgn = sign(sin(chi)) + phi = atan2(-sgn * V[2, 1], -sgn * V[2, 0]) + eta = atan2(-sgn * V[1, 2], sgn * V[0, 2]) + yield mu, eta, chi, phi + + elif constraint_name == 'phi': # (37) + phi = constraint_value + V = N_lab * N_phi.I * calcPHI(phi).T + try: + asin_eta = asin(bound(V[0, 1])) + except AssertionError: + return + if is_small(cos(asin_eta)): + raise DiffcalcException('Chi and mu cannot be chosen uniquely ' + 'with eta so close to +/-90.') + for eta in [asin_eta, pi - asin_eta]: + sgn = sign(cos(eta)) + mu = atan2(sgn * V[2, 1], sgn * V[1, 1]) + chi = atan2(sgn * V[0, 2], sgn * V[0, 0]) + yield mu, eta, chi, phi + + elif constraint_name in ('eta', 'chi'): + if constraint_name == 'eta': # (39) + eta = constraint_value + cos_eta = cos(eta) + if is_small(cos_eta): + #TODO: Not likely to happen in real world!? + raise DiffcalcException( + 'Chi and mu cannot be chosen uniquely with eta ' + 'constrained so close to +-90.') + try: + asin_chi = asin(bound(Z[0, 2] / cos_eta)) + except AssertionError: + return + all_eta = [eta,] + all_chi = [asin_chi, pi - asin_chi] + + else: # constraint_name == 'chi' # (40) + chi = constraint_value + sin_chi = sin(chi) + if is_small(sin_chi): + raise DiffcalcException( + 'Eta and phi cannot be chosen uniquely with chi ' + 'constrained so close to 0. (Please contact developer ' + 'if this case is useful for you)') + try: + acos_eta = acos(bound(Z[0, 2] / sin_chi)) + except AssertionError: + return + all_eta = [acos_eta, -acos_eta] + all_chi = [chi,] + + for chi, eta in product(all_chi, all_eta): + top_for_mu = Z[2, 2] * sin(eta) * sin(chi) + Z[1, 2] * cos(chi) + bot_for_mu = -Z[2, 2] * cos(chi) + Z[1, 2] * sin(eta) * sin(chi) + if is_small(top_for_mu) and is_small(bot_for_mu): + # chi == +-90, eta == 0/180 and therefore phi || mu cos(chi) == + # 0 and sin(eta) == 0 Experience shows that even though e.g. + # the z[2, 2] and z[1, 2] values used to calculate mu may be + # basically 0 (1e-34) their ratio in every case tested so far + # still remains valid and using them will result in a phi + # solution that is continuous with neighbouring positions. + # + # We cannot test phi minus mu here unfortunately as the final + # phi and mu solutions have not yet been chosen (they may be + # +-x or 180+-x). Otherwise we could choose a sensible solution + # here if the one found was incorrect. + + # tan(phi+eta)=v12/v11 from extensions_to_yous_paper.wxm + phi_minus_mu = -atan2(Z[2, 0], Z[1, 1]) + logger.debug( + 'Mu and phi cannot be chosen uniquely with chi so close ' + 'to +/-90 and eta so close 0 or 180.\n After the final ' + 'solution has been chose phi-mu should equal: %.3f', + phi_minus_mu * TODEG) + mu = atan2(-top_for_mu, -bot_for_mu) # (41) + + top_for_phi = Z[0, 1] * cos(eta) * cos(chi) - Z[0, 0] * sin(eta) + bot_for_phi = Z[0, 1] * sin(eta) + Z[0, 0] * cos(eta) * cos(chi) + phi = atan2(top_for_phi, bot_for_phi) # (42) + # if is_small(bot_for_phi) and is_small(top_for_phi): + # raise DiffcalcException( + # 'phi=%.3f cannot be known with confidence as top and ' + # 'bottom are both close to zero. chi=%.3f, eta=%.3f' + # % (mu * TODEG, chi * TODEG, eta * TODEG)) + yield mu, eta, chi, phi + + else: + raise DiffcalcException('Given angle must be one of phi, chi, eta or mu') + + def _calc_angles_given_three_sample_constraints( + self, h, k, l, wavelength, return_all_solutions, samp_constraints, + h_phi, theta): + + if not 'mu' in samp_constraints: + eta_ = self.constraints.sample['eta'] + chi_ = self.constraints.sample['chi'] + phi_ = self.constraints.sample['phi'] + try: + two_mu_qaz_pairs = _mu_and_qaz_from_eta_chi_phi(eta_, chi_, phi_, theta, h_phi) + except AssertionError: + return + else: + raise DiffcalcException( + 'No code yet to handle this combination of 3 sample constraints!') + # TODO: Code duplicated above + for mu_, qaz in two_mu_qaz_pairs: + logger.debug("--- Trying mu_:%.f qaz_%.f", mu_ * TODEG, qaz * TODEG) + for delta, nu, _ in self._calc_remaining_detector_angles('qaz', qaz, theta): + logger.info("delta=%.3f, %s=%.3f", delta * TODEG, NUNAME, nu * TODEG) + yield mu_, delta, nu, eta_, chi_, phi_ + + def _calc_sample_angles_given_two_sample_and_reference( + self, samp_constraints, psi, theta, q_phi, n_phi): + """Available combinations: + chi, phi, reference + mu, eta, reference, + chi, eta, reference + chi, mu, reference + """ + + def __get_phi_and_qaz(chi, eta, mu): + a = sin(chi) * cos(eta) + b = sin(chi) * sin(eta) * sin(mu) - cos(chi) * cos(mu) + #atan2_xi = atan2(V[2, 2] * a + V[2, 0] * b, + # V[2, 0] * a - V[2, 2] * b) # (54) + qaz = atan2(V[2, 0] * a - V[2, 2] * b, + -V[2, 2] * a - V[2, 0] * b) # (54) + + a = sin(chi) * sin(mu) - cos(mu) * cos(chi) * sin(eta) + b = cos(mu) * cos(eta) + phi = atan2(V[1, 1] * a - V[0, 1] * b, + V[0, 1] * a + V[1, 1] * b) # (55) + # if is_small(mu+pi/2) and is_small(eta) and False: + # phi_general = phi + # # solved in extensions_to_yous_paper.wxm + # phi = atan2(V[1, 1], V[0, 1]) + # logger.info("phi = %.3f or %.3f (std)", + # phi*TODEG, phi_general*TODEG ) + + return qaz, phi + + N_phi = _calc_N(q_phi, n_phi) + THETA = z_rotation(-theta) + PSI = x_rotation(psi) + + if 'chi' in samp_constraints and 'phi' in samp_constraints: + + chi = samp_constraints['chi'] + phi = samp_constraints['phi'] + + CHI = calcCHI(chi) + PHI = calcPHI(phi) + V = CHI * PHI * N_phi * PSI.T * THETA.T # (46) + + #atan2_xi = atan2(-V[2, 0], V[2, 2]) + #atan2_eta = atan2(-V[0, 1], V[1, 1]) + #atan2_mu = atan2(-V[2, 1], sqrt(V[2, 2] ** 2 + V[2, 0] ** 2)) + try: + asin_mu = asin(bound(-V[2, 1])) + except AssertionError: + return + for mu in [asin_mu, pi - asin_mu]: + sgn_cosmu = sign(cos(mu)) + #xi = atan2(-sgn_cosmu * V[2, 0], sgn_cosmu * V[2, 2]) + qaz = atan2(sgn_cosmu * V[2, 2], sgn_cosmu * V[2, 0], ) + eta = atan2(-sgn_cosmu * V[0, 1], sgn_cosmu * V[1, 1]) + yield qaz, psi, mu, eta, chi, phi + + elif 'mu' in samp_constraints and 'eta' in samp_constraints: + + mu = samp_constraints['mu'] + eta = samp_constraints['eta'] + + V = N_phi * PSI.T * THETA.T # (49) + try: + bot = bound(-V[2, 1] / sqrt(sin(eta) ** 2 * cos(mu) ** 2 + sin(mu) ** 2)) + except AssertionError: + return + if is_small(cos(mu) * sin(eta)): + eps = atan2(sin(eta) * cos(mu), sin(mu)) + chi_vals = [eps + acos(bot), eps - acos(bot)] + else: + eps = atan2(sin(mu), sin(eta) * cos(mu)) + chi_vals = [asin(bot) - eps, pi - asin(bot) - eps] # (52) + + ## Choose final chi solution here to obtain compatable xi and mu + ## TODO: This temporary solution works only for one case used on i07 + ## Return a list of possible solutions? + #if is_small(eta) and is_small(mu + pi / 2): + # for chi in _generate_transformed_values(chi_orig): + # if pi / 2 <= chi < pi: + # break + #else: + # chi = chi_orig + + for chi in chi_vals: + qaz, phi = __get_phi_and_qaz(chi, eta, mu) + yield qaz, psi, mu, eta, chi, phi + + elif 'chi' in samp_constraints and 'eta' in samp_constraints: + + chi = samp_constraints['chi'] + eta = samp_constraints['eta'] + + V = N_phi * PSI.T * THETA.T # (49) + try: + bot = bound(-V[2, 1] / sqrt(sin(eta) ** 2 * sin(chi) ** 2 + cos(chi) ** 2)) + except AssertionError: + return + if is_small(cos(chi)): + eps = atan2(cos(chi), sin(chi) * sin(eta)) + mu_vals = [eps + acos(bot), eps - acos(bot)] + else: + eps = atan2(sin(chi) * sin(eta), cos(chi)) + mu_vals = [asin(bot) - eps, pi - asin(bot) - eps] # (52) + + for mu in mu_vals: + qaz, phi = __get_phi_and_qaz(chi, eta, mu) + yield qaz, psi, mu, eta, chi, phi + + elif 'chi' in samp_constraints and 'mu' in samp_constraints: + + chi = samp_constraints['chi'] + mu = samp_constraints['mu'] + + V = N_phi * PSI.T * THETA.T # (49) + + try: + asin_eta = asin(bound((-V[2, 1] - cos(chi) * sin(mu)) / (sin(chi) * cos(mu)))) + except AssertionError: + return + + for eta in [asin_eta, pi - asin_eta]: + qaz, phi = __get_phi_and_qaz(chi, eta, mu) + yield qaz, psi, mu, eta, chi, phi + + else: + raise DiffcalcException( + 'No code yet to handle this combination of 2 sample ' + 'constraints and one reference!:' + str(samp_constraints)) + + def _calc_sample_angles_given_two_sample_and_detector( + self, samp_constraints, qaz, theta, q_phi, n_phi): + """Available combinations: + chi, phi, detector + mu, eta, detector + mu, phi, detector + """ + + N_phi = _calc_N(q_phi, n_phi) + + if 'mu' in samp_constraints and 'eta' in samp_constraints: + + mu = samp_constraints['mu'] + eta = samp_constraints['eta'] + + F = y_rotation(qaz - pi/2.) + THETA = z_rotation(-theta) + V = calcETA(eta).T * calcMU(mu).T * F * THETA # (56) + + try: + bot = bound(-V[1, 0] / sqrt(N_phi[0, 0]**2 + N_phi[1, 0]**2)) + eps = atan2(N_phi[1, 0], N_phi[0, 0]) + phi_vals = [asin(bot) + eps, pi - asin(bot) + eps] # (59) + except (AssertionError, ZeroDivisionError): + # For the case of (00l) reflection, where N_phi[0,0] = N_phi[1,0] = 0 + chi = atan2(V[0, 0] * N_phi[2, 0], V[2, 0] * N_phi[2, 0]) # (57) + sgn_denom = sign(N_phi[1, 1] * N_phi[0, 2] - N_phi[1, 2] * N_phi[0, 1]) + sin_phi = V[1, 1] * N_phi[1, 2] - V[1, 2] * N_phi[1, 1] + cos_phi = V[1, 1] * N_phi[0, 2] - V[1, 2] * N_phi[0, 1] + phi = atan2(sin_phi * sgn_denom, cos_phi * sgn_denom) + yield mu, eta, chi, phi + return + for phi in phi_vals: + a = N_phi[0, 0] * cos(phi) + N_phi[1, 0] * sin(phi) + chi = atan2(N_phi[2, 0] * V[0, 0] - a * V[2, 0], + N_phi[2, 0] * V[2, 0] + a * V[0, 0]) # (60) + yield mu, eta, chi, phi + + elif 'chi' in samp_constraints and 'phi' in samp_constraints: + + chi = samp_constraints['chi'] + phi = samp_constraints['phi'] + + CHI = calcCHI(chi) + PHI = calcPHI(phi) + V = CHI * PHI * N_phi # (62) + + try: + bot = bound(V[2, 0] / sqrt(cos(qaz) ** 2 * cos(theta) ** 2 + sin(theta) ** 2)) + except AssertionError: + return + eps = atan2(-cos(qaz) * cos(theta), sin(theta)) + for mu in [asin(bot) + eps, pi - asin(bot) + eps]: + a = cos(theta) * sin(qaz) + b = -cos(theta) * sin(mu) * cos(qaz) + cos(mu) * sin(theta) + eta = atan2(V[1, 0] * a + V[0, 0] * b, V[0, 0] * a - V[1, 0]* b) + + #a = -cos(mu) * cos(qaz) * sin(theta) + sin(mu) * cos(theta) + #b = cos(mu) * sin(qaz) + #psi = atan2(-V[2, 2] * a - V[2, 1] * b, V[2, 1] * a - V[2, 2] * b) + yield mu, eta, chi, phi + + elif 'mu' in samp_constraints and 'phi' in samp_constraints: + + mu = samp_constraints['mu'] + phi = samp_constraints['phi'] + + F = y_rotation(qaz - pi/2.) + THETA = z_rotation(-theta) + V = calcMU(mu).T * F * THETA + E = calcPHI(phi) * N_phi + + try: + bot = bound(-V[2, 0] / sqrt(E[0, 0]**2 + E[2, 0]**2)) + except AssertionError: + return + eps = atan2(E[2, 0], E[0, 0]) + for chi in [asin(bot) + eps, pi - asin(bot) + eps]: + a = E[0, 0] * cos(chi) + E[2, 0] * sin(chi) + eta = atan2(V[0, 0] * E[1, 0] - V[1, 0] * a, V[0, 0] * a + V[1, 0] * E[1, 0]) + yield mu, eta, chi, phi + else: + raise DiffcalcException( + 'No code yet to handle this combination of 2 sample ' + 'constraints and one detector!:' + str(samp_constraints)) + + def _filter_angle_limits(self, possible_solutions, filter_out_of_limits=True): + res = [] + angle_names = self._hardware.get_axes_names() + for possible_solution in possible_solutions: + hw_sol = [] + hw_possible_solution = self._geometry.internal_position_to_physical_angles(YouPosition(*possible_solution, unit='RAD')) + for name, value in zip(angle_names, hw_possible_solution): + hw_sol.append(self._hardware.cut_angle(name, value)) + if filter_out_of_limits: + is_in_limits = all([self._hardware.is_axis_value_within_limits(name, value) for name, value in zip(angle_names, hw_sol)]) + else: + is_in_limits = True + if is_in_limits: + sol = self._geometry.physical_angles_to_internal_position(tuple(hw_sol)) + sol.changeToRadians() + res.append(sol.totuple()) + return res + +def _mu_and_qaz_from_eta_chi_phi(eta, chi, phi, theta, h_phi): + + h_phi_norm = normalised(h_phi) # (68,69) + h1, h2, h3 = h_phi_norm[0, 0], h_phi_norm[1, 0], h_phi_norm[2, 0] + a = sin(chi) * h2 * sin(phi) + sin(chi) * h1 * cos(phi) - cos(chi) * h3 + b = (- cos(chi) * sin(eta) * h2 * sin(phi) + - cos(eta) * h1 * sin(phi) + cos(eta) * h2 * cos(phi) + - cos(chi) * sin(eta) * h1 * cos(phi) + - sin(chi) * sin(eta) * h3) + c = -sin(theta) + sin_bit = bound(c / sqrt(a * a + b * b)) + mu1 = asin(sin_bit) - atan2(b, a) + mu2 = pi - asin(sin_bit) - atan2(b, a) + + mu1 = cut_at_minus_pi(mu1) + mu2 = cut_at_minus_pi(mu2) + + # TODO: This special case should be *removed* when the general case has shown + # to encompass it. It exists as fallback for a particular i16 experiment in + # May 2013 --RobW. +# if eta == chi == 0: +# logger.debug("Testing against simplified equations for eta == chi == 0") +# a = - h3 +# b = - h1 * sin(phi) + h2 * cos(phi) +# sin_bit = bound(c / sqrt(a * a + b * b)) +# mu_simplified = pi - asin(sin_bit) - atan2(b, a) +# mu_simplified = cut_at_minus_pi(mu_simplified) +# if not ne(mu_simplified, mu): +# raise AssertionError("mu_simplified != mu , %f!=%f" % (mu_simplified, mu)) + + + [MU, _, _, ETA, CHI, PHI] = create_you_matrices(mu1, None, None, eta, chi, phi) + h_lab = MU * ETA * CHI * PHI * h_phi # (11) + qaz1 = atan2(h_lab[0, 0] , h_lab[2, 0]) + + [MU, _, _, ETA, CHI, PHI] = create_you_matrices(mu2, None, None, eta, chi, phi) + h_lab = MU * ETA * CHI * PHI * h_phi # (11) + qaz2 = atan2(h_lab[0, 0] , h_lab[2, 0]) + + return (mu1, qaz1) , (mu2, qaz2) diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/you/constraints.py b/script/test/diffcalc (copy)/diffcalc/hkl/you/constraints.py new file mode 100644 index 0000000..1fe08d7 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/you/constraints.py @@ -0,0 +1,377 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from math import pi + +try: + from numpy import matrix +except ImportError: + from numjy import matrix + +from diffcalc.util import DiffcalcException, bold + +TODEG = 180 / pi +TORAD = pi / 180 + +NUNAME = 'gam' + +def filter_dict(d, keys): + """Return a copy of d containing only keys that are in keys""" + ##return {k: d[k] for k in keys} # requires Python 2.6 + return dict((k, d[k]) for k in keys if k in d.keys()) + + +det_constraints = ('delta', NUNAME, 'qaz', 'naz') +ref_constraints = ('a_eq_b', 'alpha', 'beta', 'psi') +samp_constraints = ('mu', 'eta', 'chi', 'phi', 'mu_is_' + NUNAME) + +valueless_constraints = ('a_eq_b', 'mu_is_' + NUNAME) +all_constraints = det_constraints + ref_constraints + samp_constraints + + +number_single_sample = (len(det_constraints) * len(ref_constraints) * + len(samp_constraints)) + + +class YouConstraintManager(object): + + def __init__(self, hardware, fixed_constraints = {}): + self._hardware = hardware + self._constrained = {} +# self._tracking = [] + self.n_phi = matrix([[0], [0], [1]]) + self._hide_detector_constraint = False # default + self._fixed_samp_constraints = () + self._fix_constraints(fixed_constraints) + + def __str__(self): + lines = [] +# TODO: Put somewhere with access to UB matrix! +# WIDTH = 13 +# n_phi = self.n_phi +# fmt = "% 9.5f % 9.5f % 9.5f" +# lines.append(" n_phi:".ljust(WIDTH) + +# fmt % (n_phi[0, 0], n_phi[1, 0], n_phi[2, 0])) +# if self._getUBMatrix(): +# n_cryst = self._getUMatrix().I * self.n_phi +# lines.append(" n_cryst:".ljust(WIDTH) + +# fmt % (n_cryst[0, 0], n_cryst[1, 0], n_cryst[2, 0])) +# n_recip = self._getUBMatrix().I * self.n_phi +# lines.append(" n_recip:".ljust(WIDTH) + +# fmt % (n_recip[0, 0], n_recip[1, 0], n_recip[2, 0])) +# else: +# lines.append( +# " n_cryst:".ljust(WIDTH) + ' "<<< No U matrix >>>"') +# lines.append( +# " n_recip:".ljust(WIDTH) + ' "<<< No UB matrix >>>"') + + lines.extend(self.build_display_table_lines()) + lines.append("") + lines.extend(self.report_constraints_lines()) + lines.append("") + if (self.is_fully_constrained() and + not self.is_current_mode_implemented()): + lines.append( + " Sorry, this constraint combination is not implemented") + lines.append(" Type 'help con' for available combinations") + else: + lines.append(" Type 'help con' for instructions") # okay + return '\n'.join(lines) + + @property + def available_constraint_names(self): + """list of all available constraints""" + return all_constraints + + @property + def settable_constraint_names(self): + """list of all available constraints that have settable values""" + all_copy = list(all_constraints) + for valueless in valueless_constraints: + all_copy.remove(valueless) + return all_copy + + @property + def all(self): # @ReservedAssignment + """dictionary of all constrained values""" + return self._constrained.copy() + + @property + def detector(self): + """dictionary of constrained detector circles""" + return filter_dict(self.all, det_constraints[:-1]) + + @property + def reference(self): + """dictionary of constrained reference circles""" + return filter_dict(self.all, ref_constraints) + + @property + def sample(self): + """dictionary of constrained sample circles""" + return filter_dict(self.all, samp_constraints) + + @property + def naz(self): + """dictionary with naz and value if constrained""" + return filter_dict(self.all, ('naz',)) + + @property + def constrained_names(self): + """ordered tuple of constained circles""" + names = self.all.keys() + names.sort(key=lambda name: list(all_constraints).index(name)) + return tuple(names) + + def _fix_constraints(self, fixed_constraints): + for name in fixed_constraints: + self.constrain(name) + self.set_constraint(name, fixed_constraints[name]) + + if self.detector or self.naz: + self._hide_detector_constraint = True + + fixed_samp_constraints = list(self.sample.keys()) + if 'mu' in self.sample or NUNAME in self.detector: + fixed_samp_constraints.append('mu_is_' + NUNAME) + self._fixed_samp_constraints = tuple(fixed_samp_constraints) + + + def is_constrained(self, name): + return name in self._constrained + + def get_value(self, name): + return self._constrained[name] + + def build_display_table_lines(self): + unfixed_samp_constraints = list(samp_constraints) + for name in self._fixed_samp_constraints: + unfixed_samp_constraints.remove(name) + if self._hide_detector_constraint: + constraint_types = (ref_constraints, unfixed_samp_constraints) + else: + constraint_types = (det_constraints, ref_constraints, + unfixed_samp_constraints) + num_rows = max([len(col) for col in constraint_types]) + max_name_width = max( + [len(name) for name in sum(constraint_types[:-1], ())]) + + cells = [] + + header_cells = [] + if not self._hide_detector_constraint: + header_cells.append(bold(' ' + 'DET'.ljust(max_name_width))) + header_cells.append(bold(' ' + 'REF'.ljust(max_name_width))) + header_cells.append(bold(' ' + 'SAMP')) + cells.append(header_cells) + + underline_cells = [' ' + '-' * max_name_width] * len(constraint_types) + cells.append(underline_cells) + + for n_row in range(num_rows): + row_cells = [] + for col in constraint_types: + name = col[n_row] if n_row < len(col) else '' + row_cells.append(self._label_constraint(name)) + row_cells.append(('%-' + str(max_name_width) + 's') % name) + cells.append(row_cells) + + lines = [' '.join(row_cells).rstrip() for row_cells in cells] + return lines + + def _report_constraint(self, name): + val = self.get_constraint(name) + if name in valueless_constraints: + return " %s" % name + else: + if val is None: + return "! %-5s: ---" % name + else: + return " %-5s: %.4f" % (name, val) + + def report_constraints_lines(self): + lines = [] + required = 3 - len(self.all) + if required == 0: + pass + elif required == 1: + lines.append('! 1 more constraint required') + else: + lines.append('! %d more constraints required' % required) + constraints = [] + constraints.extend(self.detector.keys()) + constraints.extend(self.naz.keys()) + constraints.extend(self.reference.keys()) + constraints.extend(sorted(self.sample.keys())) + for name in constraints: + lines.append(self._report_constraint(name)) + return lines + + def is_fully_constrained(self): + return len(self.all) == 3 + + def is_current_mode_implemented(self): + if not self.is_fully_constrained(): + raise ValueError("Three constraints required") + + if len(self.sample) == 3: + if set(self.sample.keys()) == set(['chi', 'phi', 'eta']): + return True + return False + + if len(self.sample) == 1: + return True + + if len(self.reference) == 1: + return (set(self.sample.keys()) == set(['chi', 'phi']) or + set(self.sample.keys()) == set(['chi', 'eta']) or + set(self.sample.keys()) == set(['chi', 'mu']) or + set(self.sample.keys()) == set(['mu', 'eta'])) + + if len(self.detector) == 1: + return (set(self.sample.keys()) == set(['chi', 'phi']) or + set(self.sample.keys()) == set(['mu', 'eta']) or + set(self.sample.keys()) == set(['mu', 'phi']) + ) + + return False + + + def _label_constraint(self, name): + if name == '': + label = ' ' +# elif self.is_tracking(name): # implies constrained +# label = '~~> ' + elif (self.is_constrained(name) and (self.get_value(name) is None) and + name not in valueless_constraints): + label = 'o->' + elif self.is_constrained(name): + label = '-->' + else: + label = ' ' + return label + + def constrain(self, name): + if self.is_constraint_fixed(name): + raise DiffcalcException('%s is not a valid constraint name' % name) + if name in self.all: + return "%s is already constrained." % name.capitalize() + elif name in det_constraints: + return self._constrain_detector(name) + elif name in ref_constraints: + return self._constrain_reference(name) + elif name in samp_constraints: + return self._constrain_sample(name) + else: + raise DiffcalcException("%s is not a valid constraint name. Type 'con' for a table of constraint name" % name) + + def is_constraint_fixed(self, name): + return ((name in det_constraints and self._hide_detector_constraint) or + (name in samp_constraints and name in self._fixed_samp_constraints)) + + def _constrain_detector(self, name): + if self.naz: + del self._constrained['naz'] + self._constrained[name] = None + return 'Naz constraint replaced.' + elif self.detector: + constrained_name = self.detector.keys()[0] + del self._constrained[constrained_name] + self._constrained[name] = None + return'%s constraint replaced.' % constrained_name.capitalize() + elif len(self.all) == 3: # and no detector + raise self._could_not_constrain_exception(name) + else: + self._constrained[name] = None + + def _could_not_constrain_exception(self, name): + return DiffcalcException( + "%s could not be constrained. First un-constrain one of the\n" + "angles %s, %s or %s (with 'uncon')" % + ((name.capitalize(),) + self.constrained_names)) + + def _constrain_reference(self, name): + if self.reference: + constrained_name = self.reference.keys()[0] + del self._constrained[constrained_name] + self._constrained[name] = None + return '%s constraint replaced.' % constrained_name.capitalize() + elif len(self.all) == 3: # and no reference + raise self._could_not_constrain_exception(name) + else: + self._constrained[name] = None + + def _constrain_sample(self, name): + if len(self._constrained) < 3: + # okay, more to add + self._constrained[name] = None + # else: three constraints are set + elif len(self.sample) == 1: + # (detector and reference constraints set) + # it is clear which sample constraint to remove + constrained_name = self.sample.keys()[0] + del self._constrained[constrained_name] + self._constrained[name] = None + return '%s constraint replaced.' % constrained_name.capitalize() + else: + raise self._could_not_constrain_exception(name) + + def unconstrain(self, name): + if self.is_constraint_fixed(name): + raise DiffcalcException('%s is not a valid constraint name') + if name in self._constrained: + del self._constrained[name] + else: + return "%s was not already constrained." % name.capitalize() + + def _check_constraint_settable(self, name): + if name not in all_constraints: + raise DiffcalcException( + 'Could not set %(name)s. This is not an available ' + 'constraint.' % locals()) + elif name not in self.all.keys(): + raise DiffcalcException( + 'Could not set %(name)s. This is not currently ' + 'constrained.' % locals()) + elif name in valueless_constraints: + raise DiffcalcException( + 'Could not set %(name)s. This constraint takes no ' + 'value.' % locals()) + + def clear_constraints(self): + self._constrained = {} + + def set_constraint(self, name, value): # @ReservedAssignment + if self.is_constraint_fixed(name): + raise DiffcalcException('%s is not a valid constraint name') + self._check_constraint_settable(name) +# if name in self._tracking: +# raise DiffcalcException( +# "Could not set %s as this constraint is configured to track " +# "its associated\nphysical angle. First remove this tracking " +# "(use 'untrack %s').""" % (name, name)) + old_value = self.get_constraint(name) + old = str(old_value) if old_value is not None else '---' + self._constrained[name] = float(value) * TORAD + new = str(value) + return "%(name)s : %(old)s --> %(new)s" % locals() + + def get_constraint(self, name): + value = self.all[name] + return None if value is None else value * TODEG + \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/you/geometry.py b/script/test/diffcalc (copy)/diffcalc/hkl/you/geometry.py new file mode 100644 index 0000000..d8d034a --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/you/geometry.py @@ -0,0 +1,219 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from math import pi + +from diffcalc.util import AbstractPosition, DiffcalcException + +TORAD = pi / 180 +TODEG = 180 / pi +from diffcalc.util import x_rotation, z_rotation, y_rotation + +from diffcalc.hkl.you.constraints import NUNAME + +class YouGeometry(object): + + def __init__(self, name, fixed_constraints, beamline_axes_transform=None): + self.name = name + self.fixed_constraints = fixed_constraints + # beamline_axes_transform matrix is composed of the diffcalc basis vector coordinates + # in the beamline coordinate system, i.e. it transforms the beamline coordinate system + # into the reference diffcalc one. + self.beamline_axes_transform = beamline_axes_transform + + def physical_angles_to_internal_position(self, physical_angle_tuple): + raise NotImplementedError() + + def internal_position_to_physical_angles(self, internal_position): + raise NotImplementedError() + + def create_position(self, *args): + return YouPosition(*args, unit='DEG') + + +#============================================================================== +#============================================================================== +# Geometry plugins for use with 'You' hkl calculation engine +#============================================================================== +#============================================================================== + + +class SixCircle(YouGeometry): + def __init__(self, beamline_axes_transform=None): + YouGeometry.__init__(self, 'sixc', {}, beamline_axes_transform) + + def physical_angles_to_internal_position(self, physical_angle_tuple): + # mu, delta, nu, eta, chi, phi + return YouPosition(*physical_angle_tuple, unit='DEG') + + def internal_position_to_physical_angles(self, internal_position): + clone_position = internal_position.clone() + clone_position.changeToDegrees() + return clone_position.totuple() + + +class FourCircle(YouGeometry): + """For a diffractometer with angles: + delta, eta, chi, phi + """ + def __init__(self, beamline_axes_transform=None): + YouGeometry.__init__(self, 'fourc', {'mu': 0, NUNAME: 0}, beamline_axes_transform) + + def physical_angles_to_internal_position(self, physical_angle_tuple): + # mu, delta, nu, eta, chi, phi + delta, eta, chi, phi = physical_angle_tuple + return YouPosition(0, delta, 0, eta, chi, phi, 'DEG') + + def internal_position_to_physical_angles(self, internal_position): + clone_position = internal_position.clone() + clone_position.changeToDegrees() + _, delta, _, eta, chi, phi = clone_position.totuple() + return delta, eta, chi, phi + + +class FiveCircle(YouGeometry): + """For a diffractometer with angles: + delta, nu, eta, chi, phi + """ + def __init__(self, beamline_axes_transform=None): + YouGeometry.__init__(self, 'fivec', {'mu': 0}, beamline_axes_transform) + + def physical_angles_to_internal_position(self, physical_angle_tuple): + # mu, delta, nu, eta, chi, phi + delta, nu, eta, chi, phi = physical_angle_tuple + return YouPosition(0, delta, nu, eta, chi, phi, 'DEG') + + def internal_position_to_physical_angles(self, internal_position): + clone_position = internal_position.clone() + clone_position.changeToDegrees() + _, delta, nu, eta, chi, phi = clone_position.totuple() + return delta, nu, eta, chi, phi + + +#============================================================================== + + +def create_you_matrices(mu=None, delta=None, nu=None, eta=None, chi=None, + phi=None): + """ + Create transformation matrices from H. You's paper. + """ + MU = None if mu is None else calcMU(mu) + DELTA = None if delta is None else calcDELTA(delta) + NU = None if nu is None else calcNU(nu) + ETA = None if eta is None else calcETA(eta) + CHI = None if chi is None else calcCHI(chi) + PHI = None if phi is None else calcPHI(phi) + return MU, DELTA, NU, ETA, CHI, PHI + + +def calcNU(nu): + return x_rotation(nu) + + +def calcDELTA(delta): + return z_rotation(-delta) + + +def calcMU(mu_or_alpha): + return x_rotation(mu_or_alpha) + + +def calcETA(eta): + return z_rotation(-eta) + + +def calcCHI(chi): + return y_rotation(chi) + + +def calcPHI(phi): + return z_rotation(-phi) + + +def you_position_names(): + return ('mu', 'delta', NUNAME, 'eta', 'chi', 'phi') + +class YouPosition(AbstractPosition): + + def __init__(self, mu, delta, nu, eta, chi, phi, unit): + self.mu = mu + self.delta = delta + self.nu = nu + self.eta = eta + self.chi = chi + self.phi = phi + if unit not in ['DEG', 'RAD']: + raise DiffcalcException("Invalid angle unit value %s." % str(unit)) + else: + self.unit = unit + + def clone(self): + return YouPosition(self.mu, self.delta, self.nu, self.eta, self.chi, + self.phi, self.unit) + + def changeToRadians(self): + if self.unit == 'DEG': + self.mu *= TORAD + self.delta *= TORAD + self.nu *= TORAD + self.eta *= TORAD + self.chi *= TORAD + self.phi *= TORAD + self.unit = 'RAD' + elif self.unit == 'RAD': + return + else: + raise DiffcalcException("Invalid angle unit value %s." % str(self.unit)) + + def changeToDegrees(self): + if self.unit == 'RAD': + self.mu *= TODEG + self.delta *= TODEG + self.nu *= TODEG + self.eta *= TODEG + self.chi *= TODEG + self.phi *= TODEG + self.unit = 'DEG' + elif self.unit == 'DEG': + return + else: + raise DiffcalcException("Invalid angle unit value %s." % str(self.unit)) + + def totuple(self): + return (self.mu, self.delta, self.nu, self.eta, self.chi, self.phi) + + def __str__(self): + mu, delta, nu, eta, chi, phi = self.totuple() + return ("YouPosition(mu %r delta: %r nu: %r eta: %r chi: %r phi: %r) in %s" + % (mu, delta, nu, eta, chi, phi, self.unit)) + + def __eq__(self, other): + return self.totuple() == other.totuple() + + +class WillmottHorizontalPosition(YouPosition): + + def __init__(self, delta=None, gamma=None, omegah=None, phi=None): + self.mu = 0 + self.delta = delta + self.nu = gamma + self.eta = omegah + self.chi = -90 + self.phi = phi + self.unit= 'DEG' diff --git a/script/test/diffcalc (copy)/diffcalc/hkl/you/hkl.py b/script/test/diffcalc (copy)/diffcalc/hkl/you/hkl.py new file mode 100644 index 0000000..fe3e806 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/hkl/you/hkl.py @@ -0,0 +1,187 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from __future__ import absolute_import + +from diffcalc.hkl.common import getNameFromScannableOrString +from diffcalc.util import command +from diffcalc.hkl.you.calc import YouHklCalculator +from diffcalc import settings + + + +import diffcalc.ub.ub +from diffcalc.hkl.you.constraints import YouConstraintManager + +__all__ = ['allhkl', 'con', 'uncon', 'hklcalc', 'constraint_manager'] + + +_fixed_constraints = settings.geometry.fixed_constraints # @UndefinedVariable + +constraint_manager = YouConstraintManager(settings.hardware, _fixed_constraints) + +hklcalc = YouHklCalculator( + diffcalc.ub.ub.ubcalc, settings.geometry, settings.hardware, constraint_manager) + + +def __str__(self): + return hklcalc.__str__() + +@command +def con(*args): + """ + con -- list available constraints and values + con {val} -- constrains and optionally sets one constraint + con {val} {val} {val} -- clears and then fully constrains + + Select three constraints using 'con' and 'uncon'. Choose up to one + from each of the sample and detector columns and up to three from + the sample column. + + Not all constraint combinations are currently available: + + 1 x samp: all 80 of 80 + + 2 x samp and 1 x ref: chi & phi + chi & eta + chi & mu + mu & eta (4 of 6) + + 2 x samp and 1 x det: chi & phi + mu & eta + mu & phi (3 of 6) + + 3 x samp: eta, chi & phi (1 of 4) + + See also 'uncon' + """ + args = list(args) + msg = _handle_con(args) + if (hklcalc.constraints.is_fully_constrained() and + not hklcalc.constraints.is_current_mode_implemented()): + msg += ("\n\nWARNING:. The selected constraint combination is valid but " + "is not implemented.\n\nType 'help con' to see implemented combinations") + + if msg: + print msg + +def _handle_con(args): + if not args: + return hklcalc.constraints.__str__() + + if len(args) > 6: + raise TypeError("Unexpected args: " + str(args)) + + cons_value_pairs = [] + while args: + scn_or_str = args.pop(0) + name = getNameFromScannableOrString(scn_or_str) + if args and isinstance(args[0], (int, long, float)): + value = args.pop(0) + else: + try: + value = settings.hardware.get_position_by_name(name) + except ValueError: + value = None + cons_value_pairs.append((name, value)) + + if len(cons_value_pairs) == 1: + pass + elif len(cons_value_pairs) == 3: + hklcalc.constraints.clear_constraints() + else: + raise TypeError("Either one or three constraints must be specified") + for name, value in cons_value_pairs: + hklcalc.constraints.constrain(name) + if value is not None: + hklcalc.constraints.set_constraint(name, value) + return '\n'.join(hklcalc.constraints.report_constraints_lines()) + + +@command +def uncon(scn_or_string): + """uncon -- remove constraint + + See also 'con' + """ + name = getNameFromScannableOrString(scn_or_string) + hklcalc.constraints.unconstrain(name) + print '\n'.join(hklcalc.constraints.report_constraints_lines()) + +@command +def allhkl(hkl, wavelength=None): + """allhkl [h k l] -- print all hkl solutions ignoring limits + + """ + hardware = hklcalc._hardware + geometry = hklcalc._geometry + if wavelength is None: + wavelength = hardware.get_wavelength() + h, k, l = hkl + pos_virtual_angles_pairs = hklcalc.hkl_to_all_angles( + h, k, l, wavelength) + cells = [] + # virtual_angle_names = list(pos_virtual_angles_pairs[0][1].keys()) + # virtual_angle_names.sort() + virtual_angle_names = ['qaz', 'psi', 'naz', 'tau', 'theta', 'alpha', 'beta'] + header_cells = list(hardware.get_axes_names()) + [' '] + virtual_angle_names + cells.append(['%9s' % s for s in header_cells]) + cells.append([''] * len(header_cells)) + + + for pos, virtual_angles in pos_virtual_angles_pairs: + row_cells = [] + + + angle_tuple = geometry.internal_position_to_physical_angles(pos) + angle_tuple = hardware.cut_angles(angle_tuple) + for val in angle_tuple: + row_cells.append('%9.4f' % val) + + row_cells.append('|') + + for name in virtual_angle_names: + row_cells.append('%9.4f' % virtual_angles[name]) + cells.append(row_cells) + + + column_widths = [] + for col in range(len(cells[0])): + widths = [] + for row in range(len(cells)): + cell = cells[row][col] + width = len(cell.strip()) + widths.append(width) + column_widths.append(max(widths)) + + lines = [] + for row_cells in cells: + trimmed_row_cells = [] + for cell, width in zip(row_cells, column_widths): + trimmed_cell = cell.strip().rjust(width) + trimmed_row_cells.append(trimmed_cell) + lines.append(' '.join(trimmed_row_cells)) + print '\n'.join(lines) + + +commands_for_help = ['Constraints', + con, + uncon, + 'Hkl', + allhkl + ] diff --git a/script/test/diffcalc (copy)/diffcalc/log.py b/script/test/diffcalc (copy)/diffcalc/log.py new file mode 100644 index 0000000..ac4f1af --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/log.py @@ -0,0 +1,27 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from __future__ import absolute_import + +import logging +import getpass + +logging.basicConfig(format="%(asctime)s %(levelname)s:%(name)s:%(message)s", + datefmt='%m/%d/%Y %I:%M:%S', + filename='/tmp/diffcalc_%s.log' % getpass.getuser(), + level=logging.DEBUG) diff --git a/script/test/diffcalc (copy)/diffcalc/settings.py b/script/test/diffcalc (copy)/diffcalc/settings.py new file mode 100644 index 0000000..5221212 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/settings.py @@ -0,0 +1,24 @@ +''' +Created on Aug 5, 2013 + +@author: walton +''' +import os + +from diffcalc.ub.persistence import UbCalculationNonPersister + +# These should be by the user *before* importing other modules +geometry = None +hardware = None +ubcalc_persister = UbCalculationNonPersister() + +axes_scannable_group = None +energy_scannable = None +energy_scannable_multiplier_to_get_KeV=1 + + +# These will be set by dcyou, dcvlieg or dcwillmot +ubcalc_strategy = None +angles_to_hkl_function = None # Used by checkub to avoid coupling it to an hkl module +include_sigtau=False +include_reference=False \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/ub/__init__.py b/script/test/diffcalc (copy)/diffcalc/ub/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcalc/ub/calc.py b/script/test/diffcalc (copy)/diffcalc/ub/calc.py new file mode 100644 index 0000000..d71bc5e --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/ub/calc.py @@ -0,0 +1,854 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from diffcalc.ub.calcstate import decode_ubcalcstate +from diffcalc.ub.calcstate import UBCalcState +from diffcalc.ub.crystal import CrystalUnderTest +from diffcalc.ub.reflections import ReflectionList +from diffcalc.ub.persistence import UBCalculationJSONPersister, UBCalculationPersister +from diffcalc.util import DiffcalcException, cross3, dot3, bold, xyz_rotation,\ + bound +from math import acos, cos, sin, pi +from diffcalc.ub.reference import YouReference +from diffcalc.ub.orientations import OrientationList + +try: + from numpy import matrix, hstack + from numpy.linalg import norm +except ImportError: + from numjy import matrix, hstack + from numjy.linalg import norm + +SMALL = 1e-7 +TODEG = 180 / pi + +WIDTH = 13 + +def z(num): + """Round to zero if small. This is useful to get rid of erroneous + minus signs resulting from float representation close to zero. + """ + if abs(num) < SMALL: + num = 0 + return num + +#The UB matrix is used to find or set the orientation of a set of +#planes described by an hkl vector. The U matrix can be used to find +#or set the orientation of the crystal lattices' y axis. If there is +#crystal miscut the crystal lattices y axis is not parallel to the +#crystals optical surface normal. For surface diffraction experiments, +#where not only the crystal lattice must be oriented appropriately but +#so must the crystal's optical surface, two angles tau and sigma are +#used to describe the difference between the two. Sigma is (minus) the +#ammount of chi axis rotation and tau (minus) the ammount of phi axis +#rotation needed to move the surface normal into the direction of the + + +class PaperSpecificUbCalcStrategy(object): + + def calculate_q_phi(self, pos): + """Calculate hkl in the phi frame in units of 2 * pi / lambda from + pos object in radians""" + raise NotImplementedError() + + +class UBCalculation: + """A UB matrix calculation for an experiment. + + Contains the parameters for the _crystal under test, a list of measured + reflections and, if its been calculated, a UB matrix to be used by the rest + of the code. + """ + + def __init__(self, hardware, diffractometerPluginObject, + persister, strategy, include_sigtau=True, include_reference=True): + + # The diffractometer geometry is required to map the internal angles + # into those used by this diffractometer (for display only) + + self._hardware = hardware + self._geometry = diffractometerPluginObject + self._persister = persister + self._strategy = strategy + self.include_sigtau = include_sigtau + self.include_reference = include_reference + try: + self._ROT = diffractometerPluginObject.beamline_axes_transform + except AttributeError: + self._ROT = None + self._clear() + + def _get_diffractometer_axes_names(self): + return self._hardware.get_axes_names() + + def _clear(self, name=None): + # NOTE the Diffraction calculator is expecting this object to exist in + # the long run. We can't remove this entire object, and recreate it. + # It also contains a required link to the angle calculator. + reflist = ReflectionList(self._geometry, self._get_diffractometer_axes_names()) + orientlist = OrientationList() + reference = YouReference(self._get_UB) + self._state = UBCalcState(name=name, reflist=reflist, orientlist=orientlist, reference=reference) + self._U = None + self._UB = None + self._state.configure_calc_type() + +### State ### + def start_new(self, name): + """start_new(name) --- creates a new blank ub calculation""" + # Create storage object if name does not exist (TODO) + if name in self._persister.list(): + print ("No UBCalculation started: There is already a calculation " + "called: " + name) + print "Saved calculations: " + repr(self._persister.list()) + return + self._clear(name) + self.save() + + def load(self, name): + state = self._persister.load(name) + if isinstance(self._persister, UBCalculationJSONPersister): + self._state = decode_ubcalcstate(state, self._geometry, self._get_diffractometer_axes_names()) + self._state.reference.get_UB = self._get_UB + elif isinstance(self._persister, UBCalculationPersister): + self._state = state + else: + raise Exception('Unexpected persister type: ' + str(self._persister)) + if self._state.manual_U is not None: + self._U = self._state.manual_U + self._UB = self._U * self._state.crystal.B + self.save() + elif self._state.manual_UB is not None: + self._UB = self._state.manual_UB + self.save() + elif self._state.or0 is not None: + if self._state.or1 is None: + self.calculate_UB_from_primary_only() + else: + if self._state.reflist: + self.calculate_UB() + elif self._state.orientlist: + self.calculate_UB_from_orientation() + else: + pass + else: + pass + + def save(self): + self.saveas(self._state.name) + + def saveas(self, name): + self._state.name = name + self._persister.save(self._state, name) + + def listub(self): + return self._persister.list() + + def listub_metadata(self): + return self._persister.list_metadata() + + def remove(self, name): + self._persister.remove(name) + if self._state == name: + self._clear(name) + + def getState(self): + return self._state.getState() + + def __str__(self): + + if self._state.name is None: + return "<<< No UB calculation started >>>" + lines = [] + lines.append(bold("UBCALC")) + lines.append("") + lines.append( + " name:".ljust(WIDTH) + self._state.name.rjust(9)) + + if self.include_sigtau: + lines.append("") + lines.append( + " sigma:".ljust(WIDTH) + ("% 9.5f" % self._state.sigma).rjust(9)) + lines.append( + " tau:".ljust(WIDTH) + ("% 9.5f" % self._state.tau).rjust(9)) + + if self.include_reference: + lines.append("") + ub_calculated = self._UB is not None + lines.extend(self._state.reference.repr_lines(ub_calculated, WIDTH, self._ROT)) + + lines.append("") + lines.append(bold("CRYSTAL")) + lines.append("") + + if self._state.crystal is None: + lines.append(" <<< none specified >>>") + else: + lines.extend(self._state.crystal.str_lines()) + + lines.append("") + lines.append(bold("UB MATRIX")) + lines.append("") + + if self._UB is None: + lines.append(" <<< none calculated >>>") + else: + lines.extend(self.str_lines_u()) + lines.append("") + lines.extend(self.str_lines_u_angle_and_axis()) + lines.append("") + lines.extend(self.str_lines_ub()) + + lines.append("") + lines.append(bold("REFLECTIONS")) + lines.append("") + + lines.extend(self._state.reflist.str_lines()) + + lines.append("") + lines.append(bold("CRYSTAL ORIENTATIONS")) + lines.append("") + + lines.extend(self._state.orientlist.str_lines(R=self._ROT)) + + return '\n'.join(lines) + + def str_lines_u(self): + lines = [] + fmt = "% 9.5f % 9.5f % 9.5f" + try: + U = self._ROT.I * self.U * self._ROT + except AttributeError: + U = self.U + lines.append(" U matrix:".ljust(WIDTH) + + fmt % (z(U[0, 0]), z(U[0, 1]), z(U[0, 2]))) + lines.append(' ' * WIDTH + fmt % (z(U[1, 0]), z(U[1, 1]), z(U[1, 2]))) + lines.append(' ' * WIDTH + fmt % (z(U[2, 0]), z(U[2, 1]), z(U[2, 2]))) + return lines + + def str_lines_u_angle_and_axis(self): + lines = [] + fmt = "% 9.5f % 9.5f % 9.5f" + y = matrix('0; 0; 1') + try: + rotation_axis = cross3(self._ROT * y, self._ROT * self.U * y) + except TypeError: + rotation_axis = cross3(y, self.U * y) + if abs(norm(rotation_axis)) < SMALL: + lines.append(" miscut angle:".ljust(WIDTH) + " 0") + else: + rotation_axis = rotation_axis * (1 / norm(rotation_axis)) + cos_rotation_angle = dot3(y, self.U * y) + rotation_angle = acos(cos_rotation_angle) + + lines.append(" miscut:") + lines.append(" angle:".ljust(WIDTH) + "% 9.5f" % (rotation_angle * TODEG)) + lines.append(" axis:".ljust(WIDTH) + fmt % tuple((rotation_axis.T).tolist()[0])) + + return lines + + def str_lines_ub(self): + lines = [] + fmt = "% 9.5f % 9.5f % 9.5f" + try: + RI = self._ROT.I + B = self._state.crystal.B + UB = RI * self.UB * B.I * self._ROT * B + except AttributeError: + UB = self.UB + lines.append(" UB matrix:".ljust(WIDTH) + + fmt % (z(UB[0, 0]), z(UB[0, 1]), z(UB[0, 2]))) + lines.append(' ' * WIDTH + fmt % (z(UB[1, 0]), z(UB[1, 1]), z(UB[1, 2]))) + lines.append(' ' * WIDTH + fmt % (z(UB[2, 0]), z(UB[2, 1]), z(UB[2, 2]))) + return lines + + @property + def name(self): + return self._state.name +### Lattice ### + + def set_lattice(self, name, *shortform): + """ + Converts a list shortform crystal parameter specification to a six-long + tuple returned as . Returns None if wrong number of input args. See + set_lattice() for a description of the shortforms supported. + + shortformLattice -- a tuple as follows: + [a] - assumes cubic + [a,b]) - assumes tetragonal + [a,b,c]) - assumes ortho + [a,b,c,gam]) - assumes mon/hex gam different from 90. + [a,b,c,alp,bet,gam]) - for arbitrary + where all measurements in angstroms and angles in degrees + """ + self._set_lattice_without_saving(name, *shortform) + self.save() + + def _set_lattice_without_saving(self, name, *shortform): + sf = shortform + if len(sf) == 1: + fullform = (sf[0], sf[0], sf[0], 90., 90., 90.) # cubic + elif len(sf) == 2: + fullform = (sf[0], sf[0], sf[1], 90., 90., 90.) # tetragonal + elif len(sf) == 3: + fullform = (sf[0], sf[1], sf[2], 90., 90., 90.) # ortho + elif len(sf) == 4: + fullform = (sf[0], sf[1], sf[2], 90., 90., sf[3]) # mon/hex gam + # not 90 + elif len(sf) == 5: + raise ValueError("wrong length input to set_lattice") + elif len(sf) == 6: + fullform = sf # triclinic/arbitrary + else: + raise ValueError("wrong length input to set_lattice") + self._set_lattice(name, *fullform) + + def _set_lattice(self, name, a, b, c, alpha, beta, gamma): + """set lattice parameters in degrees""" + if self._state.name is None: + raise DiffcalcException( + "Cannot set lattice until a UBCalcaluation has been started " + "with newubcalc") + self._state.crystal = CrystalUnderTest(name, a, b, c, alpha, beta, gamma) + # Clear U and UB if these exist + if self._U is not None: # (UB will also exist) + print "Warning: the old UB calculation has been cleared." + print " Use 'calcub' to recalculate with old reflections or" + print " 'orientub' to recalculate with old orientations." + +### Surface normal stuff ### + + def _gettau(self): + """ + Returns tau (in degrees): the (minus) ammount of phi axis rotation , + that together with some chi axis rotation (minus sigma) brings the + optical surface normal parallel to the omega axis. + """ + return self._state.tau + + def _settau(self, tau): + self._state.tau = tau + self.save() + + tau = property(_gettau, _settau) + + def _getsigma(self): + """ + Returns sigma (in degrees): the (minus) ammount of phi axis rotation , + that together with some phi axis rotation (minus tau) brings the + optical surface normal parallel to the omega axis. + """ + return self._state.sigma + + def _setsigma(self, sigma): + self.state._sigma = sigma + self.save() + + sigma = property(_getsigma, _setsigma) + + +### Reference vector ### + + def _get_n_phi(self): + return self._state.reference.n_phi + + n_phi = property(_get_n_phi) + + def set_n_phi_configured(self, n_phi): + try: + self._state.reference.n_phi_configured = self._ROT.I * n_phi + except AttributeError: + self._state.reference.n_phi_configured = n_phi + self.save() + + def set_n_hkl_configured(self, n_hkl): + self._state.reference.n_hkl_configured = n_hkl + self.save() + + def print_reference(self): + print '\n'.join(self._state.reference.repr_lines(self.is_ub_calculated(), R=self._ROT)) + +### Reflections ### + + def add_reflection(self, h, k, l, position, energy, tag, time): + """add_reflection(h, k, l, position, tag=None) -- adds a reflection + + position is in degrees and in the systems internal representation. + """ + if self._state.reflist is None: + raise DiffcalcException("No UBCalculation loaded") + self._state.reflist.add_reflection(h, k, l, position, energy, tag, time) + self.save() # incase autocalculateUbAndReport fails + + # If second reflection has just been added then calculateUB + if len(self._state.reflist) == 2: + self._autocalculateUbAndReport() + self.save() + + def edit_reflection(self, num, h, k, l, position, energy, tag, time): + """ + edit_reflection(num, h, k, l, position, tag=None) -- adds a reflection + + position is in degrees and in the systems internal representation. + """ + if self._state.reflist is None: + raise DiffcalcException("No UBCalculation loaded") + self._state.reflist.edit_reflection(num, h, k, l, position, energy, tag, time) + + # If first or second reflection has been changed and there are at least + # two reflections then recalculate UB + if (num == 1 or num == 2) and len(self._state.reflist) >= 2: + self._autocalculateUbAndReport() + self.save() + + def get_reflection(self, num): + """--> ( [h, k, l], position, energy, tag, time + num starts at 1, position in degrees""" + return self._state.reflist.getReflection(num) + + def get_reflection_in_external_angles(self, num): + """--> ( [h, k, l], position, energy, tag, time + num starts at 1, position in degrees""" + return self._state.reflist.get_reflection_in_external_angles(num) + + def get_number_reflections(self): + return 0 if self._state.reflist is None else len(self._state.reflist) + + def del_reflection(self, reflectionNumber): + self._state.reflist.removeReflection(reflectionNumber) + if ((reflectionNumber == 1 or reflectionNumber == 2) and + (self._U is not None)): + self._autocalculateUbAndReport() + self.save() + + def swap_reflections(self, num1, num2): + self._state.reflist.swap_reflections(num1, num2) + if ((num1 == 1 or num1 == 2 or num2 == 1 or num2 == 2) and + (self._U is not None)): + self._autocalculateUbAndReport() + self.save() + + def _autocalculateUbAndReport(self): + if len(self._state.reflist) < 2: + pass + elif self._state.crystal is None: + print ("Not calculating UB matrix as no lattice parameters have " + "been specified.") + elif not self._state.is_okay_to_autocalculate_ub: + print ("Not calculating UB matrix as it has been manually set. " + "Use 'calcub' to explicitly recalculate it.") + else: # okay to autocalculate + if self._UB is None: + print "Calculating UB matrix." + else: + print "Recalculating UB matrix." + self.calculate_UB() + +### Orientations ### + + def add_orientation(self, h, k, l, x, y, z, tag, time): + """add_reflection(h, k, l, x, y, z, tag=None) -- adds a crystal orientation + """ + if self._state.orientlist is None: + raise DiffcalcException("No UBCalculation loaded") + try: + xyz_rot = self._ROT * matrix([[x],[y],[z]]) + xr, yr, zr = xyz_rot.T.tolist()[0] + self._state.orientlist.add_orientation(h, k, l, xr, yr, zr, tag, time) + except TypeError: + self._state.orientlist.add_orientation(h, k, l, x, y, z, tag, time) + self.save() # incase autocalculateUbAndReport fails + + # If second reflection has just been added then calculateUB + if len(self._state.orientlist) == 2: + self._autocalculateOrientationUbAndReport() + self.save() + + def edit_orientation(self, num, h, k, l, x, y, z, tag, time): + """ + edit_orientation(num, h, k, l, x, y, z, tag=None) -- edit a crystal reflection """ + if self._state.orientlist is None: + raise DiffcalcException("No UBCalculation loaded") + try: + xyz_rot = self._ROT * matrix([[x],[y],[z]]) + xr, yr, zr = xyz_rot.T.tolist()[0] + self._state.orientlist.edit_orientation(num, h, k, l, xr, yr, zr, tag, time) + except TypeError: + self._state.orientlist.edit_orientation(num, h, k, l, x, y, z, tag, time) + + # If first or second orientation has been changed and there are + # two orientations then recalculate UB + if (num == 1 or num == 2) and len(self._state.orientlist) == 2: + self._autocalculateOrientationUbAndReport() + self.save() + + def get_orientation(self, num): + """--> ( [h, k, l], [x, y, z], tag, time ) + num starts at 1""" + try: + hkl, xyz, tg, tm = self._state.orientlist.getOrientation(num) + xyz_rot = self._ROT.I * matrix([[xyz[0]],[xyz[1]],[xyz[2]]]) + xyz_lst = xyz_rot.T.tolist()[0] + return hkl, xyz_lst, tg, tm + except AttributeError: + return self._state.orientlist.getOrientation(num) + + + def get_number_orientations(self): + return 0 if self._state.orientlist is None else len(self._state.reflist) + + def del_orientation(self, orientationNumber): + self._state.orientlist.removeOrientation(orientationNumber) + if ((orientationNumber == 2) and (self._U is not None)): + self._autocalculateOrientationUbAndReport() + self.save() + + def swap_orientations(self, num1, num2): + self._state.orientlist.swap_orientations(num1, num2) + if ((num1 == 2 or num2 == 2) and + (self._U is not None)): + self._autocalculateOrientationUbAndReport() + self.save() + + def _autocalculateOrientationUbAndReport(self): + if len(self._state.orientlist) < 2: + pass + elif self._state.crystal is None: + print ("Not calculating UB matrix as no lattice parameters have " + "been specified.") + elif not self._state.is_okay_to_autocalculate_ub: + print ("Not calculating UB matrix as it has been manually set. " + "Use 'orientub' to explicitly recalculate it.") + else: # okay to autocalculate + if self._UB is None: + print "Calculating UB matrix." + else: + print "Recalculating UB matrix." + self.calculate_UB_from_orientation() + +# @property +# def reflist(self): +# return self._state.reflist +### Calculations ### + + def set_U_manually(self, m): + """Manually sets U. matrix must be 3*3 Jama or python matrix. + Turns off aution UB calcualtion.""" + + # Check matrix is a 3*3 Jama matrix + if m.__class__ != matrix: + m = matrix(m) # assume its a python matrix + if m.shape[0] != 3 or m.shape[1] != 3: + raise ValueError("Expects 3*3 matrix") + + if self._UB is None: + print "Calculating UB matrix." + else: + print "Recalculating UB matrix." + + if self._ROT is not None: + self._U = self._ROT * m * self._ROT.I + else: + self._U = m + self._state.configure_calc_type(manual_U=self._U) + if self._state.crystal is None: + raise DiffcalcException( + "A crystal must be specified before manually setting U") + self._UB = self._U * self._state.crystal.B + print ("NOTE: A new UB matrix will not be automatically calculated " + "when the orientation reflections are modified.") + self.save() + + def set_UB_manually(self, m): + """Manually sets UB. matrix must be 3*3 Jama or python matrix. + Turns off aution UB calcualtion.""" + + # Check matrix is a 3*3 Jama matrix + if m.__class__ != matrix: + m = matrix(m) # assume its a python matrix + if m.shape[0] != 3 or m.shape[1] != 3: + raise ValueError("Expects 3*3 matrix") + + if self._ROT is not None: + self._UB = self._ROT * m + else: + self._UB = m + self._state.configure_calc_type(manual_UB=self._UB) + self.save() + + @property + def U(self): + if self._U is None: + raise DiffcalcException( + "No U matrix has been calculated during this ub calculation") + return self._U + + @property + def UB(self): + return self._get_UB() + + def is_ub_calculated(self): + return self._UB is not None + + def _get_UB(self): + if not self.is_ub_calculated(): + raise DiffcalcException( + "No UB matrix has been calculated during this ub calculation") + else: + return self._UB + + def _calc_UB(self, h1, h2, u1p, u2p): + B = self._state.crystal.B + h1c = B * h1 + h2c = B * h2 + + # Create modified unit vectors t1, t2 and t3 in crystal and phi systems + t1c = h1c + t3c = cross3(h1c, h2c) + t2c = cross3(t3c, t1c) + + t1p = u1p # FIXED from h1c 9July08 + t3p = cross3(u1p, u2p) + t2p = cross3(t3p, t1p) + + # ...and nornmalise and check that the reflections used are appropriate + SMALL = 1e-4 # Taken from Vlieg's code + e = DiffcalcException("Invalid orientation reflection(s)") + + def normalise(m): + d = norm(m) + if d < SMALL: + raise e + return m / d + + t1c = normalise(t1c) + t2c = normalise(t2c) + t3c = normalise(t3c) + + t1p = normalise(t1p) + t2p = normalise(t2p) + t3p = normalise(t3p) + + Tc = hstack([t1c, t2c, t3c]) + Tp = hstack([t1p, t2p, t3p]) + self._state.configure_calc_type(or0=1, or1=2) + self._U = Tp * Tc.I + self._UB = self._U * B + self.save() + + def calculate_UB(self): + """ + Calculate orientation matrix. Uses first two orientation reflections + as in Busang and Levy, but for the diffractometer in Lohmeier and + Vlieg. + """ + + # Major variables: + # h1, h2: user input reciprical lattice vectors of the two reflections + # h1c, h2c: user input vectors in cartesian crystal plane + # pos1, pos2: measured diffractometer positions of the two reflections + # u1a, u2a: measured reflection vectors in alpha frame + # u1p, u2p: measured reflection vectors in phi frame + + + # Get hkl and angle values for the first two refelctions + if self._state.reflist is None: + raise DiffcalcException("Cannot calculate a U matrix until a " + "UBCalculation has been started with " + "'newub'") + try: + (h1, pos1, _, _, _) = self._state.reflist.getReflection(1) + (h2, pos2, _, _, _) = self._state.reflist.getReflection(2) + except IndexError: + raise DiffcalcException( + "Two reflections are required to calculate a U matrix") + h1 = matrix([h1]).T # row->column + h2 = matrix([h2]).T + pos1.changeToRadians() + pos2.changeToRadians() + + # Compute the two reflections' reciprical lattice vectors in the + # cartesian crystal frame + u1p = self._strategy.calculate_q_phi(pos1) + u2p = self._strategy.calculate_q_phi(pos2) + + self._calc_UB(h1, h2, u1p, u2p) + + def calculate_UB_from_orientation(self): + """ + Calculate orientation matrix. Uses first two crystal orientations. + """ + + # Major variables: + # h1, h2: user input reciprical lattice vectors of the two reflections + # h1c, h2c: user input vectors in cartesian crystal plane + # pos1, pos2: measured diffractometer positions of the two reflections + # u1a, u2a: measured reflection vectors in alpha frame + # u1p, u2p: measured reflection vectors in phi frame + + + # Get hkl and angle values for the first two crystal orientations + if self._state.orientlist is None: + raise DiffcalcException("Cannot calculate a U matrix until a " + "UBCalculation has been started with " + "'newub'") + try: + (h1, x1, _, _) = self._state.orientlist.getOrientation(1) + (h2, x2, _, _) = self._state.orientlist.getOrientation(2) + except IndexError: + raise DiffcalcException( + "Two crystal orientations are required to calculate a U matrix") + h1 = matrix([h1]).T # row->column + h2 = matrix([h2]).T + u1p = matrix([x1]).T + u2p = matrix([x2]).T + + self._calc_UB(h1, h2, u1p, u2p) + + def calculate_UB_from_primary_only(self): + """ + Calculate orientation matrix with the shortest absolute angle change. + Uses first orientation reflection + """ + + # Algorithm from http://www.j3d.org/matrix_faq/matrfaq_latest.html + + # Get hkl and angle values for the first two refelctions + if self._state.reflist is None: + raise DiffcalcException( + "Cannot calculate a u matrix until a UBCalcaluation has been " + "started with newub") + try: + (h, pos, _, _, _) = self._state.reflist.getReflection(1) + except IndexError: + raise DiffcalcException( + "One reflection is required to calculate a u matrix") + + h = matrix([h]).T # row->column + pos.changeToRadians() + B = self._state.crystal.B + h_crystal = B * h + h_crystal = h_crystal * (1 / norm(h_crystal)) + + q_measured_phi = self._strategy.calculate_q_phi(pos) + q_measured_phi = q_measured_phi * (1 / norm(q_measured_phi)) + + rotation_axis = cross3(h_crystal, q_measured_phi) + rotation_axis = rotation_axis * (1 / norm(rotation_axis)) + + cos_rotation_angle = dot3(h_crystal, q_measured_phi) + rotation_angle = acos(cos_rotation_angle) + + uvw = rotation_axis.T.tolist()[0] # TODO: cleanup + print "resulting U angle: %.5f deg" % (rotation_angle * TODEG) + u_repr = (', '.join(['% .5f' % el for el in uvw])) + print "resulting U axis direction: [%s]" % u_repr + + u, v, w = uvw + rcos = cos(rotation_angle) + rsin = sin(rotation_angle) + m = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] # TODO: tidy + m[0][0] = rcos + u * u * (1 - rcos) + m[1][0] = w * rsin + v * u * (1 - rcos) + m[2][0] = -v * rsin + w * u * (1 - rcos) + m[0][1] = -w * rsin + u * v * (1 - rcos) + m[1][1] = rcos + v * v * (1 - rcos) + m[2][1] = u * rsin + w * v * (1 - rcos) + m[0][2] = v * rsin + u * w * (1 - rcos) + m[1][2] = -u * rsin + v * w * (1 - rcos) + m[2][2] = rcos + w * w * (1 - rcos) + + if self._UB is None: + print "Calculating UB matrix from the first reflection only." + else: + print "Recalculating UB matrix from the first reflection only." + print ("NOTE: A new UB matrix will not be automatically calculated " + "when the orientation reflections are modified.") + + self._state.configure_calc_type(or0=1) + + self._U = matrix(m) + self._UB = self._U * B + + self.save() + + def set_miscut(self, xyz, angle, add_miscut=False): + """Calculate U matrix using a miscut axis and an angle""" + if xyz is None: + rot_matrix = xyz_rotation([0, 1, 0], angle) + if self.is_ub_calculated() and add_miscut: + self._U = rot_matrix * self._U + else: + self._U = rot_matrix + else: + rot_matrix = xyz_rotation(xyz, angle) + try: + rot_matrix = self._ROT * rot_matrix * self._ROT.I + except TypeError: + pass + if self.is_ub_calculated() and add_miscut: + self._U = rot_matrix * self._U + else: + self._U = rot_matrix + self._state.configure_calc_type(manual_U=self._U) + self._UB = self._U * self._state.crystal.B + self.print_reference() + self.save() + + def get_hkl_plane_distance(self, hkl): + """Calculates and returns the distance between planes""" + return self._state.crystal.get_hkl_plane_distance(hkl) + + def get_hkl_plane_angle(self, hkl1, hkl2): + """Calculates and returns the angle between planes""" + return self._state.crystal.get_hkl_plane_angle(hkl1, hkl2) + + def rescale_unit_cell(self, h, k, l, pos): + """ + Calculate unit cell scaling parameter that matches + given hkl position and diffractometer angles + """ + q_vec = self._strategy.calculate_q_phi(pos) + q_hkl = norm(q_vec) / self._hardware.get_wavelength() + d_hkl = self._state.crystal.get_hkl_plane_distance([h, k, l]) + sc = 1/ (q_hkl * d_hkl) + name, a1, a2, a3, alpha1, alpha2, alpha3 = self._state.crystal.getLattice() + if abs(sc - 1.) < SMALL: + return None, None + return sc, (name, sc * a1, sc* a2, sc * a3, alpha1, alpha2, alpha3) + + def calc_miscut(self, h, k, l, pos): + """ + Calculate miscut angle and axis that matches + given hkl position and diffractometer angles + """ + q_vec = self._strategy.calculate_q_phi(pos) + hkl_nphi = self._UB * matrix([[h], [k], [l]]) + try: + axis = cross3(self._ROT.I * q_vec, self._ROT.I * hkl_nphi) + except AttributeError: + axis = cross3(q_vec, hkl_nphi) + norm_axis = norm(axis) + if norm_axis < SMALL: + return None, None + axis = axis / norm(axis) + try: + miscut = acos(bound(dot3(q_vec, hkl_nphi) / (norm(q_vec) * norm(hkl_nphi)))) * TODEG + except AssertionError: + return None, None + return miscut, axis.T.tolist()[0] diff --git a/script/test/diffcalc (copy)/diffcalc/ub/calcstate.py b/script/test/diffcalc (copy)/diffcalc/ub/calcstate.py new file mode 100644 index 0000000..1e8f438 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/ub/calcstate.py @@ -0,0 +1,220 @@ +from diffcalc.hkl.vlieg.geometry import VliegPosition +from diffcalc.ub.crystal import CrystalUnderTest +from diffcalc.ub.reflections import ReflectionList, _Reflection +from math import pi +import datetime # @UnusedImport For crazy time eval code! +from diffcalc.ub.reference import YouReference +from diffcalc.ub.orientations import _Orientation, OrientationList + +try: + from collection import OrderedDict +except ImportError: + from simplejson import OrderedDict + +try: + import json +except ImportError: + import simplejson as json + +try: + from numpy import matrix +except ImportError: + from numjy import matrix + + +TODEG = 180 / pi + + +class UBCalcState(): + + def __init__(self, name=None, crystal=None, reflist=None, orientlist=None, tau=0, sigma=0, + manual_U=None, manual_UB=None, or0=None, or1=None, reference=None): + + assert reflist is not None + self.name = name + self.crystal = crystal + self.reflist = reflist + self.orientlist = orientlist + self.tau = tau # degrees + self.sigma = sigma # degrees + self.manual_U = manual_U + self.manual_UB = manual_UB + self.or0 = or0 + self.or1 = or1 + self.reference = reference + + @property + def is_okay_to_autocalculate_ub(self): + nothing_set = ((self.manual_U is None) and + (self.manual_UB is None) and + (self.or0 is None) and + (self.or1 is None)) + or0_and_or1_used = (self.or0 is not None) and (self.or1 is not None) + return nothing_set or or0_and_or1_used + + + def configure_calc_type(self, + manual_U=None, + manual_UB=None, + or0=None, + or1=None): + self.manual_U = manual_U + self.manual_UB = manual_UB + self.or0 = or0 + self.or1 = or1 + + +class UBCalcStateEncoder(json.JSONEncoder): + + def default(self, obj): + + if isinstance(obj, UBCalcState): + d = OrderedDict() + d['name'] = obj.name + d['crystal'] = obj.crystal + d['reflist'] = obj.reflist + d['orientlist'] = obj.orientlist + d['tau'] = obj.tau + d['sigma'] = obj.sigma + d['reference'] = obj.reference + d['u'] = obj.manual_U + d['ub'] = obj.manual_UB + d['or0'] = obj.or0 + d['or1'] = obj.or1 + + return d + + if isinstance(obj, CrystalUnderTest): + return repr([obj._name, obj._a1, obj._a2, obj._a3, obj._alpha1 * TODEG, + obj._alpha2 * TODEG, obj._alpha3 * TODEG]) + + if isinstance(obj, matrix): + l = [', '.join((repr(e) for e in row)) for row in obj.tolist()] + return l + + if isinstance(obj, ReflectionList): + d = OrderedDict() + for n, ref in enumerate(obj._reflist): + d[str(n+1)] = ref + return d + + if isinstance(obj, _Reflection): + d = OrderedDict() + d['tag'] = obj.tag + d['hkl'] = repr([obj.h, obj.k, obj.l]) + d['pos'] = repr(list(obj.pos.totuple())) + d['energy'] = obj.energy + dt = eval(obj.time) # e.g. --> datetime.datetime(2013, 8, 5, 15, 47, 7, 962432) + d['time'] = None if dt is None else dt.isoformat() + return d + + if isinstance(obj, OrientationList): + d = OrderedDict() + for n, orient in enumerate(obj._orientlist): + d[str(n+1)] = orient + return d + + if isinstance(obj, _Orientation): + d = OrderedDict() + d['tag'] = obj.tag + d['hkl'] = repr([obj.h, obj.k, obj.l]) + d['xyz'] = repr([obj.x, obj.y, obj.z]) + dt = eval(obj.time) # e.g. --> datetime.datetime(2013, 8, 5, 15, 47, 7, 962432) + d['time'] = None if dt is None else dt.isoformat() + return d + + if isinstance(obj, YouReference): + d = OrderedDict() + if obj.n_hkl_configured is not None: + d['n_hkl_configured'] = repr(obj.n_hkl_configured.T.tolist()[0]) + else: + d['n_hkl_configured'] = None + if obj.n_phi_configured is not None: + d['n_phi_configured'] = repr(obj.n_phi_configured.T.tolist()[0]) + else: + d['n_phi_configured'] = None + return d + + + return json.JSONEncoder.default(self, obj) + + +def decode_ubcalcstate(state, geometry, diffractometer_axes_names): + + # Backwards compatibility code + orientlist_=OrientationList([]) + try: + orientlist_=decode_orientlist(state['orientlist']) + except KeyError: + pass + return UBCalcState( + name=state['name'], + crystal=state['crystal'] and CrystalUnderTest(*eval(state['crystal'])), + reflist=decode_reflist(state['reflist'], geometry, diffractometer_axes_names), + orientlist=orientlist_, + tau=state['tau'], + sigma=state['sigma'], + manual_U=state['u'] and decode_matrix(state['u']), + manual_UB=state['ub'] and decode_matrix(state['ub']), + or0=state['or0'], + or1=state['or1'], + reference=decode_reference(state.get('reference', None)) + ) + + +def decode_matrix(rows): + return matrix([[eval(e) for e in row.split(', ')] for row in rows]) + + +def decode_reflist(reflist_dict, geometry, diffractometer_axes_names): + reflections = [] + for key in sorted(reflist_dict.keys()): + reflections.append(decode_reflection(reflist_dict[key], geometry)) + + return ReflectionList(geometry, diffractometer_axes_names, reflections) + + +def decode_orientlist(orientlist_dict): + orientations = [] + for key in sorted(orientlist_dict.keys()): + orientations.append(decode_orientation(orientlist_dict[key])) + + return OrientationList(orientations) + + +def decode_reflection(ref_dict, geometry): + h, k, l = eval(ref_dict['hkl']) + time = ref_dict['time'] and gt(ref_dict['time']) + pos_tuple = eval(ref_dict['pos']) + try: + position = geometry.create_position(*pos_tuple) + except AttributeError: + position = VliegPosition(*pos_tuple) + return _Reflection(h, k, l, position, ref_dict['energy'], str(ref_dict['tag']), repr(time)) + + +def decode_reference(ref_dict): + reference = YouReference(None) # TODO: We can't set get_ub method yet (tangles!) + if ref_dict: + nhkl = ref_dict.get('n_hkl_configured', None) + nphi = ref_dict.get('n_phi_configured', None) + if nhkl: + reference.n_hkl_configured = matrix([eval(nhkl)]).T + if nphi: + reference.n_phi_configured = matrix([eval(nphi)]).T + return reference + + +def decode_orientation(orient_dict): + h, k, l = eval(orient_dict['hkl']) + x, y, z = eval(orient_dict['xyz']) + time = orient_dict['time'] and gt(orient_dict['time']) + return _Orientation(h, k, l, x, y, z, str(orient_dict['tag']), repr(time)) + + +# From: http://stackoverflow.com/questions/127803/how-to-parse-iso-formatted-date-in-python +def gt(dt_str): + dt, _, us= dt_str.partition(".") + dt= datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S") + us= int(us.rstrip("Z"), 10) + return dt + datetime.timedelta(microseconds=us) \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/ub/crystal.py b/script/test/diffcalc (copy)/diffcalc/ub/crystal.py new file mode 100644 index 0000000..84072d9 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/ub/crystal.py @@ -0,0 +1,147 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from math import pi, cos, sin, acos, sqrt +from diffcalc.util import angle_between_vectors + + +try: + from numpy import matrix +except ImportError: + from numjy import matrix + +TORAD = pi / 180 +TODEG = 180 / pi +SMALL = 1e-7 + +def z(num): + """Round to zero if small. This is useful to get rid of erroneous + minus signs resulting from float representation close to zero. + """ + if abs(num) < SMALL: + num = 0 + return num + + +class CrystalUnderTest(object): + """ + Contains the lattice parameters and calculated B matrix for the crytsal + under test. Also Calculates the distance between planes at a given hkl + value. + + The lattice paraemters can be specified and then if desired saved to a + __library to be loaded later. The parameters are persisted across restarts. + Lattices stored in config/var/crystals.xml . + """ + + def __init__(self, name, a, b, c, alpha, beta, gamma): + '''Creates a new lattice and calculates related values. + + Keyword arguments: + name -- a string + a,b,c,alpha,beta,gamma -- lengths and angles (in degrees) + ''' + + self._name = name + + # Set the direct lattice parameters + self._a1 = a1 = a + self._a2 = a2 = b + self._a3 = a3 = c + self._alpha1 = alpha1 = alpha * TORAD + self._alpha2 = alpha2 = beta * TORAD + self._alpha3 = alpha3 = gamma * TORAD + + # Calculate the reciprocal lattice parameters + self._beta1 = acos((cos(alpha2) * cos(alpha3) - cos(alpha1)) / + (sin(alpha2) * sin(alpha3))) + + self._beta2 = beta2 = acos((cos(alpha1) * cos(alpha3) - cos(alpha2)) / + (sin(alpha1) * sin(alpha3))) + + self._beta3 = beta3 = acos((cos(alpha1) * cos(alpha2) - cos(alpha3)) / + (sin(alpha1) * sin(alpha2))) + + volume = (a1 * a2 * a3 * + sqrt(1 + 2 * cos(alpha1) * cos(alpha2) * cos(alpha3) - + cos(alpha1) ** 2 - cos(alpha2) ** 2 - cos(alpha3) ** 2)) + + self._b1 = b1 = 2 * pi * a2 * a3 * sin(alpha1) / volume + self._b2 = b2 = 2 * pi * a1 * a3 * sin(alpha2) / volume + self._b3 = b3 = 2 * pi * a1 * a2 * sin(alpha3) / volume + + # Calculate the BMatrix from the direct and reciprical parameters. + # Reference: Busang and Levy (1967) + self._bMatrix = matrix([ + [b1, b2 * cos(beta3), b3 * cos(beta2)], + [0.0, b2 * sin(beta3), -b3 * sin(beta2) * cos(alpha1)], + [0.0, 0.0, 2 * pi / a3]]) + + @property + def B(self): + ''' + Returns the B matrix, may be null if crystal is not set, or if there + was a problem calculating this''' + return self._bMatrix + + def get_hkl_plane_distance(self, hkl): + '''Calculates and returns the distance between planes''' + hkl = matrix([hkl]) + bReduced = self._bMatrix / (2 * pi) + bMT = bReduced.I * bReduced.T.I + return 1.0 / sqrt((hkl * bMT.I * hkl.T)[0,0]) + + def get_hkl_plane_angle(self, hkl1, hkl2): + '''Calculates and returns the angle between [hkl1] and [hkl2] planes''' + hkl1 = matrix([hkl1]).T + hkl2 = matrix([hkl2]).T + nphi1 = self._bMatrix * hkl1 + nphi2 = self._bMatrix * hkl2 + angle = angle_between_vectors(nphi1, nphi2) + return angle + + def __str__(self): + ''' Returns lattice name and all set and calculated parameters''' + return '\n'.join(self.str_lines()) + + def str_lines(self): + WIDTH = 13 + if self._name is None: + return [" none specified"] + + b = self._bMatrix + lines = [] + lines.append(" name:".ljust(WIDTH) + self._name.rjust(9)) + lines.append("") + lines.append(" a, b, c:".ljust(WIDTH) + + "% 9.5f % 9.5f % 9.5f" % (self.getLattice()[1:4])) + lines.append(" " * WIDTH + + "% 9.5f % 9.5f % 9.5f" % (self.getLattice()[4:])) + lines.append("") + + fmt = "% 9.5f % 9.5f % 9.5f" + lines.append(" B matrix:".ljust(WIDTH) + + fmt % (z(b[0, 0]), z(b[0, 1]), z(b[0, 2]))) + lines.append(' ' * WIDTH + fmt % (z(b[1, 0]), z(b[1, 1]), z(b[1, 2]))) + lines.append(' ' * WIDTH + fmt % (z(b[2, 0]), z(b[2, 1]), z(b[2, 2]))) + return lines + + def getLattice(self): + return(self._name, self._a1, self._a2, self._a3, self._alpha1 * TODEG, + self._alpha2 * TODEG, self._alpha3 * TODEG) + diff --git a/script/test/diffcalc (copy)/diffcalc/ub/orientations.py b/script/test/diffcalc (copy)/diffcalc/ub/orientations.py new file mode 100644 index 0000000..1726136 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/ub/orientations.py @@ -0,0 +1,118 @@ +### +# Copyright 2008-2017 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from copy import deepcopy +import datetime # @UnusedImport for the eval below + +try: + from numpy import matrix +except ImportError: + from numjy import matrix + +from diffcalc.util import DiffcalcException, bold + + +class _Orientation: + """A orientation""" + def __init__(self, h, k, l, x, y, z, tag, time): + + self.h = float(h) + self.k = float(k) + self.l = float(l) + self.x = float(x) + self.y = float(y) + self.z = float(z) + self.tag = tag + self.time = time # Saved as e.g. repr(datetime.now()) + + def __str__(self): + return ("h=%-4.2f k=%-4.2f l=%-4.2f x=%-4.2f " + "y=%-4.2f z=%-4.2 " + " %-s %s" % (self.h, self.k, self.l, + self.x, self.y, self.z, + self.tag, self.time)) + + +class OrientationList: + + def __init__(self, orientations=None): + self._orientlist = orientations if orientations else [] + + + def add_orientation(self, h, k, l, x, y, z, tag, time): + """adds a crystal orientation + """ + self._orientlist += [_Orientation(h, k, l, x, y, z, tag, + time.__repr__())] + + def edit_orientation(self, num, h, k, l, x, y, z, tag, time): + """num starts at 1""" + try: + self._orientlist[num - 1] = _Orientation(h, k, l, x, y, z, tag, + time.__repr__()) + except IndexError: + raise DiffcalcException("There is no orientation " + repr(num) + + " to edit.") + + def getOrientation(self, num): + """ + getOrientation(num) --> ( [h, k, l], [x, y, z], tag, time ) -- + num starts at 1 + """ + r = deepcopy(self._orientlist[num - 1]) # for convenience + return [r.h, r.k, r.l], [r.x, r.y, r.z], r.tag, eval(r.time) + + def removeOrientation(self, num): + del self._orientlist[num - 1] + + def swap_orientations(self, num1, num2): + orig1 = self._orientlist[num1 - 1] + self._orientlist[num1 - 1] = self._orientlist[num2 - 1] + self._orientlist[num2 - 1] = orig1 + + def __len__(self): + return len(self._orientlist) + + def __str__(self): + return '\n'.join(self.str_lines()) + + def str_lines(self, R=None): + if not self._orientlist: + return [" <<< none specified >>>"] + + lines = [] + + str_format = (" %5s %5s %5s %5s %5s %5s TAG") + values = ('H', 'K', 'L', 'X', 'Y', 'Z') + lines.append(bold(str_format % values)) + + for n in range(len(self._orientlist)): + orient_tuple = self.getOrientation(n + 1) + [h, k, l], [x, y, z], tag, _ = orient_tuple + try: + xyz_rot = R.I * matrix([[x],[y],[z]]) + xr, yr, zr = xyz_rot.T.tolist()[0] + except AttributeError: + xr, yr, zr = x ,y ,z + if tag is None: + tag = "" + str_format = (" %2d % 4.2f % 4.2f % 4.2f " + + "% 4.2f % 4.2f % 4.2f %s") + values = (n + 1, h, k, l, xr, yr, zr, tag) + lines.append(str_format % values) + return lines diff --git a/script/test/diffcalc (copy)/diffcalc/ub/persistence.py b/script/test/diffcalc (copy)/diffcalc/ub/persistence.py new file mode 100644 index 0000000..526dbcd --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/ub/persistence.py @@ -0,0 +1,151 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from __future__ import with_statement + +import os, glob +from diffcalc.ub.calcstate import UBCalcStateEncoder +import datetime + +try: + import json +except ImportError: + import simplejson as json + + +def is_writable(directory): + """Return true if the file is writable from the current user + """ + probe = os.path.join(directory, "probe") + try: + open(probe, 'w') + except IOError: + return False + else: + os.remove(probe) + return True + +def check_directory_appropriate(directory): + + if not os.path.exists(directory): + raise IOError("'%s' does not exist") + + if not os.path.isdir(directory): + raise IOError("'%s' is not a directory") + + if not is_writable(directory): + raise IOError("'%s' is not writable") + + +class UBCalculationJSONPersister(object): + + def __init__(self, directory): + check_directory_appropriate(directory) + self.directory = directory + self.description = directory + + def filepath(self, name): + return os.path.join(self.directory, name + '.json') + + def save(self, state, name): + # FORMAT = '%Y-%m-%d %H:%M:%S' + # time_string = datetime.datetime.strftime(datetime.datetime.now(), FORMAT) + with open(self.filepath(name), 'w') as f: + json.dump(state, f, indent=4, cls=UBCalcStateEncoder) + + def load(self, name): + with open(self.filepath(name), 'r') as f: + return json.load(f) + + def list(self): # @ReservedAssignment + files = self._get_save_files() + return [os.path.basename(f + '.json').split('.json')[0] for f in files] + + def list_metadata(self): + metadata = [] + for f in self._get_save_files(): + dt = datetime.datetime.fromtimestamp(os.path.getmtime(f)) + metadata.append(dt.strftime('%d %b %Y (%H:%M)')) + return metadata + + def _get_save_files(self): + files = filter(os.path.isfile, glob.glob(os.path.join(self.directory, '*.json'))) + files.sort(key=lambda x: os.path.getmtime(x)) + files.reverse() + return files + + def remove(self, name): + os.remove(self.filepath(name)) + + + +class UBCalculationPersister(object): + """Attempts to the use the gda's database to store ub calculation state + """ + def __init__(self): + try: + from uk.ac.diamond.daq.persistence.jythonshelf import LocalJythonShelfManager + from uk.ac.diamond.daq.persistence.jythonshelf.LocalDatabase import \ + LocalDatabaseException + self.shelf = LocalJythonShelfManager.getLocalObjectShelf( + 'diffcalc.ub') + except ImportError, e: + print ("!!! UBCalculationPersister could not import the gda database " + "code: " + repr(e)) + self.shelf = None + except LocalDatabaseException, e: + print ("UBCalculationPersister could not connect to the gda " + "database: " + repr(e)) + self.shelf = None + self.description = 'GDA sql database' + + def save(self, state, key): + if self.shelf is not None: + self.shelf[key] = state + else: + print "<<>>" + + def load(self, name): + if self.shelf is not None: + return self.shelf[name] + else: + raise IOError("Could not load UB calculation: no database available") + + def list(self): # @ReservedAssignment + if self.shelf is not None: + names = list(self.shelf.keys()) + names.sort() + return names + else: + return [] + + def remove(self, name): + if self.shelf is not None: + del self.shelf[name] + else: + raise IOError("Could not remove UB calculation: no database available") + + +class UbCalculationNonPersister(UBCalculationPersister): + """ + A version of UBCalculationPersister that simply stores to a local dict + rather than a database. Useful for testing. + """ + def __init__(self): + self.shelf = dict() + self.description = 'memory only' diff --git a/script/test/diffcalc (copy)/diffcalc/ub/reference.py b/script/test/diffcalc (copy)/diffcalc/ub/reference.py new file mode 100644 index 0000000..53a8e4b --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/ub/reference.py @@ -0,0 +1,99 @@ +from math import pi, acos + +try: + from numpy import matrix + from numpy.linalg import norm +except ImportError: + from numjy import matrix + from numjy.linalg import norm + +from diffcalc.util import cross3, dot3 + + +SMALL = 1e-7 +TODEG = 180 / pi + +class YouReference(object): + + def __init__(self, get_UB): + self.get_UB = get_UB # callable + self._n_phi_configured = None + self._n_hkl_configured = None + self._set_n_phi_configured(matrix('0; 0; 1')) + + def _set_n_phi_configured(self, n_phi): + self._n_phi_configured = n_phi + self._n_hkl_configured = None + + def _get_n_phi_configured(self): + return self._n_phi_configured + + n_phi_configured = property(_get_n_phi_configured, _set_n_phi_configured) + + def _set_n_hkl_configured(self, n_hkl): + self._n_phi_configured = None + self._n_hkl_configured = n_hkl + + def _get_n_hkl_configured(self): + return self._n_hkl_configured + + n_hkl_configured = property(_get_n_hkl_configured, _set_n_hkl_configured) + + @property + def n_phi(self): + n_phi = (self.get_UB() * self._n_hkl_configured if self._n_phi_configured is None + else self._n_phi_configured) + return n_phi / norm(n_phi) + + @property + def n_hkl(self): + n_hkl = (self.get_UB().I * self._n_phi_configured if self._n_hkl_configured is None + else self._n_hkl_configured) + return n_hkl / norm(n_hkl) + + def _pretty_vector(self, m): + return ' '.join([('% 9.5f' % e).rjust(9) for e in m.T.tolist()[0]]) + + def repr_lines(self, ub_calculated, WIDTH=9, R=None): + SET_LABEL = ' <- set' + lines = [] + if self._n_phi_configured is not None: + nphi_label = SET_LABEL + nhkl_label = '' + elif self._n_hkl_configured is not None: + nphi_label = '' + nhkl_label = SET_LABEL + else: + raise AssertionError("Neither a manual n_phi nor n_hkl is configured") + + if ub_calculated: + try: + lines.append(" n_phi:".ljust(WIDTH) + self._pretty_vector(R.I * self.n_phi) + nphi_label) + except AttributeError: + lines.append(" n_phi:".ljust(WIDTH) + self._pretty_vector(self.n_phi) + nphi_label) + lines.append(" n_hkl:".ljust(WIDTH) + self._pretty_vector(self.n_hkl) + nhkl_label) + try: + rotation_axis = R.I * cross3(matrix('0; 0; 1'), self.n_phi) + except AttributeError: + rotation_axis = cross3(matrix('0; 0; 1'), self.n_phi) + if abs(norm(rotation_axis)) < SMALL: + lines.append(" normal:".ljust(WIDTH) + " None") + else: + rotation_axis = rotation_axis * (1 / norm(rotation_axis)) + cos_rotation_angle = dot3(matrix('0; 0; 1'), self.n_phi) + rotation_angle = acos(cos_rotation_angle) + lines.append(" normal:") + lines.append(" angle:".ljust(WIDTH) + "% 9.5f" % (rotation_angle * TODEG)) + lines.append(" axis:".ljust(WIDTH) + self._pretty_vector(rotation_axis)) + + else: # no ub calculated + if self._n_phi_configured is not None: + try: + lines.append(" n_phi:".ljust(WIDTH) + self._pretty_vector(R.I * self._n_phi_configured) + SET_LABEL) + except AttributeError: + lines.append(" n_phi:".ljust(WIDTH) + self._pretty_vector(self._n_phi_configured) + SET_LABEL) + elif self._n_hkl_configured is not None: + lines.append(" n_hkl:".ljust(WIDTH) + self._pretty_vector(self._n_hkl_configured) + SET_LABEL) + + return lines + diff --git a/script/test/diffcalc (copy)/diffcalc/ub/reflections.py b/script/test/diffcalc (copy)/diffcalc/ub/reflections.py new file mode 100644 index 0000000..c447468 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/ub/reflections.py @@ -0,0 +1,126 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from copy import deepcopy +import datetime # @UnusedImport for the eval below +from diffcalc.util import DiffcalcException, bold +from diffcalc.hkl.vlieg.geometry import VliegPosition + + +class _Reflection: + """A reflection""" + def __init__(self, h, k, l, position, energy, tag, time): + + self.h = float(h) + self.k = float(k) + self.l = float(l) + self.pos = position + self.tag = tag + self.energy = float(energy) # energy=12.39842/lambda + self.wavelength = 12.3984 / self.energy + self.time = time # Saved as e.g. repr(datetime.now()) + + def __str__(self): + return ("energy=%-6.3f h=%-4.2f k=%-4.2f l=%-4.2f alpha=%-8.4f " + "delta=%-8.4f gamma=%-8.4f omega=%-8.4f chi=%-8.4f " + "phi=%-8.4f %-s %s" % (self.energy, self.h, self.k, self.l, + self.pos.alpha, self.pos.delta, self.pos.gamma, self.pos.omega, + self.pos.chi, self.pos.phi, self.tag, self.time)) + + +class ReflectionList: + + def __init__(self, diffractometerPluginObject, externalAngleNames, reflections=None): + self._geometry = diffractometerPluginObject + self._externalAngleNames = externalAngleNames + self._reflist = reflections if reflections else [] + + + def add_reflection(self, h, k, l, position, energy, tag, time): + """adds a reflection, position in degrees + """ + if type(position) in (list, tuple): + try: + position = self._geometry.create_position(*position) + except AttributeError: + position = VliegPosition(*position) + self._reflist += [_Reflection(h, k, l, position, energy, tag, + time.__repr__())] + + def edit_reflection(self, num, h, k, l, position, energy, tag, time): + """num starts at 1""" + if type(position) in (list, tuple): + position = VliegPosition(*position) + try: + self._reflist[num - 1] = _Reflection(h, k, l, position, energy, tag, + time.__repr__()) + except IndexError: + raise DiffcalcException("There is no reflection " + repr(num) + + " to edit.") + + def getReflection(self, num): + """ + getReflection(num) --> ( [h, k, l], position, energy, tag, time ) -- + num starts at 1 position in degrees + """ + r = deepcopy(self._reflist[num - 1]) # for convenience + return [r.h, r.k, r.l], deepcopy(r.pos), r.energy, r.tag, eval(r.time) + + def get_reflection_in_external_angles(self, num): + """getReflection(num) --> ( [h, k, l], (angle1...angleN), energy, tag ) + -- num starts at 1 position in degrees""" + r = deepcopy(self._reflist[num - 1]) # for convenience + externalAngles = self._geometry.internal_position_to_physical_angles(r.pos) + result = [r.h, r.k, r.l], externalAngles, r.energy, r.tag, eval(r.time) + return result + + def removeReflection(self, num): + del self._reflist[num - 1] + + def swap_reflections(self, num1, num2): + orig1 = self._reflist[num1 - 1] + self._reflist[num1 - 1] = self._reflist[num2 - 1] + self._reflist[num2 - 1] = orig1 + + def __len__(self): + return len(self._reflist) + + def __str__(self): + return '\n'.join(self.str_lines()) + + def str_lines(self): + axes = tuple(s.upper() for s in self._externalAngleNames) + if not self._reflist: + return [" <<< none specified >>>"] + + lines = [] + + format = (" %6s %5s %5s %5s " + "%8s " * len(axes) + " TAG") + values = ('ENERGY', 'H', 'K', 'L') + axes + lines.append(bold(format % values)) + + for n in range(len(self._reflist)): + ref_tuple = self.get_reflection_in_external_angles(n + 1) + [h, k, l], externalAngles, energy, tag, _ = ref_tuple + if tag is None: + tag = "" + format = (" %2d %6.3f % 4.2f % 4.2f % 4.2f " + + "% 8.4f " * len(axes) + " %s") + values = (n + 1, energy, h, k, l) + externalAngles + (tag,) + lines.append(format % values) + return lines diff --git a/script/test/diffcalc (copy)/diffcalc/ub/ub.py b/script/test/diffcalc (copy)/diffcalc/ub/ub.py new file mode 100644 index 0000000..afd2f47 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/ub/ub.py @@ -0,0 +1,768 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from diffcalc import settings +from diffcalc.ub.calc import UBCalculation + +from math import asin, pi +from datetime import datetime + +try: + from numpy import matrix +except ImportError: + from numjy import matrix + + +from diffcalc.util import getInputWithDefault as promptForInput, \ + promptForNumber, promptForList, allnum, isnum, bold, xyz_rotation +from diffcalc.util import command + +TORAD = pi / 180 +TODEG = 180 / pi + + +# When using ipython magic, these functions must not be imported to the top +# level namespace. Doing so will stop them from being called with magic. + +__all__ = ['addorient', 'addref', 'c2th', 'hklangle', 'calcub', 'delorient', 'delref', 'editorient', + 'editref', 'listub', 'loadub', 'newub', 'orientub', 'saveubas', 'setlat', + 'addmiscut', 'setmiscut', 'setu', 'setub', 'showorient', 'showref', 'swaporient', + 'swapref', 'trialub', 'checkub', 'ub', 'ubcalc', 'rmub', 'clearorient', + 'clearref', 'lastub', 'refineub'] + +if settings.include_sigtau: + __all__.append('sigtau') + +if settings.include_reference: + __all__.append('setnphi') + __all__.append('setnhkl') + + +ubcalc = UBCalculation(settings.hardware, + settings.geometry, + settings.ubcalc_persister, + settings.ubcalc_strategy, + settings.include_sigtau, + settings.include_reference) + + + +### UB state ### + +@command +def newub(name=None): + """newub {'name'} -- start a new ub calculation name + """ + if name is None: + # interactive + name = promptForInput('calculation name') + ubcalc.start_new(name) + setlat() + elif isinstance(name, str): + # just trying might cause confusion here + ubcalc.start_new(name) + else: + raise TypeError() + +@command +def loadub(name_or_num): + """loadub 'name' | num -- load an existing ub calculation + """ + if isinstance(name_or_num, str): + ubcalc.load(name_or_num) + else: + ubcalc.load(ubcalc.listub()[int(name_or_num)]) + +@command +def lastub(): + """lastub -- load the last used ub calculation + """ + try: + lastub_name = ubcalc.listub()[0] + print "Loading ub calculation: '%s'" % lastub_name + loadub(0) + except IndexError: + print "WARNING: There is no record of the last ub calculation used" + +@command +def rmub(name_or_num): + """rmub 'name'|num -- remove existing ub calculation + """ + if isinstance(name_or_num, str): + ubcalc.remove(name_or_num) + else: + ubcalc.remove(ubcalc.listub()[int(name_or_num)]) + +@command +def listub(): + """listub -- list the ub calculations available to load. + """ + if hasattr(ubcalc._persister, 'description'): + print "UB calculations in: " + ubcalc._persister.description + else: + print "UB calculations:" + print + ubnames = ubcalc.listub() + # TODO: whole mechanism of making two calls is messy + try: + ub_metadata = ubcalc.listub_metadata() + except AttributeError: + ub_metadata = [''] * len(ubnames) + + for n, name, data in zip(range(len(ubnames)), ubnames, ub_metadata): + print "%2i) %-15s %s" % (n, name, data) + +@command +def saveubas(name): + """saveubas 'name' -- save the ub calculation with a new name + """ + if isinstance(name, str): + # just trying might cause confusion here + ubcalc.saveas(name) + else: + raise TypeError() + +@command +def ub(): + """ub -- show the complete state of the ub calculation + """ + #wavelength = float(hardware.get_wavelength()) + #energy = float(hardware.get_energy()) + print ubcalc.__str__() + +@command +def refineub(*args): + """ + refineub {[h k l]} {pos} -- refine unit cell dimensions and U matrix to match diffractometer angles for a given hkl value + """ + if len(args) > 0: + args = list(args) + h, k, l = args.pop(0) + if not (isnum(h) and isnum(k) and isnum(l)): + raise TypeError() + else: + h = promptForNumber('h', 0.) + k = promptForNumber('k', 0.) + l = promptForNumber('l', 0.) + if None in (h, k, l): + _handleInputError("h,k and l must all be numbers") + if len(args) == 1: + pos = settings.geometry.physical_angles_to_internal_position( # @UndefinedVariable + args.pop(0)) + elif len(args) == 0: + reply = promptForInput('current pos', 'y') + if reply in ('y', 'Y', 'yes'): + positionList = settings.hardware.get_position() # @UndefinedVariable + else: + currentPos = settings.hardware.get_position() # @UndefinedVariable + positionList = [] + names = settings.hardware.get_axes_names() # @UndefinedVariable + for i, angleName in enumerate(names): + val = promptForNumber(angleName.rjust(7), currentPos[i]) + if val is None: + _handleInputError("Please enter a number, or press" + " Return to accept default!") + return + positionList.append(val) + pos = settings.geometry.physical_angles_to_internal_position(positionList) # @UndefinedVariable + else: + raise TypeError() + + pos.changeToRadians() + scale, lat = ubcalc.rescale_unit_cell(h, k, l, pos) + if scale: + lines = ["Unit cell scaling factor:".ljust(9) + + "% 9.5f" % scale] + lines.append("Refined crystal lattice:") + lines.append(" a, b, c:".ljust(9) + + "% 9.5f % 9.5f % 9.5f" % (lat[1:4])) + lines.append(" " * 12 + + "% 9.5f % 9.5f % 9.5f" % (lat[4:])) + lines.append("") + print '\n'.join(lines) + reply = promptForInput('Update crystal settings?', 'y') + if reply in ('y', 'Y', 'yes'): + ubcalc.set_lattice(*lat) + else: + print "No unit cell mismatch detected" + mc_angle, mc_axis = ubcalc.calc_miscut(h, k, l, pos) + if mc_angle: + lines = ["Miscut parameters:",] + lines.append(" angle:".ljust(9) + "% 9.5f" % mc_angle) + lines.append(" axis:".ljust(9) + "% 9.5f % 9.5f % 9.5f" % tuple(mc_axis)) + print '\n'.join(lines) + reply = promptForInput('Apply miscut parameters?', 'y') + if reply in ('y', 'Y', 'yes'): + ubcalc.set_miscut(mc_axis, -mc_angle * TORAD, True) + else: + print "No miscut detected for the given settings" + +### UB lattice ### + +@command +def setlat(name=None, *args): + """ + setlat -- interactively enter lattice parameters (Angstroms and Deg) + setlat name a -- assumes cubic + setlat name a b -- assumes tetragonal + setlat name a b c -- assumes ortho + setlat name a b c gamma -- assumes mon/hex with gam not equal to 90 + setlat name a b c alpha beta gamma -- arbitrary + """ + + if name is None: # Interactive + name = promptForInput("crystal name") + a = promptForNumber(' a', 1) + b = promptForNumber(' b', a) + c = promptForNumber(' c', a) + alpha = promptForNumber('alpha', 90) + beta = promptForNumber('beta', 90) + gamma = promptForNumber('gamma', 90) + ubcalc.set_lattice(name, a, b, c, alpha, beta, gamma) + + elif (isinstance(name, str) and + len(args) in (1, 2, 3, 4, 6) and + allnum(args)): + # first arg is string and rest are numbers + ubcalc.set_lattice(name, *args) + else: + raise TypeError() + +@command +def c2th(hkl, en=None): + """ + c2th [h k l] -- calculate two-theta angle for reflection + """ + if en is None: + wl = settings.hardware.get_wavelength() # @UndefinedVariable + else: + wl = 12.39842 / en + d = ubcalc.get_hkl_plane_distance(hkl) + if wl > (2 * d): + raise ValueError( + 'Reflection un-reachable as wavelength (%f) is more than twice\n' + 'the plane distance (%f)' % (wl, d)) + try: + return 2.0 * asin(wl / (d * 2)) * TODEG + except ValueError as e: + raise ValueError('asin(wl / (d * 2) with wl=%f and d=%f: ' %(wl, d) + e.args[0]) + + +@command +def hklangle(hkl1, hkl2): + """ + hklangle [h1 k1 l1] [h2 k2 l2] -- calculate angle between [h1 k1 l1] and [h2 k2 l2] planes + """ + return ubcalc.get_hkl_plane_angle(hkl1, hkl2) * TODEG + +### Surface and reference vector stuff ### + +@command +def sigtau(sigma=None, tau=None): + """sigtau {sigma tau} -- sets or displays sigma and tau""" + if sigma is None and tau is None: + chi = settings.hardware.get_position_by_name('chi') # @UndefinedVariable + phi = settings.hardware.get_position_by_name('phi') # @UndefinedVariable + _sigma, _tau = ubcalc.sigma, ubcalc.tau + print "sigma, tau = %f, %f" % (_sigma, _tau) + print " chi, phi = %f, %f" % (chi, phi) + sigma = promptForInput("sigma", -chi) + tau = promptForInput(" tau", -phi) + ubcalc.sigma = sigma + ubcalc.tau = tau + else: + ubcalc.sigma = float(sigma) + ubcalc.tau = float(tau) + + +@command +def setnphi(xyz=None): + """setnphi {[x y z]} -- sets or displays n_phi reference""" + if xyz is None: + ubcalc.print_reference() + else: + ubcalc.set_n_phi_configured(_to_column_vector_triple(xyz)) + ubcalc.print_reference() + +@command +def setnhkl(hkl=None): + """setnhkl {[h k l]} -- sets or displays n_hkl reference""" + if hkl is None: + ubcalc.print_reference() + else: + ubcalc.set_n_hkl_configured(_to_column_vector_triple(hkl)) + ubcalc.print_reference() + + +def _to_column_vector_triple(o): + m = matrix(o) + if m.shape == (1, 3): + return m.T + elif m.shape == (3, 1): + return m + else: + raise ValueError("Unexpected shape matrix: " + m) + +### UB refelections ### + +@command +def showref(): + """showref -- shows full reflection list""" + if ubcalc._state.reflist: + print '\n'.join(ubcalc._state.reflist.str_lines()) + else: + print "<<< No reflections stored >>>" + +@command +def addref(*args): + """ + addref -- add reflection interactively + addref [h k l] {'tag'} -- add reflection with current position and energy + addref [h k l] (p1, .., pN) energy {'tag'} -- add arbitrary reflection + """ + + if len(args) == 0: + h = promptForNumber('h', 0.) + k = promptForNumber('k', 0.) + l = promptForNumber('l', 0.) + if None in (h, k, l): + _handleInputError("h,k and l must all be numbers") + reply = promptForInput('current pos', 'y') + if reply in ('y', 'Y', 'yes'): + positionList = settings.hardware.get_position() # @UndefinedVariable + energy = settings.hardware.get_energy() # @UndefinedVariable + else: + currentPos = settings.hardware.get_position() # @UndefinedVariable + positionList = [] + names = settings.hardware.get_axes_names() # @UndefinedVariable + for i, angleName in enumerate(names): + val = promptForNumber(angleName.rjust(7), currentPos[i]) + if val is None: + _handleInputError("Please enter a number, or press" + " Return to accept default!") + return + positionList.append(val) + muliplier = settings.hardware.energyScannableMultiplierToGetKeV # @UndefinedVariable + energy = promptForNumber('energy', settings.hardware.get_energy() / muliplier) # @UndefinedVariable + if val is None: + _handleInputError("Please enter a number, or press " + "Return to accept default!") + return + energy = energy * muliplier + tag = promptForInput("tag") + if tag == '': + tag = None + pos = settings.geometry.physical_angles_to_internal_position(positionList) # @UndefinedVariable + ubcalc.add_reflection(h, k, l, pos, energy, tag, + datetime.now()) + elif len(args) in (1, 2, 3, 4): + args = list(args) + h, k, l = args.pop(0) + if not (isnum(h) and isnum(k) and isnum(l)): + raise TypeError() + if len(args) >= 2: + pos = settings.geometry.physical_angles_to_internal_position( # @UndefinedVariable + args.pop(0)) + energy = args.pop(0) + if not isnum(energy): + raise TypeError() + else: + pos = settings.geometry.physical_angles_to_internal_position( # @UndefinedVariable + settings.hardware.get_position()) # @UndefinedVariable + energy = settings.hardware.get_energy() # @UndefinedVariable + if len(args) == 1: + tag = args.pop(0) + if not isinstance(tag, str): + raise TypeError() + else: + tag = None + ubcalc.add_reflection(h, k, l, pos, energy, tag, + datetime.now()) + else: + raise TypeError() + +@command +def editref(num): + """editref num -- interactively edit a reflection. + """ + num = int(num) + + # Get old reflection values + [oldh, oldk, oldl], oldExternalAngles, oldEnergy, oldTag, oldT = \ + ubcalc.get_reflection_in_external_angles(num) + del oldT # current time will be used. + + h = promptForNumber('h', oldh) + k = promptForNumber('k', oldk) + l = promptForNumber('l', oldl) + if None in (h, k, l): + _handleInputError("h,k and l must all be numbers") + reply = promptForInput('update position with current hardware setting', + 'n') + if reply in ('y', 'Y', 'yes'): + positionList = settings.hardware.get_position() # @UndefinedVariable + energy = settings.hardware.get_energy() # @UndefinedVariable + else: + positionList = [] + names = settings.hardware.get_axes_names() # @UndefinedVariable + for i, angleName in enumerate(names): + val = promptForNumber(angleName.rjust(7), oldExternalAngles[i]) + if val is None: + _handleInputError("Please enter a number, or press " + "Return to accept default!") + return + positionList.append(val) + muliplier = settings.hardware.energyScannableMultiplierToGetKeV # @UndefinedVariable + energy = promptForNumber('energy', oldEnergy / muliplier) + if val is None: + _handleInputError("Please enter a number, or press Return " + "to accept default!") + return + energy = energy * muliplier + tag = promptForInput("tag", oldTag) + if tag == '': + tag = None + pos = settings.geometry.physical_angles_to_internal_position(positionList) # @UndefinedVariable + ubcalc.edit_reflection(num, h, k, l, pos, energy, tag, + datetime.now()) + +@command +def delref(num): + """delref num -- deletes a reflection (numbered from 1) + """ + ubcalc.del_reflection(int(num)) + +@command +def clearref(): + """clearref -- deletes all the reflections + """ + while ubcalc.get_number_reflections(): + ubcalc.del_reflection(1) + +@command +def swapref(num1=None, num2=None): + """ + swapref -- swaps first two reflections used for calulating U matrix + swapref num1 num2 -- swaps two reflections (numbered from 1) + """ + if num1 is None and num2 is None: + ubcalc.swap_reflections(1, 2) + elif isinstance(num1, int) and isinstance(num2, int): + ubcalc.swap_reflections(num1, num2) + else: + raise TypeError() + +### U calculation from crystal orientation +@command +def showorient(): + """showorient -- shows full list of crystal orientations""" + if ubcalc._state.orientlist: + print '\n'.join(ubcalc._state.orientlist.str_lines()) + else: + print "<<< No crystal orientations stored >>>" + +@command +def addorient(*args): + """ + addorient -- add crystal orientation interactively + addorient [h k l] [x y z] {'tag'} -- add crystal orientation in laboratory frame + """ + + if len(args) == 0: + h = promptForNumber('h', 0.) + k = promptForNumber('k', 0.) + l = promptForNumber('l', 0.) + if None in (h, k, l): + _handleInputError("h,k and l must all be numbers") + + x = promptForNumber('x', 0.) + y = promptForNumber('y', 0.) + z = promptForNumber('z', 0.) + if None in (x, y, z): + _handleInputError("x,y and z must all be numbers") + + tag = promptForInput("tag") + if tag == '': + tag = None + ubcalc.add_orientation(h, k, l, x , y, z, tag, + datetime.now()) + elif len(args) in (1, 2, 3): + args = list(args) + h, k, l = args.pop(0) + if not (isnum(h) and isnum(k) and isnum(l)): + raise TypeError() + x, y, z = args.pop(0) + if not (isnum(x) and isnum(y) and isnum(z)): + raise TypeError() + if len(args) == 1: + tag = args.pop(0) + if not isinstance(tag, str): + raise TypeError() + else: + tag = None + ubcalc.add_orientation(h, k, l, x, y ,z, tag, + datetime.now()) + else: + raise TypeError() + +@command +def editorient(num): + """editorient num -- interactively edit a crystal orientation. + """ + num = int(num) + + # Get old reflection values + [oldh, oldk, oldl], [oldx, oldy, oldz], oldTag, oldT = \ + ubcalc.get_orientation(num) + del oldT # current time will be used. + + h = promptForNumber('h', oldh) + k = promptForNumber('k', oldk) + l = promptForNumber('l', oldl) + if None in (h, k, l): + _handleInputError("h,k and l must all be numbers") + x = promptForNumber('x', oldx) + y = promptForNumber('y', oldy) + z = promptForNumber('z', oldz) + if None in (x, y, z): + _handleInputError("x,y and z must all be numbers") + tag = promptForInput("tag", oldTag) + if tag == '': + tag = None + ubcalc.edit_orientation(num, h, k, l, x, y, z, tag, + datetime.now()) + +@command +def delorient(num): + """delorient num -- deletes a crystal orientation (numbered from 1) + """ + ubcalc.del_orientation(int(num)) + +@command +def clearorient(): + """clearorient -- deletes all the crystal orientations + """ + while ubcalc.get_number_orientations(): + ubcalc.del_orientation(1) + +@command +def swaporient(num1=None, num2=None): + """ + swaporient -- swaps first two crystal orientations used for calulating U matrix + swaporient num1 num2 -- swaps two crystal orientations (numbered from 1) + """ + if num1 is None and num2 is None: + ubcalc.swap_orientations(1, 2) + elif isinstance(num1, int) and isinstance(num2, int): + ubcalc.swap_orientations(num1, num2) + else: + raise TypeError() + + +### UB calculations ### + +@command +def setu(U=None): + """setu {[[..][..][..]]} -- manually set U matrix + """ + if U is None: + U = _promptFor3x3MatrixDefaultingToIdentity() + if U is None: + return # an error will have been printed or thrown + if _is3x3TupleOrList(U) or _is3x3Matrix(U): + ubcalc.set_U_manually(U) + else: + raise TypeError("U must be given as 3x3 list or tuple") + +@command +def setub(UB=None): + """setub {[[..][..][..]]} -- manually set UB matrix""" + if UB is None: + UB = _promptFor3x3MatrixDefaultingToIdentity() + if UB is None: + return # an error will have been printed or thrown + if _is3x3TupleOrList(UB): + ubcalc.set_UB_manually(UB) + else: + raise TypeError("UB must be given as 3x3 list or tuple") + +def _promptFor3x3MatrixDefaultingToIdentity(): + estring = "Please enter a number, or press Return to accept default!" + row1 = promptForList("row1", (1, 0, 0)) + if row1 is None: + _handleInputError(estring) + return None + row2 = promptForList("row2", (0, 1, 0)) + if row2 is None: + _handleInputError(estring) + return None + row3 = promptForList("row3", (0, 0, 1)) + if row3 is None: + _handleInputError(estring) + return None + return [row1, row2, row3] + +@command +def calcub(): + """calcub -- (re)calculate U matrix from ref1 and ref2. + """ + ubcalc.calculate_UB() + +@command +def trialub(): + """trialub -- (re)calculate U matrix from ref1 only (check carefully). + """ + ubcalc.calculate_UB_from_primary_only() + +@command +def orientub(): + """orientub -- (re)calculate U matrix from orient1 and orient2. + """ + ubcalc.calculate_UB_from_orientation() + + + # This command requires the ubcalc + +def checkub(): + """checkub -- show calculated and entered hkl values for reflections. + """ + + s = "\n %7s %4s %4s %4s %6s %6s %6s TAG\n" % \ + ('ENERGY', 'H', 'K', 'L', 'H_COMP', 'K_COMP', 'L_COMP') + s = bold(s) + nref = ubcalc.get_number_reflections() + if not nref: + s += "<>" + for n in range(nref): + hklguess, pos, energy, tag, _ = ubcalc.get_reflection(n + 1) + wavelength = 12.39842 / energy + hkl = settings.angles_to_hkl_function(pos.inRadians(), wavelength, ubcalc.UB) + h, k, l = hkl + if tag is None: + tag = "" + s += ("% 2d % 6.4f % 4.2f % 4.2f % 4.2f % 6.4f % 6.4f " + "% 6.4f %6s\n" % (n + 1, energy, hklguess[0], + hklguess[1], hklguess[2], h, k, l, tag)) + print s + + +@command +def addmiscut(*args): + """addmiscut angle {[x y z]} -- apply miscut to U matrix using a specified miscut angle in degrees and a rotation axis""" + + if len(args) == 0: + _handleInputError("Please specify a miscut angle in degrees " + "and, optionally, a rotation axis (default: [0 1 0])") + else: + args=list(args) + angle = args.pop(0) + rad_angle = float(angle) * TORAD + if len(args) == 0: + xyz = None + else: + xyz = args.pop(0) + ubcalc.set_miscut(xyz, rad_angle, True) + +@command +def setmiscut(*args): + """setmiscut angle {[x y z]} -- manually set U matrix using a specified miscut angle in degrees and a rotation axis (default: [0 1 0])""" + + if len(args) == 0: + _handleInputError("Please specify a miscut angle in degrees " + "and, optionally, a rotation axis (default: [0 1 0])") + else: + args=list(args) + angle = args.pop(0) + rad_angle = float(angle) * TORAD + if len(args) == 0: + xyz = None + else: + xyz = args.pop(0) + ubcalc.set_miscut(xyz, rad_angle, False) + +commands_for_help = ['State', + newub, + loadub, + lastub, + listub, + rmub, + saveubas, + ub, + 'Lattice', + setlat, + c2th, + hklangle] + +if ubcalc.include_reference: + commands_for_help.extend([ + 'Reference (surface)', + setnphi, + setnhkl]) + +if ubcalc.include_sigtau: + commands_for_help.extend([ + 'Surface', + sigtau]) + +commands_for_help.extend([ + 'Reflections', + showref, + addref, + editref, + delref, + clearref, + swapref, + 'Orientations', + showorient, + addorient, + editorient, + delorient, + clearorient, + swaporient, + 'UB matrix', + checkub, + setu, + setub, + calcub, + orientub, + trialub, + refineub, + addmiscut, + setmiscut]) + + + +def _is3x3TupleOrList(m): + if type(m) not in (list, tuple): + return False + if len(m) != 3: + return False + for mrow in m: + if type(mrow) not in (list, tuple): + return False + if len(mrow) != 3: + return False + return True + + +def _is3x3Matrix(m): + return isinstance(m, matrix) and tuple(m.shape) == (3, 3) + + +def _handleInputError(msg): + raise TypeError(msg) \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcalc/util.py b/script/test/diffcalc (copy)/diffcalc/util.py new file mode 100644 index 0000000..873040c --- /dev/null +++ b/script/test/diffcalc (copy)/diffcalc/util.py @@ -0,0 +1,347 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +from math import pi, acos, cos, sin +from functools import wraps +import textwrap + +try: + from gda.jython.commands.InputCommands import requestInput as raw_input + GDA = True +except ImportError: + GDA = False + pass # raw_input unavailable in gda +try: + from numpy import matrix + from numpy.linalg import norm +except ImportError: + from numjy import matrix + from numjy.linalg import norm + + +# from http://physics.nist.gov/ +h_in_eV_per_s = 4.135667516E-15 +c = 299792458 +TWELVEISH = c * h_in_eV_per_s # 12.39842 + + +SMALL = 1e-10 +TORAD = pi / 180 +TODEG = 180 / pi + + +COLOURISE_TERMINAL_OUTPUT = not GDA + +def bold(s): + if not COLOURISE_TERMINAL_OUTPUT: + return s + else: + BOLD = '\033[1m' + END = '\033[0m' + return BOLD + s + END + + +def x_rotation(th): + return matrix(((1, 0, 0), (0, cos(th), -sin(th)), (0, sin(th), cos(th)))) + + +def y_rotation(th): + return matrix(((cos(th), 0, sin(th)), (0, 1, 0), (-sin(th), 0, cos(th)))) + + +def z_rotation(th): + return matrix(((cos(th), -sin(th), 0), (sin(th), cos(th), 0), (0, 0, 1))) + + +def xyz_rotation(u, angle): + u = [list(u), [0, 0, 0], [0, 0, 0]] + u = matrix(u) / norm(matrix(u)) + e11=u[0,0]**2+(1-u[0,0]**2)*cos(angle) + e12=u[0,0]*u[0,1]*(1-cos(angle))-u[0,2]*sin(angle) + e13=u[0,0]*u[0,2]*(1-cos(angle))+u[0,1]*sin(angle) + e21=u[0,0]*u[0,1]*(1-cos(angle))+u[0,2]*sin(angle) + e22=u[0,1]**2+(1-u[0,1]**2)*cos(angle) + e23=u[0,1]*u[0,2]*(1-cos(angle))-u[0,0]*sin(angle) + e31=u[0,0]*u[0,2]*(1-cos(angle))-u[0,1]*sin(angle) + e32=u[0,1]*u[0,2]*(1-cos(angle))+u[0,0]*sin(angle) + e33=u[0,2]**2+(1-u[0,2]**2)*cos(angle) + return matrix([[e11,e12,e13],[e21,e22,e23],[e31,e32,e33]]) + + +class DiffcalcException(Exception): + """Error caused by user misuse of diffraction calculator. + """ + def __str__(self): + lines = [] + for msg_line in self.message.split('\n'): + lines.append('* ' + msg_line) + width = max(len(l) for l in lines) + lines.insert(0, '\n\n' + '*' * width) + lines.append('*' * width) + return '\n'.join(lines) + + +class AbstractPosition(object): + + def inRadians(self): + pos = self.clone() + pos.changeToRadians() + return pos + + def inDegrees(self): + pos = self.clone() + pos.changeToDegrees() + return pos + + def changeToRadians(self): + raise NotImplementedError() + + def changeToDegrees(self): + raise NotImplementedError() + + def totuple(self): + raise NotImplementedError() + + +### Matrices + +def cross3(x, y): + """z = cross3(x ,y) -- where x, y & z are 3*1 Jama matrices""" + [[x1], [x2], [x3]] = x.tolist() + [[y1], [y2], [y3]] = y.tolist() + return matrix([[x2 * y3 - x3 * y2], + [x3 * y1 - x1 * y3], + [x1 * y2 - x2 * y1]]) + + +def dot3(x, y): + """z = dot3(x ,y) -- where x, y are 3*1 Jama matrices""" + return x[0, 0] * y[0, 0] + x[1, 0] * y[1, 0] + x[2, 0] * y[2, 0] + + +def angle_between_vectors(a, b): + costheta = dot3(a * (1 / norm(a)), b * (1 / norm(b))) + return acos(bound(costheta)) + + +## Math + +def bound(x): + """ + moves x between -1 and 1. Used to correct for rounding errors which may + have moved the sin or cosine of a value outside this range. + """ + if abs(x) > (1 + SMALL): + raise AssertionError( + "The value (%f) was unexpectedly too far outside -1 or 1 to " + "safely bound. Please report this." % x) + if x > 1: + return 1 + if x < -1: + return -1 + return x + + +def matrixToString(m): + ''' str = matrixToString(m) --- displays a Jama matrix m as a string + ''' + toReturn = '' + for row in m.array: + for el in row: + toReturn += str(el) + '\t' + toReturn += '\n' + return toReturn + + +def nearlyEqual(first, second, tolerance): + if type(first) in (int, float): + return abs(first - second) <= tolerance + + if type(first) != type(matrix([[1]])): + # lists + first = matrix([list(first)]) + second = matrix([list(second)]) + diff = first - (second) + return norm(diff) <= tolerance + + +def radiansEquivilant(first, second, tolerance): + if abs(first - second) <= tolerance: + return True + if abs((first - 2 * pi) - second) <= tolerance: + return True + if abs((first + 2 * pi) - second) <= tolerance: + return True + if abs(first - (second - 2 * pi)) <= tolerance: + return True + if abs(first - (second + 2 * pi)) <= tolerance: + return True + + return False + + +def degreesEquivilant(first, second, tolerance): + return radiansEquivilant(first * TORAD, second * TORAD, tolerance) + + +def differ(first, second, tolerance): + """Returns error message if the norm of the difference between two arrays + or numbers is greater than the given tolerance. Else returns False. + """ + # TODO: Fix spaghetti + nonArray = False + if type(first) in (int, float): + if type(second) not in (int, float): + raise TypeError( + "If first is an int or float, so must second. " + "first=%s, second=%s" & (repr(first), repr(second))) + first = [first] + second = [second] + nonArray = True + if not isinstance(first, matrix): + first = matrix([list(first)]) + if not isinstance(second, matrix): + second = matrix([list(second)]) + diff = first - second + if norm(diff) >= tolerance: + if nonArray: + return ('%s!=%s' % + (repr(first.tolist()[0][0]), repr(second.tolist()[0][0]))) + return ('%s!=%s' % + (repr(tuple(first.tolist()[0])), + repr(tuple(second.tolist()[0])))) + return False + + +### user input + +def getInputWithDefault(prompt, default=""): + """ + Prompts user for input and returns if possible a float or a list of floats, + or if failing this a string. default may be a number, array of numbers, + or string. + """ + if default is not "": + # Generate default string + if type(default) in (list, tuple): + defaultString = "" + for val in default: + defaultString += str(val) + ' ' + defaultString = defaultString.strip() + else: + defaultString = str(default) + prompt = str(prompt) + '[' + defaultString + ']: ' + else: + prompt = str(prompt) + ': ' + + rawresult = raw_input(prompt) + + # Return default if no input provided + if rawresult == "": + return default + + # Try to process result into list of numbers + try: + result = [] + for val in rawresult.split(): + result.append(float(val)) + except ValueError: + # return a string + return rawresult + if len(result) == 1: + result = result[0] + return result + + +class MockRawInput(object): + def __init__(self, toReturnList): + if type(toReturnList) != list: + toReturnList = [toReturnList] + self.toReturnList = toReturnList + + def __call__(self, prompt): + toReturn = self.toReturnList.pop(0) + if type(toReturn) != str: + raise TypeError + print prompt + toReturn + return toReturn + + +def getMessageFromException(e): + try: # Jython + return e.args[0] + except: + try: # Python + return e.message + except: + # Java + return e.args[0] + + +def promptForNumber(prompt, default=""): + val = getInputWithDefault(prompt, default) + if type(val) not in (float, int): + return None + return val + + +def promptForList(prompt, default=""): + val = getInputWithDefault(prompt, default) + if type(val) not in (list, tuple): + return None + return val + + +def isnum(o): + return isinstance(o, (int, float)) + + +def allnum(l): + return not [o for o in l if not isnum(o)] + + +DEBUG = False + + + +def command(f): + """A decorator to wrap a command method or function. + + Calls to the decorated method or function are wrapped by call_command. + """ + # TODO: remove one level of stack trace by not using wraps + @wraps(f) + def wrapper(*args, **kwds): + return call_command(f, args) + + return wrapper + + +def call_command(f, args): + + if DEBUG: + return f(*args) + try: + return f(*args) + except TypeError, e: + # NOTE: TypeErrors resulting from bugs in the core code will be + # erroneously caught here! TODO: check depth of TypeError stack + raise TypeError(e.message + '\n\nUSAGE:\n' + f.__doc__) + except DiffcalcException, e: + # TODO: log and create a new one to shorten stack trace for user + raise DiffcalcException(e.message) diff --git a/script/test/diffcalc (copy)/diffcmd/__init__.py b/script/test/diffcalc (copy)/diffcmd/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script/test/diffcalc (copy)/diffcmd/diffcalc_launcher.py b/script/test/diffcalc (copy)/diffcmd/diffcalc_launcher.py new file mode 100755 index 0000000..0653460 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcmd/diffcalc_launcher.py @@ -0,0 +1,86 @@ +#!/usr/bin/python + +import argparse +import subprocess +import os +import getpass + +DIFFCALC_BIN = os.path.split(os.path.realpath(__file__))[0] +DIFFCALC_ROOT = os.path.abspath(os.path.join(DIFFCALC_BIN, os.pardir)) + +MODULE_FOR_MANUALS = '_make_sixcircle_manual' + +def main(): + parser = argparse.ArgumentParser(description='Diffcalc: A diffraction condition calculator of x-ray and neutron crystalography') + parser.add_argument('--modules', dest='show_modules', action='store_true', + help='list available modules') + parser.add_argument('--python', dest='use_python', action='store_true', + help='run within python rather than ipython') + parser.add_argument('--debug', dest='debug', action='store_true', + help='run in debug mode') + parser.add_argument('--make-manuals-source', dest='make_manuals', action='store_true', + help='make .rst manual files by running template through sixcircle') + parser.add_argument('--non-interactive', dest='non_interactive', action='store_true', + help='do not enter interactive mode after startup') + parser.add_argument('module', type=str, nargs='?', + help='the module to startup with') + args = parser.parse_args() + + # Create list of available modules + module_names = [] + for module_path in os.listdir(os.path.join(DIFFCALC_ROOT, 'startup')): + if not module_path.startswith('_') and module_path.endswith('.py'): + module_names.append(module_path.split('.')[0]) + module_names.sort() + + if args.show_modules: + print_available_modules(module_names) + exit(0) + + if not args.make_manuals and not args.module: + print "A module name should be provided. Choose one of:" + print_available_modules(module_names) + exit(0) + + if args.make_manuals: + if args.module: + print "When building the manuals no module should be given" + exit(1) + args.module = MODULE_FOR_MANUALS + + if not args.make_manuals and args.module not in module_names: + print "The provided argument '%s' is not one of:" % args.module + print_available_modules(module_names) + exit(1) + + env = os.environ.copy() + + if 'PYTHONPATH' not in env: + env['PYTHONPATH'] = '' + env['PYTHONPATH'] = DIFFCALC_ROOT + ':' + env['PYTHONPATH'] + + diffcmd_start_path = os.path.join(DIFFCALC_ROOT, 'diffcmd', 'start.py') + + if args.use_python: + cmd = 'python' + else: # ipython + cmd = 'ipython --no-banner --HistoryManager.hist_file=/tmp/ipython_hist_%s.sqlite' % getpass.getuser() + + iflag = '' if args.non_interactive else '-i' + cmd = cmd + ' ' + ' '.join([iflag, diffcmd_start_path, args.module, str(args.debug)]) + + print 'Running: ' + cmd + rc = subprocess.call(cmd, env=env, shell=True) + exit(rc) + + +def print_available_modules(module_names): + lines = [] + for m in sorted(module_names): + lines.append(' ' + m) + print '\n'.join(lines) + + +if __name__ == '__main__': + main() +# diff --git a/script/test/diffcalc (copy)/diffcmd/diffcmd_utils.py b/script/test/diffcalc (copy)/diffcmd/diffcmd_utils.py new file mode 100644 index 0000000..57ed4aa --- /dev/null +++ b/script/test/diffcalc (copy)/diffcmd/diffcmd_utils.py @@ -0,0 +1,43 @@ +# +# General utility functions to handle Diffcalc commands +# + +from gda.jython.commands.GeneralCommands import alias + +try: + import gda + GDA = True +except ImportError: + GDA = False + + + +def alias_commands(global_namespace_dict): + """Alias commands left in global_namespace_dict by previous import from + diffcalc. + + This is the equivalent of diffcmd/ipython/magic_commands() for use + when IPython is not available + """ + gnd = global_namespace_dict + global GLOBAL_NAMESPACE_DICT + GLOBAL_NAMESPACE_DICT = gnd + print "Aliasing commands" + + ### Alias commands in namespace ### + commands = gnd['hkl_commands_for_help'] + commands += gnd['ub_commands_for_help'] + if not GDA: # TODO: encapsulation issue: this should be done outside this function! + commands.append(gnd['pos']) + commands.append(gnd['scan']) + aliased_names = [] + + for f in commands: + # Skip section headers like 'Motion' + if not hasattr(f, '__call__'): + continue + + alias(f.__name__) + aliased_names.append(f.__name__) + + print "Aliased commands: " + ' '.join(aliased_names) diff --git a/script/test/diffcalc (copy)/diffcmd/ipython.py b/script/test/diffcalc (copy)/diffcmd/ipython.py new file mode 100644 index 0000000..81b4c33 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcmd/ipython.py @@ -0,0 +1,302 @@ +import re +from functools import wraps +from IPython.core.magic import register_line_magic +from IPython import get_ipython # @UnusedImport (used by register_line_magic) +from diffcalc.gdasupport.scannable.hkl import Hkl + +""" +For wrapping functions: + + In [1]: import diffcmd.ipython + + In [2]: diffcmd.ipython.GLOBAL_NAMESPACE_DICT = globals() + + In [3]: from IPython.core.magic import register_line_magic + + In [4]: from diffcmd.ipython import parse_line + + In [5]: @register_line_magic + ...: @parse_line + ...: def check_parser(*args): + ...: return args + ...: + + In [6]: check_parser + Out[6]: + + In [7]: del check_parser + + In [8]: check_parser + Out[8]: () + + In [9]: check_parser 1 + Out[9]: (1,) + + In [10]: check_parser 1 2 + Out[10]: (1, 2) + + In [11]: check_parser 1 2 [3] + Out[11]: (1, 2, [3]) + + In [12]: b='bbb' + + In [13]: check_parser 1 2 [3] b + Out[13]: (1, 2, [3], 'bbb') + + +And to create something dynamically from a function: + + In [28]: def f(a, b, c): + ....: ....: return a, b, c + ....: + + In [29]: register_line_magic(parse_line(f)) + Out[29]: + + In [30]: del f + + In [31]: f 'a' -2 [1 3 -4] + Out[31]: ('a', -2, [1, 3, -4]) + +And from a list of functions: + + In [32]: def one(a): + ....: return a + ....: + + In [33]: def two(a, b): + ....: return a, b + ....: + + In [34]: functions = one, two + + In [35]: del one, two + + In [36]: for f in functions: + ....: register_line_magic(parse_line(f)) + ....: + + In [37]: one 1 + Out[37]: 1 + + In [39]: two 1 2 + Out[39]: (1, 2) + +And to check if we are running in iPython: + + In [47]: 'get_ipython' in globals() + Out[47]: True + +def in_ipython(): + try: + get_ipython() + return True + except NameError: + return False + + +""" + + +GLOBAL_NAMESPACE_DICT = {} + +MATH_OPERATORS = set(['-', '+', '/', '*']) + +# Keep a copy of python's original help as we may remove it later +if 'help' in __builtins__: + ORIGINAL_PYTHON_HELP = __builtins__['help'] + + +COMMA_USAGE_HELP = \ +''' +| When calling a function without brackets, whitespace must be used in +| place of commas. For example: +| +| >>> function a b [1 2 3] 'c' +| +| is equivalent to: +| +| >>> function(a, b, [1, 2, 3], 'c') +| +''' + + +MATH_OPERATOR_USAGE_HELP = \ +''' +| When calling a function without brackets, whitespace is used in place of +| commas. Therefore terms which require evaluation must contain no space. +| These will fail for example: +| +| >>> function - 1 +| >>> function a() * 2 + +| But this + +| >>> function -1 1-1 +1 a()+1 [-1 0+1 b()] c+1 +| +| is okay and equivalent to: +| +| >>> function(-1, 0, 1, a() + 1, [-1, 1, b()], c + 1) +| + +''' + +comma_finder = re.compile(r'''((?:[^,"']|"[^"]*"|'[^']*')+)''') +space_finder = re.compile(r'''((?:[^ "']|"[^"]*"|'[^']*')+)''') +hash_finder = re.compile(r'''((?:[^#"']|"[^"]*"|'[^']*')+)''') +open_square_finder = re.compile(r'''((?:[^["']|"[^"]*"|'[^']*')+)''') +close_square_finder = re.compile(r'''((?:[^]"']|"[^"]*"|'[^']*')+)''') + +def tokenify(s): + + # Don't accept commas outside strings. + # Users are frustrated by not knowing when commas _are_ required. + # Making it clear when they are not helps them understand the + # difference. + + if ',' in comma_finder.split(s): + print COMMA_USAGE_HELP + print "(string was: %s)" % s + raise SyntaxError('unexpected comma') + + # ignore comment + hash_split = hash_finder.split(s) + if '#' in hash_split: + s = '' if hash_split[0] == '#' else hash_split[1] + + # surround square brackets with spaces to simplify token extraction + s = ''.join(' [ ' if e == '[' else e for e in open_square_finder.split(s)) + s = ''.join(' ] ' if e == ']' else e for e in close_square_finder.split(s)) + + + # tokens are now separated by spaces + + tokens = space_finder.split(s)[1::2] + tokens = [tok for tok in tokens if tok != ''] + return tokens + + +def parse(s, d): + s = str(s) + tokens = tokenify(s) + for tok in tokens: + if tok in MATH_OPERATORS: + print MATH_OPERATOR_USAGE_HELP + raise SyntaxError('could not evaluate: "%s"' % tok) + + + s = ', '.join(tokens) + + s = s.replace('[, ', '[') + s = s.replace(',]', ']') + s = s.replace(', ]', ']') + + try: + args = eval('[' + s + ']', d) + except SyntaxError: + raise SyntaxError('could not evaluate: "%s"' % s) + return args + +def parse_line(f, global_namespace_dict=None): + '''A decorator that parses a single string argument into a list of arguments + and calls the wrapped function with these. + ''' + if not global_namespace_dict: + global_namespace_dict = GLOBAL_NAMESPACE_DICT + @wraps(f) + def wrapper(line): + args = parse(line, global_namespace_dict) + return f(*args) + return wrapper + + + +_DEFAULT_HELP = \ +""" +For help with diffcalc's orientation phase try: + + >>> help ub + +For help with moving in reciprocal lattice space try: + + >>> help hkl + +For more detailed help try for example: + + >>> help newub + +For help with driving axes or scanning: + + >>> help pos + >>> help scan + +For help with regular python try for example: + + >>> help list + +For more detailed help with diffcalc go to: + + https://diffcalc.readthedocs.io + +""" + +def magic_commands(global_namespace_dict): + """Magic commands left in global_namespace_dict by previous import from + diffcalc. + + Also creates a help command. NOTE that calling this will + remove the original commands from the global namespace as otherwise these + would shadow the ipython magiced versions. + + Depends on hkl_commands_for_help & ub_commands_for_help list having been + left in the global namespace and assumes there is pos and scan command. + """ + gnd = global_namespace_dict + global GLOBAL_NAMESPACE_DICT + GLOBAL_NAMESPACE_DICT = gnd + + ### Magic commands in namespace ### + commands = list(gnd['hkl_commands_for_help']) + commands += gnd['ub_commands_for_help'] + commands.append(gnd['pos']) + commands.append(gnd['scan']) + command_map = {} + for f in commands: + # Skip section headers like 'Motion' + if not hasattr(f, '__call__'): + continue + # magic the function and remove from namespace (otherwise it would + # shadow the magiced command) + register_line_magic(parse_line(f, gnd)) + del gnd[f.__name__] + command_map[f.__name__] = f + + ### Create help function ### + #Expects python's original help to be named pythons_help and to be + #available in the top-level global namespace (where non-diffcalc + #objects may have help called from). + def help(s): # @ReservedAssignment + """Diffcalc help for iPython + """ + if s == '': + print _DEFAULT_HELP + elif s == 'hkl': + # Use help injected into hkl object + print Hkl.dynamic_docstring + elif s == 'ub': + # Use help injected into ub command + print command_map['ub'].__doc__ + elif s in command_map: + print "%s (diffcalc command):" %s + print command_map[s].__doc__ + else: + exec('pythons_help(%s)' %s, gnd) + + + ### Setup help command ### + gnd['pythons_help'] = ORIGINAL_PYTHON_HELP + register_line_magic(help) + # Remove builtin help + # (otherwise it would shadow magiced command + if 'help' in __builtins__: + del __builtins__['help'] diff --git a/script/test/diffcalc (copy)/diffcmd/ipythonmagic.py b/script/test/diffcalc (copy)/diffcmd/ipythonmagic.py new file mode 100644 index 0000000..6679864 --- /dev/null +++ b/script/test/diffcalc (copy)/diffcmd/ipythonmagic.py @@ -0,0 +1,79 @@ + +import diffcmd.ipython +from IPython.core.magic import register_line_magic +from diffcmd.ipython import parse_line + +command_map = {} + +_DEFAULT_HELP = """ +For help with diffcalc's orientation phase try: + + >>> help ub + +For help with moving in reciprocal lattice space try: + + >>> help hkl + +For more detailed help try for example: + + >>> help newub + +For help with driving axes or scanning: + + >>> help pos + >>> help scan + +For help with regular python try for example: + + >>> help list + +For more detailed help with diffcalc go to: + + https://diffcalc.readthedocs.io + +""" + +# This function should be called with parameter globals() +def define_commands(dictionary): + print "Ipython detected - magicing commands" + magiced_names = [] + commands = hkl_commands_for_help + ub_commands_for_help # @UndefinedVariable + commands += [pos, scan] # @UndefinedVariable + ipython.GLOBAL_NAMESPACE_DICT = dictionary + for f in commands: + # Skip section headers like 'Motion' + if not hasattr(f, '__call__'): + continue + + # magic the function and remove from namespace (otherwise it would + # shadow the magiced command) + register_line_magic(parse_line(f)) + del dictionary[f.__name__] + command_map[f.__name__] = f + magiced_names.append(f.__name__) + + print "Magiced commands: " + ' '.join(magiced_names) + + # because the functions have gone from namespace we need to override + pythons_help = __builtins__.help + del __builtins__.help + + register_line_magic(help) + del help + +def help(s): + """Diffcalc help for iPython + """ + if s == '': + print _DEFAULT_HELP + elif s == 'hkl': + # Use help injected into hkl object + print hkl.__doc__ + elif s == 'ub': + # Use help injected into ub command + print command_map['ub'].__doc__ + elif s in command_map: + print "%s (diffcalc command):" %s + print command_map[s].__doc__ + else: + exec('pythons_help(%s)' %s) diff --git a/script/test/diffcalc (copy)/diffcmd/make_manual.py b/script/test/diffcalc (copy)/diffcmd/make_manual.py new file mode 100644 index 0000000..a3b77bf --- /dev/null +++ b/script/test/diffcalc (copy)/diffcmd/make_manual.py @@ -0,0 +1,146 @@ + + +from StringIO import StringIO +from IPython import get_ipython +import sys +from diffcalc.dc.help import format_commands_for_rst_table + + +TEST_INPUT=""" +Diffcalc's Scannables +===================== + +Please see :ref:`moving-in-hkl-space` and :ref:`scanning-in-hkl-space` for some relevant examples. + +To list and show the current positions of your beamline's scannables +use ``pos`` with no arguments:: + + >>> pos wl + +should do nought, but this should be replaced:: + + ==> pos wl 2 + +should do the thing + + ==> abcd +""" + + + +def echorun(magic_cmd): + print "\n>>> " + str(magic_cmd) + + + +def make_manual(input_file_path, + output_file_path, + ub_commands_for_help, + hkl_commands_for_help): + + # Read input file (should be .rst file) + with open(input_file_path, 'r') as f: + input_string = f.read() + + # Parse input string + output_lines = [] + for lineno, line in enumerate(input_string.split('\n')): + process = '==>' in line + + if process and 'STOP' in line: + print "'==> STOP' found on line. STOPPING", lineno + 1 + return + + elif process and 'UB_HELP_TABLE' in line: + print 'Creating UB help table' + output_lines_from_line = format_commands_for_rst_table( + '', ub_commands_for_help) + + elif process and 'HKL_HELP_TABLE' in line: + print 'Creating HKL help table' + output_lines_from_line = format_commands_for_rst_table( + '', hkl_commands_for_help) + + else: + output_lines_from_line = parse_line( + line, lineno + 1, input_file_path) + +# print '\n'.join(output_lines_from_line) + output_lines.extend(output_lines_from_line) + + # Write output file + if output_file_path: + with open(output_file_path, 'w') as f: + f.write('\n'.join(output_lines)) + print "Wrote file:", output_file_path +# try: +# if output_file_path: +# orig_stdout = sys.stdout +# f = file(output_file_path, 'w') +# sys.stdout = f +# +# +# +# finally: +# if output_file_path: +# sys.stdout = orig_stdout +# f.close() + + +def parse_line(linein, lineno, filepath): + output_lines = [] + if '==>' in linein: + pre, cmd = linein.split('==>') + _check_spaces_only(pre, lineno, filepath) + cmd = cmd.strip() # strip whitespace + output_lines.append(pre + ">>> " + cmd) + result_lines = _capture_magic_command_output(cmd, lineno, filepath) + + + # append to output + for line in result_lines: + output_lines.append(pre + line) + else: + output_lines.append(linein) + return output_lines + + +def _check_spaces_only(s, lineno, filepath): + for c in s: + if c != ' ': + raise Exception('Error on line %i of %s :\n text proceeding --> must be ' + 'spaces only' % (lineno, filepath)) + +def _capture_magic_command_output(magic_cmd, lineno, filepath): + orig_stdout = sys.stdout + result = StringIO() + sys.stdout = result + + def log_error(): + msg = "Error on line %i of %s evaluating '%s'" % (lineno, filepath, magic_cmd) + sys.stderr.write('\n' + '=' * 79 + '\n' + msg + '\n' +'v' * 79 + '\n') + return msg + + try: + line_magics = get_ipython().magics_manager.magics['line'] + magic = magic_cmd.split(' ')[0] + if magic not in line_magics: + msg = log_error() + raise Exception(msg + " ('%s' is not a magic command)" % magic) + get_ipython().magic(magic_cmd) + except: + log_error() + raise + finally: + sys.stdout = orig_stdout + + result_lines = result.getvalue().split('\n') + + # trim trailing lines which are whitespace only + while result_lines and (result_lines[-1].isspace() or not result_lines[-1]): + result_lines.pop() + + return result_lines + + + \ No newline at end of file diff --git a/script/test/diffcalc (copy)/diffcmd/start.py b/script/test/diffcalc (copy)/diffcmd/start.py new file mode 100644 index 0000000..faa7b6d --- /dev/null +++ b/script/test/diffcalc (copy)/diffcmd/start.py @@ -0,0 +1,87 @@ +""" +start the diffcmd environemt using a script from startup. +This should normally be run by the main diffcalc.py program. + +with diffcalc on PYTHONPATH +$ ipython -i -m diffcm.diffcmd module_name_string debug_bool +""" +from __future__ import absolute_import + +import diffcalc +import diffcalc.settings +import os +import sys +from diffcalc.ub.persistence import UBCalculationJSONPersister +from diffcalc.util import bold +import diffcalc.util +import diffcalc.gdasupport.minigda.command +DIFFCALC_ROOT = os.path.realpath(diffcalc.__file__).split('diffcalc/__init__.py')[0] + +try: + __IPYTHON__ # @UndefinedVariable + IPYTHON = True +except NameError: + IPYTHON = False + + +module_name = sys.argv[1] #3 if IPYTHON else 1] +debug = sys.argv[2] == 'True' #4 if IPYTHON else 2]) + +print +print bold('-' * 34 + ' DIFFCALC ' + '-' * 35) + +# configure persisentence +DIFFCALC_VAR = os.path.join(os.path.expanduser('~'), '.diffcalc', module_name) +if not os.path.exists(DIFFCALC_VAR): + print "Making diffcalc var folder:'%s'" % DIFFCALC_VAR + os.makedirs(DIFFCALC_VAR) +diffcalc.settings.ubcalc_persister = UBCalculationJSONPersister(DIFFCALC_VAR) + +# configure debug +diffcalc.util.DEBUG = debug +if debug: + print "WARNING: debug mode on; help for command syntax errors disabled." + +# import script +script = os.path.join(DIFFCALC_ROOT, 'startup', module_name) + '.py' + +print "Startup script: '%s'" % script +namespace = {} +execfile(script, namespace) +globals().update(namespace) +diffcalc.gdasupport.minigda.command.ROOT_NAMESPACE_DICT = dict(namespace) +print bold('-' * 36 + ' Help ' + '-' * 37) +print HELP_STRING # @UndefinedVariable +if 'LOCAL_MANUAL' in locals(): + print "Local: " + LOCAL_MANUAL # @UndefinedVariable +print bold('-' * 79) +print + + +# magic commands if IPython +if IPYTHON: + from diffcmd.ipython import magic_commands + magic_commands(globals()) + + +if 'MANUALS_TO_MAKE' in locals(): + summary_lines = ['Made manuals:'] + from diffcmd.make_manual import make_manual + for source_path in MANUALS_TO_MAKE: # @UndefinedVariable + import diffcalc.ub.ub + try: + diffcalc.ub.ub.rmub('example') + except KeyError: + pass + target_path = source_path.replace('_template', '') + print '@' * 79 + print "Making manual" + print " Source:", source_path + print " Target:", target_path + + make_manual(source_path, target_path, + ub_commands_for_help, # @UndefinedVariable + hkl_commands_for_help) # @UndefinedVariable + summary_lines.append(' - ' + source_path + ' -- > ' + target_path) + print '\n'.join(summary_lines) + \ No newline at end of file diff --git a/script/test/diffcalc (copy)/doc/Makefile b/script/test/diffcalc (copy)/doc/Makefile new file mode 100644 index 0000000..032954a --- /dev/null +++ b/script/test/diffcalc (copy)/doc/Makefile @@ -0,0 +1,193 @@ +# Makefile for documentation +# This makefile is a modified version of the makefile generated by the sphinx-quickstart command + +# set environment for Diamond Light Source +ifeq ($(CONTEXT),diamond) + @echo "Environment variable CONTEXT=diamond, so setting build environment suitable for Diamond Light Source" + # get the location of a Python that has Sphinx installed + SPHINXBUILD?=$(shell module load python/2.7.2;which sphinx-build) + # set http proxy + export http_proxy?=http://wwwcache.rl.ac.uk:8080 + export https_proxy?=https://wwwcache.rl.ac.uk:8080 +endif + +# optionally use Sphinx's "-W" options, which converts warnings into errors +ifeq ($(HALTONWARNING),y) + SPHINXEXTRAOPT=-W +else ifeq ($(HALTONWARNING),Y) + SPHINXEXTRAOPT=-W +else + SPHINXEXTRAOPT= +endif + +### <-- start of Makefile contents generated by sphinx-quickstart --> ### + +# You can set these variables from the command line. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +PAPER ?= +BUILDDIR ?= build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXEXTRAOPT) $(SPHINXOPTS) source + +.PHONY: help helpfull pwd clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest pdfa4 pdfletter pdf all + +help: + @echo + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " pdf to create pdf files for both A4- and Letter-sized paper" + @echo " pdfa4 to create pdf files for A4-sized paper" + @echo " pdfletter to create pdf files for Letter-sized paper" + @echo " all to build html and pdf" + @echo " clean to wipe the build directory" + @echo + @echo "You can use \`make helpfull' to get a full list of targets (not all have been tested)" + +helpfull: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +pwd: + @echo + @echo "*******************************************************************************************************" + @echo "Current directory = "`pwd` + @echo "*******************************************************************************************************" + +clean: pwd + -rm -rf $(BUILDDIR)/* + +html: pwd + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/GDA.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GDA.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/GDA" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GDA" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: pwd + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: pwd + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +### <-- end of Makefile contents generated by sphinx-quickstart --> ### + +pdfa4: pwd + rm -rf $(BUILDDIR)/latex + rm -rf $(BUILDDIR)/pdf-a4 + make latexpdf PAPER=a4 + mkdir $(BUILDDIR)/pdf-a4/ + cp $(BUILDDIR)/latex/*.pdf $(BUILDDIR)/pdf-a4/. + @echo + @echo "Build finished for A4-sized PDFs. The PDF files are in $(BUILDDIR)/pdf-a4." + +pdfletter: pwd + rm -rf $(BUILDDIR)/latex + rm -rf $(BUILDDIR)/pdf-letter + make latexpdf PAPER=letter + mkdir $(BUILDDIR)/pdf-letter/ + cp $(BUILDDIR)/latex/*.pdf $(BUILDDIR)/pdf-letter/. + @echo + @echo "Build finished for Letter-sized PDFs. The PDF files are in $(BUILDDIR)/pdf-letter." + +pdf: pwd pdfa4 pdfletter + +all: pwd html pdf diff --git a/script/test/diffcalc (copy)/doc/docs-build-diffcalc_doc-linux.launch b/script/test/diffcalc (copy)/doc/docs-build-diffcalc_doc-linux.launch new file mode 100644 index 0000000..0bba525 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/docs-build-diffcalc_doc-linux.launch @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/script/test/diffcalc (copy)/doc/references b/script/test/diffcalc (copy)/doc/references new file mode 100644 index 0000000..a094e55 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/references @@ -0,0 +1,22 @@ + +.. [You1999] H. You. *Angle calculations for a '4S+2D' six-circle diffractometer.* + J. Appl. Cryst. (1999). **32**, 614-623. `(pdf link) + `__. + +.. [Busing1967] W. R. Busing and H. A. Levy. *Angle calculations for 3- and 4-circle X-ray + and neutron diffractometers.* Acta Cryst. (1967). **22**, 457-464. `(pdf link) + `__. + +.. [Vlieg1993] Martin Lohmeier and Elias Vlieg. *Angle calculations for a six-circle + surface x-ray diffractometer.* J. Appl. Cryst. (1993). **26**, 706-716. `(pdf link) + `__. + +.. [Vlieg1998] Elias Vlieg. *A (2+3)-type surface diffractometer: mergence of the z-axis and + (2+2)-type geometries.* J. Appl. Cryst. (1998). **31**, 198-203. `(pdf link) + `__. + +.. [Willmott2011] C. M. Schlepütz, S. O. Mariager, S. A. Pauli, R. Feidenhans'l and + P. R. Willmott. *Angle calculations for a (2+3)-type diffractometer: focus + on area detectors.* J. Appl. Cryst. (2011). **44**, 73-83. `(pdf link) + `__. + diff --git a/script/test/diffcalc (copy)/doc/source/ACKS.rst b/script/test/diffcalc (copy)/doc/source/ACKS.rst new file mode 100644 index 0000000..81fb355 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/ACKS.rst @@ -0,0 +1,34 @@ +################ +Acknowledgements +################ + +.. toctree:: + :maxdepth: 2 + :numbered: + +We would like to acknowledge the people who have made a direct impact on the +Diffcalc project, knowingly or not, in terms of encouragement, suggestions, +criticism, bug reports, code contributions, and related projects. + +Names are ordered alphabetically by surname. + +.. If you add new entries, keep the list sorted by surname! + +.. acks:: + + * Allesandro Bombardi + * Mark Booth + * W. R. Busing + * Steve Collins + * Mirian Garcia-Fernandez + * H. A. Levy + * Martin Lohmier + * Chris Nicklin + * Elias Vlieg --- writer of DIF software used as a model for Diffcalc + * Robert Walton + * H. You + * Fajin Yuan + +Thank you! + +Rob Walton & Irakli Sikharulidze \ No newline at end of file diff --git a/script/test/diffcalc (copy)/doc/source/conf.py b/script/test/diffcalc (copy)/doc/source/conf.py new file mode 100644 index 0000000..fe9ba1d --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/conf.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- + +# +# documentation build configuration file, created by +# sphinx-quickstart on Fri Apr 15 10:03:07 2011. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os, time + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.extlinks', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.imgmath', 'sphinx.ext.ifconfig'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Diffcalc' +copyright = u'2017-%s, Diamond Light Source' % time.strftime('%Y') + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '2.1' +# The full version, including alpha/beta/rc tags. +release = '2.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +#html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} +# html_theme_options = { +# 'sidebarbgcolor' : '#f2f2f2', +# 'sidebartextcolor': '#444a95', +# 'sidebarlinkcolor': '#0b0f40', +# } + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + + +import sphinx_rtd_theme + +html_theme = "sphinx_rtd_theme" + +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = 'diffcalc_web.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] +html_static_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True +html_show_sourcelink = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True +html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' +latex_paper_size = 'a4' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('youmanual', 'diffcalc_user_guide.tex', u'Diffcalc User Guide', + u'Diamond Light Source', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page +latex_logo = 'diffcalc_pdf.png' + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +#man_pages = [] + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {} + +""" + Additional options for Diffcalc +""" + +todo_include_todos = True + +extlinks = { + 'opengda_url' :('http://www.opengda.org/%s', None), + } + +rst_prolog = """ +.. |DLS| replace:: :abbr:`DLS (Diamond Light Source)` +""" diff --git a/script/test/diffcalc (copy)/doc/source/developer/contents.rst b/script/test/diffcalc (copy)/doc/source/developer/contents.rst new file mode 100644 index 0000000..d52fdd9 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/developer/contents.rst @@ -0,0 +1,25 @@ +######################## +Diffcalc Developer Guide +######################## + +:Author: Rob Walton +:Contact: rob.walton (at) diamond (dot) ac (dot) uk +:Website: http://www.opengda.org/ + +.. rubric:: Diffcalc: A diffraction condition calculator for diffractometer control + +.. toctree:: + :maxdepth: 2 + :numbered: + + intro + package + quickstart_api + development + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` \ No newline at end of file diff --git a/script/test/diffcalc (copy)/doc/source/developer/development.rst b/script/test/diffcalc (copy)/doc/source/developer/development.rst new file mode 100644 index 0000000..12b90c6 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/developer/development.rst @@ -0,0 +1,24 @@ +Development +=========== + +The files are kept here_ on github_. See bootcamp for an introduction to +using github. To contribute please fork the project. Otherwise you can make +a read-only clone or export. + +Code format should follow pep8 guidelines. PyDev has a good pep8 checker. + +To run the tests install nose_, change directory into the test folder and run:: + + $ nosetests + .......... ... + ---------------------------------------------------------------------- + Ran 3914 tests in 9.584s + + OK (SKIP=15) + + + +.. _here: https://github.com/DiamondLightSource/diffcalc +.. _github: https://github.com +.. _nose: http://nose.readthedocs.org/en/latest/ +.. _pep8: http://www.python.org/dev/peps/pep-0008/ diff --git a/script/test/diffcalc (copy)/doc/source/developer/intro.rst b/script/test/diffcalc (copy)/doc/source/developer/intro.rst new file mode 100644 index 0000000..a45eae4 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/developer/intro.rst @@ -0,0 +1,55 @@ +Introduction +============ + +Diffcalc is a diffraction condition calculator used for controlling +diffractometers within reciprocal lattice space. It performs the same +task as the ``fourc``, ``sixc``, ``twoc``, ``kappa``, ``psic`` and +``surf`` macros from SPEC_. + +Diffcalc's standard calculation engine is an implementation of +[You1999]_ . The first versions of Diffcalc were based on +[Vlieg1993]_ and [Vlieg1998]_ and a 'Vlieg' engine is still +available. The 'You' engine is more generic and the plan is to remove +the old 'Vlieg' engine once beamlines have been migrated. New users +should use the 'You' engine. + +The foundations for this type of calculation were laid by by Busing & +Levi in their classic paper [Busing1967]_. Diffcalc's orientation +algorithm is taken from this paper. Busing & Levi also provided the +original definition of the coordinate frames and of the U and B +matrices used to describe a crystal's orientation and to convert between +Cartesian and reciprical lattice space. + +Geometry plugins are used to adapt the six circle model used +internally by Diffcalc to apply to other diffractometers. These +contain a dictionary of the 'missing' angles which Diffcalc uses to +constrain these angles internally, and a methods to map from external +angles to Diffcalc angles and visa versa. + +Options to use Diffcalc: + +- **The User manual next to this developer manual or README file on github.** +- The :ref:`quickstart-api` section describes how to run up only + the core in Python_. This provides a base option for system integration. + +Diffcalc will work with Python 2.7 or higher with numpy_, or with +Jython 2.7 of higher with Jama_. + + +.. [*] The very small 'Willmott' engine currently handles the case for + surface diffraction where the surface normal is held vertical + [Willmott2011]_. The 'You' engine handles this case fine, but + currently spins nu into an unhelpful quadrant. We hope to + remove the need for this engine soon. + + +.. _SPEC: http://www.certif.com/ +.. _Python: http://python.org +.. _IPython: http://http://ipython.org/ +.. _Jython: http://jython.org +.. _OpenGDA: http://opengda.org +.. _numpy: http://numpy.scipy.org/ +.. _Jama: http://math.nist.gov/javanumerics/jama/ + + +.. include:: ../../references diff --git a/script/test/diffcalc (copy)/doc/source/developer/package.rst b/script/test/diffcalc (copy)/doc/source/developer/package.rst new file mode 100644 index 0000000..e4d3210 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/developer/package.rst @@ -0,0 +1,30 @@ +Project Files & Directories +=========================== + +diffcalc + The main source package. + +test + Diffcalcs unit-test package (use Nose_ to run them). + +diffcmd + A spec-like openGDA emulator. + +numjy + A *very* minimal implentation of numpy for jython. It supports only what + Diffcalc needs. + +doc + The documentation is written in reStructuredText and can be compiled into + html and pdf using Python's `Sphinx `_. With Sphinx + installed use ``make clean all`` from within the user and developer guide + folders to build the documentation. + +startup + Starup scripts called by diffcmd or openGDA to startup diffcalc + +model + Vrml models of diffractometers and a hokey script for animating then and + controlling them from diffcalc. + +.. _Nose: http://readthedocs.org/docs/nose/en/latest/ \ No newline at end of file diff --git a/script/test/diffcalc (copy)/doc/source/developer/quickstart_api.rst b/script/test/diffcalc (copy)/doc/source/developer/quickstart_api.rst new file mode 100644 index 0000000..96bb9a2 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/developer/quickstart_api.rst @@ -0,0 +1,266 @@ +.. _quickstart-api: + +.. warning:: This documentation is out of date. The README and the user doc has been updated recently. For now if you need help with API, please contact me at Diamond. -- Rob Walton + +Quick-Start: Python API +======================= + +This section describes how to run up only the core in Python or +IPython. This provides an API which could be used to integrate Diffcalc into an +existing data acquisition system; although the interface described in +the README would normally provide a better starting point. + +For a full description of what Diffcalc does and how to use it please +see the 'Diffcalc user manual'. + +Setup environment +----------------- + +.. include:: quickstart_setup_environment + + +Start +----- + +With Python start the sixcircle_api.py example startup script (notice +the -i and -m) and call `demo_all()`:: + + $ python -i -m startup.api.sixcircle + >>> demo_all() + +IPython requires:: + + $ ipython -i startup/api/sixcircle.py + >>> demo_all() + +Alternatively start Python or IPython and cut and paste lines from the rest of +this tutorial. + +Configure a diffraction calculator +---------------------------------- + +By default some exceptions are handled in a way to make user interaction +friendlier. Switch this off with:: + + >>> import diffcalc.util + >>> diffcalc.util.DEBUG = True + +To setup a Diffcalc calculator, first configure `diffcalc.settings` module:: + + >>> from diffcalc import settings + >>> from diffcalc.hkl.you.geometry import SixCircle + >>> from diffcalc.hardware import DummyHardwareAdapter + >>> settings.hardware = DummyHardwareAdapter(('mu', 'delta', 'gam', 'eta', 'chi', 'phi')) + >>> settings.geometry = SixCircle() # @UndefinedVariable + +The hardware adapter is used by Diffcalc to read up the current angle +settings, wavelength and axes limits. It is primarily used to simplify +commands for end users. It could be dropped for this API use, but it +is also used for the important job of checking axes limits while +choosing solutions. + +Geometry plugins are used to adapt the six circle model used +internally by Diffcalc to apply to other diffractometers. These +contain a dictionary of the 'missing' angles which Diffcalc internally +uses to constrain these angles, and a methods to map from +external angles to Diffcalc angles and visa versa. + + +Calling the API +--------------- + +The ``diffcalc.dc.dcyou`` module (and others) read the ``diffcalc.settings`` module when first +imported. Note that this means that changes to the settings will most likely +have no effect unless ``diffcalc.dc.dcyou`` is reloaded:: + + >>> import diffcalc.dc.dcyou as dc + +This includes the two critical functions:: + + def hkl_to_angles(h, k, l, energy=None): + """Convert a given hkl vector to a set of diffractometer angles + + return angle tuple and virtual angles dictionary + """ + + def angles_to_hkl(angle_tuple, energy=None): + """Converts a set of diffractometer angles to an hkl position + + Return hkl tuple and virtual angles dictionary + """ + +``diffcalc.dc.dcyou`` also brings in all the commands from ``diffcalc.ub.ub``, +``diffcalc.hardware`` and ``diffcalc.hkl.you.hkl``. That is it includes all the +commands exposed in the top level namespace when diffcalc is used interactively:: + + >>> dir(dc) + + ['__builtins__', '__doc__', '__file__', '__name__', '__package__', + '_hardware','_hkl', '_ub', 'addref', 'allhkl', 'angles_to_hkl', 'c2th', + 'calcub', 'checkub', 'clearref', 'con', 'constraint_manager', 'delref', + 'diffcalc', 'editref', 'energy_to_wavelength', 'hardware', 'hkl_to_angles', + 'hklcalc', 'lastub', 'listub', 'loadub', 'newub', 'rmub', 'saveubas', 'setcut', + 'setlat', 'setmax', 'setmin', 'settings', 'setu', 'setub', 'showref', + 'swapref', 'trialub', 'ub', 'ub_commands_for_help', 'ubcalc', 'uncon'] + +This doesn't form the best API to program against though, so it is best to +use the four modules more directly. The example below assumes you have +also imported:: + + >>> from diffcalc.ub import ub + >>> from diffcalc import hardware + >>> from diffcalc.hkl.you import hkl + +Getting help +------------ + +To get help for the diffcalc angle calculations, the orientation phase, the +angle calculation phase, and the dummy hardware adapter commands:: + + >>> help(dc) + >>> help(ub) + >>> help(hkl) + >>> help(hardware) + + +Orientation +----------- + +To orient the crystal for example (see the user manual for a fuller +tutorial) first find some reflections:: + + # Create a new ub calculation and set lattice parameters + ub.newub('test') + ub.setlat('cubic', 1, 1, 1, 90, 90, 90) + + # Add 1st reflection (demonstrating the hardware adapter) + hardware.settings.hardware.wavelength = 1 + ub.c2th([1, 0, 0]) # energy from hardware + settings.hardware.position = 0, 60, 0, 30, 0, 0 # mu del nu eta chi ph + ub.addref([1, 0, 0]) # energy & pos from hardware + + # Add 2nd reflection (this time without the hardware adapter) + ub.c2th([0, 1, 0], 12.39842) + ub.addref([0, 1, 0], [0, 60, 0, 30, 0, 90], 12.39842) + + +To check the state of the current UB calculation:: + + >>> ub.ub() + + UBCALC + + name: test + + n_phi: 0.00000 0.00000 1.00000 <- set + n_hkl: -0.00000 0.00000 1.00000 + miscut: None + + CRYSTAL + + name: cubic + + a, b, c: 1.00000 1.00000 1.00000 + 90.00000 90.00000 90.00000 + + B matrix: 6.28319 0.00000 0.00000 + 0.00000 6.28319 0.00000 + 0.00000 0.00000 6.28319 + + UB MATRIX + + U matrix: 1.00000 0.00000 0.00000 + 0.00000 1.00000 0.00000 + 0.00000 0.00000 1.00000 + + U angle: 0 + + UB matrix: 6.28319 0.00000 0.00000 + 0.00000 6.28319 0.00000 + 0.00000 0.00000 6.28319 + + REFLECTIONS + + ENERGY H K L MU DELTA GAM ETA CHI PHI TAG + 1 12.398 1.00 0.00 0.00 0.0000 60.0000 0.0000 30.0000 0.0000 0.0000 + 2 12.398 0.00 1.00 0.00 0.0000 60.0000 0.0000 30.0000 0.0000 90.0000 + +And finally to check the reflections were specified acurately:: + + >>> dc.checkub() + + ENERGY H K L H_COMP K_COMP L_COMP TAG + 1 12.3984 1.00 0.00 0.00 1.0000 0.0000 0.0000 + 2 12.3984 0.00 1.00 0.00 -0.0000 1.0000 0.0000 + +Motion +------ + +Hkl positions and virtual angles can now be read up from angle +settings (the easy direction!):: + + + >>> dc.angles_to_hkl((0., 60., 0., 30., 0., 0.)) # energy from hardware + + ((1.0, 5.5511151231257827e-17, 0.0), + {'alpha': -0.0, + 'beta': 3.5083546492674376e-15, + 'naz': 0.0, + 'psi': 90.0, + 'qaz': 90.0, + 'tau': 90.0, + 'theta': 29.999999999999996}) + +Before calculating the settings to reach an hkl position (the trickier +direction) hardware limits must be set and combination of constraints +chosen. The constraints here result in a four circle like mode with a +vertical scattering plane and incident angle 'alpha' equal to the exit +angle 'beta':: + + >>> hkl.con('qaz', 90) + ! 2 more constraints required + qaz: 90.0000 + + >>> hkl.con('a_eq_b') + ! 1 more constraint required + qaz: 90.0000 + a_eq_b + + >>> hkl.con('mu', 0) + qaz: 90.0000 + a_eq_b + mu: 0.0000 + +To check the constraints:: + + >>> hkl.con() + DET REF SAMP + ====== ====== ====== + delta --> a_eq_b --> mu + alpha eta + --> qaz beta chi + naz psi phi + mu_is_nu + + qaz: 90.0000 + a_eq_b + mu: 0.0000 + + Type 'help con' for instructions + +Limits can be set to help Diffcalc choose a solution:: + + >>> hardware.setmin('delta', 0) # used when choosing solution + +Angles and virtual angles are then easily determined for a given hkl reflection:: + + >>> dc.hkl_to_angles(1, 0, 0) # energy from hardware + ((0.0, 60.0, 0.0, 30.0, 0.0, 0.0), + {'alpha': -0.0, + 'beta': 0.0, + 'naz': 0.0, + 'psi': 90.0, + 'qaz': 90.0, + 'tau': 90.0, + 'theta': 30.0} + ) diff --git a/script/test/diffcalc (copy)/doc/source/developer/quickstart_setup_environment b/script/test/diffcalc (copy)/doc/source/developer/quickstart_setup_environment new file mode 100644 index 0000000..fe36a74 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/developer/quickstart_setup_environment @@ -0,0 +1,25 @@ + +Change directory to the diffcalc project (python adds the current +working directory to the path):: + + $ cd diffcalc + $ ls + COPYING diffcalc doc example mock.py mock.pyc model numjy test + +If using Python make sure numpy and diffcalc can be imported:: + + $ python + Python 2.7.2+ (default, Oct 4 2011, 20:06:09) + [GCC 4.6.1] on linux2 + Type "help", "copyright", "credits" or "license" for more information. + >>> import numpy + >>> import diffcalc + +If using Jython make sure Jama and diffcalc can be imported:: + + $ jython -Dpython.path=:/Jama-1.0.1.jar + + Jython 2.2.1 on java1.5.0_11 + Type "copyright", "credits" or "license" for more information. + >>> import Jama + >>> import diffcalc diff --git a/script/test/diffcalc (copy)/doc/source/diffcalc_pdf.png b/script/test/diffcalc (copy)/doc/source/diffcalc_pdf.png new file mode 100644 index 0000000..7e08c66 Binary files /dev/null and b/script/test/diffcalc (copy)/doc/source/diffcalc_pdf.png differ diff --git a/script/test/diffcalc (copy)/doc/source/diffcalc_web.png b/script/test/diffcalc (copy)/doc/source/diffcalc_web.png new file mode 100644 index 0000000..d7ddec5 Binary files /dev/null and b/script/test/diffcalc (copy)/doc/source/diffcalc_web.png differ diff --git a/script/test/diffcalc (copy)/doc/source/index.rst b/script/test/diffcalc (copy)/doc/source/index.rst new file mode 100644 index 0000000..b65fbd4 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/index.rst @@ -0,0 +1,26 @@ +################################### + Diffcalc User and Developer Guide +################################### + +:Author: Rob Walton +:Contact: rob.walton (at) diamond.ac.uk +:Web site: https://github.com/DiamondLightSource/diffcalc + +.. rubric:: Diffcalc: A Diffraction Condition Calculator for Diffractometer Control + +See also the `quickstart guide at github `_. + +.. toctree:: + :maxdepth: 2 + + youmanual + vliegmanual/contents + developer/contents + ACKS + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/script/test/diffcalc (copy)/doc/source/vliegmanual/contents.rst b/script/test/diffcalc (copy)/doc/source/vliegmanual/contents.rst new file mode 100644 index 0000000..7d60a69 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/vliegmanual/contents.rst @@ -0,0 +1,22 @@ +############################################# +Diffcalc User Guide (Deprecated Vlieg Engine) +############################################# + +:Author: Rob Walton +:Contact: rob.walton (at) diamond (dot) ac (dot) uk +:Website: http://www.opengda.org/ + +.. rubric:: Diffcalc: A diffraction condition calculator for diffractometer control + +.. toctree:: + :maxdepth: 2 + :numbered: + + vliegmanual + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` \ No newline at end of file diff --git a/script/test/diffcalc (copy)/doc/source/vliegmanual/images/fix.png b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/fix.png new file mode 100644 index 0000000..b4307d4 Binary files /dev/null and b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/fix.png differ diff --git a/script/test/diffcalc (copy)/doc/source/vliegmanual/images/sixcircle_gamma_on_arm.pdf b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/sixcircle_gamma_on_arm.pdf new file mode 100644 index 0000000..907a254 Binary files /dev/null and b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/sixcircle_gamma_on_arm.pdf differ diff --git a/script/test/diffcalc (copy)/doc/source/vliegmanual/images/sixcircle_gamma_on_arm.png b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/sixcircle_gamma_on_arm.png new file mode 100755 index 0000000..a1f2d93 Binary files /dev/null and b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/sixcircle_gamma_on_arm.png differ diff --git a/script/test/diffcalc (copy)/doc/source/vliegmanual/images/sixcircle_gamma_on_arm.ppt b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/sixcircle_gamma_on_arm.ppt new file mode 100755 index 0000000..0af759a Binary files /dev/null and b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/sixcircle_gamma_on_arm.ppt differ diff --git a/script/test/diffcalc (copy)/doc/source/vliegmanual/images/unit_cell.pdf b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/unit_cell.pdf new file mode 100644 index 0000000..3114c35 Binary files /dev/null and b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/unit_cell.pdf differ diff --git a/script/test/diffcalc (copy)/doc/source/vliegmanual/images/unit_cell.png b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/unit_cell.png new file mode 100644 index 0000000..b431458 Binary files /dev/null and b/script/test/diffcalc (copy)/doc/source/vliegmanual/images/unit_cell.png differ diff --git a/script/test/diffcalc (copy)/doc/source/vliegmanual/vliegmanual.rst b/script/test/diffcalc (copy)/doc/source/vliegmanual/vliegmanual.rst new file mode 100644 index 0000000..4638b3e --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/vliegmanual/vliegmanual.rst @@ -0,0 +1,827 @@ +Introduction +============ + +.. warning:: + + This manual refers to the 'Vlieg' calculation available in Diffcalc I. By + default Diffcalc II now uses its 'You' engine. This manual will be updated + soon. For now the developer guide shows how the new constraint system works. + +This manual assumes that you are running Diffcalc within the external +framework of the GDA or Minigda and that Diffcalc has been configured +for the six circle diffractometer pictured here: + +.. figure:: images/sixcircle_gamma_on_arm.* + :scale: 50 + :align: center + + Gamma-on-delta six-circle diffractometer, modified from Elias Vlieg + & Martin Lohmeier (1993) + +Your Diffcalc configuration will have been customised for the geometry +of your diffractometer and possibly the types of experiment you +perform. For example: a five-circle diffractometer might be missing +the Gamma circle above, some six-circle modes and the option to fix +gamma that would otherwise exist in some modes. + +The laboratory, crystal and reciprocal-lattice coordinate frames are +defined with respect to the beam and to gravity to be (for a cubic crystal): + +.. figure:: images/fix.png + :align: center + + Laboratory and illustratrive crystal coordinate frames for a cubic crystal + +The crystal lattice basis vectors are defined within the Cartesian +crystal coordinate frame to be: + +.. figure:: images/unit_cell.* + :align: center + :scale: 100 + + Unit cell defined in crystal coordinate frame + +.. _overview: + +Overview +======== + +The following assumes that the diffractometer has been properly levelled, aligned with +the beam and zeroed. See the `SPEC fourc manual `__. + +Before moving in hkl space you must calculate a UB matrix by +specifying the crystal's lattice parameters (which define the B +matrix) and finding two reflections (from which the +U matrix can be inferred); and, optionally for surface-diffraction +experiments, determine how the surface of the crystal is oriented with +respect to the phi axis. + + +Once a UB matrix has been calculated, the diffractometer may be driven +in hkl coordinates. A valid diffractometer setting maps easily into a +single hkl value. However for a diffractometer with more than three circles +there are excess degrees of freedom when calculating a diffractometer +setting from an hkl value. Diffcalc provides modes for using up +the excess degrees of freedom. + +Diffcalc does not perform scans directly. Instead, scannables that use +diffcalc to map between reciprocal lattice space and real +diffractometer settings are scanned using the Gda's (or minigda's) +generic scan mechanism. + + +Theory +------ + +Thanks to Elias Vlieg for sharing his dos based ``DIF`` software that +Diffcalc has borrowed heavily from. (See also the THANKS.txt file). + +See the papers (included in ``docs/ref``): + +* Busing & Levi (1966), "Angle Calculations for 3- and 4- Circle X-ray + and Neutron Diffractometers", Acta Cryst. 22, 457 + +* Elias Vlieg & Martin Lohmeier (1993), "Angle Calculations for a Six-Circle + Surface X-ray Diffractometer", J. Appl. Cryst. 26, 706-716 + +Getting Help +============ + +There are few commands to remember. If a command is called without +arguments, Diffcalc will prompt for arguments and provide sensible +defaults which can be chosen by pressing enter. + +The ``helpub`` and ``helphkl`` commands provide help with the crystal +orientation and hkl movement phases of an experiment respectively:: + + >>> helpub + + Diffcalc + -------- + helpub ['command'] - lists all ub commands, or one if command is given + helphkl ['command'] - lists all hkl commands, or one if command is given + + UB State + -------- + newub 'name' - starts a new ub calculation with no lattice or + reflection list + loadub 'name' - loads an existing ub calculation: lattice and + reflection list + saveubas 'name' - saves the ubcalculation with a new name (other + changes autosaved) + ub - shows the complete state of the ub calculation + + UB lattice + ---------- + setlat - prompts user to enter lattice parameters (in + Angstroms and Deg.) + setlat 'name' a - assumes cubic + setlat 'name' a b - assumes tetragonal + setlat 'name' a b c - assumes ortho + setlat 'name' a b c gam - assumes mon/hex with gam not equal to 90 + setlat 'name' a b c alpha beta gamma - arbitrary + + UB surface + ---------- + sigtau [sigma tau] - sets sigma and tau + + UB reflections + -------------- + showref - shows full reflection list + addref - add reflection + addref h k l ['tag'] - add reflection with hardware position and energy + addref h k l (p1,p2...pN) energy ['tag']- add reflection with specified position + and energy + delref num - deletes a reflection (numbered from 1) + swapref - swaps first two reflections used for calculating U + swapref num1 num2 - swaps two reflections (numbered from 1) + + UB calculation + -------------- + setu [((,,),(,,),(,,))] - manually set u matrix + setub ((,,),(,,),(,,)) - manually set ub matrix + calcub - (re)calculate u matrix from ref1 and ref2 + checkub - show calculated and entered hkl values for reflections + + >>> helphkl + + Diffcalc + -------- + helphkl [command] - lists all hkl commands, or one if command is given + helpub [command] - lists all ub commands, or one if command is given + + Settings + -------- + hklmode [num] - changes mode or shows current and available modes + and all settings + setalpha [num] - fixes alpha, or shows all settings if no num given + setgamma [num] - fixes gamma, or shows all settings if no num given + setbetain [num] - fixes betain, or shows all settings if no num given + setbetaout [num] - fixes betaout, or shows all settings if no num given + trackalpha [boolean] - determines wether alpha parameter will track alpha axis + trackgamma [boolean] - determines wether gamma parameter will track gamma axis + trackphi [boolean] - determines wether phi parameter will track phi axis + setsectorlim [omega_high omega_low phi_high phi_low]- sets sector limits + + Motion + ------ + pos hkl [h k l] - move diffractometer to hkl, or read hkl position. + Use None to hold a value still + sim hkl [h k l] - simulates moving hkl + hkl - shows loads of info about current hkl position + pos sixc [alpha, delta, gamma, omega, chi, phi,]- move diffractometer to Eularian + position. Use None to hold a + value still + sim sixc [alpha, delta, gamma, omega, chi, phi,]- simulates moving sixc + sixc - shows loads of info about current sixc position + + +Diffcalc's Scannables +===================== + +Please see :ref:`moving-in-hkl-space` and :ref:`scanning-in-hkl-space` for some relevant examples. + +To list and show the current positions of your beamline's scannables +use ``pos`` with no arguments:: + + >>> pos + +Results in: + +**Energy and wavelength scannables**:: + + energy 12.3984 + wl: 1.0000 + +**Diffractometer scannables**, as a group and in component axes (in +the real GDA these have limits):: + + sixc: alpha: 0.0000 delta: 0.0000 gamma: 0.0000 omega: 0.0000 chi: 0.0000 phi: 0.0000 + alpha: 0.0000 + chi: 0.0000 + delta: 0.0000 + gamma: 0.0000 + omega: 0.0000 + phi: 0.0000 + +**Dummy counter**, which in this example simply counts at 1hit/s:: + + cnt: 0.0000 + +**Hkl scannable**, as a group and in component:: + + hkl: Error: No UB matrix + h: Error: No UB matrix + k: Error: No UB matrix + l: Error: No UB matrix + +**Parameter scannables**, used in some modes, these provide a +scannable alternative to the series of ``fix`` commands described in +:ref:`moving-in-hkl-space`.:: + + alpha_par:0.00000 + azimuth: --- + betain: --- + betaout: --- + gamma_par:0.00000 + phi_par: --- + + Note that where a parameter corresponds with a physical + diffractometer axis, it can also be set to track that axis + directly. See `Tracking axis`_ below. + +Crystal orientation +=================== + +Before moving in hkl space you must calculate a UB matrix by +specifying the crystal's lattice parameters (which define the B +matrix) and finding two reflections (from which the +U matrix can be inferred); and, optionally for surface-diffraction +experiments, determine how the surface of the crystal is oriented with +respect to the phi axis (see :ref:`overview`). + +Starting a UB calculation +------------------------- + +A *UB-calculation* contains the description of the crystal-under-test, +any saved reflections, sigma & tau (both default to 0), and a B & UB +matrix pair if they have been calculated or manually specified. +Starting a new UB calculation will clear all of these. + +Before starting a UB-calculation, the ``ub`` command used to summarise +the state of the current UB-calculation, will reflect that no +UB-calculation has been started:: + + >>> ub + No UB calculation started. + Wavelength: 1.239842 + Energy: 10.000000 + +A new UB-calculation calculation may be started and lattice specified +explicitly:: + + >>> newub 'b16_270608' + >>> setlat 'xtal' 3.8401 3.8401 5.43072 90 90 90 + +or interactively:: + + >>> newub + calculation name: b16_270608 + crystal name: xtal + a [1]: 3.8401 + b [3.8401]: 3.8401 + c [3.8401]: 5.43072 + alpha [90]: 90 + beta [90]: 90 + gamma [90]: 90 + +where a,b and c are the lengths of the three unit cell basis vectors +in Angstroms, and alpha, beta and gamma the typically used angles +(defined in the figure above) in Degrees. + +The ``ub`` command will show the state of the current UB-calculation +(and the current energy for reference):: + + UBCalc: b16_270608 + ====== + + Crystal + ------- + name: xtal + + lattice: a ,b ,c = 3.84010, 3.84010, 5.43072 + alpha, beta , gamma = 90.00000, 90.00000, 90.00000 + + reciprocal: b1, b2, b3 = 1.63620, 1.63620, 1.15697 + beta1, beta2, beta3 = 1.57080, 1.57080, 1.57080 + + B matrix: 1.6362035642769 -0.0000000000000 -0.000000000000 + 0.0000000000000 1.6362035642769 -0.000000000000 + 0.0000000000000 0.0000000000000 1.156970955450 + + Reflections + ----------- + energy h k l alpha delta gamma omega chi phi tag + + UB matrix + --------- + none calculated + + Sigma: 0.000000 + Tau: 0.000000 + Wavelength: 1.000000 + Energy: 12.398420 + + +Specifying Sigma and Tau for surface diffraction experiments +------------------------------------------------------------ +Sigma and Tau are used in modes that fix either the beam exit or entry angle with +respect to the crystal surface, or that keep the surface normal in the horizontal +laboratory plane. For non surface-diffraction experiments these can +safely be left at zero. + +For surface diffraction experiments, where not only the crystal's +lattice planes must be oriented appropriately but so must the crystal's +optical surface, two angles _Tau_ and _Sigma_ define the orientation of +the surface with respect to the phi axis. Sigma is (minus) the amount of chi axis +rotation and Tau (minus) the amount of phi axis rotation needed to +move the surface normal parallel to the omega circle +axis. These angles are often determined by reflecting a laser from the +surface of the Crystal onto some thing and moving chi and tau until +the reflected spot remains stationary with movements of omega. + +Use ``sigtau`` with no args to set interactively:: + + >>> pos chi -3.1 + chi: -3.1000 + >>> pos phi 10.0 + phi: 10.0000 + >>> sigtau + sigma, tau = 0.000000, 0.000000 + chi, phi = -3.100000, 10.000000 + sigma[ 3.1]: 3.1 + tau[-10.0]: 10.0 + +Sigma and Tau can also be set explicitly:: + + >>>sigtau 0 0 + + +Managing reflections +-------------------- +The normal way to calculate a UB matrix is to find the position of **two** +reflections with known hkl values. Diffcalc allows many +reflections to be recorded but currently only uses the first two when +calculating a UB matrix. + +Add reflection at current location +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is normal to first move to a reflection:: + + >>> pos en 10 + en: 10.0000 + >>> pos sixc [5.000, 22.790, 0.000, 1.552, 22.400, 14.255] + sixc: alpha: 5.0000 delta: 22.7900 gamma: 0.0000 omega: 1.5520 chi: 22.4000 phi: 14.2550 + + +and then use the ``addref`` command either explicitly:: + + addref 1 0 1.0628 'optional_tag' + +or interactively:: + + >>> addref + h: 1 + k: 0 + l: 1.0628 + current pos[y]: y + tag: 'tag_string' + +to add a reflection. + +Add a reflection manually +~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a reflection cannot be reached but its position is known (or if its +position has been previously determined), a reflection may be added +without first moving to it either explicitly:: + + >>> addref 0 1 1.0628 [5.000, 22.790, 0.000,4.575, 24.275, 101.320] 'optional_tag' + +or interactively:: + + >>> addref + h: 0 + k: 1 + l: 1.0628 + current pos[y]: n + alpha[5.000]: + delta[22.79]: + gamma[0.000]: + omega[1.552]: 4.575 + chi[22.40]: 24.275 + phi[14.25]: 101.320 + en[9.998]: + tag: optional_tag2 + +Edit reflection list +~~~~~~~~~~~~~~~~~~~~ + +Use ``showref`` to show the reflection list:: + + >>> showref + energy h k l alpha delta gamma omega chi phi tag + 1 9.999 1.00 0.00 1.06 5.0000 22.7900 0.0000 1.5520 22.4000 14.2550 1st + 2 9.999 0.00 1.00 1.06 5.0000 22.7900 0.0000 4.5750 24.2750 101.32000 2nd + +Use ``swapref`` to swap reflections:: + + >>> swapref 1 2 + Recalculating UB matrix. + >>> showref + energy h k l alpha delta gamma omega chi phi tag + 1 9.999 0.00 1.00 1.06 5.0000 22.7900 0.0000 4.5750 24.2750 101.3200 2nd + 2 9.999 1.00 0.00 1.06 5.0000 22.7900 0.0000 1.5520 22.4000 14.2550 1st + +Use ``delref`` to delete a reflection:: + + >>> delref 1 + >>> showref + energy h k l alpha delta gamma omega chi phi tag + 1 9.999 1.00 0.00 1.06 5.0000 22.7900 0.0000 1.5520 22.4000 14.2550 1st + +Calculating a UB matrix +----------------------- + +Unless a U or UB matrix has been manually specified, a new UB matrix +will be calculated after the second reflection has been found, or +whenever one of the first two reflections is changed. + +Use the command ``calcub`` to force the UB matrix to be calculated +from the first two reflections. + +If you have misidentified a reflection used for the orientation the +resulting UB matrix will be incorrect. Always use the ``checkub`` +command to check that the computed values agree with the estimated values:: + + >>>checkub + energy h k l h_comp k_comp l_comp tag + 1 9.9987 1.00 0.00 1.06 1.0000 0.0000 1.0628 1st + 2 9.9987 0.00 1.00 1.06 -0.0329 1.0114 1.0400 2nd + +Notice that the first reflection will always match, but that the +second will not match exactly. (The system of equations used to +calculate the U matrix is overdetermined and some information from the +second reflection is thrown away.) + +Manually setting U and UB +------------------------- + +*To help find the initial reflections* it may be useful to set the U +matrix manually---to the identity matrix for example. Use the ``setu`` +command to do this. Once set the diffractometer may be driven to the +ideal location of a reflection and then the actual reflection +sought. Normally this would be done in the default mode, four-circle-bisecting, (see +:ref:`moving-in-hkl-space`). In the following example this has been done +by setting the alpha to 5 and leaving gamma at 0 (it would be normal +to leave alpha at 0):: + + >>> hklmode 1 + 1) fourc bisecting + alpha: 0.0 + gamma: 0.0 + + >>> setalpha 5 + alpha: 0 --> 5.000000 + >>> setu + row1[1 0 0]: + row2[0 1 0]: + row3[0 0 1]: + >>> sim hkl [1,0,1.0628] # Check it all makes sense + sixc would move to: + alpha : 5.00000 deg + delta : 22.79026 deg + gamma : 0.00000 deg + omega : 5.82845 deg + chi : 24.57658 deg + phi : 6.14137 deg + + theta : 70702.991919 + 2theta : 23.303705 + Bin : 6.969151 + Bout : 6.969151 + azimuth : 7.262472 + + >>> pos hkl [1,0,1.0628] + hkl: h: 1.00000 k: 0.00000 l: 1.06280 + + >>> # scan about to find actual reflection + + >>> addref + h[0.0]: 1 + k[0.0]: 0 + l[0.0]: 1.0628 + current pos[y]: y + tag: 'ref1' + >>> + + +There is currently no way to refine a manually specified U matrix by +inferring as much as possible from just one found reflection. + +.. _moving-in-hkl-space: + +Moving in hkl space +=================== + +Once a UB matrix has been calculated, the diffractometer may be driven +in hkl coordinates. A given diffractometer setting maps easily into a +single hkl value. However for a diffractometer with more than three circles +there are excess degrees of freedom when calculating a diffractometer +setting from an hkl value. Diffcalc provides many for using up +the excess degrees of freedom. + +By default Diffcalc selects four-circle bisecting mode (see below). + +Note that to play along with the following ``run`` the file in +``example/session/sixc_example.py`` to configure the UB-calculation. + + +Modes +----- + +Use the command ``hklmode`` to summarise the state of Diffcalc's angle +calculator. It shows a list the available modes for your +diffractometer and the parameters that must be fixed for each, the +current mode and the current parameter settings:: + + >>> hklmode + Available modes: + 0) fourc fixed-bandlw (alpha, gamma, blw) (Not impl.) + 1) fourc bisecting (alpha, gamma) + 2) fourc incoming (alpha, gamma, betain) + 3) fourc outgoing (alpha, gamma, betaout) + 4) fourc azimuth (alpha, gamma, azimuth) (Not impl.) + 5) fourc fixed-phi (alpha, gamma, phi) (Not impl.) + 10) fivec bisecting (gamma) + 11) fivec incoming (gamma, betain) + 12) fivec outgoing (gamma, betaout) + 13) fivec bisecting (alpha) + 14) fivec incoming (alpha, betain) + 15) fivec outgoing (alpha, betaout) + 20) zaxis bisecting () + 21) zaxis incoming (betain) + 22) zaxiz outgoing (betaout) + + Current mode: + + 1) fourc bisecting + Parameters: + + alpha: 0.0 + gamma: 0.0 + betain: --- (not relevant in this mode) + betaout: --- (not relevant in this mode) + azimuth: --- (not relevant in this mode) + phi: --- (not relevant in this mode) + blw: --- (not relevant in this mode) + +Note that 'Not impl.' is short for 'not implemented'. Standby. + +Your output may differ. For example: + + - When listed with a typical five-circle diffractometer with no gamma + circle: the fourc modes will have no gamma parameter to fix + (actually it will have been fixed under the covers to 0), there + will be no gamma or alpha parameters to fix in the five circle + modes (again, under the covers gamma will have been fixed) and + there will be no zaxis modes (as these require six circles, or an + actual z-axis diffractometer). + + - When listed with a typical four-circle diffractometer with no alpha + or gamma circle, the four-circle modes will appear with no alpha or + gamma parameters (again, they are fixed under the covers), and + there will be no five circle or zaxis modes. + +To change the current mode, call ``hklmode`` with an argument:: + + >>> hklmode 2 + 2) fourc incoming + alpha: 0.0 + gamma: 0.0 + betain: --- + +(The dashes next to the betain parameter indicate that a parameter +has not yet been set.) + +Mode parameters +--------------- + +A parameter can be set using either one of the series of {{{set}}} +commands, by moving one of the scannables associated with each +parameter or, where appropriate, by asking that a parameter track an +axis. + +Set commands +~~~~~~~~~~~~ +Use the series of commands ``set`` to set a parameter:: + + >>> setalpha 3 + alpha: 0 --> 3.000000 + >>> setbetain 5 + WARNING: The parameter betain is not used in mode 1 + betain: --- --> 5.000000 + >>> setalpha # With no args, the current value is displayed + alpha: 3 + >>> setbetain + betain: --- + + +Parameter Scannables +~~~~~~~~~~~~~~~~~~~~ + +In most installations there will be a scannable for each parameter. In +this example installation, the parameters which correspond to physical +axes have had '_par' appended to their names to prevent clashes. These +may be used to change a parameter either with the ``pos`` command or +by using them within a scan (see :ref:`scanning-in-hkl-space`).:: + + >>> pos betain + betain: 0.00000 + >>> pos betain 5 + betain: 5.00000 + >>> setbetain + betain: 5 + + >>> pos alpha_par + alpha_par:3.00000 + >>> setalpha + alpha: 3 + + +Tracking Axis +~~~~~~~~~~~~~ +Where a parameter matches an axis name, that parameter may be set to +track that axis:: + + >>> pos alpha + alpha: 5.0000 + + >>> hklmode 1 + 1) fourc bisecting + alpha: 0.0 + gamma: 0.0 + + >>> trackalpha + alpha: 5 + + >>> pos alpha + alpha: 6.0000 + + >>> hklmode 1 + 1) fourc bisecting + alpha: 6.0 (tracking physical axis) + gamma: 0.0 + + +Although convenient, there is a danger with this method that in +geometries where the axes are built from other axes (such as in a +kappa geometry), the position of an axis may drift slightly during a +scan. + +Sectors +------- + +When mapping from reciprocal lattice space to a set of diffractometer +settings, there is normally a choice of solutions for the sample +orientation. The selected sector mode will determine which solution is +used. There is currently only one sector mode: + +Sector mode: Find first solution within sector limits +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In this sector mode, taken from 'DIF', the first solution found within +the 'sector limits' is chosen. These are different from the physical +or software limits on the axes and can be checked/modified using +``setsectorlim``:: + + >>> setsectorlim + omega_high[270]: + omega_low[-90]: + phi_high[180]: + phi_low[-180]: + + +The hkl scannable +----------------- +Once a UB matrix has been calculated, a mode chosen and parmeters set, +use the hkl scannable to move to a point in reciprocal lattice space:: + + >>> pos hkl [1,0,0] + hkl: h: 1.00000 k: -0.00000 l: -0.00000 + >>> pos sixc + sixc: alpha: 3.0000 delta: 17.2252 gamma: 4.0000 omega: 7.5046 chi: -24.6257 phi: 4.8026 + >>> pos hkl + hkl: h: 1.00000 k: -0.00000 l: -0.00000 + >>> hkl + hkl: + h : 1.000000 + k : -0.000000 + l : -0.000000 + 2theta : 18.582618 + Bin : -0.387976 + Bout : -0.387976 + azimuth : 1.646099 + +Notice that typing ``hkl`` will also display some virtual angles (such +as twotheta and Bin), that checking the position with ``pos hkl`` will +not. + +To get this extra information into a scan use the scannable hklverbose +instead of hkl:: + + >>> pos hklverbose [1,0,0] + hklverbose: h: 1.00000 k: -0.00000 l: -0.00000 2theta : 18.582618 Bin : -0.387976 + Bout :-0.387976 azimuth : 1.646099 + +The ``sim`` command will report, without moving the diffractometer, +where an hkl position would be found:: + + >>> sim hkl [1,0,0] + sixc would move to: + alpha : 3.00000 deg + delta : 17.22516 deg + gamma : 4.00000 deg + omega : 7.50461 deg + chi : -24.62568 deg + phi : 4.80260 deg + + theta : 70702.991919 + 2theta : 18.582618 + Bin : -0.387976 + Bout : -0.387976 + azimuth : 1.646099 + + + +Moving out of range +~~~~~~~~~~~~~~~~~~~ +Not every hkl position can be reached:: + + >>> pos hkl [10,10,10] + Exception: Could not compute delta for this hkl position + +The diffractometer scannable (sixc) +----------------------------------- +We've seen this before, but it also works with sim:: + + gda>>>sim sixc [3, 17.22516, 4, 7.50461, -24.62568, 4.80260] + hkl would move to: + h : 1.000000 + k : 0.000000 + l : -0.000000 + +.. _scanning-in-hkl-space: + +Scanning in hkl space +===================== + +All scans described below use the same generic scanning mechanism +provided by the GDA system or by minigda. Here are some examples. + +Fixed hkl scans +--------------- + +In a 'fixed hkl scan' something (such as energy or Bin) is scanned, +and at each step hkl is 'moved' to keep the sample and detector +aligned. Also plonk the diffractometer scannable (sixc) on there with no +destination to monitor what is actually happening and then +throw on a detector (cnt) with an exposure time if appropriate:: + + >>> #scan scannable_name start stop step [scannable_name [pos or time]].. + + >>> scan en 9 11 .5 hkl [1,0,0] sixc cnt 1 + + >>> scan en 9 11 .5 hklverbose [1,0,0] sixc cnt 1 + + >>> scan betain 4 5 .2 hkl [1,0,0] sixc cnt 1 + + >>> scan alpha_par 0 10 2 hkl [1,0,0] sixc cnt 1 + + >>> trackalpha + >>> scan alpha 0 10 2 hkl [1,0,0] sixc cnt 1 # Equivalent to last scan + +Scanning hkl +------------ + +Hkl, or one component, may also be scanned directly:: + + >>> scan h .8 1.2 .1 hklverbose sixc cnt 1 + +At each step, this will read the current hkl position, modify the h +component and then move to the resulting vector. There is a danger +that with this method k and l may drift. To get around this the start, +stop and step values may also be specified as vectors. So for example:: + + >>> scan hkl [1,0,0] [1,.3,0] [1,0.1,0] cnt1 + +is equivilant to:: + + >>> pos hkl [1,0,0] + >>> scan k 0 .3 .1 cnt1 + +but will not suffer from drifting. This method also allows scans along +any direction in hkl space to be performed. + +Multidimension scans +-------------------- + +Two and three dimensional scans:: + + >>> scan en 9 11 .5 h .9 1.1 .2 hklverbose sixc cnt 1 + >>> scan h 1 3 1 k 1 3 1 l 1 3 1 hkl cnt 1 + + + +Good luck --- RobW diff --git a/script/test/diffcalc (copy)/doc/source/youmanual.rst b/script/test/diffcalc (copy)/doc/source/youmanual.rst new file mode 100644 index 0000000..a6f1b30 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/youmanual.rst @@ -0,0 +1,888 @@ +################################ +Diffcalc User Guide (You Engine) +################################ + +.. rubric:: Diffcalc: A diffraction condition calculator for diffractometer control + +:Author: Rob Walton +:Contact: rob.walton (at) diamond (dot) ac (dot) uk +:Website: https://github.com/DiamondLightSource/diffcalc + +.. toctree:: + :maxdepth: 2 + :numbered: + +See also the `quickstart guide at github `_ + +Introduction +============ + +This manual assumes that you are running Diffcalc within OpenGDA or have started +it using IPython. It assumes that Diffcalc has been configured for the six +circle diffractometer pictured here: + +.. figure:: youmanual_images/4s_2d_diffractometer.png + :scale: 100 + :align: center + + 4s + 2d six-circle diffractometer, from H.You (1999) + +Your Diffcalc configuration may have been customised for the geometry of your +diffractometer and possibly the types of experiment you perform. For example, a +five-circle diffractometer might be missing the nu circle above. + +The laboratory frame is shown above. With all settings at zero as shown the +crystal cartesian frame aligns with the laboratory frame. Therefor a cubic +crystal mounted squarely in a way that the U matrix (defined below) is unitary +will have h||a||x, k||b||y & l||c||z, crystal and reciprocal-lattice coordinate +frames are defined with respect to the beam and to gravity to be (for a cubic +crystal): + +Overview +======== + +The following assumes that the diffractometer has been properly leveled, aligned +with the beam and zeroed. See the `SPEC fourc manual +`__. + +Before moving in hkl space you must calculate a UB matrix by specifying the +crystal's lattice parameters (which define the B matrix) and finding two +reflections (from which the U matrix defining any mismount can be inferred); +and, optionally for surface-diffraction experiments, determine how the surface +of the crystal is oriented with respect to the phi axis. + +Once a UB matrix has been calculated, the diffractometer may be driven in hkl +coordinates. A valid diffractometer setting maps easily into a single hkl value. +However for a diffractometer with more than three circles there are excess +degrees of freedom when calculating a diffractometer setting from an hkl value. +Diffcalc provides modes for using up the excess degrees of freedom. + +Diffcalc does not perform scans directly. Instead, Scannables that use diffcalc +to map between reciprocal lattice space and real diffractometer settings are +scanned using the Gda's (or minigda's) generic scan mechanism. + + +Theory +------ + +Thanks to Elias Vlieg for sharing his dos based ``DIF`` software that Diffcalc +has borrowed heavily from. The version of Diffcalc described here is based on papers by +pHH. You. [You1999]_ and Busing & Levy [Busing1967]_. (See also the THANKS.txt file.) + +Getting Help +============ + +There are few commands to remember. If a command is called without +arguments in some cases Diffcalc will prompt for arguments and provide sensible +defaults which can be chosen by pressing enter. + + +**Orientation**. The ``helpub`` command lists all commands related with crystal +orientation and the reference vector (often used with surfaces). See the +`Orientation Commands`_ section at the end of this manual:: + + >>> help ub + ... + + +**HKL movement**. The ``help hkl`` list all commands related to moving in reciprocal-lattice +space. See the `Motion Commands`_ section at the end of this manual:: + + >>> help hkl + ... + + +Call help on any command. e.g.:: + + >>> help loadub + loadub (diffcalc command): + loadub 'name' | num -- load an existing ub calculation + +Diffcalc's Scannables +===================== + +To list and show the current positions of your beamline's scannables +use ``pos`` with no arguments:: + + >>> pos + +Results in: + +**Energy and wavelength scannables**:: + + energy 12.3984 + wl: 1.0000 + +**Diffractometer scannables**, as a group and in component axes (in +the real GDA these have limits):: + + sixc: mu: 0.0000 delta: 0.0000 gamma: 0.0000 omega: 0.0000 chi: 0.0000 phi: 0.0000 + mu: 0.0000 + chi: 0.0000 + delta: 0.0000 + gamma: 0.0000 + omega: 0.0000 + phi: 0.0000 + +**Dummy counter**, which in this example simply counts at 1hit/s:: + + ct: 0.0000 + +**Hkl scannable**, as a group and in component:: + + hkl: Error: No UB matrix + h: Error: No UB matrix + k: Error: No UB matrix + l: Error: No UB matrix + +**Parameter scannables**, used in some modes, these provide a +scannable alternative to the `Motion`_ section. Some constrain of +these constrain virtual angles:: + + alpha: --- + beta: --- + naz: --- + psi: --- + qaz: --- + +and some constrain physical angles:: + + phi_con: --- + chi_con: --- + delta_con:--- + eta_con: --- + gam_con: --- + mu_con: --- + + +Crystal orientation +=================== + +Before moving in hkl space you must calculate a UB matrix by specifying the +crystal's lattice parameters (which define the B matrix) and finding two +reflections (from which the U matrix can be inferred); and, optionally for +surface-diffraction experiments, determine how the surface of the crystal is +oriented with respect to the phi axis. + +Start a new UB calculation +-------------------------- + +A *UB calculation* contains the description of the crystal-under-test, +any saved reflections, reference angle direction, and a B & UB +matrix pair if they have been calculated or manually specified. +Starting a new UB calculation will clear all of these. + +Before starting a UB-calculation, the ``ub`` command used to summarise +the state of the current UB-calculation, will reflect that no +UB-calculation has been started:: + + >>> ub + <<< No UB calculation started >>> + +A new UB-calculation calculation may be started and lattice specified +explicitly:: + + >>> newub 'example' + >>> setlat '1Acube' 1 1 1 90 90 90 + +or interactively:: + + >>> newub + calculation name: example + crystal name: 1Acube + a [1]: 1 + b [1]: 1 + c [1]: 1 + alpha [90]: 90 + beta [90]: 90 + gamma [90]: 90 + +where a,b and c are the lengths of the three unit cell basis vectors +in Angstroms, and alpha, beta and gamma are angles in Degrees. + +The ``ub`` command will show the state of the current UB-calculation +(and the current energy for reference):: + + >>> ub + UBCALC + + name: example + + n_phi: 0.00000 0.00000 1.00000 <- set + + CRYSTAL + + name: 1Acube + + a, b, c: 1.00000 1.00000 1.00000 + 90.00000 90.00000 90.00000 + + B matrix: 6.28319 0.00000 0.00000 + 0.00000 6.28319 0.00000 + 0.00000 0.00000 6.28319 + + UB MATRIX + + <<< none calculated >>> + + REFLECTIONS + + <<< none specified >>> + + CRYSTAL ORIENTATIONS + + <<< none specified >>> + +Load a UB calculation +--------------------- + +To load the last used UB-calculation:: + + >>> lastub + Loading ub calculation: 'mono-Si' + +To load a previous UB-calculation:: + + >>> listub + UB calculations in: /Users/walton/.diffcalc/i16 + + 0) mono-Si 15 Feb 2017 (22:32) + 1) i16-32 13 Feb 2017 (18:32) + + >>> loadub 0 + +Generate a U matrix from two reflections +---------------------------------------- + +The normal way to calculate a U matrix is to find the position of **two** +reflections with known hkl values. Diffcalc allows many reflections to be +recorded but currently only uses the first two when calculating a UB matrix. + +Find U matrix from two reflections:: + + >>> pos wl 1 + wl: 1.0000 + >>> c2th [0 0 1] + 59.99999999999999 + + >>> pos sixc [0 60 0 30 90 0] + sixc: mu: 0.0000 delta: 60.0000 gam: 0.0000 eta: 30.0000 chi: 90.0000 phi: 0.0000 + >>> addref [0 0 1] + + >>> pos sixc [0 90 0 45 45 90] + sixc: mu: 0.0000 delta: 90.0000 gam: 0.0000 eta: 45.0000 chi: 45.0000 phi: 90.0000 + >>> addref [0 1 1] + Calculating UB matrix. + +Check that it looks good:: + + >>> checkub + + ENERGY H K L H_COMP K_COMP L_COMP TAG + 1 12.3984 0.00 0.00 1.00 0.0000 0.0000 1.0000 + 2 12.3984 0.00 1.00 1.00 0.0000 1.0000 1.0000 + +Generate a U matrix from one reflection +--------------------------------------- + +To estimate based on first reflection only:: + + >>> trialub + resulting U angle: 0.00000 deg + resulting U axis direction: [-1.00000, 0.00000, 0.00000] + Recalculating UB matrix from the first reflection only. + NOTE: A new UB matrix will not be automatically calculated when the orientation reflections are modified. + +Edit reflection list +-------------------- + +Use ``showref`` to show the reflection list:: + + >>> showref + ENERGY H K L MU DELTA GAM ETA CHI PHI TAG + 1 12.398 0.00 0.00 1.00 0.0000 60.0000 0.0000 30.0000 90.0000 0.0000 + 2 12.398 0.00 1.00 1.00 0.0000 90.0000 0.0000 45.0000 45.0000 90.0000 + +Use ``swapref`` to swap reflections:: + + >>> swapref 1 2 + Not calculating UB matrix as it has been manually set. Use 'calcub' to explicitly recalculate it. + Recalculating UB matrix. + +Use ``delref`` to delete a reflection:: + + >>> delref 1 + +Generate a U matrix from two lattice directions +----------------------------------------------- + +Another approach to calculate a U matrix is to provide orientation of **two** crystal lattice +directions in laboratory frame of reference using ``addorient`` command. The first lattice +direction will be aligned along the specified in the laboratory frame. The second lattice +direction will be used to set azimuthal orientation of the crystal in the plane perpendicular +to the first lattice orientation. Diffcalc allows many lattice directions to be recorded but +currently uses only the first two when calculating a UB matrix. + +Find U matrix from two lattice directions:: + + >>> addorient [0 0 1] [0 0 1] + + >>> addorient [1 0 0] [1 1 0] + Calculating UB matrix. + +Calculate a UB matrix +--------------------- + +Unless a U or UB matrix has been manually specified, a new UB matrix will be +calculated after the second reflection has been found, or whenever one of the +first two reflections is changed. + +Use the command ``calcub`` to force the UB matrix to be calculated from the +first two reflections. In case of using lattice orientations instead of reflections, +use command ``orientub`` to force the UB matrix to be calculated from the first two orientations. + +If you have misidentified a reflection used for the orientation the +resulting UB matrix will be incorrect. Always use the ``checkub``command +to check that the computed reflection indices agree with the estimated values:: + + >>> checkub + + ENERGY H K L H_COMP K_COMP L_COMP TAG + 1 12.3984 0.00 1.00 1.00 0.0000 1.0000 1.0000 + 2 12.3984 0.00 0.00 1.00 0.0000 0.0000 1.0000 + +Calculate a U matrix from crystal mismount +------------------------------------------- + +U matrix can be defined from crystal mismount by using a rotation matrix calculated from a provided +mismount angle and axis. ``setmiscut`` command defines new U matrix by setting it to a rotation matrix +calculated from the specified angle and axis parameters. ``addmiscut`` command applies the calculated +rotation matrix to the existing U matrix, i.e. adds extra mismount to the already existing one:: + + >>> setmiscut 5 [1 0 0] + n_phi: -0.00000 -0.08716 0.99619 + n_hkl: 0.00000 0.00000 1.00000 <- set + normal: + angle: 5.00000 + axis: 1.00000 -0.00000 0.00000 + + +Manually specify U matrix +------------------------- + +Set U matrix manually (pretending sample is squarely mounted):: + + >>> setu [[1 0 0] [0 1 0] [0 0 1]] + Recalculating UB matrix. + NOTE: A new UB matrix will not be automatically calculated when the orientation reflections are modified. + +Refining UB matrix from reflection +---------------------------------- + +UB matrix elements can be refined to match diffractometer settings and crystal orientation experimentally +found for a given reflection with the corresponding reflection indices. ``refineub`` command rescales +crystal unit cell dimensions to match with the found scattering angle value and recalculates mismount +parameters to update U matrix:: + + >>> refineub [1 0 0] + current pos[y]: y + Unit cell scaling factor: 0.99699 + Refined crystal lattice: + a, b, c: 0.99699 0.99699 0.99699 + 90.00000 90.00000 90.00000 + + Update crystal settings?[y]: y + Warning: the old UB calculation has been cleared. + Use 'calcub' to recalculate with old reflections or + 'orientub' to recalculate with old orientations. + Miscut parameters: + angle: 2.90000 + axis: -0.00000 1.00000 -0.00000 + Apply miscut parameters?[y]: y + n_phi: 0.67043 -0.00000 0.74198 + n_hkl: 0.00000 0.00000 1.00000 <- set + normal: + angle: 42.10000 + axis: 0.00000 1.00000 0.00000 + +Set the reference vector +------------------------- + +When performing surface experiments the reference vector should be set normal +to the surface. It can also be used to define other directions within the crystal +with which we want to orient the incident or diffracted beam. + +By default the reference vector is set parallel to the phi axis. That is, +along the z-axis of the phi coordinate frame. + +The `ub` command shows the current reference vector along with the orientation relative to +the z-axis, at the top its report (or it can be shown by calling ``setnphi`` or +``setnhkl'`` with no args):: + + >>> ub + ... + n_phi: 0.00000 0.00000 1.00000 <- set + n_hkl: -0.00000 0.00000 1.00000 + normal: None + ... + +The ``<- set`` label here indicates that the reference vector is set in the phi +coordinate frame. In this case, therefore, its direction in the crystal's +reciprocal lattice space is inferred from the UB matrix. + +To set the reference vector in the phi coordinate frame use:: + + >>> setnphi [0 0 1] + ... + +This is useful if the surface normal has be found with a laser or by x-ray +occlusion. This vector must currently be manually calculated from the sample +angle settings required to level the surface (sigma and tau commands on the +way). + +To set the reference vector in the crystal's reciprocal lattice space use (this +is a quick way to determine the surface orientation if the surface is known to +be cleaved cleanly along a known axis):: + + >>> setnhkl [0 0 1] + ... + +Motion +====== + +Once a UB matrix has been calculated, the diffractometer may be driven +in hkl coordinates. A given diffractometer setting maps easily into a +single hkl value. However for a diffractometer with more than three circles +there are excess degrees of freedom when calculating a diffractometer +setting from an hkl value. Diffcalc provides many for using up +the excess degrees of freedom. + +By default Diffcalc selects no mode. + +Constraining solutions for moving in hkl space +---------------------------------------------- + +To get help and see current constraints:: + + >>> help con + ... + + >>> con + DET REF SAMP + ------ ------ ------ + delta a_eq_b mu + gam alpha eta + qaz beta chi + naz psi phi + mu_is_gam + + ! 3 more constraints required + + Type 'help con' for instructions + +Three constraints can be given: zero or one from the DET and REF columns and the +remainder from the SAMP column. Not all combinations are currently available. +Use ``help con`` to see a summary if you run into troubles. + +To configure four-circle vertical scattering:: + + >>> con gam 0 mu 0 a_eq_b + gam : 0.0000 + a_eq_b + mu : 0.0000 + +In the following the *scattering plane* is defined as the plane including the +scattering vector, or momentum transfer vector, and the incident beam. + +**DETECTOR COLUMN:** + +- **delta** - physical delta setting (vertical detector motion) *del=0 is equivalent to qaz=0* +- **gam** - physical gamma setting (horizontal detector motion) *gam=0 is equivalent to qaz=90* +- **qaz** - azimuthal rotation of scattering vector (about the beam, from horizontal) +- **naz** - azimuthal rotation of reference vector (about the beam, from horizontal) + +**REFERENCE COLUMN:** + +- **alpha** - incident angle to surface (if reference is normal to surface) +- **beta** - exit angle from surface (if reference is normal to surface) +- **psi** - azimuthal rotation about scattering vector of reference vector (from scattering plane) +- **a_eq_b** - bisecting mode with alpha=beta. *Equivalent to psi=90* + +**SAMPLE COLUMN:** + +- **mu, eta, chi & phi** - physical settings +- **mu_is_gam** - force mu to follow gamma (results in a 5-circle geometry) + +Diffcalc will report two other (un-constrainable) virtual angles: + +- **theta** - half of 2theta, the angle through the diffracted beam bends +- **tau** - longitude of reference vector from scattering vector (in scattering plane) + +Example constraint modes +------------------------ + +There is sometimes more than one way to get the same effect. + +**Vertical four-circle mode**:: + + >>> con gam 0 mu 0 a_eq_b # or equivalently: + >>> con qaz 90 mu 0 a_eq_b + + >>> con alpha 1 # replaces a_eq_b + +**Horizontal four-circle mode**:: + + >>> con del 0 eta 0 alpha 1 # or equivalently: + >>> con qaz 0 mu 0 alpha 1 + +**Surface vertical mode**:: + + >>> con naz 90 mu 0 alpha 1 + +**Surface horizontal mode**:: + + >>> con naz 0 eta 0 alpha 1 + +**Z-axis mode (surface horizontal)**:: + + >>> con chi (-sigma) phi (-tau) alpha 1 + +where sigma and tau are the offsets required in chi and phi to bring the surface +normal parallel to eta. Alpha will determine mu directly leaving eta to orient +the planes. Or:: + + >>> con naz 0 phi 0 alpha 1 # or any another sample angle + +**Z-axis mode (surface vertical)**:: + + >>> con naz 0 phi 0 alpha 1 # or any another sample angle + +Changing constrained values +--------------------------- + +Once constraints are chosen constrained values may be changed directly:: + + >>> con mu 10 + gam : 0.0000 + a_eq_b + mu : 10.0000 + +or via the associated scannable:: + + >>> pos mu_con 10 + mu_con: 10.00000 + +Configuring limits and cuts +--------------------------- + +Diffcalc maintains its own limits on axes. These limits will be used when +choosing solutions. If more than one detector solution is exists Diffcalc will +ask you to reduce the the limits until there is only one. However if more than +one solution for the sample settings is available it will choose one base on +heuristics. + +Use the ``hardware`` command to see the current limits and cuts:: + + >>> hardware + mu (cut: -180.0) + delta (cut: -180.0) + gam (cut: -180.0) + eta (cut: -180.0) + chi (cut: -180.0) + phi (cut: 0.0) + Note: When auto sector/transforms are used, + cuts are applied before checking limits. + +To set the limits:: + + >>> setmin delta -1 + >>> setmax delta 145 + +To set a cut:: + + >>> setcut phi -180 + +This causes requests to move phi to be between the configured -180 and +360 +degress above this. i.e. it might dive to -10 degrees rather than 350. + + +Moving in hkl space +------------------- + +Configure a mode, e.g. four-circle vertical:: + + >>> con gam 0 mu 0 a_eq_b + gam : 0.0000 + a_eq_b + mu : 0.0000 + +Simulate moving to a reflection:: + + >>> sim hkl [0 1 1] + sixc would move to: + mu : 0.0000 + delta : 90.0000 + gam : 0.0000 + eta : 45.0000 + chi : 45.0000 + phi : 90.0000 + + alpha : 30.0000 + beta : 30.0000 + naz : 35.2644 + psi : 90.0000 + qaz : 90.0000 + tau : 45.0000 + theta : 45.0000 + +Move to reflection:: + + >>> pos hkl [0 1 1] + hkl: h: 0.00000 k: 1.00000 l: 1.00000 + + >>> pos sixc + sixc: mu: 0.0000 delta: 90.0000 gam: 0.0000 eta: 45.0000 chi: 45.0000 phi: 90.0000 + +Simulate moving to a location:: + + >>> pos sixc [0 60 0 30 90 0] + sixc: mu: 0.0000 delta: 60.0000 gam: 0.0000 eta: 30.0000 chi: 90.0000 phi: 0.0000 + +Scanning in hkl space +===================== + +All scans described below use the same generic scanning mechanism +provided by the GDA system or by minigda. Here are some examples. + +Fixed hkl scans +--------------- + +In a 'fixed hkl scan' something (such as energy or Bin) is scanned, +and at each step hkl is 'moved' to keep the sample and detector +aligned. Also plonk the diffractometer scannable (sixc) on there with no +destination to monitor what is actually happening and then +throw on a detector (ct) with an exposure time if appropriate:: + + >>> #scan scannable_name start stop step [scannable_name [pos or time]].. + + >>> scan en 9 11 .5 hkl [1 0 0] sixc ct 1 + + >>> scan en 9 11 .5 hklverbose [1 0 0] sixc ct 1 + + >>> scan betain 4 5 .2 hkl [1 0 0] sixc ct 1 + + >>> scan alpha_par 0 10 2 hkl [1 0 0] sixc ct 1 + +Scanning hkl +------------ + +Hkl, or one component, may also be scanned directly:: + + >>> scan h .8 1.2 .1 hklverbose sixc ct 1 + +At each step, this will read the current hkl position, modify the h +component and then move to the resulting vector. There is a danger +that with this method k and l may drift. To get around this the start, +stop and step values may also be specified as vectors. So for example:: + + >>> scan hkl [1 0 0] [1 .3 0] [1 0.1 0] ct1 + +is equivilant to:: + + >>> pos hkl [1 0 0] + >>> scan k 0 .3 .1 ct1 + +but will not suffer from drifting. This method also allows scans along +any direction in hkl space to be performed. + +Multidimension scans +-------------------- + +Two and three dimensional scans:: + + >>> scan en 9 11 .5 h .9 1.1 .2 hklverbose sixc ct 1 + >>> scan h 1 3 1 k 1 3 1 l 1 3 1 hkl ct 1 + +Commands +======== + +Orientation Commands +-------------------- + ++-----------------------------+---------------------------------------------------+ +| **STATE** | ++-----------------------------+---------------------------------------------------+ +| **-- newub** {'name'} | start a new ub calculation name | ++-----------------------------+---------------------------------------------------+ +| **-- loadub** 'name' | num | load an existing ub calculation | ++-----------------------------+---------------------------------------------------+ +| **-- lastub** | load the last used ub calculation | ++-----------------------------+---------------------------------------------------+ +| **-- listub** | list the ub calculations available to load | ++-----------------------------+---------------------------------------------------+ +| **-- rmub** 'name'|num | remove existing ub calculation | ++-----------------------------+---------------------------------------------------+ +| **-- saveubas** 'name' | save the ub calculation with a new name | ++-----------------------------+---------------------------------------------------+ +| **LATTICE** | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** | interactively enter lattice parameters (Angstroms | +| | and Deg) | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** name a | assumes cubic | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** name a b | assumes tetragonal | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** name a b c | assumes ortho | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** name a b c | assumes mon/hex with gam not equal to 90 | +| gamma | | ++-----------------------------+---------------------------------------------------+ +| **-- setlat** name a b c | arbitrary | +| alpha beta gamma | | ++-----------------------------+---------------------------------------------------+ +| **-- c2th** [h k l] | calculate two-theta angle for reflection | ++-----------------------------+---------------------------------------------------+ +| **-- hklangle** [h1 k1 l1] | calculate angle between [h1 k1 l1] and [h2 k2 l2] | +| [h2 k2 l2] | crystal planes | ++-----------------------------+---------------------------------------------------+ +| **REFERENCE (SURFACE)** | ++-----------------------------+---------------------------------------------------+ +| **-- setnphi** {[x y z]} | sets or displays n_phi reference | ++-----------------------------+---------------------------------------------------+ +| **-- setnhkl** {[h k l]} | sets or displays n_hkl reference | ++-----------------------------+---------------------------------------------------+ +| **REFLECTIONS** | ++-----------------------------+---------------------------------------------------+ +| **-- showref** | shows full reflection list | ++-----------------------------+---------------------------------------------------+ +| **-- addref** | add reflection interactively | ++-----------------------------+---------------------------------------------------+ +| **-- addref** [h k l] | add reflection with current position and energy | +| {'tag'} | | ++-----------------------------+---------------------------------------------------+ +| **-- addref** [h k l] (p1, | add arbitrary reflection | +| .., pN) energy {'tag'} | | ++-----------------------------+---------------------------------------------------+ +| **-- editref** num | interactively edit a reflection | ++-----------------------------+---------------------------------------------------+ +| **-- delref** num | deletes a reflection (numbered from 1) | ++-----------------------------+---------------------------------------------------+ +| **-- clearref** | deletes all the reflections | ++-----------------------------+---------------------------------------------------+ +| **-- swapref** | swaps first two reflections used for calculating | +| | U matrix | ++-----------------------------+---------------------------------------------------+ +| **-- swapref** num1 num2 | swaps two reflections (numbered from 1) | ++-----------------------------+---------------------------------------------------+ +| **CRYSTAL ORIENTATIONS** | ++-----------------------------+---------------------------------------------------+ +| **-- showorient** | shows full list of crystal orientations | ++-----------------------------+---------------------------------------------------+ +| **-- addorient** | add crystal orientation interactively | ++-----------------------------+---------------------------------------------------+ +| **-- addorient** [h k l] | add crystal orientation in laboratory frame | +| [x y z] {'tag'} | | ++-----------------------------+---------------------------------------------------+ +| **-- editorient** num | interactively edit a crystal orientation | ++-----------------------------+---------------------------------------------------+ +| **-- delorient** num | deletes a crystal orientation (numbered from 1) | ++-----------------------------+---------------------------------------------------+ +| **-- clearorient** | deletes all the crystal orientations | ++-----------------------------+---------------------------------------------------+ +| **-- swaporient** | swaps first two crystal orientations used for | +| | calculating U matrix | ++-----------------------------+---------------------------------------------------+ +| **-- swaporient** num1 num2 | swaps two crystal orientations (numbered from 1) | ++-----------------------------+---------------------------------------------------+ +| **UB MATRIX** | ++-----------------------------+---------------------------------------------------+ +| **-- checkub** | show calculated and entered hkl values for | +| | reflections | ++-----------------------------+---------------------------------------------------+ +| **-- setu** | manually set u matrix | +| {[[..][..][..]]} | | ++-----------------------------+---------------------------------------------------+ +| **-- setub** | manually set ub matrix | +| {[[..][..][..]]} | | ++-----------------------------+---------------------------------------------------+ +| **-- calcub** | (re)calculate u matrix from ref1 and ref2 | ++-----------------------------+---------------------------------------------------+ +| **-- trialub** | (re)calculate u matrix from ref1 only (check | +| | carefully) | ++-----------------------------+---------------------------------------------------+ +| **-- refineub** {[h k l]} | refine unit cell dimensions and U matrix to match | +| {pos} | diffractometer angles for a given hkl value | ++-----------------------------+---------------------------------------------------+ +| **-- addmiscut** angle | apply miscut to U matrix using a specified miscut | +| {[x y z]} | angle in degrees and a rotation axis | +| | (default: [0 1 0]) | ++-----------------------------+---------------------------------------------------+ +| **-- setmiscut** angle | manually set U matrix using a specified miscut | +| {[x y z]} | angle in degrees and a rotation axis | +| | (default: [0 1 0]) | ++-----------------------------+---------------------------------------------------+ + +Motion commands +--------------- + ++-----------------------------+---------------------------------------------------+ +| **CONSTRAINTS** | ++-----------------------------+---------------------------------------------------+ +| **-- con** | list available constraints and values | ++-----------------------------+---------------------------------------------------+ +| **-- con** {val} | constrains and optionally sets one constraint | ++-----------------------------+---------------------------------------------------+ +| **-- con** {val} | clears and then fully constrains | +| {val} {val} | | ++-----------------------------+---------------------------------------------------+ +| **-- uncon** | remove constraint | ++-----------------------------+---------------------------------------------------+ +| **HKL** | ++-----------------------------+---------------------------------------------------+ +| **-- allhkl** [h k l] | print all hkl solutions ignoring limits | ++-----------------------------+---------------------------------------------------+ +| **HARDWARE** | ++-----------------------------+---------------------------------------------------+ +| **-- hardware** | show diffcalc limits and cuts | ++-----------------------------+---------------------------------------------------+ +| **-- setcut** {name {val}} | sets cut angle | ++-----------------------------+---------------------------------------------------+ +| **-- setmin** {axis {val}} | set lower limits used by auto sector code (None | +| | to clear) | ++-----------------------------+---------------------------------------------------+ +| **-- setmax** {name {val}} | sets upper limits used by auto sector code (None | +| | to clear) | ++-----------------------------+---------------------------------------------------+ +| **MOTION** | ++-----------------------------+---------------------------------------------------+ +| **-- sim** hkl scn | simulates moving scannable (not all) | ++-----------------------------+---------------------------------------------------+ +| **-- sixc** | show Eularian position | ++-----------------------------+---------------------------------------------------+ +| **-- pos** sixc [mu, delta, | move to Eularian position(None holds an axis | +| gam, eta, chi, phi] | still) | ++-----------------------------+---------------------------------------------------+ +| **-- sim** sixc [mu, delta, | simulate move to Eulerian positionsixc | +| gam, eta, chi, phi] | | ++-----------------------------+---------------------------------------------------+ +| **-- hkl** | show hkl position | ++-----------------------------+---------------------------------------------------+ +| **-- pos** hkl [h k l] | move to hkl position | ++-----------------------------+---------------------------------------------------+ +| **-- pos** {h | k | l} val | move h, k or l to val | ++-----------------------------+---------------------------------------------------+ +| **-- sim** hkl [h k l] | simulate move to hkl position | ++-----------------------------+---------------------------------------------------+ + +Good luck --- RobW + +References +========== + +.. [You1999] H. You. *Angle calculations for a '4S+2D' six-circle diffractometer.* + J. Appl. Cryst. (1999). **32**, 614-623. `(pdf link) + `__. +.. [Busing1967] W. R. Busing and H. A. Levy. *Angle calculations for 3- and 4-circle X-ray + and neutron diffractometers.* Acta Cryst. (1967). **22**, 457-464. `(pdf link) + `__. diff --git a/script/test/diffcalc (copy)/doc/source/youmanual_images/4s_2d_diffractometer.png b/script/test/diffcalc (copy)/doc/source/youmanual_images/4s_2d_diffractometer.png new file mode 100644 index 0000000..5216255 Binary files /dev/null and b/script/test/diffcalc (copy)/doc/source/youmanual_images/4s_2d_diffractometer.png differ diff --git a/script/test/diffcalc (copy)/doc/source/youmanual_template.rst b/script/test/diffcalc (copy)/doc/source/youmanual_template.rst new file mode 100644 index 0000000..1b32fff --- /dev/null +++ b/script/test/diffcalc (copy)/doc/source/youmanual_template.rst @@ -0,0 +1,565 @@ +################################ +Diffcalc User Guide (You Engine) +################################ + +.. rubric:: Diffcalc: A diffraction condition calculator for diffractometer control + +:Author: Rob Walton +:Contact: rob.walton (at) diamond (dot) ac (dot) uk +:Website: https://github.com/DiamondLightSource/diffcalc + +.. toctree:: + :maxdepth: 2 + :numbered: + +See also the `quickstart guide at github `_ + +Introduction +============ + +This manual assumes that you are running Diffcalc within OpenGDA or have started +it using IPython. It assumes that Diffcalc has been configured for the six +circle diffractometer pictured here: + +.. figure:: youmanual_images/4s_2d_diffractometer.png + :scale: 100 + :align: center + + 4s + 2d six-circle diffractometer, from H.You (1999) + +Your Diffcalc configuration may have been customised for the geometry of your +diffractometer and possibly the types of experiment you perform. For example, a +five-circle diffractometer might be missing the nu circle above. + +The laboratory frame is shown above. With all settings at zero as shown the +crystal cartesian frame aligns with the laboratory frame. Therefor a cubic +crystal mounted squarely in a way that the U matrix (defined below) is unitary +will have h||a||x, k||b||y & l||c||z, crystal and reciprocal-lattice coordinate +frames are defined with respect to the beam and to gravity to be (for a cubic +crystal): + +Overview +======== + +The following assumes that the diffractometer has been properly leveled, aligned +with the beam and zeroed. See the `SPEC fourc manual +`__. + +Before moving in hkl space you must calculate a UB matrix by specifying the +crystal's lattice parameters (which define the B matrix) and finding two +reflections (from which the U matrix defining any mismount can be inferred); +and, optionally for surface-diffraction experiments, determine how the surface +of the crystal is oriented with respect to the phi axis. + +Once a UB matrix has been calculated, the diffractometer may be driven in hkl +coordinates. A valid diffractometer setting maps easily into a single hkl value. +However for a diffractometer with more than three circles there are excess +degrees of freedom when calculating a diffractometer setting from an hkl value. +Diffcalc provides modes for using up the excess degrees of freedom. + +Diffcalc does not perform scans directly. Instead, Scannables that use diffcalc +to map between reciprocal lattice space and real diffractometer settings are +scanned using the Gda's (or minigda's) generic scan mechanism. + + +Theory +------ + +Thanks to Elias Vlieg for sharing his dos based ``DIF`` software that Diffcalc +has borrowed heavily from. The version of Diffcalc described here is based on papers by +pHH. You. [You1999]_ and Busing & Levy [Busing1967]_. (See also the THANKS.txt file.) + +Getting Help +============ + +There are few commands to remember. If a command is called without +arguments in some cases Diffcalc will prompt for arguments and provide sensible +defaults which can be chosen by pressing enter. + + +**Orientation**. The ``helpub`` command lists all commands related with crystal +orientation and the reference vector (often used with surfaces). See the +`Orientation Commands`_ section at the end of this manual:: + + >>> help ub + ... + + +**HKL movement**. The ``help hkl`` list all commands related to moving in reciprocal-lattice +space. See the `Motion Commands`_ section at the end of this manual:: + + >>> help hkl + ... + + +Call help on any command. e.g.:: + + ==> help loadub + +Diffcalc's Scannables +===================== + +To list and show the current positions of your beamline's scannables +use ``pos`` with no arguments:: + + >>> pos + +Results in: + +**Energy and wavelength scannables**:: + + energy 12.3984 + wl: 1.0000 + +**Diffractometer scannables**, as a group and in component axes (in +the real GDA these have limits):: + + sixc: mu: 0.0000 delta: 0.0000 gamma: 0.0000 omega: 0.0000 chi: 0.0000 phi: 0.0000 + mu: 0.0000 + chi: 0.0000 + delta: 0.0000 + gamma: 0.0000 + omega: 0.0000 + phi: 0.0000 + +**Dummy counter**, which in this example simply counts at 1hit/s:: + + ct: 0.0000 + +**Hkl scannable**, as a group and in component:: + + hkl: Error: No UB matrix + h: Error: No UB matrix + k: Error: No UB matrix + l: Error: No UB matrix + +**Parameter scannables**, used in some modes, these provide a +scannable alternative to the `Motion`_ section. Some constrain of +these constrain virtual angles:: + + alpha: --- + beta: --- + naz: --- + psi: --- + qaz: --- + +and some constrain physical angles:: + + phi_con: --- + chi_con: --- + delta_con:--- + eta_con: --- + gam_con: --- + mu_con: --- + + +Crystal orientation +=================== + +Before moving in hkl space you must calculate a UB matrix by specifying the +crystal's lattice parameters (which define the B matrix) and finding two +reflections (from which the U matrix can be inferred); and, optionally for +surface-diffraction experiments, determine how the surface of the crystal is +oriented with respect to the phi axis. + +Start a new UB calculation +-------------------------- + +A *UB calculation* contains the description of the crystal-under-test, +any saved reflections, reference angle direction, and a B & UB +matrix pair if they have been calculated or manually specified. +Starting a new UB calculation will clear all of these. + +Before starting a UB-calculation, the ``ub`` command used to summarise +the state of the current UB-calculation, will reflect that no +UB-calculation has been started:: + + ==> ub + +A new UB-calculation calculation may be started and lattice specified +explicitly:: + + ==> newub 'example' + ==> setlat '1Acube' 1 1 1 90 90 90 + +or interactively:: + + >>> newub + calculation name: example + crystal name: 1Acube + a [1]: 1 + b [1]: 1 + c [1]: 1 + alpha [90]: 90 + beta [90]: 90 + gamma [90]: 90 + +where a,b and c are the lengths of the three unit cell basis vectors +in Angstroms, and alpha, beta and gamma are angles in Degrees. + +The ``ub`` command will show the state of the current UB-calculation +(and the current energy for reference):: + + ==> ub + +Load a UB calculation +--------------------- + +To load the last used UB-calculation:: + + >>> lastub + Loading ub calculation: 'mono-Si' + +To load a previous UB-calculation:: + + >>> listub + UB calculations in: /Users/walton/.diffcalc/i16 + + 0) mono-Si 15 Feb 2017 (22:32) + 1) i16-32 13 Feb 2017 (18:32) + + >>> loadub 0 + +Generate a U matrix from two reflections +---------------------------------------- + +The normal way to calculate a U matrix is to find the position of **two** +reflections with known hkl values. Diffcalc allows many reflections to be +recorded but currently only uses the first two when calculating a UB matrix. + +Find U matrix from two reflections:: + + ==> pos wl 1 + ==> c2th [0 0 1] + 59.99999999999999 + + ==> pos sixc [0 60 0 30 90 0] + ==> addref [0 0 1] + + ==> pos sixc [0 90 0 45 45 90] + ==> addref [0 1 1] + +Check that it looks good:: + + ==> checkub + +Generate a U matrix from one reflection +--------------------------------------- + +To estimate based on first reflection only:: + + ==> trialub + +Manually specify U matrix +------------------------- + +Set U matrix manually (pretending sample is squarely mounted):: + + ==> setu [[1 0 0] [0 1 0] [0 0 1]] + +Edit reflection list +-------------------- + +Use ``showref`` to show the reflection list:: + + ==> showref + +Use ``swapref`` to swap reflections:: + + ==> swapref 1 2 + Recalculating UB matrix. + +Use ``delref`` to delete a reflection:: + + >>> delref 1 + +Calculate a UB matrix +--------------------- + +Unless a U or UB matrix has been manually specified, a new UB matrix will be +calculated after the second reflection has been found, or whenever one of the +first two reflections is changed. + +Use the command ``calcub`` to force the UB matrix to be calculated from the +first two reflections. + +If you have misidentified a reflection used for the orientation the +resulting UB matrix will be incorrect. Always use the ``checkub`` +command to check that the computed values agree with the estimated values:: + + ==> checkub + +Set the reference vector +------------------------- + +When performing surface experiments the reference vector should be set normal +to the surface. It can also be used to define other directions within the crystal +with which we want to orient the incident or diffracted beam. + +By default the reference vector is set parallel to the phi axis. That is, +along the z-axis of the phi coordinate frame. + +The `ub` command shows the current reference vector, along with any inferred +miscut, at the top its report (or it can be shown by calling ``setnphi`` or +``setnhkl'`` with no args):: + + >>> ub + ... + n_phi: 0.00000 0.00000 1.00000 <- set + n_hkl: -0.00000 0.00000 1.00000 + miscut: None + ... + +The ``<- set`` label here indicates that the reference vector is set in the phi +coordinate frame. In this case, therefor, its direction in the crystal's +reciprocal lattice space is inferred from the UB matrix. + +To set the reference vector in the phi coordinate frame use:: + + >>> setnphi [0 0 1] + ... + +This is useful if the surface normal has be found with a laser or by x-ray +occlusion. This vector must currently be manually calculated from the sample +angle settings required to level the surface (sigma and tau commands on the +way). + +To set the reference vector in the crystal's reciprocal lattice space use (this +is a quick way to determine the surface orientation if the surface is known to +be cleaved cleanly along a known axis):: + + >>> setnhkl [0 0 1] ... + +Motion +====== + +Once a UB matrix has been calculated, the diffractometer may be driven +in hkl coordinates. A given diffractometer setting maps easily into a +single hkl value. However for a diffractometer with more than three circles +there are excess degrees of freedom when calculating a diffractometer +setting from an hkl value. Diffcalc provides many for using up +the excess degrees of freedom. + +By default Diffcalc selects no mode. + +Constraining solutions for moving in hkl space +---------------------------------------------- + +To get help and see current constraints:: + + >>> help con + ... + + ==> con + +Three constraints can be given: zero or one from the DET and REF columns and the +remainder from the SAMP column. Not all combinations are currently available. +Use ``help con`` to see a summary if you run into troubles. + +To configure four-circle vertical scattering:: + + ==> con gam 0 mu 0 a_eq_b + +In the following the *scattering plane* is defined as the plane including the +scattering vector, or momentum transfer vector, and the incident beam. + +**DETECTOR COLUMN:** + +- **delta** - physical delta setting (vertical detector motion) *del=0 is equivalent to qaz=0* +- **gam** - physical gamma setting (horizontal detector motion) *gam=0 is equivalent to qaz=90* +- **qaz** - azimuthal rotation of scattering vector (about the beam, from horizontal) +- **naz** - azimuthal rotation of reference vector (about the beam, from horizontal) + +**REFERENCE COLUMN:** + +- **alpha** - incident angle to surface (if reference is normal to surface) +- **beta** - exit angle from surface (if reference is normal to surface) +- **psi** - azimuthal rotation about scattering vector of reference vector (from scattering plane) +- **a_eq_b** - bisecting mode with alpha=beta. *Equivalent to psi=90* + +**SAMPLE COLUMN:** + +- **mu, eta, chi & phi** - physical settings +- **mu_is_gam** - force mu to follow gamma (results in a 5-circle geometry) + +Diffcalc will report two other (un-constrainable) virtual angles: + +- **theta** - half of 2theta, the angle through the diffracted beam bends +- **tau** - longitude of reference vector from scattering vector (in scattering plane) + +Example constraint modes +------------------------ + +There is sometimes more than one way to get the same effect. + +**Vertical four-circle mode**:: + + >>> con gam 0 mu 0 a_eq_b # or equivalently: + >>> con qaz 90 mu 0 a_eq_b + + >>> con alpha 1 # replaces a_eq_b + +**Horizontal four-circle mode**:: + + >>> con del 0 eta 0 alpha 1 # or equivalently: + >>> con qaz 0 mu 0 alpha 1 + +**Surface vertical mode**:: + + >>> con naz 90 mu 0 alpha 1 + +**Surface horizontal mode**:: + + >>> con naz 0 eta 0 alpha 1 + +**Z-axis mode (surface horizontal)**:: + + >>> con chi (-sigma) phi (-tau) alpha 1 + +where sigma and tau are the offsets required in chi and phi to bring the surface +normal parallel to eta. Alpha will determine mu directly leaving eta to orient +the planes. Or:: + + >>> con naz 0 phi 0 alpha 1 # or any another sample angle + +**Z-axis mode (surface vertical)**:: + + >>> con naz 0 phi 0 alpha 1 # or any another sample angle + +Changing constrained values +--------------------------- + +Once constraints are chosen constrained values may be changed directly:: + + ==> con mu 10 + +or via the associated scannable:: + + ==> pos mu_con 10 + +Configuring limits and cuts +--------------------------- + +Diffcalc maintains its own limits on axes. These limits will be used when +choosing solutions. If more than one detector solution is exists Diffcalc will +ask you to reduce the the limits until there is only one. However if more than +one solution for the sample settings is available it will choose one base on +heuristics. + +Use the ``hardware`` command to see the current limits and cuts:: + + ==> hardware + +To set the limits:: + + ==> setmin delta -1 + ==> setmax delta 145 + +To set a cut:: + + ==> setcut phi -180 + +This causes requests to move phi to be between the configured -180 and +360 +degress above this. i.e. it might dive to -10 degrees rather than 350. + + +Moving in hkl space +------------------- + +Configure a mode, e.g. four-circle vertical:: + + ==> con gam 0 mu 0 a_eq_b + +Simulate moving to a reflection:: + + ==> sim hkl [0 1 1] + +Move to reflection:: + + ==> pos hkl [0 1 1] + + ==> pos sixc + +Simulate moving to a location:: + + ==> pos sixc [0 60 0 30 90 0] + +Scanning in hkl space +===================== + +All scans described below use the same generic scanning mechanism +provided by the GDA system or by minigda. Here are some examples. + +Fixed hkl scans +--------------- + +In a 'fixed hkl scan' something (such as energy or Bin) is scanned, +and at each step hkl is 'moved' to keep the sample and detector +aligned. Also plonk the diffractometer scannable (sixc) on there with no +destination to monitor what is actually happening and then +throw on a detector (ct) with an exposure time if appropriate:: + + >>> #scan scannable_name start stop step [scannable_name [pos or time]].. + + >>> scan en 9 11 .5 hkl [1 0 0] sixc ct 1 + + >>> scan en 9 11 .5 hklverbose [1 0 0] sixc ct 1 + + >>> scan betain 4 5 .2 hkl [1 0 0] sixc ct 1 + + >>> scan alpha_par 0 10 2 hkl [1 0 0] sixc ct 1 + +Scanning hkl +------------ + +Hkl, or one component, may also be scanned directly:: + + >>> scan h .8 1.2 .1 hklverbose sixc ct 1 + +At each step, this will read the current hkl position, modify the h +component and then move to the resulting vector. There is a danger +that with this method k and l may drift. To get around this the start, +stop and step values may also be specified as vectors. So for example:: + + >>> scan hkl [1 0 0] [1 .3 0] [1 0.1 0] ct1 + +is equivilant to:: + + >>> pos hkl [1 0 0] + >>> scan k 0 .3 .1 ct1 + +but will not suffer from drifting. This method also allows scans along +any direction in hkl space to be performed. + +Multidimension scans +-------------------- + +Two and three dimensional scans:: + + >>> scan en 9 11 .5 h .9 1.1 .2 hklverbose sixc ct 1 + >>> scan h 1 3 1 k 1 3 1 l 1 3 1 hkl ct 1 + +Commands +======== + +Orientation Commands +-------------------- + +==> UB_HELP_TABLE + +Motion commands +--------------- + +==> HKL_HELP_TABLE + +Good luck --- RobW + +References +========== + +.. [You1999] H. You. *Angle calculations for a '4S+2D' six-circle diffractometer.* + J. Appl. Cryst. (1999). **32**, 614-623. `(pdf link) + `__. +.. [Busing1967] W. R. Busing and H. A. Levy. *Angle calculations for 3- and 4-circle X-ray + and neutron diffractometers.* Acta Cryst. (1967). **22**, 457-464. `(pdf link) + `__. diff --git a/script/test/diffcalc (copy)/doc/tmp/constraints.txt b/script/test/diffcalc (copy)/doc/tmp/constraints.txt new file mode 100644 index 0000000..5c64bf2 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/tmp/constraints.txt @@ -0,0 +1,47 @@ +Take a six circle diffractometer with angles: + +delta, nu, mu, eta, chi, phi + +and virtual angles: + +qaz, naz, psi, alpha, beta. + +Many combinations of these can be constrained: + + +constrain +>>> con nu +>>> con psi +>>> con mu +>>> con nu mu a_eq_b + +real angles are always constrained to there last set position. If they are too far from here +an error will result. + +>>> pos nu 0 (moves, parameter tracks last set value (actually checks it when hkl moved)) +>>> pos psi 90 (does not move, sets parameter in diffcalc) +>>> pos mu 0 (moves) +>>> pos hkl [1 0 0] (moves based on last set nu, psi, mu) +>>> pos betain --> Exception, not constrained + +moving a virtual angle simply sets it to efftec the next hkl move + +Here we need onlu con, uncon and pos. + +...... + +However it would be nice to be able to pos betain for example and have it change +betain while staying on the same reflection. This might be enabled by locking the +the current hkl position. It would be trick to make this work with the physical angles though. + +>>> lock hkl +>>> pos psi 90 (moves immediately) +>>> scan psi 0 90 1 (scans, moving immediately) +>>> pos mu 0 (does not move immediately) (could work by listening to target positions, and triggering move when complete) +>>> pos c(mu) moves +>>> pos mu_c moves + +Only one thing would be varyable at a time unless they are put in a CoordinatedMotionGroup, +which would be hard for the real motors) + + diff --git a/script/test/diffcalc (copy)/doc/tmp/extensions_to_yous_paper.wxm b/script/test/diffcalc (copy)/doc/tmp/extensions_to_yous_paper.wxm new file mode 100644 index 0000000..1e866bf --- /dev/null +++ b/script/test/diffcalc (copy)/doc/tmp/extensions_to_yous_paper.wxm @@ -0,0 +1,525 @@ +/* [wxMaxima batch file version 1] [ DO NOT EDIT BY HAND! ]*/ +/* [ Created with wxMaxima version 11.08.0 ] */ + +/* [wxMaxima: input start ] */ +Rx:matrix([1,0,0],[0, cos(theta), -sin(theta)],[0, sin(theta), cos(theta)])$ +Ry:matrix([cos(theta), 0, sin(theta)], [0, 1, 0], [-sin(theta), 0, cos(theta)])$ +Rz:matrix([cos(theta), -sin(theta), 0], [sin(theta), cos(theta), 0], [0, 0, 1])$ +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +Equation 5: + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +PHI:subst(-phi, theta, Rz); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +CHI:subst(chi, theta, Ry); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +ETA:subst(-eta, theta, Rz); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +MU:subst(mu, theta, Rx); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +Generic V: + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +V:matrix([v11, v12, v13], [v21, v22, v23], [v31, v32, v33]); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] + + [wxMaxima: comment end ] */ + +/* [wxMaxima: section start ] +When one sample-orienting angle is given + [wxMaxima: section end ] */ + +/* [wxMaxima: subsect start ] +For mu fixed + [wxMaxima: subsect end ] */ + +/* [wxMaxima: input start ] */ +eq34:ETA.CHI.PHI; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq34_chi0:subst(0, chi, eq34); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*phi* with chi <> 0 + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +trigreduce(trigsimp((eq34[3,2]=V[3,2])/(eq34[3,1]=V[3,1]))); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +show that with phi with v31 == 0 and v12 <> 0, no special case is needed when using atan2 + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +solve(cos(phi)=0, phi); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +atan2(1234,0); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +show that with phi with v32 == 0 and v31 <> 0, no special case is needed when using atan2 + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +solve(sin(phi)=0, phi); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +atan2(0,1234); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*eta* with chi <> 0 + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +trigreduce(trigsimp((-eq34[2,3]=V[2,3]) / (eq34[1, 3]=V[1,3]))); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +eta with v23 == 0 and v13 == 0 (phi||eta degeneracy because chi == 0) +Then see equation for phi+eta above + [wxMaxima: comment end ] */ + +/* [wxMaxima: comment start ] +*chi* + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +trigsimp(sqrt((eq34[3,1]=V[3,1])^2+(eq34[3,2]=V[3,2])^2)/(eq34[3,3]=V[3,3])); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*phi + eta* with chi == 0 + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +trigreduce(trigrat((eq34_chi0[1,2]=V[1,2])/(eq1:eq34_chi0[1,1]=V[1,1]))); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: subsect start ] +For phi fixed + [wxMaxima: subsect end ] */ + +/* [wxMaxima: input start ] */ +eq36:MU.ETA.CHI; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq36_eta90:subst(%pi/2, eta, eq36); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*eta* + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +trigsimp((eq36[1,2]=V[1,2])/sqrt((eq36[2,2]=V[2,2])^2+(eq36[3,2]=V[3,2])^2)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*mu* with eta <> +-90 + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +trigreduce(trigsimp((eq36[3,2]=V[3,2])/(eq36[2,2]=V[2,2]))); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*chi* with eta <> +-90 + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +trigreduce(trigsimp((eq36[1,3]=V[1,3])/(eq36[1,1]=V[1,1]))); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*chi + mu* with eta == +-90 + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +trigreduce(trigrat((eq36_eta90[2,3]=V[2,3])/(eq36_eta90[2,1]=V[2,1]))); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: subsect start ] +For eta or chi fixed + [wxMaxima: subsect end ] */ + +/* [wxMaxima: input start ] */ +eq38:MU.ETA.CHI.PHI; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*chi* (eq. 39) given eta, and eta <> +-90 (with eta == +-90 chi||mu) + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +(eq38[1,3]=V[1,3])/cos(eta); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] + + [wxMaxima: comment end ] */ + +/* [wxMaxima: comment start ] +*eta* given chi, and chi <> 0 (with chi == 0 phi||eta) + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +(eq38[1,3]=V[1,3])/sin(chi); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*mu* given that chi and eta have been given or calclulated respecitively + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +top:(eq38[3,3]=V[3,3])*sin(eta)*sin(chi) + (eq38[2,3]=V[2,3])*cos(chi); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +bot:-(eq38[3,3]=V[3,3])*cos(chi)+(eq38[2,3]=V[2,3])*sin(eta)*sin(chi); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +factor(top); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +factor(bot); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +trigsimp(top/bot); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*phi - mu* when phi || mu because chi == +-90, eta == 0 or eta == 180 + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +phi_mu_parallel:subst(0, eta, subst(%pi/2, chi, eq38)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +trigreduce(trigrat((phi_mu_parallel[2,1]=V[2,1])/(phi_mu_parallel[2,2]=V[2,2]))); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: section start ] +Two sample angles given + [wxMaxima: section end ] */ + +/* [wxMaxima: input start ] */ +THETA:subst(-theta, theta, Rz); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +PSI:subst(psi, theta, Rx); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +F:subst(xi, theta, Ry); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: section start ] +psi, mu, eta given + [wxMaxima: section end ] */ + +/* [wxMaxima: input start ] */ +eq49:transpose(PHI).transpose(CHI).transpose(ETA).transpose(MU).F; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*chi* + [wxMaxima: comment end ] */ + +/* [wxMaxima: comment start ] +using the linear combination identity + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +comb_idenity:sqrt(A^2+B^2)*sin(x+omega); +omega:atan(B/A); +A*sin(x)+B*cos(x)=comb_idenity; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +a:-sin(eta)*cos(mu)$ +b:-sin(mu)$ +V32=a*sin(chi)+b*cos(chi); +V32=subst(b, B, subst(a, A, comb_idenity)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +asin(%/(sqrt(sin(mu)^2+sin(eta)^2*cos(mu)^2))), triginverses=all; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*xi and phi* + + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +top:factor(eq49[3,1]*cos(xi)+eq49[3,3]*sin(xi)); +bot:factor(eq49[3,1]*sin(xi)-eq49[3,3]*cos(xi)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +factor(top/bot); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +When mu=-90 and eta = 0, used to for a surface normal vertical mode + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +eq49_vert:subst(-%pi/2, mu, subst(0, eta, eq49)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +(eq49_vert[2,2]=V[2,2]) / (eq49_vert[1,2]=V[1,2]); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: subsect start ] +mu, phi, qaz (for three circle on i10 and i06, not in paper ) + [wxMaxima: subsect end ] */ + +/* [wxMaxima: input start ] */ +thisV:transpose(MU).F.THETA; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +Nphi:matrix([n11, n12, n13], [n21, n22, n23], [n31, n32, n33]); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq:ETA.CHI.PHI.Nphi.transpose(PSI); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*chi* + [wxMaxima: comment end ] */ + +/* [wxMaxima: comment start ] +using the linear combination identity + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +comb_idenity:sqrt(A^2+B^2)*sin(x+omega); +omega:atan(B/A); +A*sin(x)+B*cos(x)=comb_idenity; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +on V31: + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +a:-1*(n21*sin(phi)+n11*cos(phi))$ +b:n31$ +V31=a*sin(chi)+b*cos(chi); +V31=subst(b, B, subst(a, A, comb_idenity)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +asin(%/sqrt((-n21*sin(phi)-n11*cos(phi))^2+n31^2)), triginverses=all; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +*xi and phi* + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +top:factor(eq49[3,1]*cos(xi)+eq49[3,3]*sin(xi)); +bot:factor(eq49[3,1]*sin(xi)-eq49[3,3]*cos(xi)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +factor(top/bot); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +solve(); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: section start ] +Fixed mu, chi, psi (probably with mu=chi=0) (not in paper) + [wxMaxima: section end ] */ + +/* [wxMaxima: input start ] */ +Vcalulated:Nphi.transpose(PSI).transpose(THETA); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +thisV:transpose(PHI).transpose(CHI).transpose(ETA).transpose(MU).F; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +v:thisVvert:subst(0, chi, subst(0, mu, thisV)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +acos(thisVvert[3,3]=V[3,3]), triginverses=all; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +top:(v[1,2]=V[1,2])*cos(phi) + (v[2,2]=V[2,2])*sin(phi); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +bot:(v[1,2]=V[1,2])*sin(phi) - (v[2,2]=V[2,2])*cos(phi); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +factor(top); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +factor(bot); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +trigsimp(top/bot); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: title start ] +5.5 subset with eta=chi=0 & phi set + [wxMaxima: title end ] */ + +/* [wxMaxima: input start ] */ +matrix([cos(theta)*sin(qaz)], [-sin(theta)], [cos(theta)*cos(qaz)]); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +h_phi:matrix([h1], [h2], [h3]); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +Z:MU.ETA.CHI.PHI; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq68yterm:-sin(theta) = ((Z.h_phi))[2][1]; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +expands(%); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq68_chi_eta_0:subst(0, chi, subst(0, eta, Z.h_phi)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq68_chi_eta_0_yterm:-sin(theta)=eq68_chi_eta_0[2]; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +subst(0, chi, subst(0, eta, eq68yterm)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +a; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +ratsubst(a, -h3, eq68_chi_eta_0_yterm); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +a:-h3; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +b:-h1*sin(phi) +h2*cos(phi); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +c:-sin(theta); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +ratsubst(cc, c, ratsubst(bb, b, ratsubst(aa, a, eq68_chi_eta_0_yterm))); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +arcsin(c/sqrt(a^2+b^2)) - arctan(b, a); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +eq68_chi_eta_0[1] /eq68_chi_eta_0[2]; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +without chi=eta=0 + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +eq68yterm:-sin(theta) = ((Z.h_phi))[2][1]; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +expand(eq68yterm); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +solve for mu unknown + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +c:-sin(theta); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +a:sin(chi)*h2*sin(phi) + sin(chi)*h1*cos(phi) - cos(chi)*h3; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +b: -cos(chi)*sin(eta)*h2*sin(phi) -cos(eta)*h1*sin(phi) +cos(eta)*h2*cos(phi) -cos(chi)*sin(eta)*h1*cos(phi) - sin(chi)*sin(eta)*h3; +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +expand(subst(a, aa, subst(b, bb, -sin(theta)=aa*sin(mu) + bb*cos(mu)))); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +Check a and b expand to produce original (Okay) + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +expand(eq68yterm); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: comment start ] +Check against special case above (Okay) + [wxMaxima: comment end ] */ + +/* [wxMaxima: input start ] */ +subst(0, chi, subst(0, eta, a)); +/* [wxMaxima: input end ] */ + +/* [wxMaxima: input start ] */ +subst(0, chi, subst(0, eta, b)); +/* [wxMaxima: input end ] */ + +/* Maxima can't load/batch files which end with a comment! */ +"Created with wxMaxima"$ diff --git a/script/test/diffcalc (copy)/doc/tmp/i16_non_diffcalc_manual.txt b/script/test/diffcalc (copy)/doc/tmp/i16_non_diffcalc_manual.txt new file mode 100644 index 0000000..16aa9d6 --- /dev/null +++ b/script/test/diffcalc (copy)/doc/tmp/i16_non_diffcalc_manual.txt @@ -0,0 +1,71 @@ +Creating a UB Matrix +There are three euler modes: + +=bisecting: eta and delta are fixed, phi and chi are not fixed. +=fixed phi: phi and 2theta are fixed, eta and chi are not fixed. +=fixed psi: Not used for creating a UB matrix . +First creat a reflection file to store the reflections data using: + +reffile('name') + +Set the crystal lattice: + +latt([a]) assumes cubic +latt([a,b]) assumes tetragonal +latt([a,b,c]) assumes orthorhombic +latt([a,b,c,gam]) assumes monclinic/hexagonal gam different from 90. +latt([a,b,c,alp,bet,gam]) + +Use: + +c2th([h k l]) + +to calculate the 2 theta position for the hkl for example: + +c2th([0 0 2]) + +Then use the following to move the diffractometer to the correct positions: + +pos delta c2th([0 0 2]) eta c2th([0 0 2])/2 + +You can now scan eta, chi, and delta to optomise the peak intensity using the following scans for example: + +scancn eta 0.01 40 w 0.2 t 1 + +scancn chi 0.01 40 w 0.2 t 1 + +scancn delta 0.01 40 w 0.2 t 1 + +A few iterations may be required. + +Now that you have the first reflection it can be stored using the following command: + +saveref('reflable',[h k l]) + +saveref('Ti1',[0 0 2]) for the above example. + +Then create a dummy UB matrix using a dummy non-parallel hkl plane eg: + +ubm('Ti1',[2 0 0]) + +Useing the following command to calculate the direction of the second reflection: + +hkl_calc([0 2 2]) + +If the calculated positions can be safely achieved then type: + +pos hkl [0 2 2] + +Find the second reflection by scanning phi, once the reflection is found save the second reflection using: + +saveref('Ti2',[0 2 2]) for example. + +Now create a real UB-Matrix using: + +ubm('Ti1','Ti2') + +You can now drive in recriprocal space. + +To change the lattice parameters according the found Bragg peaks + +latt([latt()[0]*2/l()]) (cubic only) \ No newline at end of file diff --git a/script/test/diffcalc (copy)/install-jython-environment.sh b/script/test/diffcalc (copy)/install-jython-environment.sh new file mode 100755 index 0000000..4ac8ef1 --- /dev/null +++ b/script/test/diffcalc (copy)/install-jython-environment.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh + +JYTHON_URL='http://search.maven.org/remotecontent?filepath=org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar' +JAMA_JAR_URL='http://math.nist.gov/javanumerics/jama/Jama-1.0.3.jar' + +# Install Jama jar +mkdir -p lib +wget -P $HOME/lib $JAMA_JAR_URL +export CLASSPATH=$HOME/lib/Jama-1.0.3.jar:$CLASSPATH + +# Install Jython +wget $JYTHON_URL -O jython_installer.jar +java -jar jython_installer.jar -s -d $HOME/jython + +# Install nose for Jython +# TODO: move to a setup.py +$HOME/jython/bin/pip install nose +$HOME/jython/bin/pip install pytest==3.2.3 +$HOME/jython/bin/pip install pytest-xdist diff --git a/script/test/diffcalc (copy)/integration_checks.py b/script/test/diffcalc (copy)/integration_checks.py new file mode 100644 index 0000000..d3fdacc --- /dev/null +++ b/script/test/diffcalc (copy)/integration_checks.py @@ -0,0 +1,72 @@ +''' +Created on 9 Mar 2017 + +@author: zrb13439 + +''' + +''' +Integration tests for startup scripts (named so that nosetests will *not* pick +up by default. + +This is because these must be run with: + + $ nosetests --with-process-isolation integration_checks.py + +This is not a standard nose option. Install it with: + + $ pip install nosepipe + +nosepipe is described at https://github.com/dmccombs/nosepipe/ +''' + +from nose import SkipTest + + +def test_sixcircle_startup(): + import startup.sixcircle + startup.sixcircle.ct.pause = False + startup.sixcircle.demo.all() + + +def test_fivecircle_startup(): + import startup.fivecircle + startup.fivecircle.ct.pause = False + startup.fivecircle.demo.all() + + +def test_fourcircle_startup(): + import startup.fourcircle + startup.fourcircle.ct.pause = False + startup.fourcircle.demo.all() + + +def test_i13_startup(): + raise SkipTest('Still need to work out to use i13s very tight limits') + import startup.i13 + startup.i13.ct.pause = False + startup.i13.demo.all() + + +def test_i16_startup(): + import startup.i16 + startup.i16.ct.pause = False + startup.i16.demo.all() + + +def test_i21_startup_standard(): + import startup.i21 + startup.i21.ct.pause = False + startup.i21.demo.all() + + +def test_i21_startup_bespoke(): + import startup.i21 + startup.i21.ct.pause = False + startup.i21.demo.i21() + + +def test_sixcirle_api(): + import startup.api.sixcircle + startup.api.sixcircle.demo_all() + diff --git a/script/test/diffcalc (copy)/mock.py b/script/test/diffcalc (copy)/mock.py new file mode 100644 index 0000000..24799cf --- /dev/null +++ b/script/test/diffcalc (copy)/mock.py @@ -0,0 +1,2288 @@ +# mock.py +# Test tools for mocking and patching. +# Copyright (C) 2007-2012 Michael Foord & the mock team +# E-mail: fuzzyman AT voidspace DOT org DOT uk + +# mock 0.8.0 +# http://www.voidspace.org.uk/python/mock/ + +# Released subject to the BSD License +# Please see http://www.voidspace.org.uk/python/license.shtml + +# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml +# Comments, suggestions and bug reports welcome. + + +__all__ = ( + 'Mock', + 'MagicMock', + 'mocksignature', + 'patch', + 'sentinel', + 'DEFAULT', + 'ANY', + 'call', + 'create_autospec', + 'FILTER_DIR', + 'NonCallableMock', + 'NonCallableMagicMock', +) + + +__version__ = '0.8.0' + + +import pprint +import sys + +try: + import inspect +except ImportError: + # for alternative platforms that + # may not have inspect + inspect = None + +try: + from functools import wraps +except ImportError: + # Python 2.4 compatibility + def wraps(original): + def inner(f): + f.__name__ = original.__name__ + f.__doc__ = original.__doc__ + f.__module__ = original.__module__ + return f + return inner + +try: + unicode +except NameError: + # Python 3 + basestring = unicode = str + +try: + long +except NameError: + # Python 3 + long = int + +try: + BaseException +except NameError: + # Python 2.4 compatibility + BaseException = Exception + +try: + next +except NameError: + def next(obj): + return obj.next() + + +BaseExceptions = (BaseException,) +if 'java' in sys.platform: + # jython + import java + BaseExceptions = (BaseException, java.lang.Throwable) + +try: + _isidentifier = str.isidentifier +except AttributeError: + # Python 2.X + import keyword + import re + regex = re.compile(r'^[a-z_][a-z0-9_]*$', re.I) + def _isidentifier(string): + if string in keyword.kwlist: + return False + return regex.match(string) + + +inPy3k = sys.version_info[0] == 3 + +# Needed to work around Python 3 bug where use of "super" interferes with +# defining __class__ as a descriptor +_super = super + +self = 'im_self' +builtin = '__builtin__' +if inPy3k: + self = '__self__' + builtin = 'builtins' + +FILTER_DIR = True + + +def _is_instance_mock(obj): + # can't use isinstance on Mock objects because they override __class__ + # The base class for all mocks is NonCallableMock + return issubclass(type(obj), NonCallableMock) + + +def _is_exception(obj): + return ( + isinstance(obj, BaseExceptions) or + isinstance(obj, ClassTypes) and issubclass(obj, BaseExceptions) + ) + + +class _slotted(object): + __slots__ = ['a'] + + +DescriptorTypes = ( + type(_slotted.a), + property, +) + + +# getsignature and mocksignature heavily "inspired" by +# the decorator module: http://pypi.python.org/pypi/decorator/ +# by Michele Simionato + +def _getsignature(func, skipfirst): + if inspect is None: + raise ImportError('inspect module not available') + + if inspect.isclass(func): + func = func.__init__ + # will have a self arg + skipfirst = True + elif not (inspect.ismethod(func) or inspect.isfunction(func)): + func = func.__call__ + + regargs, varargs, varkwargs, defaults = inspect.getargspec(func) + + # instance methods need to lose the self argument + if getattr(func, self, None) is not None: + regargs = regargs[1:] + + _msg = ("_mock_ is a reserved argument name, can't mock signatures using " + "_mock_") + assert '_mock_' not in regargs, _msg + if varargs is not None: + assert '_mock_' not in varargs, _msg + if varkwargs is not None: + assert '_mock_' not in varkwargs, _msg + if skipfirst: + regargs = regargs[1:] + + signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, + formatvalue=lambda value: "") + return signature[1:-1], func + + +def _getsignature2(func, skipfirst, instance=False): + if inspect is None: + raise ImportError('inspect module not available') + + if isinstance(func, ClassTypes) and not instance: + try: + func = func.__init__ + except AttributeError: + return + skipfirst = True + elif not isinstance(func, FunctionTypes): + # for classes where instance is True we end up here too + try: + func = func.__call__ + except AttributeError: + return + + try: + regargs, varargs, varkwargs, defaults = inspect.getargspec(func) + except TypeError: + # C function / method, possibly inherited object().__init__ + return + + # instance methods and classmethods need to lose the self argument + if getattr(func, self, None) is not None: + regargs = regargs[1:] + if skipfirst: + # this condition and the above one are never both True - why? + regargs = regargs[1:] + + signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, + formatvalue=lambda value: "") + return signature[1:-1], func + + +def _check_signature(func, mock, skipfirst, instance=False): + if not _callable(func): + return + + result = _getsignature2(func, skipfirst, instance) + if result is None: + return + signature, func = result + + # can't use self because "self" is common as an argument name + # unfortunately even not in the first place + src = "lambda _mock_self, %s: None" % signature + checksig = eval(src, {}) + _copy_func_details(func, checksig) + type(mock)._mock_check_sig = checksig + + +def _copy_func_details(func, funcopy): + funcopy.__name__ = func.__name__ + funcopy.__doc__ = func.__doc__ + #funcopy.__dict__.update(func.__dict__) + funcopy.__module__ = func.__module__ + if not inPy3k: + funcopy.func_defaults = func.func_defaults + return + funcopy.__defaults__ = func.__defaults__ + funcopy.__kwdefaults__ = func.__kwdefaults__ + + +def _callable(obj): + if isinstance(obj, ClassTypes): + return True + if getattr(obj, '__call__', None) is not None: + return True + return False + + +def _is_list(obj): + # checks for list or tuples + # XXXX badly named! + return type(obj) in (list, tuple) + + +def _instance_callable(obj): + """Given an object, return True if the object is callable. + For classes, return True if instances would be callable.""" + if not isinstance(obj, ClassTypes): + # already an instance + return getattr(obj, '__call__', None) is not None + + klass = obj + # uses __bases__ instead of __mro__ so that we work with old style classes + if klass.__dict__.get('__call__') is not None: + return True + + for base in klass.__bases__: + if _instance_callable(base): + return True + return False + + +def _set_signature(mock, original, instance=False): + # creates a function with signature (*args, **kwargs) that delegates to a + # mock. It still does signature checking by calling a lambda with the same + # signature as the original. This is effectively mocksignature2. + if not _callable(original): + return + + skipfirst = isinstance(original, ClassTypes) + result = _getsignature2(original, skipfirst, instance) + if result is None: + # was a C function (e.g. object().__init__ ) that can't be mocked + return + + signature, func = result + + src = "lambda %s: None" % signature + context = {'_mock_': mock} + checksig = eval(src, context) + _copy_func_details(func, checksig) + + name = original.__name__ + if not _isidentifier(name): + name = 'funcopy' + context = {'checksig': checksig, 'mock': mock} + src = """def %s(*args, **kwargs): + checksig(*args, **kwargs) + return mock(*args, **kwargs)""" % name + exec (src, context) + funcopy = context[name] + _setup_func(funcopy, mock) + return funcopy + + +def mocksignature(func, mock=None, skipfirst=False): + """ + mocksignature(func, mock=None, skipfirst=False) + + Create a new function with the same signature as `func` that delegates + to `mock`. If `skipfirst` is True the first argument is skipped, useful + for methods where `self` needs to be omitted from the new function. + + If you don't pass in a `mock` then one will be created for you. + + The mock is set as the `mock` attribute of the returned function for easy + access. + + Functions returned by `mocksignature` have many of the same attributes + and assert methods as a mock object. + + `mocksignature` can also be used with classes. It copies the signature of + the `__init__` method. + + When used with callable objects (instances) it copies the signature of the + `__call__` method. + """ + if mock is None: + mock = Mock() + signature, func = _getsignature(func, skipfirst) + src = "lambda %(signature)s: _mock_(%(signature)s)" % { + 'signature': signature + } + + funcopy = eval(src, dict(_mock_=mock)) + _copy_func_details(func, funcopy) + _setup_func(funcopy, mock) + return funcopy + + +def _setup_func(funcopy, mock): + funcopy.mock = mock + + # can't use isinstance with mocks + if not _is_instance_mock(mock): + return + + def assert_called_with(*args, **kwargs): + return mock.assert_called_with(*args, **kwargs) + def assert_called_once_with(*args, **kwargs): + return mock.assert_called_once_with(*args, **kwargs) + def assert_has_calls(*args, **kwargs): + return mock.assert_has_calls(*args, **kwargs) + def assert_any_call(*args, **kwargs): + return mock.assert_any_call(*args, **kwargs) + def reset_mock(): + funcopy.method_calls = _CallList() + funcopy.mock_calls = _CallList() + mock.reset_mock() + ret = funcopy.return_value + if _is_instance_mock(ret) and not ret is mock: + ret.reset_mock() + + funcopy.called = False + funcopy.call_count = 0 + funcopy.call_args = None + funcopy.call_args_list = _CallList() + funcopy.method_calls = _CallList() + funcopy.mock_calls = _CallList() + + funcopy.return_value = mock.return_value + funcopy.side_effect = mock.side_effect + funcopy._mock_children = mock._mock_children + + funcopy.assert_called_with = assert_called_with + funcopy.assert_called_once_with = assert_called_once_with + funcopy.assert_has_calls = assert_has_calls + funcopy.assert_any_call = assert_any_call + funcopy.reset_mock = reset_mock + + mock._mock_signature = funcopy + + +def _is_magic(name): + return '__%s__' % name[2:-2] == name + + +class _SentinelObject(object): + "A unique, named, sentinel object." + def __init__(self, name): + self.name = name + + def __repr__(self): + return 'sentinel.%s' % self.name + + +class _Sentinel(object): + """Access attributes to return a named object, usable as a sentinel.""" + def __init__(self): + self._sentinels = {} + + def __getattr__(self, name): + if name == '__bases__': + # Without this help(mock) raises an exception + raise AttributeError + return self._sentinels.setdefault(name, _SentinelObject(name)) + + +sentinel = _Sentinel() + +DEFAULT = sentinel.DEFAULT + + +class OldStyleClass: + pass +ClassType = type(OldStyleClass) + + +def _copy(value): + if type(value) in (dict, list, tuple, set): + return type(value)(value) + return value + + +ClassTypes = (type,) +if not inPy3k: + ClassTypes = (type, ClassType) + +_allowed_names = set( + [ + 'return_value', '_mock_return_value', 'side_effect', + '_mock_side_effect', '_mock_parent', '_mock_new_parent', + '_mock_name', '_mock_new_name' + ] +) + + +def _mock_signature_property(name): + _allowed_names.add(name) + _the_name = '_mock_' + name + def _get(self, name=name, _the_name=_the_name): + sig = self._mock_signature + if sig is None: + return getattr(self, _the_name) + return getattr(sig, name) + def _set(self, value, name=name, _the_name=_the_name): + sig = self._mock_signature + if sig is None: + self.__dict__[_the_name] = value + else: + setattr(sig, name, value) + + return property(_get, _set) + + + +class _CallList(list): + + def __contains__(self, value): + if not isinstance(value, list): + return list.__contains__(self, value) + len_value = len(value) + len_self = len(self) + if len_value > len_self: + return False + + for i in range(0, len_self - len_value + 1): + sub_list = self[i:i+len_value] + if sub_list == value: + return True + return False + + def __repr__(self): + return pprint.pformat(list(self)) + + +def _check_and_set_parent(parent, value, name, new_name): + if not _is_instance_mock(value): + return False + if ((value._mock_name or value._mock_new_name) or + (value._mock_parent is not None) or + (value._mock_new_parent is not None)): + return False + + _parent = parent + while _parent is not None: + # setting a mock (value) as a child or return value of itself + # should not modify the mock + if _parent is value: + return False + _parent = _parent._mock_new_parent + + if new_name: + value._mock_new_parent = parent + value._mock_new_name = new_name + if name: + value._mock_parent = parent + value._mock_name = name + return True + + + +class Base(object): + _mock_return_value = DEFAULT + _mock_side_effect = None + def __init__(self, *args, **kwargs): + pass + + + +class NonCallableMock(Base): + """A non-callable version of `Mock`""" + + def __new__(cls, *args, **kw): + # every instance has its own class + # so we can create magic methods on the + # class without stomping on other mocks + new = type(cls.__name__, (cls,), {'__doc__': cls.__doc__}) + instance = object.__new__(new) + return instance + + + def __init__( + self, spec=None, wraps=None, name=None, spec_set=None, + parent=None, _spec_state=None, _new_name='', _new_parent=None, + **kwargs + ): + if _new_parent is None: + _new_parent = parent + + __dict__ = self.__dict__ + __dict__['_mock_parent'] = parent + __dict__['_mock_name'] = name + __dict__['_mock_new_name'] = _new_name + __dict__['_mock_new_parent'] = _new_parent + + if spec_set is not None: + spec = spec_set + spec_set = True + + self._mock_add_spec(spec, spec_set) + + __dict__['_mock_children'] = {} + __dict__['_mock_wraps'] = wraps + __dict__['_mock_signature'] = None + + __dict__['_mock_called'] = False + __dict__['_mock_call_args'] = None + __dict__['_mock_call_count'] = 0 + __dict__['_mock_call_args_list'] = _CallList() + __dict__['_mock_mock_calls'] = _CallList() + + __dict__['method_calls'] = _CallList() + + if kwargs: + self.configure_mock(**kwargs) + + _super(NonCallableMock, self).__init__( + spec, wraps, name, spec_set, parent, + _spec_state + ) + + + def attach_mock(self, mock, attribute): + """ + Attach a mock as an attribute of this one, replacing its name and + parent. Calls to the attached mock will be recorded in the + `method_calls` and `mock_calls` attributes of this one.""" + mock._mock_parent = None + mock._mock_new_parent = None + mock._mock_name = '' + mock._mock_new_name = None + + setattr(self, attribute, mock) + + + def mock_add_spec(self, spec, spec_set=False): + """Add a spec to a mock. `spec` can either be an object or a + list of strings. Only attributes on the `spec` can be fetched as + attributes from the mock. + + If `spec_set` is True then only attributes on the spec can be set.""" + self._mock_add_spec(spec, spec_set) + + + def _mock_add_spec(self, spec, spec_set): + _spec_class = None + + if spec is not None and not _is_list(spec): + if isinstance(spec, ClassTypes): + _spec_class = spec + else: + _spec_class = _get_class(spec) + + spec = dir(spec) + + __dict__ = self.__dict__ + __dict__['_spec_class'] = _spec_class + __dict__['_spec_set'] = spec_set + __dict__['_mock_methods'] = spec + + + def __get_return_value(self): + ret = self._mock_return_value + if self._mock_signature is not None: + ret = self._mock_signature.return_value + + if ret is DEFAULT: + ret = self._get_child_mock( + _new_parent=self, _new_name='()' + ) + self.return_value = ret + return ret + + + def __set_return_value(self, value): + if self._mock_signature is not None: + self._mock_signature.return_value = value + else: + self._mock_return_value = value + _check_and_set_parent(self, value, None, '()') + + __return_value_doc = "The value to be returned when the mock is called." + return_value = property(__get_return_value, __set_return_value, + __return_value_doc) + + + @property + def __class__(self): + if self._spec_class is None: + return type(self) + return self._spec_class + + called = _mock_signature_property('called') + call_count = _mock_signature_property('call_count') + call_args = _mock_signature_property('call_args') + call_args_list = _mock_signature_property('call_args_list') + mock_calls = _mock_signature_property('mock_calls') + + + def __get_side_effect(self): + sig = self._mock_signature + if sig is None: + return self._mock_side_effect + return sig.side_effect + + def __set_side_effect(self, value): + value = _try_iter(value) + sig = self._mock_signature + if sig is None: + self._mock_side_effect = value + else: + sig.side_effect = value + + side_effect = property(__get_side_effect, __set_side_effect) + + + def reset_mock(self): + "Restore the mock object to its initial state." + self.called = False + self.call_args = None + self.call_count = 0 + self.mock_calls = _CallList() + self.call_args_list = _CallList() + self.method_calls = _CallList() + + for child in self._mock_children.values(): + child.reset_mock() + + ret = self._mock_return_value + if _is_instance_mock(ret) and ret is not self: + ret.reset_mock() + + + def configure_mock(self, **kwargs): + """Set attributes on the mock through keyword arguments. + + Attributes plus return values and side effects can be set on child + mocks using standard dot notation and unpacking a dictionary in the + method call: + + >>> attrs = {'method.return_value': 3, 'other.side_effect': KeyError} + >>> mock.configure_mock(**attrs)""" + for arg, val in sorted(kwargs.items(), + # we sort on the number of dots so that + # attributes are set before we set attributes on + # attributes + key=lambda entry: entry[0].count('.')): + args = arg.split('.') + final = args.pop() + obj = self + for entry in args: + obj = getattr(obj, entry) + setattr(obj, final, val) + + + def __getattr__(self, name): + if name == '_mock_methods': + raise AttributeError(name) + elif self._mock_methods is not None: + if name not in self._mock_methods or name in _all_magics: + raise AttributeError("Mock object has no attribute %r" % name) + elif _is_magic(name): + raise AttributeError(name) + + result = self._mock_children.get(name) + if result is None: + wraps = None + if self._mock_wraps is not None: + # XXXX should we get the attribute without triggering code + # execution? + wraps = getattr(self._mock_wraps, name) + + result = self._get_child_mock( + parent=self, name=name, wraps=wraps, _new_name=name, + _new_parent=self + ) + self._mock_children[name] = result + + elif isinstance(result, _SpecState): + result = create_autospec( + result.spec, result.spec_set, result.instance, + result.parent, result.name + ) + self._mock_children[name] = result + + return result + + + def __repr__(self): + _name_list = [self._mock_new_name] + _parent = self._mock_new_parent + last = self + + dot = '.' + if _name_list == ['()']: + dot = '' + seen = set() + while _parent is not None: + last = _parent + + _name_list.append(_parent._mock_new_name + dot) + dot = '.' + if _parent._mock_new_name == '()': + dot = '' + + _parent = _parent._mock_new_parent + + # use ids here so as not to call __hash__ on the mocks + if id(_parent) in seen: + break + seen.add(id(_parent)) + + _name_list = list(reversed(_name_list)) + _first = last._mock_name or 'mock' + if len(_name_list) > 1: + if _name_list[1] not in ('()', '().'): + _first += '.' + _name_list[0] = _first + name = ''.join(_name_list) + + name_string = '' + if name not in ('mock', 'mock.'): + name_string = ' name=%r' % name + + spec_string = '' + if self._spec_class is not None: + spec_string = ' spec=%r' + if self._spec_set: + spec_string = ' spec_set=%r' + spec_string = spec_string % self._spec_class.__name__ + return "<%s%s%s id='%s'>" % ( + type(self).__name__, + name_string, + spec_string, + id(self) + ) + + + def __dir__(self): + """Filter the output of `dir(mock)` to only useful members. + XXXX + """ + extras = self._mock_methods or [] + from_type = dir(type(self)) + from_dict = list(self.__dict__) + + if FILTER_DIR: + from_type = [e for e in from_type if not e.startswith('_')] + from_dict = [e for e in from_dict if not e.startswith('_') or + _is_magic(e)] + return sorted(set(extras + from_type + from_dict + + list(self._mock_children))) + + + def __setattr__(self, name, value): + if name in _allowed_names: + # property setters go through here + return object.__setattr__(self, name, value) + elif (self._spec_set and self._mock_methods is not None and + name not in self._mock_methods and + name not in self.__dict__): + raise AttributeError("Mock object has no attribute '%s'" % name) + elif name in _unsupported_magics: + msg = 'Attempting to set unsupported magic method %r.' % name + raise AttributeError(msg) + elif name in _all_magics: + if self._mock_methods is not None and name not in self._mock_methods: + raise AttributeError("Mock object has no attribute '%s'" % name) + + if not _is_instance_mock(value): + setattr(type(self), name, _get_method(name, value)) + original = value + real = lambda *args, **kw: original(self, *args, **kw) + value = mocksignature(value, real, skipfirst=True) + else: + # only set _new_name and not name so that mock_calls is tracked + # but not method calls + _check_and_set_parent(self, value, None, name) + setattr(type(self), name, value) + else: + if _check_and_set_parent(self, value, name, name): + self._mock_children[name] = value + return object.__setattr__(self, name, value) + + + def __delattr__(self, name): + if name in _all_magics and name in type(self).__dict__: + delattr(type(self), name) + if name not in self.__dict__: + # for magic methods that are still MagicProxy objects and + # not set on the instance itself + return + + return object.__delattr__(self, name) + + + def _format_mock_call_signature(self, args, kwargs): + name = self._mock_name or 'mock' + return _format_call_signature(name, args, kwargs) + + + def _format_mock_failure_message(self, args, kwargs): + message = 'Expected call: %s\nActual call: %s' + expected_string = self._format_mock_call_signature(args, kwargs) + call_args = self.call_args + if len(call_args) == 3: + call_args = call_args[1:] + actual_string = self._format_mock_call_signature(*call_args) + return message % (expected_string, actual_string) + + + def assert_called_with(_mock_self, *args, **kwargs): + """assert that the mock was called with the specified arguments. + + Raises an AssertionError if the args and keyword args passed in are + different to the last call to the mock.""" + self = _mock_self + if self.call_args is None: + expected = self._format_mock_call_signature(args, kwargs) + raise AssertionError('Expected call: %s\nNot called' % (expected,)) + + if self.call_args != (args, kwargs): + msg = self._format_mock_failure_message(args, kwargs) + raise AssertionError(msg) + + + def assert_called_once_with(_mock_self, *args, **kwargs): + """assert that the mock was called exactly once and with the specified + arguments.""" + self = _mock_self + if not self.call_count == 1: + msg = ("Expected to be called once. Called %s times." % + self.call_count) + raise AssertionError(msg) + return self.assert_called_with(*args, **kwargs) + + + def assert_has_calls(self, calls, any_order=False): + """assert the mock has been called with the specified calls. + The `mock_calls` list is checked for the calls. + + If `any_order` is False (the default) then the calls must be + sequential. There can be extra calls before or after the + specified calls. + + If `any_order` is True then the calls can be in any order, but + they must all appear in `mock_calls`.""" + if not any_order: + if calls not in self.mock_calls: + raise AssertionError( + 'Calls not found.\nExpected: %r\n' + 'Actual: %r' % (calls, self.mock_calls) + ) + return + + all_calls = list(self.mock_calls) + + not_found = [] + for kall in calls: + try: + all_calls.remove(kall) + except ValueError: + not_found.append(kall) + if not_found: + raise AssertionError( + '%r not all found in call list' % (tuple(not_found),) + ) + + + def assert_any_call(self, *args, **kwargs): + """assert the mock has been called with the specified arguments. + + The assert passes if the mock has *ever* been called, unlike + `assert_called_with` and `assert_called_once_with` that only pass if + the call is the most recent one.""" + kall = call(*args, **kwargs) + if kall not in self.call_args_list: + expected_string = self._format_mock_call_signature(args, kwargs) + raise AssertionError( + '%s call not found' % expected_string + ) + + + def _get_child_mock(self, **kw): + """Create the child mocks for attributes and return value. + By default child mocks will be the same type as the parent. + Subclasses of Mock may want to override this to customize the way + child mocks are made. + + For non-callable mocks the callable variant will be used (rather than + any custom subclass).""" + _type = type(self) + if not issubclass(_type, CallableMixin): + if issubclass(_type, NonCallableMagicMock): + klass = MagicMock + elif issubclass(_type, NonCallableMock) : + klass = Mock + else: + klass = _type.__mro__[1] + return klass(**kw) + + + +def _try_iter(obj): + if obj is None: + return obj + if _is_exception(obj): + return obj + if _callable(obj): + return obj + try: + return iter(obj) + except TypeError: + # XXXX backwards compatibility + # but this will blow up on first call - so maybe we should fail early? + return obj + + + +class CallableMixin(Base): + + def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, + wraps=None, name=None, spec_set=None, parent=None, + _spec_state=None, _new_name='', _new_parent=None, **kwargs): + self.__dict__['_mock_return_value'] = return_value + + _super(CallableMixin, self).__init__( + spec, wraps, name, spec_set, parent, + _spec_state, _new_name, _new_parent, **kwargs + ) + + self.side_effect = side_effect + + + def _mock_check_sig(self, *args, **kwargs): + # stub method that can be replaced with one with a specific signature + pass + + + def __call__(_mock_self, *args, **kwargs): + # can't use self in-case a function / method we are mocking uses self + # in the signature + _mock_self._mock_check_sig(*args, **kwargs) + return _mock_self._mock_call(*args, **kwargs) + + + def _mock_call(_mock_self, *args, **kwargs): + self = _mock_self + self.called = True + self.call_count += 1 + self.call_args = _Call((args, kwargs), two=True) + self.call_args_list.append(_Call((args, kwargs), two=True)) + + _new_name = self._mock_new_name + _new_parent = self._mock_new_parent + self.mock_calls.append(_Call(('', args, kwargs))) + + seen = set() + skip_next_dot = _new_name == '()' + do_method_calls = self._mock_parent is not None + name = self._mock_name + while _new_parent is not None: + this_mock_call = _Call((_new_name, args, kwargs)) + if _new_parent._mock_new_name: + dot = '.' + if skip_next_dot: + dot = '' + + skip_next_dot = False + if _new_parent._mock_new_name == '()': + skip_next_dot = True + + _new_name = _new_parent._mock_new_name + dot + _new_name + + if do_method_calls: + if _new_name == name: + this_method_call = this_mock_call + else: + this_method_call = _Call((name, args, kwargs)) + _new_parent.method_calls.append(this_method_call) + + do_method_calls = _new_parent._mock_parent is not None + if do_method_calls: + name = _new_parent._mock_name + '.' + name + + _new_parent.mock_calls.append(this_mock_call) + _new_parent = _new_parent._mock_new_parent + + # use ids here so as not to call __hash__ on the mocks + _new_parent_id = id(_new_parent) + if _new_parent_id in seen: + break + seen.add(_new_parent_id) + + ret_val = DEFAULT + effect = self.side_effect + if effect is not None: + if _is_exception(effect): + raise effect + + if not _callable(effect): + return next(effect) + + ret_val = effect(*args, **kwargs) + if ret_val is DEFAULT: + ret_val = self.return_value + + if (self._mock_wraps is not None and + self._mock_return_value is DEFAULT): + return self._mock_wraps(*args, **kwargs) + if ret_val is DEFAULT: + ret_val = self.return_value + return ret_val + + + +class Mock(CallableMixin, NonCallableMock): + """ + Create a new `Mock` object. `Mock` takes several optional arguments + that specify the behaviour of the Mock object: + + * `spec`: This can be either a list of strings or an existing object (a + class or instance) that acts as the specification for the mock object. If + you pass in an object then a list of strings is formed by calling dir on + the object (excluding unsupported magic attributes and methods). Accessing + any attribute not in this list will raise an `AttributeError`. + + If `spec` is an object (rather than a list of strings) then + `mock.__class__` returns the class of the spec object. This allows mocks + to pass `isinstance` tests. + + * `spec_set`: A stricter variant of `spec`. If used, attempting to *set* + or get an attribute on the mock that isn't on the object passed as + `spec_set` will raise an `AttributeError`. + + * `side_effect`: A function to be called whenever the Mock is called. See + the `side_effect` attribute. Useful for raising exceptions or + dynamically changing return values. The function is called with the same + arguments as the mock, and unless it returns `DEFAULT`, the return + value of this function is used as the return value. + + Alternatively `side_effect` can be an exception class or instance. In + this case the exception will be raised when the mock is called. + + If `side_effect` is an iterable then each call to the mock will return + the next value from the iterable. + + * `return_value`: The value returned when the mock is called. By default + this is a new Mock (created on first access). See the + `return_value` attribute. + + * `wraps`: Item for the mock object to wrap. If `wraps` is not None + then calling the Mock will pass the call through to the wrapped object + (returning the real result and ignoring `return_value`). Attribute + access on the mock will return a Mock object that wraps the corresponding + attribute of the wrapped object (so attempting to access an attribute that + doesn't exist will raise an `AttributeError`). + + If the mock has an explicit `return_value` set then calls are not passed + to the wrapped object and the `return_value` is returned instead. + + * `name`: If the mock has a name then it will be used in the repr of the + mock. This can be useful for debugging. The name is propagated to child + mocks. + + Mocks can also be called with arbitrary keyword arguments. These will be + used to set attributes on the mock after it is created. + """ + + + +def _dot_lookup(thing, comp, import_path): + try: + return getattr(thing, comp) + except AttributeError: + __import__(import_path) + return getattr(thing, comp) + + +def _importer(target): + components = target.split('.') + import_path = components.pop(0) + thing = __import__(import_path) + + for comp in components: + import_path += ".%s" % comp + thing = _dot_lookup(thing, comp, import_path) + return thing + + +def _is_started(patcher): + # XXXX horrible + return hasattr(patcher, 'is_local') + + +class _patch(object): + + attribute_name = None + + def __init__( + self, getter, attribute, new, spec, create, + mocksignature, spec_set, autospec, new_callable, kwargs + ): + if new_callable is not None: + if new is not DEFAULT: + raise ValueError( + "Cannot use 'new' and 'new_callable' together" + ) + if autospec is not False: + raise ValueError( + "Cannot use 'autospec' and 'new_callable' together" + ) + + self.getter = getter + self.attribute = attribute + self.new = new + self.new_callable = new_callable + self.spec = spec + self.create = create + self.has_local = False + self.mocksignature = mocksignature + self.spec_set = spec_set + self.autospec = autospec + self.kwargs = kwargs + self.additional_patchers = [] + + + def copy(self): + patcher = _patch( + self.getter, self.attribute, self.new, self.spec, + self.create, self.mocksignature, self.spec_set, + self.autospec, self.new_callable, self.kwargs + ) + patcher.attribute_name = self.attribute_name + patcher.additional_patchers = [ + p.copy() for p in self.additional_patchers + ] + return patcher + + + def __call__(self, func): + if isinstance(func, ClassTypes): + return self.decorate_class(func) + return self.decorate_callable(func) + + + def decorate_class(self, klass): + for attr in dir(klass): + if not attr.startswith(patch.TEST_PREFIX): + continue + + attr_value = getattr(klass, attr) + if not hasattr(attr_value, "__call__"): + continue + + patcher = self.copy() + setattr(klass, attr, patcher(attr_value)) + return klass + + + def decorate_callable(self, func): + if hasattr(func, 'patchings'): + func.patchings.append(self) + return func + + @wraps(func) + def patched(*args, **keywargs): + # don't use a with here (backwards compatability with Python 2.4) + extra_args = [] + entered_patchers = [] + + # can't use try...except...finally because of Python 2.4 + # compatibility + try: + try: + for patching in patched.patchings: + arg = patching.__enter__() + entered_patchers.append(patching) + if patching.attribute_name is not None: + keywargs.update(arg) + elif patching.new is DEFAULT: + extra_args.append(arg) + + args += tuple(extra_args) + return func(*args, **keywargs) + except: + if (patching not in entered_patchers and + _is_started(patching)): + # the patcher may have been started, but an exception + # raised whilst entering one of its additional_patchers + entered_patchers.append(patching) + # re-raise the exception + raise + finally: + for patching in reversed(entered_patchers): + patching.__exit__() + + patched.patchings = [self] + if hasattr(func, 'func_code'): + # not in Python 3 + patched.compat_co_firstlineno = getattr( + func, "compat_co_firstlineno", + func.func_code.co_firstlineno + ) + return patched + + + def get_original(self): + target = self.getter() + name = self.attribute + + original = DEFAULT + local = False + + try: + original = target.__dict__[name] + except (AttributeError, KeyError): + original = getattr(target, name, DEFAULT) + else: + local = True + + if not self.create and original is DEFAULT: + raise AttributeError( + "%s does not have the attribute %r" % (target, name) + ) + return original, local + + + def __enter__(self): + """Perform the patch.""" + new, spec, spec_set = self.new, self.spec, self.spec_set + autospec, kwargs = self.autospec, self.kwargs + new_callable = self.new_callable + self.target = self.getter() + + original, local = self.get_original() + + if new is DEFAULT and autospec is False: + inherit = False + if spec_set == True: + spec_set = original + elif spec == True: + # set spec to the object we are replacing + spec = original + + if (spec or spec_set) is not None: + if isinstance(original, ClassTypes): + # If we're patching out a class and there is a spec + inherit = True + + Klass = MagicMock + _kwargs = {} + if new_callable is not None: + Klass = new_callable + elif (spec or spec_set) is not None: + if not _callable(spec or spec_set): + Klass = NonCallableMagicMock + + if spec is not None: + _kwargs['spec'] = spec + if spec_set is not None: + _kwargs['spec_set'] = spec_set + + # add a name to mocks + if (isinstance(Klass, type) and + issubclass(Klass, NonCallableMock) and self.attribute): + _kwargs['name'] = self.attribute + + _kwargs.update(kwargs) + new = Klass(**_kwargs) + + if inherit and _is_instance_mock(new): + # we can only tell if the instance should be callable if the + # spec is not a list + if (not _is_list(spec or spec_set) and not + _instance_callable(spec or spec_set)): + Klass = NonCallableMagicMock + + _kwargs.pop('name') + new.return_value = Klass(_new_parent=new, _new_name='()', + **_kwargs) + elif autospec is not False: + # spec is ignored, new *must* be default, spec_set is treated + # as a boolean. Should we check spec is not None and that spec_set + # is a bool? mocksignature should also not be used. Should we + # check this? + if new is not DEFAULT: + raise TypeError( + "autospec creates the mock for you. Can't specify " + "autospec and new." + ) + spec_set = bool(spec_set) + if autospec is True: + autospec = original + + new = create_autospec(autospec, spec_set=spec_set, + _name=self.attribute, **kwargs) + elif kwargs: + # can't set keyword args when we aren't creating the mock + # XXXX If new is a Mock we could call new.configure_mock(**kwargs) + raise TypeError("Can't pass kwargs to a mock we aren't creating") + + new_attr = new + if self.mocksignature: + new_attr = mocksignature(original, new) + + self.temp_original = original + self.is_local = local + setattr(self.target, self.attribute, new_attr) + if self.attribute_name is not None: + extra_args = {} + if self.new is DEFAULT: + extra_args[self.attribute_name] = new + for patching in self.additional_patchers: + arg = patching.__enter__() + if patching.new is DEFAULT: + extra_args.update(arg) + return extra_args + + return new + + + def __exit__(self, *_): + """Undo the patch.""" + if not _is_started(self): + raise RuntimeError('stop called on unstarted patcher') + + if self.is_local and self.temp_original is not DEFAULT: + setattr(self.target, self.attribute, self.temp_original) + else: + delattr(self.target, self.attribute) + if not self.create and not hasattr(self.target, self.attribute): + # needed for proxy objects like django settings + setattr(self.target, self.attribute, self.temp_original) + + del self.temp_original + del self.is_local + del self.target + for patcher in reversed(self.additional_patchers): + if _is_started(patcher): + patcher.__exit__() + + start = __enter__ + stop = __exit__ + + + +def _get_target(target): + try: + target, attribute = target.rsplit('.', 1) + except (TypeError, ValueError): + raise TypeError("Need a valid target to patch. You supplied: %r" % + (target,)) + getter = lambda: _importer(target) + return getter, attribute + + +def _patch_object( + target, attribute, new=DEFAULT, spec=None, + create=False, mocksignature=False, spec_set=None, autospec=False, + new_callable=None, **kwargs + ): + """ + patch.object(target, attribute, new=DEFAULT, spec=None, create=False, + mocksignature=False, spec_set=None, autospec=False, + new_callable=None, **kwargs) + + patch the named member (`attribute`) on an object (`target`) with a mock + object. + + `patch.object` can be used as a decorator, class decorator or a context + manager. Arguments `new`, `spec`, `create`, `mocksignature`, `spec_set`, + `autospec` and `new_callable` have the same meaning as for `patch`. Like + `patch`, `patch.object` takes arbitrary keyword arguments for configuring + the mock object it creates. + + When used as a class decorator `patch.object` honours `patch.TEST_PREFIX` + for choosing which methods to wrap. + """ + getter = lambda: target + return _patch( + getter, attribute, new, spec, create, mocksignature, + spec_set, autospec, new_callable, kwargs + ) + + +def _patch_multiple(target, spec=None, create=False, + mocksignature=False, spec_set=None, autospec=False, + new_callable=None, **kwargs + ): + """Perform multiple patches in a single call. It takes the object to be + patched (either as an object or a string to fetch the object by importing) + and keyword arguments for the patches:: + + with patch.multiple(settings, FIRST_PATCH='one', SECOND_PATCH='two'): + ... + + Use `DEFAULT` as the value if you want `patch.multiple` to create + mocks for you. In this case the created mocks are passed into a decorated + function by keyword, and a dictionary is returned when `patch.multiple` is + used as a context manager. + + `patch.multiple` can be used as a decorator, class decorator or a context + manager. The arguments `spec`, `spec_set`, `create`, `mocksignature`, + `autospec` and `new_callable` have the same meaning as for `patch`. These + arguments will be applied to *all* patches done by `patch.multiple`. + + When used as a class decorator `patch.multiple` honours `patch.TEST_PREFIX` + for choosing which methods to wrap. + """ + if type(target) in (unicode, str): + getter = lambda: _importer(target) + else: + getter = lambda: target + + if not kwargs: + raise ValueError( + 'Must supply at least one keyword argument with patch.multiple' + ) + # need to wrap in a list for python 3, where items is a view + items = list(kwargs.items()) + attribute, new = items[0] + patcher = _patch( + getter, attribute, new, spec, create, mocksignature, spec_set, + autospec, new_callable, {} + ) + patcher.attribute_name = attribute + for attribute, new in items[1:]: + this_patcher = _patch( + getter, attribute, new, spec, create, mocksignature, spec_set, + autospec, new_callable, {} + ) + this_patcher.attribute_name = attribute + patcher.additional_patchers.append(this_patcher) + return patcher + + +def patch( + target, new=DEFAULT, spec=None, create=False, + mocksignature=False, spec_set=None, autospec=False, + new_callable=None, **kwargs + ): + """ + `patch` acts as a function decorator, class decorator or a context + manager. Inside the body of the function or with statement, the `target` + (specified in the form `'package.module.ClassName'`) is patched + with a `new` object. When the function/with statement exits the patch is + undone. + + The `target` is imported and the specified attribute patched with the new + object, so it must be importable from the environment you are calling the + decorator from. The target is imported when the decorated function is + executed, not at decoration time. + + If `new` is omitted, then a new `MagicMock` is created and passed in as an + extra argument to the decorated function. + + The `spec` and `spec_set` keyword arguments are passed to the `MagicMock` + if patch is creating one for you. + + In addition you can pass `spec=True` or `spec_set=True`, which causes + patch to pass in the object being mocked as the spec/spec_set object. + + `new_callable` allows you to specify a different class, or callable object, + that will be called to create the `new` object. By default `MagicMock` is + used. + + A more powerful form of `spec` is `autospec`. If you set `autospec=True` + then the mock with be created with a spec from the object being replaced. + All attributes of the mock will also have the spec of the corresponding + attribute of the object being replaced. Methods and functions being mocked + will have their arguments checked and will raise a `TypeError` if they are + called with the wrong signature (similar to `mocksignature`). For mocks + replacing a class, their return value (the 'instance') will have the same + spec as the class. + + Instead of `autospec=True` you can pass `autospec=some_object` to use an + arbitrary object as the spec instead of the one being replaced. + + If `mocksignature` is True then the patch will be done with a function + created by mocking the one being replaced. If the object being replaced is + a class then the signature of `__init__` will be copied. If the object + being replaced is a callable object then the signature of `__call__` will + be copied. + + By default `patch` will fail to replace attributes that don't exist. If + you pass in `create=True`, and the attribute doesn't exist, patch will + create the attribute for you when the patched function is called, and + delete it again afterwards. This is useful for writing tests against + attributes that your production code creates at runtime. It is off by by + default because it can be dangerous. With it switched on you can write + passing tests against APIs that don't actually exist! + + Patch can be used as a `TestCase` class decorator. It works by + decorating each test method in the class. This reduces the boilerplate + code when your test methods share a common patchings set. `patch` finds + tests by looking for method names that start with `patch.TEST_PREFIX`. + By default this is `test`, which matches the way `unittest` finds tests. + You can specify an alternative prefix by setting `patch.TEST_PREFIX`. + + Patch can be used as a context manager, with the with statement. Here the + patching applies to the indented block after the with statement. If you + use "as" then the patched object will be bound to the name after the + "as"; very useful if `patch` is creating a mock object for you. + + `patch` takes arbitrary keyword arguments. These will be passed to + the `Mock` (or `new_callable`) on construction. + + `patch.dict(...)`, `patch.multiple(...)` and `patch.object(...)` are + available for alternate use-cases. + """ + getter, attribute = _get_target(target) + return _patch( + getter, attribute, new, spec, create, mocksignature, + spec_set, autospec, new_callable, kwargs + ) + + +class _patch_dict(object): + """ + Patch a dictionary, or dictionary like object, and restore the dictionary + to its original state after the test. + + `in_dict` can be a dictionary or a mapping like container. If it is a + mapping then it must at least support getting, setting and deleting items + plus iterating over keys. + + `in_dict` can also be a string specifying the name of the dictionary, which + will then be fetched by importing it. + + `values` can be a dictionary of values to set in the dictionary. `values` + can also be an iterable of `(key, value)` pairs. + + If `clear` is True then the dictionary will be cleared before the new + values are set. + + `patch.dict` can also be called with arbitrary keyword arguments to set + values in the dictionary:: + + with patch.dict('sys.modules', mymodule=Mock(), other_module=Mock()): + ... + + `patch.dict` can be used as a context manager, decorator or class + decorator. When used as a class decorator `patch.dict` honours + `patch.TEST_PREFIX` for choosing which methods to wrap. + """ + + def __init__(self, in_dict, values=(), clear=False, **kwargs): + if isinstance(in_dict, basestring): + in_dict = _importer(in_dict) + self.in_dict = in_dict + # support any argument supported by dict(...) constructor + self.values = dict(values) + self.values.update(kwargs) + self.clear = clear + self._original = None + + + def __call__(self, f): + if isinstance(f, ClassTypes): + return self.decorate_class(f) + @wraps(f) + def _inner(*args, **kw): + self._patch_dict() + try: + return f(*args, **kw) + finally: + self._unpatch_dict() + + return _inner + + + def decorate_class(self, klass): + for attr in dir(klass): + attr_value = getattr(klass, attr) + if (attr.startswith(patch.TEST_PREFIX) and + hasattr(attr_value, "__call__")): + decorator = _patch_dict(self.in_dict, self.values, self.clear) + decorated = decorator(attr_value) + setattr(klass, attr, decorated) + return klass + + + def __enter__(self): + """Patch the dict.""" + self._patch_dict() + + + def _patch_dict(self): + values = self.values + in_dict = self.in_dict + clear = self.clear + + try: + original = in_dict.copy() + except AttributeError: + # dict like object with no copy method + # must support iteration over keys + original = {} + for key in in_dict: + original[key] = in_dict[key] + self._original = original + + if clear: + _clear_dict(in_dict) + + try: + in_dict.update(values) + except AttributeError: + # dict like object with no update method + for key in values: + in_dict[key] = values[key] + + + def _unpatch_dict(self): + in_dict = self.in_dict + original = self._original + + _clear_dict(in_dict) + + try: + in_dict.update(original) + except AttributeError: + for key in original: + in_dict[key] = original[key] + + + def __exit__(self, *args): + """Unpatch the dict.""" + self._unpatch_dict() + return False + + start = __enter__ + stop = __exit__ + + +def _clear_dict(in_dict): + try: + in_dict.clear() + except AttributeError: + keys = list(in_dict) + for key in keys: + del in_dict[key] + + +patch.object = _patch_object +patch.dict = _patch_dict +patch.multiple = _patch_multiple +patch.TEST_PREFIX = 'test' + +magic_methods = ( + "lt le gt ge eq ne " + "getitem setitem delitem " + "len contains iter " + "hash str sizeof " + "enter exit " + "divmod neg pos abs invert " + "complex int float index " + "trunc floor ceil " +) + +numerics = "add sub mul div floordiv mod lshift rshift and xor or pow " +inplace = ' '.join('i%s' % n for n in numerics.split()) +right = ' '.join('r%s' % n for n in numerics.split()) +extra = '' +if inPy3k: + extra = 'bool next ' +else: + extra = 'unicode long nonzero oct hex truediv rtruediv ' + +# not including __prepare__, __instancecheck__, __subclasscheck__ +# (as they are metaclass methods) +# __del__ is not supported at all as it causes problems if it exists + +_non_defaults = set('__%s__' % method for method in [ + 'cmp', 'getslice', 'setslice', 'coerce', 'subclasses', + 'format', 'get', 'set', 'delete', 'reversed', + 'missing', 'reduce', 'reduce_ex', 'getinitargs', + 'getnewargs', 'getstate', 'setstate', 'getformat', + 'setformat', 'repr', 'dir' +]) + + +def _get_method(name, func): + "Turns a callable object (like a mock) into a real function" + def method(self, *args, **kw): + return func(self, *args, **kw) + method.__name__ = name + return method + + +_magics = set( + '__%s__' % method for method in + ' '.join([magic_methods, numerics, inplace, right, extra]).split() +) + +_all_magics = _magics | _non_defaults + +_unsupported_magics = set([ + '__getattr__', '__setattr__', + '__init__', '__new__', '__prepare__' + '__instancecheck__', '__subclasscheck__', + '__del__' +]) + +_calculate_return_value = { + '__hash__': lambda self: object.__hash__(self), + '__str__': lambda self: object.__str__(self), + '__sizeof__': lambda self: object.__sizeof__(self), + '__unicode__': lambda self: unicode(object.__str__(self)), +} + +_return_values = { + '__int__': 1, + '__contains__': False, + '__len__': 0, + '__exit__': False, + '__complex__': 1j, + '__float__': 1.0, + '__bool__': True, + '__nonzero__': True, + '__oct__': '1', + '__hex__': '0x1', + '__long__': long(1), + '__index__': 1, +} + + +def _get_eq(self): + def __eq__(other): + ret_val = self.__eq__._mock_return_value + if ret_val is not DEFAULT: + return ret_val + return self is other + return __eq__ + +def _get_ne(self): + def __ne__(other): + if self.__ne__._mock_return_value is not DEFAULT: + return DEFAULT + return self is not other + return __ne__ + +def _get_iter(self): + def __iter__(): + ret_val = self.__iter__._mock_return_value + if ret_val is DEFAULT: + return iter([]) + # if ret_val was already an iterator, then calling iter on it should + # return the iterator unchanged + return iter(ret_val) + return __iter__ + +_side_effect_methods = { + '__eq__': _get_eq, + '__ne__': _get_ne, + '__iter__': _get_iter, +} + + + +def _set_return_value(mock, method, name): + fixed = _return_values.get(name, DEFAULT) + if fixed is not DEFAULT: + method.return_value = fixed + return + + return_calulator = _calculate_return_value.get(name) + if return_calulator is not None: + try: + return_value = return_calulator(mock) + except AttributeError: + # XXXX why do we return AttributeError here? + # set it as a side_effect instead? + return_value = AttributeError(name) + method.return_value = return_value + return + + side_effector = _side_effect_methods.get(name) + if side_effector is not None: + method.side_effect = side_effector(mock) + + + +class MagicMixin(object): + def __init__(self, *args, **kw): + _super(MagicMixin, self).__init__(*args, **kw) + self._mock_set_magics() + + + def _mock_set_magics(self): + these_magics = _magics + + if self._mock_methods is not None: + these_magics = _magics.intersection(self._mock_methods) + + remove_magics = set() + remove_magics = _magics - these_magics + + for entry in remove_magics: + if entry in type(self).__dict__: + # remove unneeded magic methods + delattr(self, entry) + + # don't overwrite existing attributes if called a second time + these_magics = these_magics - set(type(self).__dict__) + + _type = type(self) + for entry in these_magics: + setattr(_type, entry, MagicProxy(entry, self)) + + + +class NonCallableMagicMock(MagicMixin, NonCallableMock): + """A version of `MagicMock` that isn't callable.""" + def mock_add_spec(self, spec, spec_set=False): + """Add a spec to a mock. `spec` can either be an object or a + list of strings. Only attributes on the `spec` can be fetched as + attributes from the mock. + + If `spec_set` is True then only attributes on the spec can be set.""" + self._mock_add_spec(spec, spec_set) + self._mock_set_magics() + + + +class MagicMock(MagicMixin, Mock): + """ + MagicMock is a subclass of Mock with default implementations + of most of the magic methods. You can use MagicMock without having to + configure the magic methods yourself. + + If you use the `spec` or `spec_set` arguments then *only* magic + methods that exist in the spec will be created. + + Attributes and the return value of a `MagicMock` will also be `MagicMocks`. + """ + def mock_add_spec(self, spec, spec_set=False): + """Add a spec to a mock. `spec` can either be an object or a + list of strings. Only attributes on the `spec` can be fetched as + attributes from the mock. + + If `spec_set` is True then only attributes on the spec can be set.""" + self._mock_add_spec(spec, spec_set) + self._mock_set_magics() + + + +class MagicProxy(object): + def __init__(self, name, parent): + self.name = name + self.parent = parent + + def __call__(self, *args, **kwargs): + m = self.create_mock() + return m(*args, **kwargs) + + def create_mock(self): + entry = self.name + parent = self.parent + m = parent._get_child_mock(name=entry, _new_name=entry, + _new_parent=parent) + setattr(parent, entry, m) + _set_return_value(parent, m, entry) + return m + + def __get__(self, obj, _type=None): + return self.create_mock() + + + +class _ANY(object): + "A helper object that compares equal to everything." + + def __eq__(self, other): + return True + + def __ne__(self, other): + return False + + def __repr__(self): + return '' + +ANY = _ANY() + + + +def _format_call_signature(name, args, kwargs): + message = '%s(%%s)' % name + formatted_args = '' + args_string = ', '.join([repr(arg) for arg in args]) + kwargs_string = ', '.join([ + '%s=%r' % (key, value) for key, value in kwargs.items() + ]) + if args_string: + formatted_args = args_string + if kwargs_string: + if formatted_args: + formatted_args += ', ' + formatted_args += kwargs_string + + return message % formatted_args + + + +class _Call(tuple): + """ + A tuple for holding the results of a call to a mock, either in the form + `(args, kwargs)` or `(name, args, kwargs)`. + + If args or kwargs are empty then a call tuple will compare equal to + a tuple without those values. This makes comparisons less verbose:: + + _Call(('name', (), {})) == ('name',) + _Call(('name', (1,), {})) == ('name', (1,)) + _Call(((), {'a': 'b'})) == ({'a': 'b'},) + + The `_Call` object provides a useful shortcut for comparing with call:: + + _Call(((1, 2), {'a': 3})) == call(1, 2, a=3) + _Call(('foo', (1, 2), {'a': 3})) == call.foo(1, 2, a=3) + + If the _Call has no name then it will match any name. + """ + def __new__(cls, value=(), name=None, parent=None, two=False, + from_kall=True): + name = '' + args = () + kwargs = {} + _len = len(value) + if _len == 3: + name, args, kwargs = value + elif _len == 2: + first, second = value + if isinstance(first, basestring): + name = first + if isinstance(second, tuple): + args = second + else: + kwargs = second + else: + args, kwargs = first, second + elif _len == 1: + value, = value + if isinstance(value, basestring): + name = value + elif isinstance(value, tuple): + args = value + else: + kwargs = value + + if two: + return tuple.__new__(cls, (args, kwargs)) + + return tuple.__new__(cls, (name, args, kwargs)) + + + def __init__(self, value=(), name=None, parent=None, two=False, + from_kall=True): + self.name = name + self.parent = parent + self.from_kall = from_kall + + + def __eq__(self, other): + if other is ANY: + return True + try: + len_other = len(other) + except TypeError: + return False + + self_name = '' + if len(self) == 2: + self_args, self_kwargs = self + else: + self_name, self_args, self_kwargs = self + + other_name = '' + if len_other == 0: + other_args, other_kwargs = (), {} + elif len_other == 3: + other_name, other_args, other_kwargs = other + elif len_other == 1: + value, = other + if isinstance(value, tuple): + other_args = value + other_kwargs = {} + elif isinstance(value, basestring): + other_name = value + other_args, other_kwargs = (), {} + else: + other_args = () + other_kwargs = value + else: + # len 2 + # could be (name, args) or (name, kwargs) or (args, kwargs) + first, second = other + if isinstance(first, basestring): + other_name = first + if isinstance(second, tuple): + other_args, other_kwargs = second, {} + else: + other_args, other_kwargs = (), second + else: + other_args, other_kwargs = first, second + + if self_name and other_name != self_name: + return False + + # this order is important for ANY to work! + return (other_args, other_kwargs) == (self_args, self_kwargs) + + + def __ne__(self, other): + return not self.__eq__(other) + + + def __call__(self, *args, **kwargs): + if self.name is None: + return _Call(('', args, kwargs), name='()') + + name = self.name + '()' + return _Call((self.name, args, kwargs), name=name, parent=self) + + + def __getattr__(self, attr): + if self.name is None: + return _Call(name=attr, from_kall=False) + name = '%s.%s' % (self.name, attr) + return _Call(name=name, parent=self, from_kall=False) + + + def __repr__(self): + if not self.from_kall: + name = self.name or 'call' + if name.startswith('()'): + name = 'call%s' % name + return name + + if len(self) == 2: + name = 'call' + args, kwargs = self + else: + name, args, kwargs = self + if not name: + name = 'call' + elif not name.startswith('()'): + name = 'call.%s' % name + else: + name = 'call%s' % name + return _format_call_signature(name, args, kwargs) + + + def call_list(self): + """For a call object that represents multiple calls, `call_list` + returns a list of all the intermediate calls as well as the + final call.""" + vals = [] + thing = self + while thing is not None: + if thing.from_kall: + vals.append(thing) + thing = thing.parent + return _CallList(reversed(vals)) + + +call = _Call(from_kall=False) + + + +def create_autospec(spec, spec_set=False, instance=False, _parent=None, + _name=None, **kwargs): + """Create a mock object using another object as a spec. Attributes on the + mock will use the corresponding attribute on the `spec` object as their + spec. + + Functions or methods being mocked will have their arguments checked in a + similar way to `mocksignature` to check that they are called with the + correct signature. + + If `spec_set` is True then attempting to set attributes that don't exist + on the spec object will raise an `AttributeError`. + + If a class is used as a spec then the return value of the mock (the + instance of the class) will have the same spec. You can use a class as the + spec for an instance object by passing `instance=True`. The returned mock + will only be callable if instances of the mock are callable. + + `create_autospec` also takes arbitrary keyword arguments that are passed to + the constructor of the created mock.""" + if _is_list(spec): + # can't pass a list instance to the mock constructor as it will be + # interpreted as a list of strings + spec = type(spec) + + is_type = isinstance(spec, ClassTypes) + + _kwargs = {'spec': spec} + if spec_set: + _kwargs = {'spec_set': spec} + elif spec is None: + # None we mock with a normal mock without a spec + _kwargs = {} + + _kwargs.update(kwargs) + + Klass = MagicMock + if type(spec) in DescriptorTypes: + # descriptors don't have a spec + # because we don't know what type they return + _kwargs = {} + elif not _callable(spec): + Klass = NonCallableMagicMock + elif is_type and instance and not _instance_callable(spec): + Klass = NonCallableMagicMock + + _new_name = _name + if _parent is None: + # for a top level object no _new_name should be set + _new_name = '' + + mock = Klass(parent=_parent, _new_parent=_parent, _new_name=_new_name, + name=_name, **_kwargs) + + if isinstance(spec, FunctionTypes): + # should only happen at the top level because we don't + # recurse for functions + mock = _set_signature(mock, spec) + else: + _check_signature(spec, mock, is_type, instance) + + if _parent is not None and not instance: + _parent._mock_children[_name] = mock + + if is_type and not instance and 'return_value' not in kwargs: + # XXXX could give a name to the return_value mock? + mock.return_value = create_autospec(spec, spec_set, instance=True, + _name='()', _parent=mock) + + for entry in dir(spec): + if _is_magic(entry): + # MagicMock already does the useful magic methods for us + continue + + if isinstance(spec, FunctionTypes) and entry in FunctionAttributes: + # allow a mock to actually be a function from mocksignature + continue + + # XXXX do we need a better way of getting attributes without + # triggering code execution (?) Probably not - we need the actual + # object to mock it so we would rather trigger a property than mock + # the property descriptor. Likewise we want to mock out dynamically + # provided attributes. + # XXXX what about attributes that raise exceptions on being fetched + # we could be resilient against it, or catch and propagate the + # exception when the attribute is fetched from the mock + original = getattr(spec, entry) + + kwargs = {'spec': original} + if spec_set: + kwargs = {'spec_set': original} + + if not isinstance(original, FunctionTypes): + new = _SpecState(original, spec_set, mock, entry, instance) + mock._mock_children[entry] = new + else: + parent = mock + if isinstance(spec, FunctionTypes): + parent = mock.mock + + new = MagicMock(parent=parent, name=entry, _new_name=entry, + _new_parent=parent, **kwargs) + mock._mock_children[entry] = new + skipfirst = _must_skip(spec, entry, is_type) + _check_signature(original, new, skipfirst=skipfirst) + + # so functions created with mocksignature become instance attributes, + # *plus* their underlying mock exists in _mock_children of the parent + # mock. Adding to _mock_children may be unnecessary where we are also + # setting as an instance attribute? + if isinstance(new, FunctionTypes): + setattr(mock, entry, new) + + return mock + + +def _must_skip(spec, entry, is_type): + if not isinstance(spec, ClassTypes): + if entry in getattr(spec, '__dict__', {}): + # instance attribute - shouldn't skip + return False + # can't use type because of old style classes + spec = spec.__class__ + if not hasattr(spec, '__mro__'): + # old style class: can't have descriptors anyway + return is_type + + for klass in spec.__mro__: + result = klass.__dict__.get(entry, DEFAULT) + if result is DEFAULT: + continue + if isinstance(result, (staticmethod, classmethod)): + return False + return is_type + + # shouldn't get here unless function is a dynamically provided attribute + # XXXX untested behaviour + return is_type + + +def _get_class(obj): + try: + return obj.__class__ + except AttributeError: + # in Python 2, _sre.SRE_Pattern objects have no __class__ + return type(obj) + + +class _SpecState(object): + + def __init__(self, spec, spec_set=False, parent=None, + name=None, ids=None, instance=False): + self.spec = spec + self.ids = ids + self.spec_set = spec_set + self.parent = parent + self.instance = instance + self.name = name + + +FunctionTypes = ( + # python function + type(create_autospec), + # instance method + type(ANY.__eq__), + # unbound method + type(_ANY.__eq__), +) + +FunctionAttributes = set([ + 'func_closure', + 'func_code', + 'func_defaults', + 'func_dict', + 'func_doc', + 'func_globals', + 'func_name', +]) diff --git a/script/test/diffcalc (copy)/model/README.txt b/script/test/diffcalc (copy)/model/README.txt new file mode 100644 index 0000000..45d8fcb --- /dev/null +++ b/script/test/diffcalc (copy)/model/README.txt @@ -0,0 +1,13 @@ +Setting up the sixc vrml model to work via Epics pv's from b16's simulation running on the office network. + + +1. Synoptic +$ launcher --port 6064 + +2. Shell +export EPICS_CA_REPEATER_PORT=6065 +export EPICS_CA_SERVER_PORT=6064 + +3. Vrml viewer +export EPICS_CA_SERVER_PORT=6064 +dls-vrml-epics-viewer.py fivec.wrl fivec.wcfg diff --git a/script/test/diffcalc (copy)/model/fivec.fxw b/script/test/diffcalc (copy)/model/fivec.fxw new file mode 100644 index 0000000..de9ae05 Binary files /dev/null and b/script/test/diffcalc (copy)/model/fivec.fxw differ diff --git a/script/test/diffcalc (copy)/model/fivec.wcfg b/script/test/diffcalc (copy)/model/fivec.wcfg new file mode 100644 index 0000000..8f96e1e --- /dev/null +++ b/script/test/diffcalc (copy)/model/fivec.wcfg @@ -0,0 +1,5 @@ +dad_alpha_frame rotation pv["DIFFSIM:FIVEC:ALPHA.RBV"] +dad_delta_frame rotation pv["DIFFSIM:FIVEC:DELTA.RBV"] +dad_omega_frame rotation pv["DIFFSIM:FIVEC:OMEGA.RBV"] +dad_chi_frame rotation pv["DIFFSIM:FIVEC:CHI.RBV"] +dad_phi_frame rotation pv["DIFFSIM:FIVEC:PHI.RBV"] \ No newline at end of file diff --git a/script/test/diffcalc (copy)/model/fivec.wrl b/script/test/diffcalc (copy)/model/fivec.wrl new file mode 100644 index 0000000..85f6fad --- /dev/null +++ b/script/test/diffcalc (copy)/model/fivec.wrl @@ -0,0 +1,1011 @@ +#VRML V2.0 utf8 +WorldInfo { + title "fourc" + info ["This Web3D Content was created with Flux Studio, a Web3D authoring tool" + "www.mediamachines.com" + "This Web3D Content was created with Flux Studio, a Web3D authoring tool"] +} +## Vizthumbnail Thumb_fivec_wrl5688291255594457.jpg +DirectionalLight { + intensity 1.000 + ambientIntensity 0.500 + direction -.621 -.23944 -.74634 + color 1 1 1 + on TRUE +} +DirectionalLight { + intensity 1.000 + ambientIntensity 0.000 + direction -.01373 -.46642 -.88446 + color 1 1 1 + on TRUE +} +DEF lab_x Shape { + appearance Appearance { + material DEF Rust Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .50196 .50196 .50196 + } + } + geometry DEF GeoCylinder15 Cylinder { + height 60.000 + radius 0.020 + } +} +DEF dad_lab_z Transform { + rotation 1 0 0 1.571 + children [ + DEF lab_z Shape { + appearance Appearance { + material USE Rust + } + geometry DEF GeoCylinder17 Cylinder { + height 40.000 + radius 0.020 + } + } + ] +} +DEF dad_lab_beam Transform { + translation 10 0 0 + rotation 0 0 1 1.571 + children [ + DEF lab_beam Shape { + appearance Appearance { + material DEF White Material { + ambientIntensity 0.500 + shininess 0.100 + transparency 0.500 + diffuseColor 1 1 1 + emissiveColor 1 1 1 + } + } + geometry DEF GeoCylinder20 Cylinder { + height 20.000 + radius 0.050 + } + } + ] +} +DEF dad_lab_beam0 Transform { + translation 20 0 0 + rotation 0 0 1 1.571 + children [ + DEF lab_beam0 Shape { + appearance Appearance { + material DEF Shiny_Black Material { + ambientIntensity 0.200 + shininess 0.100 + diffuseColor 1 1 1 + } + } + geometry DEF GeoCylinder22 Cylinder { + height 4.000 + radius 0.200 + } + } + ] +} +DEF dad_Cone2 Transform { + translation 17.5 0 0 + rotation 0 0 1 1.571 + children [ + DEF Cone2 Shape { + appearance Appearance { + material USE Shiny_Black + } + geometry DEF GeoCone2 Cone { + height 1.500 + bottomRadius 0.500 + } + } + ] +} +DEF dad_lab_y Transform { + rotation 0 0 -1 1.571 + children [ + DEF lab_y Shape { + appearance Appearance { + material USE Rust + } + geometry DEF GeoCylinder16 Cylinder { + height 60.000 + radius 0.020 + } + } + ] +} +DEF dad_alpha_frame Transform { + rotation 0 1 0 +.349 + children [ + DEF dad_alpha_base Transform { + translation 0 -20.5 0 + children [ + DEF alpha_base Shape { + appearance Appearance { + material DEF Red Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .50196 0 0 + } + } + geometry DEF GeoCylinder1 Cylinder { + height 2.000 + radius 6.000 + } + } + ] + } + DEF dad_lab_post Transform { + translation 0 -10 -11 + children [ + DEF alpha_post Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox1 Box { + size 4 20 1 + } + } + ] + } + DEF dad_omega_frame Transform { + rotation 0 0 1 -.349 + children [ + DEF dad_chi_frame Transform { + rotation 1 0 0 -.785 + children [ + DEF dad_phi_frame Transform { + rotation 0 0 1 -.349 + children [ + DEF dad_Cylinder7 Transform { + translation 0 0 -4 + rotation 1 0 0 1.571 + children [ + DEF Cylinder7 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoCylinder7 Cylinder { + height 2.000 + radius 1.000 + } + } + ] + } + DEF dad_Cylinder8 Transform { + translation 0 0 -3.5 + rotation 1 0 0 1.571 + children [ + DEF Cylinder8 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoCylinder8 Cylinder { + height 6.000 + radius 0.250 + } + } + ] + } + DEF Box3 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoBox3 Box { + size 1 1 1 + } + } + DEF dad_Box11 Transform { + translation 0 1 -4 + children [ + DEF Box11 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoBox11 Box { + size .25 .5 2 + } + } + ] + } + DEF Box4 Shape { + appearance Appearance { + material DEF White_wire Material { + emissiveColor 1 1 1 + } + } + geometry IndexedLineSet { + coordIndex [ + 0 1 2 0 + -1 0 2 3 + 0 -1 1 5 + 6 1 -1 1 + 6 2 1 -1 + 2 6 7 2 + -1 2 7 3 + 2 -1 3 7 + 4 3 -1 3 + 4 0 3 -1 + 0 4 5 0 + -1 0 5 1 + 0 -1 6 5 + 4 6 -1 6 + 4 7 6 -1 + ] + coord Coordinate { + point [ + -.5 .5 -.5 + -.5 .5 .5 + .5 .5 .5 + .5 .5 -.5 + -.5 -.5 -.5 + -.5 -.5 .5 + .5 -.5 .5 + .5 -.5 -.5 + ] + } + } + } + ] + } + DEF dad_Box2 Transform { + translation 0 0 -5.5 + children [ + DEF Box2 Shape { + appearance Appearance { + material DEF Purple Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .9176 0 .874 + } + } + geometry DEF GeoBox2 Box { + size 2.8 2.8 1.5 + } + } + ] + } + DEF dad_Box6 Transform { + translation 0 1.25 -5.5 + children [ + DEF Box6 Shape { + appearance Appearance { + material USE Purple + } + geometry DEF GeoBox21 Box { + size .25 1 1.5 + } + } + ] + } + ] + } + DEF dad_Cylinder2 Transform { + rotation 0 0 1 1.571 + scale 1.1 1.5 1.1 + children [ + DEF Cylinder2 Shape { + appearance Appearance { + material DEF Green Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .00784 .65098 .02353 + } + } + geometry DEF Cylinder2_Geo IndexedFaceSet { + creaseAngle 0.524 + coordIndex [ + 49 50 48 -1 + 48 50 51 -1 + 48 51 47 -1 + 47 51 20 -1 + 47 20 21 -1 + 12 13 56 -1 + 56 13 14 -1 + 56 14 55 -1 + 55 14 15 -1 + 55 15 54 -1 + 54 15 16 -1 + 54 16 53 -1 + 53 16 17 -1 + 53 17 52 -1 + 52 17 18 -1 + 52 18 51 -1 + 51 18 19 -1 + 51 19 20 -1 + 5 6 62 -1 + 62 6 7 -1 + 62 7 61 -1 + 61 7 8 -1 + 61 8 60 -1 + 60 8 9 -1 + 60 9 59 -1 + 59 9 10 -1 + 59 10 58 -1 + 58 10 11 -1 + 58 11 57 -1 + 57 11 12 -1 + 57 12 56 -1 + 5 63 4 -1 + 4 63 64 -1 + 4 64 3 -1 + 3 64 1 -1 + 3 1 2 -1 + 2 1 35 -1 + 2 35 36 -1 + 28 41 27 -1 + 27 41 42 -1 + 27 42 26 -1 + 26 42 43 -1 + 26 43 25 -1 + 25 43 44 -1 + 25 44 24 -1 + 24 44 45 -1 + 24 45 23 -1 + 23 45 46 -1 + 23 46 22 -1 + 22 46 47 -1 + 22 47 21 -1 + 34 36 33 -1 + 33 36 37 -1 + 33 37 32 -1 + 32 37 38 -1 + 32 38 31 -1 + 31 38 39 -1 + 31 39 30 -1 + 30 39 40 -1 + 30 40 29 -1 + 29 40 41 -1 + 29 41 28 -1 + 1 64 0 -1 + 2 36 34 -1 + 63 5 62 -1 + 95 96 97 -1 + 97 96 65 -1 + 97 65 129 -1 + 129 65 66 -1 + 129 66 128 -1 + 128 66 67 -1 + 128 67 127 -1 + 127 67 126 -1 + 104 105 88 -1 + 88 105 106 -1 + 88 106 87 -1 + 87 106 107 -1 + 87 107 86 -1 + 86 107 108 -1 + 86 108 85 -1 + 85 108 109 -1 + 85 109 84 -1 + 84 109 110 -1 + 84 110 83 -1 + 83 110 111 -1 + 83 111 82 -1 + 82 111 112 -1 + 82 112 113 -1 + 98 99 93 -1 + 93 99 100 -1 + 93 100 92 -1 + 92 100 101 -1 + 92 101 91 -1 + 91 101 102 -1 + 91 102 90 -1 + 90 102 103 -1 + 90 103 89 -1 + 89 103 104 -1 + 89 104 88 -1 + 94 95 97 -1 + 94 97 98 -1 + 94 98 93 -1 + 81 82 78 -1 + 81 78 79 -1 + 81 79 80 -1 + 78 82 113 -1 + 78 113 114 -1 + 78 114 115 -1 + 78 115 77 -1 + 120 73 119 -1 + 119 73 74 -1 + 119 74 118 -1 + 118 74 75 -1 + 118 75 117 -1 + 117 75 76 -1 + 117 76 116 -1 + 116 76 77 -1 + 116 77 115 -1 + 126 68 125 -1 + 125 68 69 -1 + 125 69 124 -1 + 124 69 70 -1 + 124 70 123 -1 + 123 70 71 -1 + 123 71 122 -1 + 122 71 72 -1 + 122 72 121 -1 + 121 72 73 -1 + 121 73 120 -1 + 126 67 68 -1 + 0 64 65 -1 + 0 65 96 -1 + 64 63 66 -1 + 64 66 65 -1 + 63 62 67 -1 + 63 67 66 -1 + 62 61 68 -1 + 62 68 67 -1 + 61 60 69 -1 + 61 69 68 -1 + 60 59 70 -1 + 60 70 69 -1 + 59 58 71 -1 + 59 71 70 -1 + 58 57 72 -1 + 58 72 71 -1 + 57 56 73 -1 + 57 73 72 -1 + 56 55 74 -1 + 56 74 73 -1 + 55 54 75 -1 + 55 75 74 -1 + 54 53 76 -1 + 54 76 75 -1 + 53 52 77 -1 + 53 77 76 -1 + 52 51 78 -1 + 52 78 77 -1 + 51 50 79 -1 + 51 79 78 -1 + 50 49 80 -1 + 50 80 79 -1 + 49 48 81 -1 + 49 81 80 -1 + 48 47 82 -1 + 48 82 81 -1 + 47 46 83 -1 + 47 83 82 -1 + 46 45 84 -1 + 46 84 83 -1 + 45 44 85 -1 + 45 85 84 -1 + 44 43 86 -1 + 44 86 85 -1 + 43 42 87 -1 + 43 87 86 -1 + 42 41 88 -1 + 42 88 87 -1 + 41 40 89 -1 + 41 89 88 -1 + 40 39 90 -1 + 40 90 89 -1 + 39 38 91 -1 + 39 91 90 -1 + 38 37 92 -1 + 38 92 91 -1 + 37 36 93 -1 + 37 93 92 -1 + 36 35 94 -1 + 36 94 93 -1 + 35 1 95 -1 + 35 95 94 -1 + 1 0 96 -1 + 1 96 95 -1 + ] + coord DEF Cylinder2_Coord Coordinate { + point [ + 0 1 -6 + -1.17054 1 -5.88471 + -1.07212 1 -5.3899 + -1.05189 1 -5.39596 + .02145 1 -5.49809 + 1.09408 1 -5.38886 + 2.1248 1 -5.07248 + 3.07398 1 -4.56108 + 3.90517 1 -3.87434 + 4.5864 1 -3.03864 + 5.0915 1 -2.08609 + 5.40107 1 -1.0533 + 5.5032 1 .02003 + 5.39398 1 1.09267 + 5.07759 1 2.12338 + 4.5662 1 3.07257 + 3.87945 1 3.90375 + 3.04375 1 4.58498 + 2.0912 1 5.09009 + 1.05842 1 5.39966 + -.01492 1 5.50179 + -1.08755 1 5.39256 + -2.11827 1 5.07617 + -3.06746 1 4.56478 + -3.89864 1 3.87804 + -4.57987 1 3.04233 + -5.08498 1 2.08979 + -5.39454 1 1.057 + -5.49668 1 -.01633 + -5.38745 1 -1.08897 + -5.07106 1 -2.11968 + -4.55967 1 -3.06887 + -3.87292 1 -3.90005 + -3.03722 1 -4.58129 + -2.08467 1 -5.08639 + -2.2961 1 -5.54328 + -3.33342 1 -4.98882 + -4.24264 1 -4.24264 + -4.98882 1 -3.33342 + -5.54328 1 -2.2961 + -5.88471 1 -1.17054 + -6 1 -0 + -5.88471 1 1.17054 + -5.54328 1 2.2961 + -4.98882 1 3.33342 + -4.24264 1 4.24264 + -3.33342 1 4.98882 + -2.2961 1 5.54328 + -1.17054 1 5.88471 + -0 1 6 + 1.17054 1 5.88471 + 2.2961 1 5.54328 + 3.33342 1 4.98882 + 4.24264 1 4.24264 + 4.98882 1 3.33342 + 5.54328 1 2.2961 + 5.88471 1 1.17054 + 6 1 0 + 5.88471 1 -1.17054 + 5.54328 1 -2.2961 + 4.98882 1 -3.33342 + 4.24264 1 -4.24264 + 3.33342 1 -4.98882 + 2.2961 1 -5.54328 + 1.17054 1 -5.88471 + 1.17054 -1 -5.88471 + 2.2961 -1 -5.54328 + 3.33342 -1 -4.98882 + 4.24264 -1 -4.24264 + 4.98882 -1 -3.33342 + 5.54328 -1 -2.2961 + 5.88471 -1 -1.17054 + 6 -1 -0 + 5.88471 -1 1.17054 + 5.54328 -1 2.2961 + 4.98882 -1 3.33342 + 4.24264 -1 4.24264 + 3.33342 -1 4.98882 + 2.2961 -1 5.54328 + 1.17054 -1 5.88471 + -0 -1 6 + -1.17054 -1 5.88471 + -2.2961 -1 5.54328 + -3.33342 -1 4.98882 + -4.24264 -1 4.24264 + -4.98882 -1 3.33342 + -5.54328 -1 2.2961 + -5.88471 -1 1.17054 + -6 -1 -0 + -5.88471 -1 -1.17054 + -5.54328 -1 -2.2961 + -4.98882 -1 -3.33342 + -4.24264 -1 -4.24264 + -3.33342 -1 -4.98882 + -2.2961 -1 -5.54328 + -1.17054 -1 -5.88471 + 0 -1 -6 + 0 -1 -5.50037 + -1.05842 -1 -5.39966 + -2.0912 -1 -5.09009 + -3.04375 -1 -4.58498 + -3.87945 -1 -3.90375 + -4.5662 -1 -3.07257 + -5.07759 -1 -2.12338 + -5.39398 -1 -1.09267 + -5.5032 -1 -.02003 + -5.40107 -1 1.0533 + -5.0915 -1 2.08609 + -4.5864 -1 3.03864 + -3.90517 -1 3.87434 + -3.07398 -1 4.56108 + -2.1248 -1 5.07248 + -1.09408 -1 5.38886 + -.02145 -1 5.49809 + 1.05189 -1 5.39596 + 2.08467 -1 5.08639 + 3.03722 -1 4.58129 + 3.87292 -1 3.90005 + 4.55967 -1 3.06887 + 5.07106 -1 2.11968 + 5.38745 -1 1.08897 + 5.49668 -1 .01633 + 5.39454 -1 -1.057 + 5.08498 -1 -2.08979 + 4.57987 -1 -3.04233 + 3.89864 -1 -3.87804 + 3.06746 -1 -4.56478 + 2.11827 -1 -5.07617 + 1.08755 -1 -5.39256 + .01492 -1 -5.50179 + ] + } + } + } + ] + } + DEF dad_Cylinder3 Transform { + rotation 0 0 1 1.571 + scale 1.1 1.5 1.1 + children [ + DEF Cylinder3 Shape { + appearance Appearance { + material USE Green + } + geometry DEF Cylinder3_Geo IndexedFaceSet { + creaseAngle 0.524 + coordIndex [ + 0 1 2 -1 + 0 2 3 -1 + 4 5 1 -1 + 4 1 0 -1 + 6 7 5 -1 + 6 5 4 -1 + 8 9 7 -1 + 8 7 6 -1 + 10 11 9 -1 + 10 9 8 -1 + 12 13 11 -1 + 12 11 10 -1 + 14 15 13 -1 + 14 13 12 -1 + 16 17 15 -1 + 16 15 14 -1 + 18 19 17 -1 + 18 17 16 -1 + 20 21 19 -1 + 20 19 18 -1 + 22 23 21 -1 + 22 21 20 -1 + 24 25 23 -1 + 24 23 22 -1 + 26 27 25 -1 + 26 25 24 -1 + 28 29 27 -1 + 28 27 26 -1 + 30 31 29 -1 + 30 29 28 -1 + 32 33 31 -1 + 32 31 30 -1 + 34 35 33 -1 + 34 33 32 -1 + 36 37 35 -1 + 36 35 34 -1 + 38 39 37 -1 + 38 37 36 -1 + 40 41 39 -1 + 40 39 38 -1 + 42 43 41 -1 + 42 41 40 -1 + 44 45 43 -1 + 44 43 42 -1 + 46 47 45 -1 + 46 45 44 -1 + 48 49 47 -1 + 48 47 46 -1 + 50 51 49 -1 + 50 49 48 -1 + 52 53 51 -1 + 52 51 50 -1 + 54 55 53 -1 + 54 53 52 -1 + 56 57 55 -1 + 56 55 54 -1 + 58 59 57 -1 + 58 57 56 -1 + 60 61 59 -1 + 60 59 58 -1 + 62 63 61 -1 + 62 61 60 -1 + 3 2 63 -1 + 3 63 62 -1 + ] + coord DEF Cylinder3_Coord Coordinate { + point [ + 1.073 -1.00635 -5.39432 + 1.073 .99367 -5.39432 + 0 .98997 -5.5 + 0 -1.01006 -5.5 + 2.10476 -1.0024 -5.08134 + 2.10476 .99763 -5.08134 + 3.05564 -.99836 -4.57308 + 3.05564 1.00167 -4.57308 + 3.88909 -.99438 -3.88909 + 3.88909 1.00565 -3.88909 + 4.57308 -.99061 -3.05564 + 4.57308 1.00941 -3.05564 + 5.08134 -.98721 -2.10476 + 5.08134 1.01282 -2.10476 + 5.39432 -.9843 -1.073 + 5.39432 1.01573 -1.073 + 5.5 -.98199 -0 + 5.5 1.01803 -0 + 5.39432 -.98038 1.073 + 5.39432 1.01965 1.073 + 5.08134 -.97952 2.10476 + 5.08134 1.02051 2.10476 + 4.57308 -.97945 3.05564 + 4.57308 1.02058 3.05564 + 3.88909 -.98017 3.88909 + 3.88909 1.01986 3.88909 + 3.05564 -.98165 4.57308 + 3.05564 1.01838 4.57308 + 2.10476 -.98383 5.08134 + 2.10476 1.01619 5.08134 + 1.073 -.98664 5.39432 + 1.073 1.01338 5.39432 + -0 -.98997 5.5 + -0 1.01006 5.5 + -1.073 -.99367 5.39432 + -1.073 1.00635 5.39432 + -2.10476 -.99763 5.08134 + -2.10476 1.0024 5.08134 + -3.05564 -1.00167 4.57308 + -3.05564 .99836 4.57308 + -3.88909 -1.00565 3.88909 + -3.88909 .99438 3.88909 + -4.57308 -1.00941 3.05564 + -4.57308 .99061 3.05564 + -5.08134 -1.01282 2.10476 + -5.08134 .98721 2.10476 + -5.39432 -1.01573 1.073 + -5.39432 .9843 1.073 + -5.5 -1.01803 -0 + -5.5 .98199 -0 + -5.39432 -1.01965 -1.073 + -5.39432 .98038 -1.073 + -5.08134 -1.02051 -2.10476 + -5.08134 .97952 -2.10476 + -4.57308 -1.02058 -3.05564 + -4.57308 .97945 -3.05564 + -3.88909 -1.01986 -3.88909 + -3.88909 .98017 -3.88909 + -3.05564 -1.01838 -4.57308 + -3.05564 .98165 -4.57308 + -2.10476 -1.01619 -5.08134 + -2.10476 .98383 -5.08134 + -1.073 -1.01338 -5.39432 + -1.073 .98664 -5.39432 + ] + } + } + } + ] + } + DEF dad_Cylinder5 Transform { + translation 0 0 -8.4 + rotation 1 0 0 1.571 + children [ + DEF Cylinder5 Shape { + appearance Appearance { + material USE Green + } + geometry DEF GeoCylinder5 Cylinder { + height 4.250 + radius 2.000 + } + } + ] + } + DEF dad_Box10 Transform { + translation 0 2 -8.4 + children [ + DEF Box10 Shape { + appearance Appearance { + material USE Green + } + geometry DEF GeoBox10 Box { + size .25 1 4.25 + } + } + ] + } + ] + } + DEF dad_lab_posttop Transform { + translation 0 0 -11 + rotation -1 0 0 1.571 + children [ + DEF alpha_posttop Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoCylinder6 Cylinder { + height 1.000 + radius 2.000 + } + } + ] + } + DEF dad_alpha_arm Transform { + translation 0 -20.5 -5 + children [ + DEF alpha_arm Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox7 Box { + size 4 1 13 + } + } + ] + } + DEF dad_gamma_arm1 Transform { + translation -6 -20.5 0 + children [ + DEF gamma_arm1 Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox18 Box { + size 1 2 .25 + } + } + ] + } + DEF dad_Box12 Transform { + translation 0 2 -11 + children [ + DEF Box12 Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox12 Box { + size .25 1 1 + } + } + ] + } + DEF dad_delta_frame Transform { + rotation 0 0 1 -.349 + children [ + DEF dad_delta_mount Transform { + translation 0 0 -14.25 + rotation -1 0 0 1.571 + children [ + DEF delta_mount Shape { + appearance Appearance { + material DEF Yellow Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .83529 .83529 0 + } + } + geometry DEF GeoCylinder2 Cylinder { + height 5.500 + radius 2.000 + } + } + ] + } + DEF dad_delta_arm Transform { + translation -10 0 -15 + children [ + DEF delta_arm Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoBox8 Box { + size 20 3 1 + } + } + ] + } + DEF dad_delta_arm0 Transform { + translation -20 0 -6.5 + children [ + DEF delta_arm0 Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoBox6 Box { + size 1 3 18 + } + } + ] + } + DEF dad_Cylinder14 Transform { + translation -18 0 0 + rotation 0 0 -1 1.571 + children [ + DEF Cylinder14 Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoCylinder14 Cylinder { + height 3.000 + radius 1.000 + } + } + ] + } + DEF dad_detector_beam Transform { + translation -10 0 0 + rotation 0 0 1 1.571 + children [ + DEF detector_beam Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoCylinder21 Cylinder { + height 20.000 + radius 0.050 + } + } + ] + } + DEF dad_Box13 Transform { + translation 0 2 -14.25 + children [ + DEF Box13 Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoBox13 Box { + size .25 1 5.5 + } + } + ] + } + ] + } + ] +} +DEF VP Viewpoint { + description "VP" + jump TRUE + fieldOfView 1.165 + position 40 40 40 + orientation -.754 .657 -0 1.001 +} +DEF dad_gamma_arm2 Transform { + translation -9 -22.5 0 + children [ + DEF gamma_arm2 Shape { + appearance Appearance { + material DEF Black Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .20784 .20784 .20784 + } + } + geometry DEF GeoBox19 Box { + size 1 2 .25 + } + } + ] +} +DEF dad_gamma_base0 Transform { + translation 0 -22.5 0 + rotation 0 1 0 .349 + children [ + DEF gamma_base0 Shape { + appearance Appearance { + material USE Black + } + geometry DEF GeoCylinder9 Cylinder { + height 2.000 + radius 9.000 + } + } + ] +} +DEF dad_floor Transform { + translation 0 -23.5 0 + children [ + DEF floor Shape { + appearance Appearance { + material USE Black + } + geometry DEF GeoBox16 Box { + size 60 .2 40 + } + } + ] +} diff --git a/script/test/diffcalc (copy)/model/sixc.fxw b/script/test/diffcalc (copy)/model/sixc.fxw new file mode 100644 index 0000000..51d97a4 Binary files /dev/null and b/script/test/diffcalc (copy)/model/sixc.fxw differ diff --git a/script/test/diffcalc (copy)/model/sixc.wcfg b/script/test/diffcalc (copy)/model/sixc.wcfg new file mode 100644 index 0000000..2fa6961 --- /dev/null +++ b/script/test/diffcalc (copy)/model/sixc.wcfg @@ -0,0 +1,6 @@ +dad_alpha_frame rotation pv["DIFFSIM:SIXC:ALPHA.RBV"] +dad_delta_frame rotation pv["DIFFSIM:SIXC:DELTA.RBV"] +dad_gamma_frame rotation pv["DIFFSIM:SIXC:GAMMA.RBV"] +dad_omega_frame rotation pv["DIFFSIM:SIXC:OMEGA.RBV"] +dad_chi_frame rotation pv["DIFFSIM:SIXC:CHI.RBV"] +dad_phi_frame rotation pv["DIFFSIM:SIXC:PHI.RBV"] diff --git a/script/test/diffcalc (copy)/model/sixc.wrl b/script/test/diffcalc (copy)/model/sixc.wrl new file mode 100644 index 0000000..32ad769 --- /dev/null +++ b/script/test/diffcalc (copy)/model/sixc.wrl @@ -0,0 +1,1101 @@ +#VRML V2.0 utf8 +WorldInfo { + title "fourc" + info ["This Web3D Content was created with Flux Studio, a Web3D authoring tool" + "www.mediamachines.com" + "This Web3D Content was created with Flux Studio, a Web3D authoring tool"] +} +## Vizthumbnail Thumb_fourc_wrl36454531255104963.jpg +DirectionalLight { + intensity 1.000 + ambientIntensity 0.500 + direction -.621 -.23944 -.74634 + color 1 1 1 + on TRUE +} +DirectionalLight { + intensity 1.000 + ambientIntensity 0.000 + direction -.01373 -.46642 -.88446 + color 1 1 1 + on TRUE +} +DEF lab_x Shape { + appearance Appearance { + material DEF Rust Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .50196 .50196 .50196 + } + } + geometry DEF GeoCylinder15 Cylinder { + height 60.000 + radius 0.020 + } +} +DEF dad_lab_z Transform { + rotation 1 0 0 1.571 + children [ + DEF lab_z Shape { + appearance Appearance { + material USE Rust + } + geometry DEF GeoCylinder17 Cylinder { + height 40.000 + radius 0.020 + } + } + ] +} +DEF dad_lab_beam Transform { + translation 10 0 0 + rotation 0 0 1 1.571 + children [ + DEF lab_beam Shape { + appearance Appearance { + material DEF White Material { + ambientIntensity 0.500 + shininess 0.100 + transparency 0.500 + diffuseColor 1 1 1 + emissiveColor 1 1 1 + } + } + geometry DEF GeoCylinder20 Cylinder { + height 20.000 + radius 0.050 + } + } + ] +} +DEF dad_lab_beam0 Transform { + translation 20 0 0 + rotation 0 0 1 1.571 + children [ + DEF lab_beam0 Shape { + appearance Appearance { + material DEF Shiny_Black Material { + ambientIntensity 0.200 + shininess 0.100 + diffuseColor 1 1 1 + } + } + geometry DEF GeoCylinder22 Cylinder { + height 4.000 + radius 0.200 + } + } + ] +} +DEF dad_Cone2 Transform { + translation 17.5 0 0 + rotation 0 0 1 1.571 + children [ + DEF Cone2 Shape { + appearance Appearance { + material USE Shiny_Black + } + geometry DEF GeoCone2 Cone { + height 1.500 + bottomRadius 0.500 + } + } + ] +} +DEF dad_lab_y Transform { + rotation 0 0 -1 1.571 + children [ + DEF lab_y Shape { + appearance Appearance { + material USE Rust + } + geometry DEF GeoCylinder16 Cylinder { + height 60.000 + radius 0.020 + } + } + ] +} +DEF dad_alpha_frame Transform { + rotation 0 1 0 .175 + children [ + DEF dad_alpha_base Transform { + translation 0 -20.5 0 + children [ + DEF alpha_base Shape { + appearance Appearance { + material DEF Red Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .50196 0 0 + } + } + geometry DEF GeoCylinder1 Cylinder { + height 2.000 + radius 6.000 + } + } + ] + } + DEF dad_lab_post Transform { + translation 0 -10 -11 + children [ + DEF alpha_post Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox1 Box { + size 4 20 1 + } + } + ] + } + DEF dad_omega_frame Transform { + rotation 0 0 1 -.175 + children [ + DEF dad_chi_frame Transform { + rotation 1 0 0 -.785 + children [ + DEF dad_phi_frame Transform { + rotation 0 0 1 -.349 + children [ + DEF dad_Cylinder7 Transform { + translation 0 0 -4 + rotation 1 0 0 1.571 + children [ + DEF Cylinder7 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoCylinder7 Cylinder { + height 2.000 + radius 1.000 + } + } + ] + } + DEF dad_Cylinder8 Transform { + translation 0 0 -3.5 + rotation 1 0 0 1.571 + children [ + DEF Cylinder8 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoCylinder8 Cylinder { + height 6.000 + radius 0.250 + } + } + ] + } + DEF Box3 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoBox3 Box { + size 1 1 1 + } + } + DEF dad_Box11 Transform { + translation 0 1 -4 + children [ + DEF Box11 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoBox11 Box { + size .25 .5 2 + } + } + ] + } + DEF Box4 Shape { + appearance Appearance { + material DEF White_wire Material { + emissiveColor 1 1 1 + } + } + geometry IndexedLineSet { + coordIndex [ + 0 1 2 0 + -1 0 2 3 + 0 -1 1 5 + 6 1 -1 1 + 6 2 1 -1 + 2 6 7 2 + -1 2 7 3 + 2 -1 3 7 + 4 3 -1 3 + 4 0 3 -1 + 0 4 5 0 + -1 0 5 1 + 0 -1 6 5 + 4 6 -1 6 + 4 7 6 -1 + ] + coord Coordinate { + point [ + -.5 .5 -.5 + -.5 .5 .5 + .5 .5 .5 + .5 .5 -.5 + -.5 -.5 -.5 + -.5 -.5 .5 + .5 -.5 .5 + .5 -.5 -.5 + ] + } + } + } + ] + } + DEF dad_Box2 Transform { + translation 0 0 -5.5 + children [ + DEF Box2 Shape { + appearance Appearance { + material DEF Purple Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .9176 0 .874 + } + } + geometry DEF GeoBox2 Box { + size 2.8 2.8 1.5 + } + } + ] + } + DEF dad_Box6 Transform { + translation 0 1.25 -5.5 + children [ + DEF Box6 Shape { + appearance Appearance { + material USE Purple + } + geometry DEF GeoBox21 Box { + size .25 1 1.5 + } + } + ] + } + ] + } + DEF dad_Cylinder2 Transform { + rotation 0 0 1 1.571 + scale 1.1 1.5 1.1 + children [ + DEF Cylinder2 Shape { + appearance Appearance { + material DEF Green Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .00784 .65098 .02353 + } + } + geometry DEF Cylinder2_Geo IndexedFaceSet { + creaseAngle 0.524 + coordIndex [ + 49 50 48 -1 + 48 50 51 -1 + 48 51 47 -1 + 47 51 20 -1 + 47 20 21 -1 + 12 13 56 -1 + 56 13 14 -1 + 56 14 55 -1 + 55 14 15 -1 + 55 15 54 -1 + 54 15 16 -1 + 54 16 53 -1 + 53 16 17 -1 + 53 17 52 -1 + 52 17 18 -1 + 52 18 51 -1 + 51 18 19 -1 + 51 19 20 -1 + 5 6 62 -1 + 62 6 7 -1 + 62 7 61 -1 + 61 7 8 -1 + 61 8 60 -1 + 60 8 9 -1 + 60 9 59 -1 + 59 9 10 -1 + 59 10 58 -1 + 58 10 11 -1 + 58 11 57 -1 + 57 11 12 -1 + 57 12 56 -1 + 5 63 4 -1 + 4 63 64 -1 + 4 64 3 -1 + 3 64 1 -1 + 3 1 2 -1 + 2 1 35 -1 + 2 35 36 -1 + 28 41 27 -1 + 27 41 42 -1 + 27 42 26 -1 + 26 42 43 -1 + 26 43 25 -1 + 25 43 44 -1 + 25 44 24 -1 + 24 44 45 -1 + 24 45 23 -1 + 23 45 46 -1 + 23 46 22 -1 + 22 46 47 -1 + 22 47 21 -1 + 34 36 33 -1 + 33 36 37 -1 + 33 37 32 -1 + 32 37 38 -1 + 32 38 31 -1 + 31 38 39 -1 + 31 39 30 -1 + 30 39 40 -1 + 30 40 29 -1 + 29 40 41 -1 + 29 41 28 -1 + 1 64 0 -1 + 2 36 34 -1 + 63 5 62 -1 + 95 96 97 -1 + 97 96 65 -1 + 97 65 129 -1 + 129 65 66 -1 + 129 66 128 -1 + 128 66 67 -1 + 128 67 127 -1 + 127 67 126 -1 + 104 105 88 -1 + 88 105 106 -1 + 88 106 87 -1 + 87 106 107 -1 + 87 107 86 -1 + 86 107 108 -1 + 86 108 85 -1 + 85 108 109 -1 + 85 109 84 -1 + 84 109 110 -1 + 84 110 83 -1 + 83 110 111 -1 + 83 111 82 -1 + 82 111 112 -1 + 82 112 113 -1 + 98 99 93 -1 + 93 99 100 -1 + 93 100 92 -1 + 92 100 101 -1 + 92 101 91 -1 + 91 101 102 -1 + 91 102 90 -1 + 90 102 103 -1 + 90 103 89 -1 + 89 103 104 -1 + 89 104 88 -1 + 94 95 97 -1 + 94 97 98 -1 + 94 98 93 -1 + 81 82 78 -1 + 81 78 79 -1 + 81 79 80 -1 + 78 82 113 -1 + 78 113 114 -1 + 78 114 115 -1 + 78 115 77 -1 + 120 73 119 -1 + 119 73 74 -1 + 119 74 118 -1 + 118 74 75 -1 + 118 75 117 -1 + 117 75 76 -1 + 117 76 116 -1 + 116 76 77 -1 + 116 77 115 -1 + 126 68 125 -1 + 125 68 69 -1 + 125 69 124 -1 + 124 69 70 -1 + 124 70 123 -1 + 123 70 71 -1 + 123 71 122 -1 + 122 71 72 -1 + 122 72 121 -1 + 121 72 73 -1 + 121 73 120 -1 + 126 67 68 -1 + 0 64 65 -1 + 0 65 96 -1 + 64 63 66 -1 + 64 66 65 -1 + 63 62 67 -1 + 63 67 66 -1 + 62 61 68 -1 + 62 68 67 -1 + 61 60 69 -1 + 61 69 68 -1 + 60 59 70 -1 + 60 70 69 -1 + 59 58 71 -1 + 59 71 70 -1 + 58 57 72 -1 + 58 72 71 -1 + 57 56 73 -1 + 57 73 72 -1 + 56 55 74 -1 + 56 74 73 -1 + 55 54 75 -1 + 55 75 74 -1 + 54 53 76 -1 + 54 76 75 -1 + 53 52 77 -1 + 53 77 76 -1 + 52 51 78 -1 + 52 78 77 -1 + 51 50 79 -1 + 51 79 78 -1 + 50 49 80 -1 + 50 80 79 -1 + 49 48 81 -1 + 49 81 80 -1 + 48 47 82 -1 + 48 82 81 -1 + 47 46 83 -1 + 47 83 82 -1 + 46 45 84 -1 + 46 84 83 -1 + 45 44 85 -1 + 45 85 84 -1 + 44 43 86 -1 + 44 86 85 -1 + 43 42 87 -1 + 43 87 86 -1 + 42 41 88 -1 + 42 88 87 -1 + 41 40 89 -1 + 41 89 88 -1 + 40 39 90 -1 + 40 90 89 -1 + 39 38 91 -1 + 39 91 90 -1 + 38 37 92 -1 + 38 92 91 -1 + 37 36 93 -1 + 37 93 92 -1 + 36 35 94 -1 + 36 94 93 -1 + 35 1 95 -1 + 35 95 94 -1 + 1 0 96 -1 + 1 96 95 -1 + ] + coord DEF Cylinder2_Coord Coordinate { + point [ + 0 1 -6 + -1.17054 1 -5.88471 + -1.07212 1 -5.3899 + -1.05189 1 -5.39596 + .02145 1 -5.49809 + 1.09408 1 -5.38886 + 2.1248 1 -5.07248 + 3.07398 1 -4.56108 + 3.90517 1 -3.87434 + 4.5864 1 -3.03864 + 5.0915 1 -2.08609 + 5.40107 1 -1.0533 + 5.5032 1 .02003 + 5.39398 1 1.09267 + 5.07759 1 2.12338 + 4.5662 1 3.07257 + 3.87945 1 3.90375 + 3.04375 1 4.58498 + 2.0912 1 5.09009 + 1.05842 1 5.39966 + -.01492 1 5.50179 + -1.08755 1 5.39256 + -2.11827 1 5.07617 + -3.06746 1 4.56478 + -3.89864 1 3.87804 + -4.57987 1 3.04233 + -5.08498 1 2.08979 + -5.39454 1 1.057 + -5.49668 1 -.01633 + -5.38745 1 -1.08897 + -5.07106 1 -2.11968 + -4.55967 1 -3.06887 + -3.87292 1 -3.90005 + -3.03722 1 -4.58129 + -2.08467 1 -5.08639 + -2.2961 1 -5.54328 + -3.33342 1 -4.98882 + -4.24264 1 -4.24264 + -4.98882 1 -3.33342 + -5.54328 1 -2.2961 + -5.88471 1 -1.17054 + -6 1 -0 + -5.88471 1 1.17054 + -5.54328 1 2.2961 + -4.98882 1 3.33342 + -4.24264 1 4.24264 + -3.33342 1 4.98882 + -2.2961 1 5.54328 + -1.17054 1 5.88471 + -0 1 6 + 1.17054 1 5.88471 + 2.2961 1 5.54328 + 3.33342 1 4.98882 + 4.24264 1 4.24264 + 4.98882 1 3.33342 + 5.54328 1 2.2961 + 5.88471 1 1.17054 + 6 1 0 + 5.88471 1 -1.17054 + 5.54328 1 -2.2961 + 4.98882 1 -3.33342 + 4.24264 1 -4.24264 + 3.33342 1 -4.98882 + 2.2961 1 -5.54328 + 1.17054 1 -5.88471 + 1.17054 -1 -5.88471 + 2.2961 -1 -5.54328 + 3.33342 -1 -4.98882 + 4.24264 -1 -4.24264 + 4.98882 -1 -3.33342 + 5.54328 -1 -2.2961 + 5.88471 -1 -1.17054 + 6 -1 -0 + 5.88471 -1 1.17054 + 5.54328 -1 2.2961 + 4.98882 -1 3.33342 + 4.24264 -1 4.24264 + 3.33342 -1 4.98882 + 2.2961 -1 5.54328 + 1.17054 -1 5.88471 + -0 -1 6 + -1.17054 -1 5.88471 + -2.2961 -1 5.54328 + -3.33342 -1 4.98882 + -4.24264 -1 4.24264 + -4.98882 -1 3.33342 + -5.54328 -1 2.2961 + -5.88471 -1 1.17054 + -6 -1 -0 + -5.88471 -1 -1.17054 + -5.54328 -1 -2.2961 + -4.98882 -1 -3.33342 + -4.24264 -1 -4.24264 + -3.33342 -1 -4.98882 + -2.2961 -1 -5.54328 + -1.17054 -1 -5.88471 + 0 -1 -6 + 0 -1 -5.50037 + -1.05842 -1 -5.39966 + -2.0912 -1 -5.09009 + -3.04375 -1 -4.58498 + -3.87945 -1 -3.90375 + -4.5662 -1 -3.07257 + -5.07759 -1 -2.12338 + -5.39398 -1 -1.09267 + -5.5032 -1 -.02003 + -5.40107 -1 1.0533 + -5.0915 -1 2.08609 + -4.5864 -1 3.03864 + -3.90517 -1 3.87434 + -3.07398 -1 4.56108 + -2.1248 -1 5.07248 + -1.09408 -1 5.38886 + -.02145 -1 5.49809 + 1.05189 -1 5.39596 + 2.08467 -1 5.08639 + 3.03722 -1 4.58129 + 3.87292 -1 3.90005 + 4.55967 -1 3.06887 + 5.07106 -1 2.11968 + 5.38745 -1 1.08897 + 5.49668 -1 .01633 + 5.39454 -1 -1.057 + 5.08498 -1 -2.08979 + 4.57987 -1 -3.04233 + 3.89864 -1 -3.87804 + 3.06746 -1 -4.56478 + 2.11827 -1 -5.07617 + 1.08755 -1 -5.39256 + .01492 -1 -5.50179 + ] + } + } + } + ] + } + DEF dad_Cylinder3 Transform { + rotation 0 0 1 1.571 + scale 1.1 1.5 1.1 + children [ + DEF Cylinder3 Shape { + appearance Appearance { + material USE Green + } + geometry DEF Cylinder3_Geo IndexedFaceSet { + creaseAngle 0.524 + coordIndex [ + 0 1 2 -1 + 0 2 3 -1 + 4 5 1 -1 + 4 1 0 -1 + 6 7 5 -1 + 6 5 4 -1 + 8 9 7 -1 + 8 7 6 -1 + 10 11 9 -1 + 10 9 8 -1 + 12 13 11 -1 + 12 11 10 -1 + 14 15 13 -1 + 14 13 12 -1 + 16 17 15 -1 + 16 15 14 -1 + 18 19 17 -1 + 18 17 16 -1 + 20 21 19 -1 + 20 19 18 -1 + 22 23 21 -1 + 22 21 20 -1 + 24 25 23 -1 + 24 23 22 -1 + 26 27 25 -1 + 26 25 24 -1 + 28 29 27 -1 + 28 27 26 -1 + 30 31 29 -1 + 30 29 28 -1 + 32 33 31 -1 + 32 31 30 -1 + 34 35 33 -1 + 34 33 32 -1 + 36 37 35 -1 + 36 35 34 -1 + 38 39 37 -1 + 38 37 36 -1 + 40 41 39 -1 + 40 39 38 -1 + 42 43 41 -1 + 42 41 40 -1 + 44 45 43 -1 + 44 43 42 -1 + 46 47 45 -1 + 46 45 44 -1 + 48 49 47 -1 + 48 47 46 -1 + 50 51 49 -1 + 50 49 48 -1 + 52 53 51 -1 + 52 51 50 -1 + 54 55 53 -1 + 54 53 52 -1 + 56 57 55 -1 + 56 55 54 -1 + 58 59 57 -1 + 58 57 56 -1 + 60 61 59 -1 + 60 59 58 -1 + 62 63 61 -1 + 62 61 60 -1 + 3 2 63 -1 + 3 63 62 -1 + ] + coord DEF Cylinder3_Coord Coordinate { + point [ + 1.073 -1.00635 -5.39432 + 1.073 .99367 -5.39432 + 0 .98997 -5.5 + 0 -1.01006 -5.5 + 2.10476 -1.0024 -5.08134 + 2.10476 .99763 -5.08134 + 3.05564 -.99836 -4.57308 + 3.05564 1.00167 -4.57308 + 3.88909 -.99438 -3.88909 + 3.88909 1.00565 -3.88909 + 4.57308 -.99061 -3.05564 + 4.57308 1.00941 -3.05564 + 5.08134 -.98721 -2.10476 + 5.08134 1.01282 -2.10476 + 5.39432 -.9843 -1.073 + 5.39432 1.01573 -1.073 + 5.5 -.98199 -0 + 5.5 1.01803 -0 + 5.39432 -.98038 1.073 + 5.39432 1.01965 1.073 + 5.08134 -.97952 2.10476 + 5.08134 1.02051 2.10476 + 4.57308 -.97945 3.05564 + 4.57308 1.02058 3.05564 + 3.88909 -.98017 3.88909 + 3.88909 1.01986 3.88909 + 3.05564 -.98165 4.57308 + 3.05564 1.01838 4.57308 + 2.10476 -.98383 5.08134 + 2.10476 1.01619 5.08134 + 1.073 -.98664 5.39432 + 1.073 1.01338 5.39432 + -0 -.98997 5.5 + -0 1.01006 5.5 + -1.073 -.99367 5.39432 + -1.073 1.00635 5.39432 + -2.10476 -.99763 5.08134 + -2.10476 1.0024 5.08134 + -3.05564 -1.00167 4.57308 + -3.05564 .99836 4.57308 + -3.88909 -1.00565 3.88909 + -3.88909 .99438 3.88909 + -4.57308 -1.00941 3.05564 + -4.57308 .99061 3.05564 + -5.08134 -1.01282 2.10476 + -5.08134 .98721 2.10476 + -5.39432 -1.01573 1.073 + -5.39432 .9843 1.073 + -5.5 -1.01803 -0 + -5.5 .98199 -0 + -5.39432 -1.01965 -1.073 + -5.39432 .98038 -1.073 + -5.08134 -1.02051 -2.10476 + -5.08134 .97952 -2.10476 + -4.57308 -1.02058 -3.05564 + -4.57308 .97945 -3.05564 + -3.88909 -1.01986 -3.88909 + -3.88909 .98017 -3.88909 + -3.05564 -1.01838 -4.57308 + -3.05564 .98165 -4.57308 + -2.10476 -1.01619 -5.08134 + -2.10476 .98383 -5.08134 + -1.073 -1.01338 -5.39432 + -1.073 .98664 -5.39432 + ] + } + } + } + ] + } + DEF dad_Cylinder5 Transform { + translation 0 0 -8.4 + rotation 1 0 0 1.571 + children [ + DEF Cylinder5 Shape { + appearance Appearance { + material USE Green + } + geometry DEF GeoCylinder5 Cylinder { + height 4.250 + radius 2.000 + } + } + ] + } + DEF dad_Box10 Transform { + translation 0 2 -8.4 + children [ + DEF Box10 Shape { + appearance Appearance { + material USE Green + } + geometry DEF GeoBox10 Box { + size .25 1 4.25 + } + } + ] + } + ] + } + DEF dad_lab_posttop Transform { + translation 0 0 -11 + rotation -1 0 0 1.571 + children [ + DEF alpha_posttop Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoCylinder6 Cylinder { + height 1.000 + radius 2.000 + } + } + ] + } + DEF dad_alpha_arm Transform { + translation 0 -20.5 -5 + children [ + DEF alpha_arm Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox7 Box { + size 4 1 13 + } + } + ] + } + DEF dad_gamma_arm1 Transform { + translation -6 -20.5 0 + children [ + DEF gamma_arm1 Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox18 Box { + size 1 2 .25 + } + } + ] + } + DEF dad_Box12 Transform { + translation 0 2 -11 + children [ + DEF Box12 Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox12 Box { + size .25 1 1 + } + } + ] + } + ] +} +DEF dad_gamma_frame Transform { + rotation 0 1 0 .349 + children [ + DEF dad_lab_posttop0 Transform { + translation 0 0 -12.5 + rotation -1 0 0 1.571 + children [ + DEF gamma_posttop Shape { + appearance Appearance { + material DEF Blue Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor 0 0 .62745 + } + } + geometry DEF GeoCylinder4 Cylinder { + height 1.000 + radius 2.000 + } + } + ] + } + DEF dad_lab_post0 Transform { + translation 0 -11 -12.5 + children [ + DEF gamma_post Shape { + appearance Appearance { + material USE Blue + } + geometry DEF GeoBox5 Box { + size 4 22 1 + } + } + ] + } + DEF dad_gamma_base Transform { + translation 0 -22.5 0 + children [ + DEF gamma_base Shape { + appearance Appearance { + material USE Blue + } + geometry DEF GeoCylinder3 Cylinder { + height 2.000 + radius 8.000 + } + } + ] + } + DEF dad_gamma_arm Transform { + translation 0 -22.5 -6.5 + children [ + DEF gamma_arm Shape { + appearance Appearance { + material USE Blue + } + geometry DEF GeoBox4 Box { + size 4 1 13 + } + } + ] + } + DEF dad_delta_frame Transform { + rotation 0 0 1 -.524 + children [ + DEF dad_delta_mount Transform { + translation 0 0 -15 + rotation -1 0 0 1.571 + children [ + DEF delta_mount Shape { + appearance Appearance { + material DEF Yellow Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .83529 .83529 0 + } + } + geometry DEF GeoCylinder2 Cylinder { + height 4.000 + radius 2.000 + } + } + ] + } + DEF dad_delta_arm Transform { + translation -10 0 -15 + children [ + DEF delta_arm Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoBox8 Box { + size 20 3 1 + } + } + ] + } + DEF dad_delta_arm0 Transform { + translation -20 0 -6.5 + children [ + DEF delta_arm0 Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoBox6 Box { + size 1 3 18 + } + } + ] + } + DEF dad_Cylinder14 Transform { + translation -18 0 0 + rotation .577 -.577 .577 2.094 + children [ + DEF Cylinder14 Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoCylinder14 Cylinder { + height 3.000 + radius 1.000 + } + } + ] + } + DEF dad_detector_beam Transform { + translation -10 0 0 + rotation 0 0 1 1.571 + children [ + DEF detector_beam Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoCylinder21 Cylinder { + height 20.000 + radius 0.050 + } + } + ] + } + DEF dad_Box13 Transform { + translation 0 2 -15 + children [ + DEF Box13 Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoBox13 Box { + size .25 1 4 + } + } + ] + } + ] + } + DEF dad_gamma_arm0 Transform { + translation -8 -22.5 0 + children [ + DEF gamma_arm0 Shape { + appearance Appearance { + material USE Blue + } + geometry DEF GeoBox17 Box { + size 1 2 .25 + } + } + ] + } + DEF dad_Box14 Transform { + translation 0 2 -12.5 + children [ + DEF Box14 Shape { + appearance Appearance { + material USE Blue + } + geometry DEF GeoBox14 Box { + size .25 1 1 + } + } + ] + } + ] +} +DEF VP Viewpoint { + description "VP" + jump TRUE + fieldOfView 1.165 + position 40 40 40 + orientation -.754 .657 -0 1.001 +} +DEF dad_gamma_arm2 Transform { + translation -9 -24.5 0 + children [ + DEF gamma_arm2 Shape { + appearance Appearance { + material DEF Black Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .20784 .20784 .20784 + } + } + geometry DEF GeoBox19 Box { + size 1 2 .25 + } + } + ] +} +DEF dad_gamma_base0 Transform { + translation 0 -24.5 0 + rotation 0 1 0 .349 + children [ + DEF gamma_base0 Shape { + appearance Appearance { + material USE Black + } + geometry DEF GeoCylinder9 Cylinder { + height 2.000 + radius 9.000 + } + } + ] +} +DEF dad_floor Transform { + translation 0 -25.5 0 + children [ + DEF floor Shape { + appearance Appearance { + material USE Black + } + geometry DEF GeoBox16 Box { + size 60 .2 40 + } + } + ] +} diff --git a/script/test/diffcalc (copy)/model/sixc_horizontal.fxw b/script/test/diffcalc (copy)/model/sixc_horizontal.fxw new file mode 100644 index 0000000..9eea919 Binary files /dev/null and b/script/test/diffcalc (copy)/model/sixc_horizontal.fxw differ diff --git a/script/test/diffcalc (copy)/model/sixc_horizontal.wrl b/script/test/diffcalc (copy)/model/sixc_horizontal.wrl new file mode 100644 index 0000000..3ec9d25 --- /dev/null +++ b/script/test/diffcalc (copy)/model/sixc_horizontal.wrl @@ -0,0 +1,1106 @@ +#VRML V2.0 utf8 +WorldInfo { + title "fourc" + info ["This Web3D Content was created with Flux Studio, a Web3D authoring tool" + "www.mediamachines.com" + "This Web3D Content was created with Flux Studio, a Web3D authoring tool"] +} +## Vizthumbnail Thumb_sixc_horizontal_wrl8844311285236111.jpg +DEF dad_GROUND_ROTATED Transform { + rotation 1 0 0 1.571 + children [ + DirectionalLight { + intensity 1.000 + ambientIntensity 0.500 + direction -.621 -.23944 -.74634 + color 1 1 1 + on TRUE + } + DirectionalLight { + intensity 1.000 + ambientIntensity 0.000 + direction -.01373 -.46642 -.88446 + color 1 1 1 + on TRUE + } + DEF lab_x Shape { + appearance Appearance { + material DEF Rust Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .50196 .50196 .50196 + } + } + geometry DEF GeoCylinder15 Cylinder { + height 60.000 + radius 0.020 + } + } + DEF dad_lab_z Transform { + rotation 1 0 0 1.571 + children [ + DEF lab_z Shape { + appearance Appearance { + material USE Rust + } + geometry DEF GeoCylinder17 Cylinder { + height 40.000 + radius 0.020 + } + } + ] + } + DEF dad_lab_beam Transform { + translation 10 0 0 + rotation 0 0 1 1.571 + children [ + DEF lab_beam Shape { + appearance Appearance { + material DEF White Material { + ambientIntensity 0.500 + shininess 0.100 + transparency 0.500 + diffuseColor 1 1 1 + emissiveColor 1 1 1 + } + } + geometry DEF GeoCylinder20 Cylinder { + height 20.000 + radius 0.050 + } + } + ] + } + DEF dad_lab_beam0 Transform { + translation 20 0 0 + rotation 0 0 1 1.571 + children [ + DEF lab_beam0 Shape { + appearance Appearance { + material DEF Shiny_Black Material { + ambientIntensity 0.200 + shininess 0.100 + diffuseColor 1 1 1 + } + } + geometry DEF GeoCylinder22 Cylinder { + height 4.000 + radius 0.200 + } + } + ] + } + DEF dad_Cone2 Transform { + translation 17.5 0 0 + rotation 0 0 1 1.571 + children [ + DEF Cone2 Shape { + appearance Appearance { + material USE Shiny_Black + } + geometry DEF GeoCone2 Cone { + height 1.500 + bottomRadius 0.500 + } + } + ] + } + DEF dad_lab_y Transform { + rotation 0 0 -1 1.571 + children [ + DEF lab_y Shape { + appearance Appearance { + material USE Rust + } + geometry DEF GeoCylinder16 Cylinder { + height 60.000 + radius 0.020 + } + } + ] + } + DEF dad_alpha_frame Transform { + rotation 0 1 0 .175 + children [ + DEF dad_alpha_base Transform { + translation 0 -20.5 0 + children [ + DEF alpha_base Shape { + appearance Appearance { + material DEF Red Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .50196 0 0 + } + } + geometry DEF GeoCylinder1 Cylinder { + height 2.000 + radius 6.000 + } + } + ] + } + DEF dad_lab_post Transform { + translation 0 -10 -11 + children [ + DEF alpha_post Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox1 Box { + size 4 20 1 + } + } + ] + } + DEF dad_omega_frame Transform { + rotation 0 0 1 -.175 + children [ + DEF dad_chi_frame Transform { + rotation 1 0 0 -.785 + children [ + DEF dad_phi_frame Transform { + rotation 0 0 1 -.349 + children [ + DEF dad_Cylinder7 Transform { + translation 0 0 -4 + rotation 1 0 0 1.571 + children [ + DEF Cylinder7 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoCylinder7 Cylinder { + height 2.000 + radius 1.000 + } + } + ] + } + DEF dad_Cylinder8 Transform { + translation 0 0 -3.5 + rotation 1 0 0 1.571 + children [ + DEF Cylinder8 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoCylinder8 Cylinder { + height 6.000 + radius 0.250 + } + } + ] + } + DEF Box3 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoBox3 Box { + size 1 1 1 + } + } + DEF dad_Box11 Transform { + translation 0 1 -4 + children [ + DEF Box11 Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoBox11 Box { + size .25 .5 2 + } + } + ] + } + DEF Box4 Shape { + appearance Appearance { + material DEF White_wire Material { + emissiveColor 1 1 1 + } + } + geometry IndexedLineSet { + coordIndex [ + 0 1 2 0 + -1 0 2 3 + 0 -1 1 5 + 6 1 -1 1 + 6 2 1 -1 + 2 6 7 2 + -1 2 7 3 + 2 -1 3 7 + 4 3 -1 3 + 4 0 3 -1 + 0 4 5 0 + -1 0 5 1 + 0 -1 6 5 + 4 6 -1 6 + 4 7 6 -1 + ] + coord Coordinate { + point [ + -.5 .5 -.5 + -.5 .5 .5 + .5 .5 .5 + .5 .5 -.5 + -.5 -.5 -.5 + -.5 -.5 .5 + .5 -.5 .5 + .5 -.5 -.5 + ] + } + } + } + ] + } + DEF dad_Box2 Transform { + translation 0 0 -5.5 + children [ + DEF Box2 Shape { + appearance Appearance { + material DEF Purple Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .9176 0 .874 + } + } + geometry DEF GeoBox2 Box { + size 2.8 2.8 1.5 + } + } + ] + } + DEF dad_Box6 Transform { + translation 0 1.25 -5.5 + children [ + DEF Box6 Shape { + appearance Appearance { + material USE Purple + } + geometry DEF GeoBox21 Box { + size .25 1 1.5 + } + } + ] + } + ] + } + DEF dad_Cylinder2 Transform { + rotation 0 0 1 1.571 + scale 1.1 1.5 1.1 + children [ + DEF Cylinder2 Shape { + appearance Appearance { + material DEF Green Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .00784 .65098 .02353 + } + } + geometry DEF Cylinder2_Geo IndexedFaceSet { + creaseAngle 0.524 + coordIndex [ + 49 50 48 -1 + 48 50 51 -1 + 48 51 47 -1 + 47 51 20 -1 + 47 20 21 -1 + 12 13 56 -1 + 56 13 14 -1 + 56 14 55 -1 + 55 14 15 -1 + 55 15 54 -1 + 54 15 16 -1 + 54 16 53 -1 + 53 16 17 -1 + 53 17 52 -1 + 52 17 18 -1 + 52 18 51 -1 + 51 18 19 -1 + 51 19 20 -1 + 5 6 62 -1 + 62 6 7 -1 + 62 7 61 -1 + 61 7 8 -1 + 61 8 60 -1 + 60 8 9 -1 + 60 9 59 -1 + 59 9 10 -1 + 59 10 58 -1 + 58 10 11 -1 + 58 11 57 -1 + 57 11 12 -1 + 57 12 56 -1 + 5 63 4 -1 + 4 63 64 -1 + 4 64 3 -1 + 3 64 1 -1 + 3 1 2 -1 + 2 1 35 -1 + 2 35 36 -1 + 28 41 27 -1 + 27 41 42 -1 + 27 42 26 -1 + 26 42 43 -1 + 26 43 25 -1 + 25 43 44 -1 + 25 44 24 -1 + 24 44 45 -1 + 24 45 23 -1 + 23 45 46 -1 + 23 46 22 -1 + 22 46 47 -1 + 22 47 21 -1 + 34 36 33 -1 + 33 36 37 -1 + 33 37 32 -1 + 32 37 38 -1 + 32 38 31 -1 + 31 38 39 -1 + 31 39 30 -1 + 30 39 40 -1 + 30 40 29 -1 + 29 40 41 -1 + 29 41 28 -1 + 1 64 0 -1 + 2 36 34 -1 + 63 5 62 -1 + 95 96 97 -1 + 97 96 65 -1 + 97 65 129 -1 + 129 65 66 -1 + 129 66 128 -1 + 128 66 67 -1 + 128 67 127 -1 + 127 67 126 -1 + 104 105 88 -1 + 88 105 106 -1 + 88 106 87 -1 + 87 106 107 -1 + 87 107 86 -1 + 86 107 108 -1 + 86 108 85 -1 + 85 108 109 -1 + 85 109 84 -1 + 84 109 110 -1 + 84 110 83 -1 + 83 110 111 -1 + 83 111 82 -1 + 82 111 112 -1 + 82 112 113 -1 + 98 99 93 -1 + 93 99 100 -1 + 93 100 92 -1 + 92 100 101 -1 + 92 101 91 -1 + 91 101 102 -1 + 91 102 90 -1 + 90 102 103 -1 + 90 103 89 -1 + 89 103 104 -1 + 89 104 88 -1 + 94 95 97 -1 + 94 97 98 -1 + 94 98 93 -1 + 81 82 78 -1 + 81 78 79 -1 + 81 79 80 -1 + 78 82 113 -1 + 78 113 114 -1 + 78 114 115 -1 + 78 115 77 -1 + 120 73 119 -1 + 119 73 74 -1 + 119 74 118 -1 + 118 74 75 -1 + 118 75 117 -1 + 117 75 76 -1 + 117 76 116 -1 + 116 76 77 -1 + 116 77 115 -1 + 126 68 125 -1 + 125 68 69 -1 + 125 69 124 -1 + 124 69 70 -1 + 124 70 123 -1 + 123 70 71 -1 + 123 71 122 -1 + 122 71 72 -1 + 122 72 121 -1 + 121 72 73 -1 + 121 73 120 -1 + 126 67 68 -1 + 0 64 65 -1 + 0 65 96 -1 + 64 63 66 -1 + 64 66 65 -1 + 63 62 67 -1 + 63 67 66 -1 + 62 61 68 -1 + 62 68 67 -1 + 61 60 69 -1 + 61 69 68 -1 + 60 59 70 -1 + 60 70 69 -1 + 59 58 71 -1 + 59 71 70 -1 + 58 57 72 -1 + 58 72 71 -1 + 57 56 73 -1 + 57 73 72 -1 + 56 55 74 -1 + 56 74 73 -1 + 55 54 75 -1 + 55 75 74 -1 + 54 53 76 -1 + 54 76 75 -1 + 53 52 77 -1 + 53 77 76 -1 + 52 51 78 -1 + 52 78 77 -1 + 51 50 79 -1 + 51 79 78 -1 + 50 49 80 -1 + 50 80 79 -1 + 49 48 81 -1 + 49 81 80 -1 + 48 47 82 -1 + 48 82 81 -1 + 47 46 83 -1 + 47 83 82 -1 + 46 45 84 -1 + 46 84 83 -1 + 45 44 85 -1 + 45 85 84 -1 + 44 43 86 -1 + 44 86 85 -1 + 43 42 87 -1 + 43 87 86 -1 + 42 41 88 -1 + 42 88 87 -1 + 41 40 89 -1 + 41 89 88 -1 + 40 39 90 -1 + 40 90 89 -1 + 39 38 91 -1 + 39 91 90 -1 + 38 37 92 -1 + 38 92 91 -1 + 37 36 93 -1 + 37 93 92 -1 + 36 35 94 -1 + 36 94 93 -1 + 35 1 95 -1 + 35 95 94 -1 + 1 0 96 -1 + 1 96 95 -1 + ] + coord DEF Cylinder2_Coord Coordinate { + point [ + 0 1 -6 + -1.17054 1 -5.88471 + -1.07212 1 -5.3899 + -1.05189 1 -5.39596 + .02145 1 -5.49809 + 1.09408 1 -5.38886 + 2.1248 1 -5.07248 + 3.07398 1 -4.56108 + 3.90517 1 -3.87434 + 4.5864 1 -3.03864 + 5.0915 1 -2.08609 + 5.40107 1 -1.0533 + 5.5032 1 .02003 + 5.39398 1 1.09267 + 5.07759 1 2.12338 + 4.5662 1 3.07257 + 3.87945 1 3.90375 + 3.04375 1 4.58498 + 2.0912 1 5.09009 + 1.05842 1 5.39966 + -.01492 1 5.50179 + -1.08755 1 5.39256 + -2.11827 1 5.07617 + -3.06746 1 4.56478 + -3.89864 1 3.87804 + -4.57987 1 3.04233 + -5.08498 1 2.08979 + -5.39454 1 1.057 + -5.49668 1 -.01633 + -5.38745 1 -1.08897 + -5.07106 1 -2.11968 + -4.55967 1 -3.06887 + -3.87292 1 -3.90005 + -3.03722 1 -4.58129 + -2.08467 1 -5.08639 + -2.2961 1 -5.54328 + -3.33342 1 -4.98882 + -4.24264 1 -4.24264 + -4.98882 1 -3.33342 + -5.54328 1 -2.2961 + -5.88471 1 -1.17054 + -6 1 -0 + -5.88471 1 1.17054 + -5.54328 1 2.2961 + -4.98882 1 3.33342 + -4.24264 1 4.24264 + -3.33342 1 4.98882 + -2.2961 1 5.54328 + -1.17054 1 5.88471 + -0 1 6 + 1.17054 1 5.88471 + 2.2961 1 5.54328 + 3.33342 1 4.98882 + 4.24264 1 4.24264 + 4.98882 1 3.33342 + 5.54328 1 2.2961 + 5.88471 1 1.17054 + 6 1 0 + 5.88471 1 -1.17054 + 5.54328 1 -2.2961 + 4.98882 1 -3.33342 + 4.24264 1 -4.24264 + 3.33342 1 -4.98882 + 2.2961 1 -5.54328 + 1.17054 1 -5.88471 + 1.17054 -1 -5.88471 + 2.2961 -1 -5.54328 + 3.33342 -1 -4.98882 + 4.24264 -1 -4.24264 + 4.98882 -1 -3.33342 + 5.54328 -1 -2.2961 + 5.88471 -1 -1.17054 + 6 -1 -0 + 5.88471 -1 1.17054 + 5.54328 -1 2.2961 + 4.98882 -1 3.33342 + 4.24264 -1 4.24264 + 3.33342 -1 4.98882 + 2.2961 -1 5.54328 + 1.17054 -1 5.88471 + -0 -1 6 + -1.17054 -1 5.88471 + -2.2961 -1 5.54328 + -3.33342 -1 4.98882 + -4.24264 -1 4.24264 + -4.98882 -1 3.33342 + -5.54328 -1 2.2961 + -5.88471 -1 1.17054 + -6 -1 -0 + -5.88471 -1 -1.17054 + -5.54328 -1 -2.2961 + -4.98882 -1 -3.33342 + -4.24264 -1 -4.24264 + -3.33342 -1 -4.98882 + -2.2961 -1 -5.54328 + -1.17054 -1 -5.88471 + 0 -1 -6 + 0 -1 -5.50037 + -1.05842 -1 -5.39966 + -2.0912 -1 -5.09009 + -3.04375 -1 -4.58498 + -3.87945 -1 -3.90375 + -4.5662 -1 -3.07257 + -5.07759 -1 -2.12338 + -5.39398 -1 -1.09267 + -5.5032 -1 -.02003 + -5.40107 -1 1.0533 + -5.0915 -1 2.08609 + -4.5864 -1 3.03864 + -3.90517 -1 3.87434 + -3.07398 -1 4.56108 + -2.1248 -1 5.07248 + -1.09408 -1 5.38886 + -.02145 -1 5.49809 + 1.05189 -1 5.39596 + 2.08467 -1 5.08639 + 3.03722 -1 4.58129 + 3.87292 -1 3.90005 + 4.55967 -1 3.06887 + 5.07106 -1 2.11968 + 5.38745 -1 1.08897 + 5.49668 -1 .01633 + 5.39454 -1 -1.057 + 5.08498 -1 -2.08979 + 4.57987 -1 -3.04233 + 3.89864 -1 -3.87804 + 3.06746 -1 -4.56478 + 2.11827 -1 -5.07617 + 1.08755 -1 -5.39256 + .01492 -1 -5.50179 + ] + } + } + } + ] + } + DEF dad_Cylinder3 Transform { + rotation 0 0 1 1.571 + scale 1.1 1.5 1.1 + children [ + DEF Cylinder3 Shape { + appearance Appearance { + material USE Green + } + geometry DEF Cylinder3_Geo IndexedFaceSet { + creaseAngle 0.524 + coordIndex [ + 0 1 2 -1 + 0 2 3 -1 + 4 5 1 -1 + 4 1 0 -1 + 6 7 5 -1 + 6 5 4 -1 + 8 9 7 -1 + 8 7 6 -1 + 10 11 9 -1 + 10 9 8 -1 + 12 13 11 -1 + 12 11 10 -1 + 14 15 13 -1 + 14 13 12 -1 + 16 17 15 -1 + 16 15 14 -1 + 18 19 17 -1 + 18 17 16 -1 + 20 21 19 -1 + 20 19 18 -1 + 22 23 21 -1 + 22 21 20 -1 + 24 25 23 -1 + 24 23 22 -1 + 26 27 25 -1 + 26 25 24 -1 + 28 29 27 -1 + 28 27 26 -1 + 30 31 29 -1 + 30 29 28 -1 + 32 33 31 -1 + 32 31 30 -1 + 34 35 33 -1 + 34 33 32 -1 + 36 37 35 -1 + 36 35 34 -1 + 38 39 37 -1 + 38 37 36 -1 + 40 41 39 -1 + 40 39 38 -1 + 42 43 41 -1 + 42 41 40 -1 + 44 45 43 -1 + 44 43 42 -1 + 46 47 45 -1 + 46 45 44 -1 + 48 49 47 -1 + 48 47 46 -1 + 50 51 49 -1 + 50 49 48 -1 + 52 53 51 -1 + 52 51 50 -1 + 54 55 53 -1 + 54 53 52 -1 + 56 57 55 -1 + 56 55 54 -1 + 58 59 57 -1 + 58 57 56 -1 + 60 61 59 -1 + 60 59 58 -1 + 62 63 61 -1 + 62 61 60 -1 + 3 2 63 -1 + 3 63 62 -1 + ] + coord DEF Cylinder3_Coord Coordinate { + point [ + 1.073 -1.00635 -5.39432 + 1.073 .99367 -5.39432 + 0 .98997 -5.5 + 0 -1.01006 -5.5 + 2.10476 -1.0024 -5.08134 + 2.10476 .99763 -5.08134 + 3.05564 -.99836 -4.57308 + 3.05564 1.00167 -4.57308 + 3.88909 -.99438 -3.88909 + 3.88909 1.00565 -3.88909 + 4.57308 -.99061 -3.05564 + 4.57308 1.00941 -3.05564 + 5.08134 -.98721 -2.10476 + 5.08134 1.01282 -2.10476 + 5.39432 -.9843 -1.073 + 5.39432 1.01573 -1.073 + 5.5 -.98199 -0 + 5.5 1.01803 -0 + 5.39432 -.98038 1.073 + 5.39432 1.01965 1.073 + 5.08134 -.97952 2.10476 + 5.08134 1.02051 2.10476 + 4.57308 -.97945 3.05564 + 4.57308 1.02058 3.05564 + 3.88909 -.98017 3.88909 + 3.88909 1.01986 3.88909 + 3.05564 -.98165 4.57308 + 3.05564 1.01838 4.57308 + 2.10476 -.98383 5.08134 + 2.10476 1.01619 5.08134 + 1.073 -.98664 5.39432 + 1.073 1.01338 5.39432 + -0 -.98997 5.5 + -0 1.01006 5.5 + -1.073 -.99367 5.39432 + -1.073 1.00635 5.39432 + -2.10476 -.99763 5.08134 + -2.10476 1.0024 5.08134 + -3.05564 -1.00167 4.57308 + -3.05564 .99836 4.57308 + -3.88909 -1.00565 3.88909 + -3.88909 .99438 3.88909 + -4.57308 -1.00941 3.05564 + -4.57308 .99061 3.05564 + -5.08134 -1.01282 2.10476 + -5.08134 .98721 2.10476 + -5.39432 -1.01573 1.073 + -5.39432 .9843 1.073 + -5.5 -1.01803 -0 + -5.5 .98199 -0 + -5.39432 -1.01965 -1.073 + -5.39432 .98038 -1.073 + -5.08134 -1.02051 -2.10476 + -5.08134 .97952 -2.10476 + -4.57308 -1.02058 -3.05564 + -4.57308 .97945 -3.05564 + -3.88909 -1.01986 -3.88909 + -3.88909 .98017 -3.88909 + -3.05564 -1.01838 -4.57308 + -3.05564 .98165 -4.57308 + -2.10476 -1.01619 -5.08134 + -2.10476 .98383 -5.08134 + -1.073 -1.01338 -5.39432 + -1.073 .98664 -5.39432 + ] + } + } + } + ] + } + DEF dad_Box10 Transform { + translation 0 2 -8.4 + children [ + DEF Box10 Shape { + appearance Appearance { + material USE Green + } + geometry DEF GeoBox10 Box { + size .25 1 4.25 + } + } + ] + } + DEF dad_Cylinder5 Transform { + translation 0 0 -8.4 + rotation 1 0 0 1.571 + children [ + DEF Cylinder5 Shape { + appearance Appearance { + material USE Green + } + geometry DEF GeoCylinder5 Cylinder { + height 4.250 + radius 2.000 + } + } + ] + } + ] + } + DEF dad_lab_posttop Transform { + translation 0 0 -11 + rotation -1 0 0 1.571 + children [ + DEF alpha_posttop Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoCylinder6 Cylinder { + height 1.000 + radius 2.000 + } + } + ] + } + DEF dad_alpha_arm Transform { + translation 0 -20.5 -5 + children [ + DEF alpha_arm Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox7 Box { + size 4 1 13 + } + } + ] + } + DEF dad_gamma_arm1 Transform { + translation -6 -20.5 0 + children [ + DEF gamma_arm1 Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox18 Box { + size 1 2 .25 + } + } + ] + } + DEF dad_Box12 Transform { + translation 0 2 -11 + children [ + DEF Box12 Shape { + appearance Appearance { + material USE Red + } + geometry DEF GeoBox12 Box { + size .25 1 1 + } + } + ] + } + ] + } + DEF dad_gamma_frame Transform { + rotation 0 1 0 .349 + children [ + DEF dad_lab_posttop0 Transform { + translation 0 0 -12.5 + rotation -1 0 0 1.571 + children [ + DEF gamma_posttop Shape { + appearance Appearance { + material DEF Blue Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor 0 0 .62745 + } + } + geometry DEF GeoCylinder4 Cylinder { + height 1.000 + radius 2.000 + } + } + ] + } + DEF dad_lab_post0 Transform { + translation 0 -11 -12.5 + children [ + DEF gamma_post Shape { + appearance Appearance { + material USE Blue + } + geometry DEF GeoBox5 Box { + size 4 22 1 + } + } + ] + } + DEF dad_gamma_base Transform { + translation 0 -22.5 0 + children [ + DEF gamma_base Shape { + appearance Appearance { + material USE Blue + } + geometry DEF GeoCylinder3 Cylinder { + height 2.000 + radius 8.000 + } + } + ] + } + DEF dad_gamma_arm Transform { + translation 0 -22.5 -6.5 + children [ + DEF gamma_arm Shape { + appearance Appearance { + material USE Blue + } + geometry DEF GeoBox4 Box { + size 4 1 13 + } + } + ] + } + DEF dad_delta_frame Transform { + rotation 0 0 1 -.524 + children [ + DEF dad_delta_mount Transform { + translation 0 0 -15 + rotation -1 0 0 1.571 + children [ + DEF delta_mount Shape { + appearance Appearance { + material DEF Yellow Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .83529 .83529 0 + } + } + geometry DEF GeoCylinder2 Cylinder { + height 4.000 + radius 2.000 + } + } + ] + } + DEF dad_delta_arm Transform { + translation -10 0 -15 + children [ + DEF delta_arm Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoBox8 Box { + size 20 3 1 + } + } + ] + } + DEF dad_delta_arm0 Transform { + translation -20 0 -6.5 + children [ + DEF delta_arm0 Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoBox6 Box { + size 1 3 18 + } + } + ] + } + DEF dad_Cylinder14 Transform { + translation -18 0 0 + rotation .577 -.577 .577 2.094 + children [ + DEF Cylinder14 Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoCylinder14 Cylinder { + height 3.000 + radius 1.000 + } + } + ] + } + DEF dad_detector_beam Transform { + translation -10 0 0 + rotation 0 0 1 1.571 + children [ + DEF detector_beam Shape { + appearance Appearance { + material USE White + } + geometry DEF GeoCylinder21 Cylinder { + height 20.000 + radius 0.050 + } + } + ] + } + DEF dad_Box13 Transform { + translation 0 2 -15 + children [ + DEF Box13 Shape { + appearance Appearance { + material USE Yellow + } + geometry DEF GeoBox13 Box { + size .25 1 4 + } + } + ] + } + ] + } + DEF dad_gamma_arm0 Transform { + translation -8 -22.5 0 + children [ + DEF gamma_arm0 Shape { + appearance Appearance { + material USE Blue + } + geometry DEF GeoBox17 Box { + size 1 2 .25 + } + } + ] + } + DEF dad_Box14 Transform { + translation 0 2 -12.5 + children [ + DEF Box14 Shape { + appearance Appearance { + material USE Blue + } + geometry DEF GeoBox14 Box { + size .25 1 1 + } + } + ] + } + ] + } + DEF dad_gamma_arm2 Transform { + translation -9 -24.5 0 + children [ + DEF gamma_arm2 Shape { + appearance Appearance { + material DEF Black Material { + ambientIntensity 0.200 + shininess 0.200 + diffuseColor .20784 .20784 .20784 + } + } + geometry DEF GeoBox19 Box { + size 1 2 .25 + } + } + ] + } + DEF dad_gamma_base0 Transform { + translation 0 -24.5 0 + rotation 0 1 0 .349 + children [ + DEF gamma_base0 Shape { + appearance Appearance { + material USE Black + } + geometry DEF GeoCylinder9 Cylinder { + height 2.000 + radius 9.000 + } + } + ] + } + DEF dad_floor Transform { + translation 0 -25.5 0 + children [ + DEF floor Shape { + appearance Appearance { + material USE Black + } + geometry DEF GeoBox16 Box { + size 60 .2 40 + } + } + ] + } + DEF VP Viewpoint { + description "VP" + jump TRUE + fieldOfView 1.165 + position 40 40 40 + orientation -.754 .657 -0 1.001 + } + ] +} diff --git a/script/test/diffcalc (copy)/model/vrml_animator.py b/script/test/diffcalc (copy)/model/vrml_animator.py new file mode 100644 index 0000000..d283469 --- /dev/null +++ b/script/test/diffcalc (copy)/model/vrml_animator.py @@ -0,0 +1,134 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +import sys +import time +import threading +from math import pi +import socket + +from pivy.coin import * +from pivy.sogui import * + + +PORT = 4567 +TORAD = pi / 180 + + +def connect_to_socket(host, port): + print "Connecting to %s on port %d" % (host, port) + connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + print "Connected" + connection.connect((host, port)) + socketfile = connection.makefile('rw', 0) + return socketfile + + +def serve_socket_connection(port): + print ("Serving connection on all interfaces on %s port %d" % + (socket.gethostname(), port)) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((socket.gethostname(), port)) + sock.listen(1) + time.sleep(1) + (connection, addr) = sock.accept() + print 'Connected from ', addr, ' accepted' + socket_file = connection.makefile('rw', 0) # no buffering + return socket_file + + +def node_name(anglename): + return 'dad_' + anglename + '_frame' + + +class SceneUpdatingThread(threading.Thread): + + def __init__(self, scene, axisnames): + threading.Thread.__init__(self) + self.scene = scene + + # Infer rotation axes based on initial orientation + self.rotation_axes = {} + self.axies_nodes = {} + for axisname in axisnames: + node = self.scene.getByName(node_name(axisname)) + self.axies_nodes[axisname] = node + value = node.rotation.getValue() + self.rotation_axes[axisname] = value.getAxisAngle()[0] + + def run(self): + socket_file = serve_socket_connection(PORT) + + while True: + msg = socket_file.readline() + if msg == '': + print '***Socket closed' + socket_file = serve_socket_connection(PORT) + continue + print msg.strip() + d = eval(msg.strip()) # msg should be a dictionary representation + for axisname in d: + self.set_axis_rotation(axisname, d[axisname]) + + def set_axis_rotation(self, anglename, degrees): + nodename = node_name(anglename) + angle = degrees * TORAD + while angle < 0: + angle = 2 * pi + angle + node = self.scene.getByName(nodename) + getattr(node, 'rotation').setValue( + self.rotation_axes[anglename], angle) + + +class Animator(object): + + def __init__(self, filename, axisnames): + print "filename : " + filename + print " axes : " + ' '.join(axisnames) + # Create viewer + self.myWindow = SoGui.init(sys.argv[0]) # @UndefinedVariable + if self.myWindow is None: sys.exit(1) + viewer = SoGuiExaminerViewer(self.myWindow) # @UndefinedVariable + # load file into scene + so_input = SoInput() # @UndefinedVariable + so_input.openFile(filename) + self.scene = SoDB.readAll(so_input) # @UndefinedVariable + # Add scene to viewer + viewer.setSceneGraph(self.scene) + viewer.setTitle(' '.join(axisnames)) + viewer.show() + + self.start_update_scene_thread(axisnames) + + def start_update_scene_thread(self, axisnames): + t = SceneUpdatingThread(self.scene, axisnames) + t.setDaemon(True) + t.start() + + def show(self): + SoGui.show(self.myWindow) # @UndefinedVariable + SoGui.mainLoop() # @UndefinedVariable + +def main(): + animator = Animator(sys.argv[1], sys.argv[2:]) + animator.show() + +if __name__ == "__main__": + main() + diff --git a/script/test/diffcalc (copy)/numjy/__init__.py b/script/test/diffcalc (copy)/numjy/__init__.py new file mode 100644 index 0000000..273b233 --- /dev/null +++ b/script/test/diffcalc (copy)/numjy/__init__.py @@ -0,0 +1,36 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +try: + import Jama + from numjy import linalg + from numjy.jama_matrix_wrapper import matrix + JAMA = True +except ImportError: + JAMA = False + + +def hstack(list_of_column_matrices): + if not Jama: + raise Exception('Jama not available, use numpy directly') + ncol = len(list_of_column_matrices) + nrow = list_of_column_matrices[0].shape[0] + m = Jama.Matrix(nrow, ncol) + for c, column_matrix in enumerate(list_of_column_matrices): + m.setMatrix(0, nrow - 1, c, c, column_matrix.m) + return matrix(m) diff --git a/script/test/diffcalc (copy)/numjy/jama_matrix_wrapper.py b/script/test/diffcalc (copy)/numjy/jama_matrix_wrapper.py new file mode 100644 index 0000000..d5e7645 --- /dev/null +++ b/script/test/diffcalc (copy)/numjy/jama_matrix_wrapper.py @@ -0,0 +1,120 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +import Jama + + +class matrix(object): + + def __init__(self, a): + if isinstance(a, Jama.Matrix): + self.m = a + elif isinstance(a, basestring): + l = [] + for row in a.strip().split(';'): + l.append([float(element) + for element in row.replace(',', ' ').split()]) + self.m = Jama.Matrix(l) + elif isinstance(a, matrix): + self.m = Jama.Matrix(a.m) + elif isinstance(a, (list, tuple)): + if isinstance(a[0], (list, tuple)): + # a is a list of lists (not rigorous test!) + self.m = Jama.Matrix(a) + else: + # a is a row vector + self.m = Jama.Matrix([a]) + else: + # give it a go + self.m = Jama.Matrix(a) + + def __eq__(self, other): + nrow, ncol = self.shape + b = matrix(Jama.Matrix(nrow, ncol)) + for i in range(nrow): + for j in range(ncol): + b[i, j] = self[i, j] == other[i, j] + return b + + @property + def shape(self): + return self.m.getRowDimension(), self.m.getColumnDimension() + + def __len__(self): + return self.m.getRowDimension() + + def all(self): # @ReservedAssignment + for row in self.m.array: + if not all(row): + return False + return True + + def tolist(self): + l = [] + nrow, ncol = self.shape + for i in range(nrow): + row = [] + for j in range(ncol): + row.append(self[i, j]) + l.append(row) + return l + + def sum(self): # @ReservedAssignment + return sum(sum(row) for row in self.m.array) + + @property + def I(self): + return matrix(self.m.inverse()) + + @property + def T(self): + return matrix(self.m.transpose()) + + def _scaler(self, scaler): + return Jama.Matrix(self.shape[0], self.shape[1], scaler) + + def __add__(self, other): + v = other.m if isinstance(other, matrix) else self._scaler(other) + return matrix(self.m.plus(v)) + + def __sub__(self, other): + v = other.m if isinstance(other, matrix) else self._scaler(other) + return matrix(self.m.minus(v)) + + def __mul__(self, other): + return matrix(self.m.times(other.m if isinstance(other, matrix) else + other)) + + def __div__(self, other): + # dividend = other.I if isinstance(other, matrix) else 1. /float(other) + return self.__mul__(1. / float(other)) + + def __getitem__(self, key): + i, j = key + return self.m.get(i, j) + + def __setitem__(self, key, value): + i, j = key + self.m.set(i, j, value) + + def __str__(self): + insides = [' '.join([str(el) for el in row]) for row in self.tolist()] + return '[[' + ']\n ['.join(insides) + ']]' + + def __repr__(self): + return 'matrix(' + '\n '.join(self.__str__().split('\n')) + ')' diff --git a/script/test/diffcalc (copy)/numjy/linalg.py b/script/test/diffcalc (copy)/numjy/linalg.py new file mode 100644 index 0000000..218d824 --- /dev/null +++ b/script/test/diffcalc (copy)/numjy/linalg.py @@ -0,0 +1,20 @@ +### +# Copyright 2008-2011 Diamond Light Source Ltd. +# This file is part of Diffcalc. +# +# Diffcalc is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Diffcalc is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Diffcalc. If not, see . +### + +def norm(mat): + return mat.m.normF() diff --git a/script/test/diffcalc (copy)/setup.py b/script/test/diffcalc (copy)/setup.py new file mode 100644 index 0000000..0d549da --- /dev/null +++ b/script/test/diffcalc (copy)/setup.py @@ -0,0 +1,31 @@ +from setuptools import setup, find_packages + +setup( + name='diffcalc', + version='2.1', + + description='A diffraction condition calculator for X-ray or neutron diffractometer control.', + long_description=open('README.rst').read(), + url='https://github.com/DiamondLightSource/diffcalc', + + author='Rob Walton', + author_email='rob.walton@diamond.ac.uk', + + license='GNU', + + packages=find_packages(exclude=['docs']), + + install_requires=[ + 'numpy', + 'ipython', + 'pytest', + 'pytest-xdist', + 'nose' + ], + + entry_points={ + 'console_scripts': [ + 'diffcalc=diffcmd.diffcalc_launcher:main', + ], + }, +) \ No newline at end of file diff --git a/script/test/diffcalc (copy)/simplejson/__init__.py b/script/test/diffcalc (copy)/simplejson/__init__.py new file mode 100644 index 0000000..fe2bd5a --- /dev/null +++ b/script/test/diffcalc (copy)/simplejson/__init__.py @@ -0,0 +1,510 @@ +r"""JSON (JavaScript Object Notation) is a subset of +JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data +interchange format. + +:mod:`simplejson` exposes an API familiar to users of the standard library +:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained +version of the :mod:`json` library contained in Python 2.6, but maintains +compatibility with Python 2.4 and Python 2.5 and (currently) has +significant performance advantages, even without using the optional C +extension for speedups. + +Encoding basic Python object hierarchies:: + + >>> import simplejson as json + >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) + '["foo", {"bar": ["baz", null, 1.0, 2]}]' + >>> print json.dumps("\"foo\bar") + "\"foo\bar" + >>> print json.dumps(u'\u1234') + "\u1234" + >>> print json.dumps('\\') + "\\" + >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) + {"a": 0, "b": 0, "c": 0} + >>> from StringIO import StringIO + >>> io = StringIO() + >>> json.dump(['streaming API'], io) + >>> io.getvalue() + '["streaming API"]' + +Compact encoding:: + + >>> import simplejson as json + >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':')) + '[1,2,3,{"4":5,"6":7}]' + +Pretty printing:: + + >>> import simplejson as json + >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' ') + >>> print '\n'.join([l.rstrip() for l in s.splitlines()]) + { + "4": 5, + "6": 7 + } + +Decoding JSON:: + + >>> import simplejson as json + >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] + >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj + True + >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar' + True + >>> from StringIO import StringIO + >>> io = StringIO('["streaming API"]') + >>> json.load(io)[0] == 'streaming API' + True + +Specializing JSON object decoding:: + + >>> import simplejson as json + >>> def as_complex(dct): + ... if '__complex__' in dct: + ... return complex(dct['real'], dct['imag']) + ... return dct + ... + >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}', + ... object_hook=as_complex) + (1+2j) + >>> from decimal import Decimal + >>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1') + True + +Specializing JSON object encoding:: + + >>> import simplejson as json + >>> def encode_complex(obj): + ... if isinstance(obj, complex): + ... return [obj.real, obj.imag] + ... raise TypeError(repr(o) + " is not JSON serializable") + ... + >>> json.dumps(2 + 1j, default=encode_complex) + '[2.0, 1.0]' + >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j) + '[2.0, 1.0]' + >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j)) + '[2.0, 1.0]' + + +Using simplejson.tool from the shell to validate and pretty-print:: + + $ echo '{"json":"obj"}' | python -m simplejson.tool + { + "json": "obj" + } + $ echo '{ 1.2:3.4}' | python -m simplejson.tool + Expecting property name: line 1 column 2 (char 2) +""" +__version__ = '2.6.2' +__all__ = [ + 'dump', 'dumps', 'load', 'loads', + 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', + 'OrderedDict', 'simple_first', +] + +__author__ = 'Bob Ippolito ' + +from decimal import Decimal + +from decoder import JSONDecoder, JSONDecodeError +from encoder import JSONEncoder, JSONEncoderForHTML +def _import_OrderedDict(): + import collections + try: + return collections.OrderedDict + except AttributeError: + import ordered_dict + return ordered_dict.OrderedDict +OrderedDict = _import_OrderedDict() + +def _import_c_make_encoder(): + try: + from simplejson._speedups import make_encoder + return make_encoder + except ImportError: + return None + +_default_encoder = JSONEncoder( + skipkeys=False, + ensure_ascii=True, + check_circular=True, + allow_nan=True, + indent=None, + separators=None, + encoding='utf-8', + default=None, + use_decimal=True, + namedtuple_as_object=True, + tuple_as_array=True, + bigint_as_string=False, + item_sort_key=None, +) + +def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, use_decimal=True, + namedtuple_as_object=True, tuple_as_array=True, + bigint_as_string=False, sort_keys=False, item_sort_key=None, + **kw): + """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a + ``.write()``-supporting file-like object). + + If ``skipkeys`` is true then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the some chunks written to ``fp`` + may be ``unicode`` instances, subject to normal Python ``str`` to + ``unicode`` coercion rules. Unless ``fp.write()`` explicitly + understands ``unicode`` (as in ``codecs.getwriter()``) this is likely + to cause an error. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) + in strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If *indent* is a string, then JSON array elements and object members + will be pretty-printed with a newline followed by that string repeated + for each level of nesting. ``None`` (the default) selects the most compact + representation without any newlines. For backwards compatibility with + versions of simplejson earlier than 2.1.0, an integer is also accepted + and is converted to a string with that many spaces. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + If *use_decimal* is true (default: ``True``) then decimal.Decimal + will be natively serialized to JSON with full precision. + + If *namedtuple_as_object* is true (default: ``True``), + :class:`tuple` subclasses with ``_asdict()`` methods will be encoded + as JSON objects. + + If *tuple_as_array* is true (default: ``True``), + :class:`tuple` (and subclasses) will be encoded as JSON arrays. + + If *bigint_as_string* is true (default: ``False``), ints 2**53 and higher + or lower than -2**53 will be encoded as strings. This is to avoid the + rounding that happens in Javascript otherwise. Note that this is still a + lossy operation that will not round-trip correctly and should be used + sparingly. + + If specified, *item_sort_key* is a callable used to sort the items in + each dictionary. This is useful if you want to sort items other than + in alphabetical order by key. This option takes precedence over + *sort_keys*. + + If *sort_keys* is true (default: ``False``), the output of dictionaries + will be sorted by item. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + + """ + # cached encoder + if (not skipkeys and ensure_ascii and + check_circular and allow_nan and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and use_decimal + and namedtuple_as_object and tuple_as_array + and not bigint_as_string and not item_sort_key and not kw): + iterable = _default_encoder.iterencode(obj) + else: + if cls is None: + cls = JSONEncoder + iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, + default=default, use_decimal=use_decimal, + namedtuple_as_object=namedtuple_as_object, + tuple_as_array=tuple_as_array, + bigint_as_string=bigint_as_string, + sort_keys=sort_keys, + item_sort_key=item_sort_key, + **kw).iterencode(obj) + # could accelerate with writelines in some versions of Python, at + # a debuggability cost + for chunk in iterable: + fp.write(chunk) + + +def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, use_decimal=True, + namedtuple_as_object=True, tuple_as_array=True, + bigint_as_string=False, sort_keys=False, item_sort_key=None, + **kw): + """Serialize ``obj`` to a JSON formatted ``str``. + + If ``skipkeys`` is false then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the return value will be a + ``unicode`` instance subject to normal Python ``str`` to ``unicode`` + coercion rules instead of being escaped to an ASCII ``str``. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in + strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a string, then JSON array elements and object members + will be pretty-printed with a newline followed by that string repeated + for each level of nesting. ``None`` (the default) selects the most compact + representation without any newlines. For backwards compatibility with + versions of simplejson earlier than 2.1.0, an integer is also accepted + and is converted to a string with that many spaces. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + If *use_decimal* is true (default: ``True``) then decimal.Decimal + will be natively serialized to JSON with full precision. + + If *namedtuple_as_object* is true (default: ``True``), + :class:`tuple` subclasses with ``_asdict()`` methods will be encoded + as JSON objects. + + If *tuple_as_array* is true (default: ``True``), + :class:`tuple` (and subclasses) will be encoded as JSON arrays. + + If *bigint_as_string* is true (not the default), ints 2**53 and higher + or lower than -2**53 will be encoded as strings. This is to avoid the + rounding that happens in Javascript otherwise. + + If specified, *item_sort_key* is a callable used to sort the items in + each dictionary. This is useful if you want to sort items other than + in alphabetical order by key. This option takes precendence over + *sort_keys*. + + If *sort_keys* is true (default: ``False``), the output of dictionaries + will be sorted by item. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + + """ + # cached encoder + if (not skipkeys and ensure_ascii and + check_circular and allow_nan and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and use_decimal + and namedtuple_as_object and tuple_as_array + and not bigint_as_string and not sort_keys + and not item_sort_key and not kw): + return _default_encoder.encode(obj) + if cls is None: + cls = JSONEncoder + return cls( + skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, default=default, + use_decimal=use_decimal, + namedtuple_as_object=namedtuple_as_object, + tuple_as_array=tuple_as_array, + bigint_as_string=bigint_as_string, + sort_keys=sort_keys, + item_sort_key=item_sort_key, + **kw).encode(obj) + + +_default_decoder = JSONDecoder(encoding=None, object_hook=None, + object_pairs_hook=None) + + +def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, object_pairs_hook=None, + use_decimal=False, namedtuple_as_object=True, tuple_as_array=True, + **kw): + """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing + a JSON document) to a Python object. + + *encoding* determines the encoding used to interpret any + :class:`str` objects decoded by this instance (``'utf-8'`` by + default). It has no effect when decoding :class:`unicode` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as :class:`unicode`. + + *object_hook*, if specified, will be called with the result of every + JSON object decoded and its return value will be used in place of the + given :class:`dict`. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + *object_pairs_hook* is an optional function that will be called with + the result of any object literal decode with an ordered list of pairs. + The return value of *object_pairs_hook* will be used instead of the + :class:`dict`. This feature can be used to implement custom decoders + that rely on the order that the key and value pairs are decoded (for + example, :func:`collections.OrderedDict` will remember the order of + insertion). If *object_hook* is also defined, the *object_pairs_hook* + takes priority. + + *parse_float*, if specified, will be called with the string of every + JSON float to be decoded. By default, this is equivalent to + ``float(num_str)``. This can be used to use another datatype or parser + for JSON floats (e.g. :class:`decimal.Decimal`). + + *parse_int*, if specified, will be called with the string of every + JSON int to be decoded. By default, this is equivalent to + ``int(num_str)``. This can be used to use another datatype or parser + for JSON integers (e.g. :class:`float`). + + *parse_constant*, if specified, will be called with one of the + following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This + can be used to raise an exception if invalid JSON numbers are + encountered. + + If *use_decimal* is true (default: ``False``) then it implies + parse_float=decimal.Decimal for parity with ``dump``. + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + + """ + return loads(fp.read(), + encoding=encoding, cls=cls, object_hook=object_hook, + parse_float=parse_float, parse_int=parse_int, + parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, + use_decimal=use_decimal, **kw) + + +def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, object_pairs_hook=None, + use_decimal=False, **kw): + """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON + document) to a Python object. + + *encoding* determines the encoding used to interpret any + :class:`str` objects decoded by this instance (``'utf-8'`` by + default). It has no effect when decoding :class:`unicode` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as :class:`unicode`. + + *object_hook*, if specified, will be called with the result of every + JSON object decoded and its return value will be used in place of the + given :class:`dict`. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + *object_pairs_hook* is an optional function that will be called with + the result of any object literal decode with an ordered list of pairs. + The return value of *object_pairs_hook* will be used instead of the + :class:`dict`. This feature can be used to implement custom decoders + that rely on the order that the key and value pairs are decoded (for + example, :func:`collections.OrderedDict` will remember the order of + insertion). If *object_hook* is also defined, the *object_pairs_hook* + takes priority. + + *parse_float*, if specified, will be called with the string of every + JSON float to be decoded. By default, this is equivalent to + ``float(num_str)``. This can be used to use another datatype or parser + for JSON floats (e.g. :class:`decimal.Decimal`). + + *parse_int*, if specified, will be called with the string of every + JSON int to be decoded. By default, this is equivalent to + ``int(num_str)``. This can be used to use another datatype or parser + for JSON integers (e.g. :class:`float`). + + *parse_constant*, if specified, will be called with one of the + following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This + can be used to raise an exception if invalid JSON numbers are + encountered. + + If *use_decimal* is true (default: ``False``) then it implies + parse_float=decimal.Decimal for parity with ``dump``. + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + + """ + if (cls is None and encoding is None and object_hook is None and + parse_int is None and parse_float is None and + parse_constant is None and object_pairs_hook is None + and not use_decimal and not kw): + return _default_decoder.decode(s) + if cls is None: + cls = JSONDecoder + if object_hook is not None: + kw['object_hook'] = object_hook + if object_pairs_hook is not None: + kw['object_pairs_hook'] = object_pairs_hook + if parse_float is not None: + kw['parse_float'] = parse_float + if parse_int is not None: + kw['parse_int'] = parse_int + if parse_constant is not None: + kw['parse_constant'] = parse_constant + if use_decimal: + if parse_float is not None: + raise TypeError("use_decimal=True implies parse_float=Decimal") + kw['parse_float'] = Decimal + return cls(encoding=encoding, **kw).decode(s) + + +def _toggle_speedups(enabled): + import simplejson.decoder as dec + import simplejson.encoder as enc + import simplejson.scanner as scan + c_make_encoder = _import_c_make_encoder() + if enabled: + dec.scanstring = dec.c_scanstring or dec.py_scanstring + enc.c_make_encoder = c_make_encoder + enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or + enc.py_encode_basestring_ascii) + scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner + else: + dec.scanstring = dec.py_scanstring + enc.c_make_encoder = None + enc.encode_basestring_ascii = enc.py_encode_basestring_ascii + scan.make_scanner = scan.py_make_scanner + dec.make_scanner = scan.make_scanner + global _default_decoder + _default_decoder = JSONDecoder( + encoding=None, + object_hook=None, + object_pairs_hook=None, + ) + global _default_encoder + _default_encoder = JSONEncoder( + skipkeys=False, + ensure_ascii=True, + check_circular=True, + allow_nan=True, + indent=None, + separators=None, + encoding='utf-8', + default=None, + ) + +def simple_first(kv): + """Helper function to pass to item_sort_key to sort simple + elements to the top, then container elements. + """ + return (isinstance(kv[1], (list, dict, tuple)), kv[0]) diff --git a/script/test/diffcalc (copy)/simplejson/_speedups.c b/script/test/diffcalc (copy)/simplejson/_speedups.c new file mode 100644 index 0000000..be68b2d --- /dev/null +++ b/script/test/diffcalc (copy)/simplejson/_speedups.c @@ -0,0 +1,2745 @@ +#include "Python.h" +#include "structmember.h" +#if PY_VERSION_HEX < 0x02070000 && !defined(PyOS_string_to_double) +#define PyOS_string_to_double json_PyOS_string_to_double +static double +json_PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception); +static double +json_PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception) { + double x; + assert(endptr == NULL); + assert(overflow_exception == NULL); + PyFPE_START_PROTECT("json_PyOS_string_to_double", return -1.0;) + x = PyOS_ascii_atof(s); + PyFPE_END_PROTECT(x) + return x; +} +#endif +#if PY_VERSION_HEX < 0x02060000 && !defined(Py_TYPE) +#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) +#endif +#if PY_VERSION_HEX < 0x02060000 && !defined(Py_SIZE) +#define Py_SIZE(ob) (((PyVarObject*)(ob))->ob_size) +#endif +#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) +typedef int Py_ssize_t; +#define PY_SSIZE_T_MAX INT_MAX +#define PY_SSIZE_T_MIN INT_MIN +#define PyInt_FromSsize_t PyInt_FromLong +#define PyInt_AsSsize_t PyInt_AsLong +#endif +#ifndef Py_IS_FINITE +#define Py_IS_FINITE(X) (!Py_IS_INFINITY(X) && !Py_IS_NAN(X)) +#endif + +#ifdef __GNUC__ +#define UNUSED __attribute__((__unused__)) +#else +#define UNUSED +#endif + +#define DEFAULT_ENCODING "utf-8" + +#define PyScanner_Check(op) PyObject_TypeCheck(op, &PyScannerType) +#define PyScanner_CheckExact(op) (Py_TYPE(op) == &PyScannerType) +#define PyEncoder_Check(op) PyObject_TypeCheck(op, &PyEncoderType) +#define PyEncoder_CheckExact(op) (Py_TYPE(op) == &PyEncoderType) + +static PyTypeObject PyScannerType; +static PyTypeObject PyEncoderType; + +typedef struct _PyScannerObject { + PyObject_HEAD + PyObject *encoding; + PyObject *strict; + PyObject *object_hook; + PyObject *pairs_hook; + PyObject *parse_float; + PyObject *parse_int; + PyObject *parse_constant; + PyObject *memo; +} PyScannerObject; + +static PyMemberDef scanner_members[] = { + {"encoding", T_OBJECT, offsetof(PyScannerObject, encoding), READONLY, "encoding"}, + {"strict", T_OBJECT, offsetof(PyScannerObject, strict), READONLY, "strict"}, + {"object_hook", T_OBJECT, offsetof(PyScannerObject, object_hook), READONLY, "object_hook"}, + {"object_pairs_hook", T_OBJECT, offsetof(PyScannerObject, pairs_hook), READONLY, "object_pairs_hook"}, + {"parse_float", T_OBJECT, offsetof(PyScannerObject, parse_float), READONLY, "parse_float"}, + {"parse_int", T_OBJECT, offsetof(PyScannerObject, parse_int), READONLY, "parse_int"}, + {"parse_constant", T_OBJECT, offsetof(PyScannerObject, parse_constant), READONLY, "parse_constant"}, + {NULL} +}; + +typedef struct _PyEncoderObject { + PyObject_HEAD + PyObject *markers; + PyObject *defaultfn; + PyObject *encoder; + PyObject *indent; + PyObject *key_separator; + PyObject *item_separator; + PyObject *sort_keys; + PyObject *skipkeys; + PyObject *key_memo; + PyObject *Decimal; + int fast_encode; + int allow_nan; + int use_decimal; + int namedtuple_as_object; + int tuple_as_array; + int bigint_as_string; + PyObject *item_sort_key; +} PyEncoderObject; + +static PyMemberDef encoder_members[] = { + {"markers", T_OBJECT, offsetof(PyEncoderObject, markers), READONLY, "markers"}, + {"default", T_OBJECT, offsetof(PyEncoderObject, defaultfn), READONLY, "default"}, + {"encoder", T_OBJECT, offsetof(PyEncoderObject, encoder), READONLY, "encoder"}, + {"indent", T_OBJECT, offsetof(PyEncoderObject, indent), READONLY, "indent"}, + {"key_separator", T_OBJECT, offsetof(PyEncoderObject, key_separator), READONLY, "key_separator"}, + {"item_separator", T_OBJECT, offsetof(PyEncoderObject, item_separator), READONLY, "item_separator"}, + {"sort_keys", T_OBJECT, offsetof(PyEncoderObject, sort_keys), READONLY, "sort_keys"}, + {"skipkeys", T_OBJECT, offsetof(PyEncoderObject, skipkeys), READONLY, "skipkeys"}, + {"key_memo", T_OBJECT, offsetof(PyEncoderObject, key_memo), READONLY, "key_memo"}, + {"item_sort_key", T_OBJECT, offsetof(PyEncoderObject, item_sort_key), READONLY, "item_sort_key"}, + {NULL} +}; + +static PyObject * +maybe_quote_bigint(PyObject *encoded, PyObject *obj); + +static Py_ssize_t +ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars); +static PyObject * +ascii_escape_unicode(PyObject *pystr); +static PyObject * +ascii_escape_str(PyObject *pystr); +static PyObject * +py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr); +void init_speedups(void); +static PyObject * +scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr); +static PyObject * +scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr); +static PyObject * +_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx); +static PyObject * +scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds); +static int +scanner_init(PyObject *self, PyObject *args, PyObject *kwds); +static void +scanner_dealloc(PyObject *self); +static int +scanner_clear(PyObject *self); +static PyObject * +encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds); +static int +encoder_init(PyObject *self, PyObject *args, PyObject *kwds); +static void +encoder_dealloc(PyObject *self); +static int +encoder_clear(PyObject *self); +static int +encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level); +static int +encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level); +static int +encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level); +static PyObject * +_encoded_const(PyObject *obj); +static void +raise_errmsg(char *msg, PyObject *s, Py_ssize_t end); +static PyObject * +encoder_encode_string(PyEncoderObject *s, PyObject *obj); +static int +_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr); +static PyObject * +_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr); +static PyObject * +encoder_encode_float(PyEncoderObject *s, PyObject *obj); +static int +_is_namedtuple(PyObject *obj); + +#define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '"') +#define IS_WHITESPACE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r')) + +#define MIN_EXPANSION 6 +#ifdef Py_UNICODE_WIDE +#define MAX_EXPANSION (2 * MIN_EXPANSION) +#else +#define MAX_EXPANSION MIN_EXPANSION +#endif + +static PyObject * +maybe_quote_bigint(PyObject *encoded, PyObject *obj) +{ + static PyObject *big_long = NULL; + static PyObject *small_long = NULL; + if (big_long == NULL) { + big_long = PyLong_FromLongLong(1LL << 53); + if (big_long == NULL) { + Py_DECREF(encoded); + return NULL; + } + } + if (small_long == NULL) { + small_long = PyLong_FromLongLong(-1LL << 53); + if (small_long == NULL) { + Py_DECREF(encoded); + return NULL; + } + } + if (PyObject_RichCompareBool(obj, big_long, Py_GE) || + PyObject_RichCompareBool(obj, small_long, Py_LE)) { + PyObject* quoted = PyString_FromFormat("\"%s\"", + PyString_AsString(encoded)); + Py_DECREF(encoded); + encoded = quoted; + } + return encoded; +} + +static int +_is_namedtuple(PyObject *obj) +{ + int rval = 0; + PyObject *_asdict = PyObject_GetAttrString(obj, "_asdict"); + if (_asdict == NULL) { + PyErr_Clear(); + return 0; + } + rval = PyCallable_Check(_asdict); + Py_DECREF(_asdict); + return rval; +} + +static int +_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr) +{ + /* PyObject to Py_ssize_t converter */ + *size_ptr = PyInt_AsSsize_t(o); + if (*size_ptr == -1 && PyErr_Occurred()) + return 0; + return 1; +} + +static PyObject * +_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr) +{ + /* Py_ssize_t to PyObject converter */ + return PyInt_FromSsize_t(*size_ptr); +} + +static Py_ssize_t +ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars) +{ + /* Escape unicode code point c to ASCII escape sequences + in char *output. output must have at least 12 bytes unused to + accommodate an escaped surrogate pair "\uXXXX\uXXXX" */ + output[chars++] = '\\'; + switch (c) { + case '\\': output[chars++] = (char)c; break; + case '"': output[chars++] = (char)c; break; + case '\b': output[chars++] = 'b'; break; + case '\f': output[chars++] = 'f'; break; + case '\n': output[chars++] = 'n'; break; + case '\r': output[chars++] = 'r'; break; + case '\t': output[chars++] = 't'; break; + default: +#ifdef Py_UNICODE_WIDE + if (c >= 0x10000) { + /* UTF-16 surrogate pair */ + Py_UNICODE v = c - 0x10000; + c = 0xd800 | ((v >> 10) & 0x3ff); + output[chars++] = 'u'; + output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 8) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 4) & 0xf]; + output[chars++] = "0123456789abcdef"[(c ) & 0xf]; + c = 0xdc00 | (v & 0x3ff); + output[chars++] = '\\'; + } +#endif + output[chars++] = 'u'; + output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 8) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 4) & 0xf]; + output[chars++] = "0123456789abcdef"[(c ) & 0xf]; + } + return chars; +} + +static PyObject * +ascii_escape_unicode(PyObject *pystr) +{ + /* Take a PyUnicode pystr and return a new ASCII-only escaped PyString */ + Py_ssize_t i; + Py_ssize_t input_chars; + Py_ssize_t output_size; + Py_ssize_t max_output_size; + Py_ssize_t chars; + PyObject *rval; + char *output; + Py_UNICODE *input_unicode; + + input_chars = PyUnicode_GET_SIZE(pystr); + input_unicode = PyUnicode_AS_UNICODE(pystr); + + /* One char input can be up to 6 chars output, estimate 4 of these */ + output_size = 2 + (MIN_EXPANSION * 4) + input_chars; + max_output_size = 2 + (input_chars * MAX_EXPANSION); + rval = PyString_FromStringAndSize(NULL, output_size); + if (rval == NULL) { + return NULL; + } + output = PyString_AS_STRING(rval); + chars = 0; + output[chars++] = '"'; + for (i = 0; i < input_chars; i++) { + Py_UNICODE c = input_unicode[i]; + if (S_CHAR(c)) { + output[chars++] = (char)c; + } + else { + chars = ascii_escape_char(c, output, chars); + } + if (output_size - chars < (1 + MAX_EXPANSION)) { + /* There's more than four, so let's resize by a lot */ + Py_ssize_t new_output_size = output_size * 2; + /* This is an upper bound */ + if (new_output_size > max_output_size) { + new_output_size = max_output_size; + } + /* Make sure that the output size changed before resizing */ + if (new_output_size != output_size) { + output_size = new_output_size; + if (_PyString_Resize(&rval, output_size) == -1) { + return NULL; + } + output = PyString_AS_STRING(rval); + } + } + } + output[chars++] = '"'; + if (_PyString_Resize(&rval, chars) == -1) { + return NULL; + } + return rval; +} + +static PyObject * +ascii_escape_str(PyObject *pystr) +{ + /* Take a PyString pystr and return a new ASCII-only escaped PyString */ + Py_ssize_t i; + Py_ssize_t input_chars; + Py_ssize_t output_size; + Py_ssize_t chars; + PyObject *rval; + char *output; + char *input_str; + + input_chars = PyString_GET_SIZE(pystr); + input_str = PyString_AS_STRING(pystr); + + /* Fast path for a string that's already ASCII */ + for (i = 0; i < input_chars; i++) { + Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i]; + if (!S_CHAR(c)) { + /* If we have to escape something, scan the string for unicode */ + Py_ssize_t j; + for (j = i; j < input_chars; j++) { + c = (Py_UNICODE)(unsigned char)input_str[j]; + if (c > 0x7f) { + /* We hit a non-ASCII character, bail to unicode mode */ + PyObject *uni; + uni = PyUnicode_DecodeUTF8(input_str, input_chars, "strict"); + if (uni == NULL) { + return NULL; + } + rval = ascii_escape_unicode(uni); + Py_DECREF(uni); + return rval; + } + } + break; + } + } + + if (i == input_chars) { + /* Input is already ASCII */ + output_size = 2 + input_chars; + } + else { + /* One char input can be up to 6 chars output, estimate 4 of these */ + output_size = 2 + (MIN_EXPANSION * 4) + input_chars; + } + rval = PyString_FromStringAndSize(NULL, output_size); + if (rval == NULL) { + return NULL; + } + output = PyString_AS_STRING(rval); + output[0] = '"'; + + /* We know that everything up to i is ASCII already */ + chars = i + 1; + memcpy(&output[1], input_str, i); + + for (; i < input_chars; i++) { + Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i]; + if (S_CHAR(c)) { + output[chars++] = (char)c; + } + else { + chars = ascii_escape_char(c, output, chars); + } + /* An ASCII char can't possibly expand to a surrogate! */ + if (output_size - chars < (1 + MIN_EXPANSION)) { + /* There's more than four, so let's resize by a lot */ + output_size *= 2; + if (output_size > 2 + (input_chars * MIN_EXPANSION)) { + output_size = 2 + (input_chars * MIN_EXPANSION); + } + if (_PyString_Resize(&rval, output_size) == -1) { + return NULL; + } + output = PyString_AS_STRING(rval); + } + } + output[chars++] = '"'; + if (_PyString_Resize(&rval, chars) == -1) { + return NULL; + } + return rval; +} + +static void +raise_errmsg(char *msg, PyObject *s, Py_ssize_t end) +{ + /* Use the Python function simplejson.decoder.errmsg to raise a nice + looking ValueError exception */ + static PyObject *JSONDecodeError = NULL; + PyObject *exc; + if (JSONDecodeError == NULL) { + PyObject *decoder = PyImport_ImportModule("simplejson.decoder"); + if (decoder == NULL) + return; + JSONDecodeError = PyObject_GetAttrString(decoder, "JSONDecodeError"); + Py_DECREF(decoder); + if (JSONDecodeError == NULL) + return; + } + exc = PyObject_CallFunction(JSONDecodeError, "(zOO&)", msg, s, _convertPyInt_FromSsize_t, &end); + if (exc) { + PyErr_SetObject(JSONDecodeError, exc); + Py_DECREF(exc); + } +} + +static PyObject * +join_list_unicode(PyObject *lst) +{ + /* return u''.join(lst) */ + static PyObject *joinfn = NULL; + if (joinfn == NULL) { + PyObject *ustr = PyUnicode_FromUnicode(NULL, 0); + if (ustr == NULL) + return NULL; + + joinfn = PyObject_GetAttrString(ustr, "join"); + Py_DECREF(ustr); + if (joinfn == NULL) + return NULL; + } + return PyObject_CallFunctionObjArgs(joinfn, lst, NULL); +} + +static PyObject * +join_list_string(PyObject *lst) +{ + /* return ''.join(lst) */ + static PyObject *joinfn = NULL; + if (joinfn == NULL) { + PyObject *ustr = PyString_FromStringAndSize(NULL, 0); + if (ustr == NULL) + return NULL; + + joinfn = PyObject_GetAttrString(ustr, "join"); + Py_DECREF(ustr); + if (joinfn == NULL) + return NULL; + } + return PyObject_CallFunctionObjArgs(joinfn, lst, NULL); +} + +static PyObject * +_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx) { + /* return (rval, idx) tuple, stealing reference to rval */ + PyObject *tpl; + PyObject *pyidx; + /* + steal a reference to rval, returns (rval, idx) + */ + if (rval == NULL) { + return NULL; + } + pyidx = PyInt_FromSsize_t(idx); + if (pyidx == NULL) { + Py_DECREF(rval); + return NULL; + } + tpl = PyTuple_New(2); + if (tpl == NULL) { + Py_DECREF(pyidx); + Py_DECREF(rval); + return NULL; + } + PyTuple_SET_ITEM(tpl, 0, rval); + PyTuple_SET_ITEM(tpl, 1, pyidx); + return tpl; +} + +#define APPEND_OLD_CHUNK \ + if (chunk != NULL) { \ + if (chunks == NULL) { \ + chunks = PyList_New(0); \ + if (chunks == NULL) { \ + goto bail; \ + } \ + } \ + if (PyList_Append(chunks, chunk)) { \ + goto bail; \ + } \ + Py_CLEAR(chunk); \ + } + +static PyObject * +scanstring_str(PyObject *pystr, Py_ssize_t end, char *encoding, int strict, Py_ssize_t *next_end_ptr) +{ + /* Read the JSON string from PyString pystr. + end is the index of the first character after the quote. + encoding is the encoding of pystr (must be an ASCII superset) + if strict is zero then literal control characters are allowed + *next_end_ptr is a return-by-reference index of the character + after the end quote + + Return value is a new PyString (if ASCII-only) or PyUnicode + */ + PyObject *rval; + Py_ssize_t len = PyString_GET_SIZE(pystr); + Py_ssize_t begin = end - 1; + Py_ssize_t next = begin; + int has_unicode = 0; + char *buf = PyString_AS_STRING(pystr); + PyObject *chunks = NULL; + PyObject *chunk = NULL; + + if (len == end) { + raise_errmsg("Unterminated string starting at", pystr, begin); + } + else if (end < 0 || len < end) { + PyErr_SetString(PyExc_ValueError, "end is out of bounds"); + goto bail; + } + while (1) { + /* Find the end of the string or the next escape */ + Py_UNICODE c = 0; + for (next = end; next < len; next++) { + c = (unsigned char)buf[next]; + if (c == '"' || c == '\\') { + break; + } + else if (strict && c <= 0x1f) { + raise_errmsg("Invalid control character at", pystr, next); + goto bail; + } + else if (c > 0x7f) { + has_unicode = 1; + } + } + if (!(c == '"' || c == '\\')) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + /* Pick up this chunk if it's not zero length */ + if (next != end) { + PyObject *strchunk; + APPEND_OLD_CHUNK + strchunk = PyString_FromStringAndSize(&buf[end], next - end); + if (strchunk == NULL) { + goto bail; + } + if (has_unicode) { + chunk = PyUnicode_FromEncodedObject(strchunk, encoding, NULL); + Py_DECREF(strchunk); + if (chunk == NULL) { + goto bail; + } + } + else { + chunk = strchunk; + } + } + next++; + if (c == '"') { + end = next; + break; + } + if (next == len) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + c = buf[next]; + if (c != 'u') { + /* Non-unicode backslash escapes */ + end = next + 1; + switch (c) { + case '"': break; + case '\\': break; + case '/': break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + default: c = 0; + } + if (c == 0) { + raise_errmsg("Invalid \\escape", pystr, end - 2); + goto bail; + } + } + else { + c = 0; + next++; + end = next + 4; + if (end >= len) { + raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1); + goto bail; + } + /* Decode 4 hex digits */ + for (; next < end; next++) { + Py_UNICODE digit = buf[next]; + c <<= 4; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } +#ifdef Py_UNICODE_WIDE + /* Surrogate pair */ + if ((c & 0xfc00) == 0xd800) { + Py_UNICODE c2 = 0; + if (end + 6 >= len) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + if (buf[next++] != '\\' || buf[next++] != 'u') { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + end += 6; + /* Decode 4 hex digits */ + for (; next < end; next++) { + c2 <<= 4; + Py_UNICODE digit = buf[next]; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c2 |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c2 |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c2 |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } + if ((c2 & 0xfc00) != 0xdc00) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00)); + } + else if ((c & 0xfc00) == 0xdc00) { + raise_errmsg("Unpaired low surrogate", pystr, end - 5); + goto bail; + } +#endif + } + if (c > 0x7f) { + has_unicode = 1; + } + APPEND_OLD_CHUNK + if (has_unicode) { + chunk = PyUnicode_FromUnicode(&c, 1); + if (chunk == NULL) { + goto bail; + } + } + else { + char c_char = Py_CHARMASK(c); + chunk = PyString_FromStringAndSize(&c_char, 1); + if (chunk == NULL) { + goto bail; + } + } + } + + if (chunks == NULL) { + if (chunk != NULL) + rval = chunk; + else + rval = PyString_FromStringAndSize("", 0); + } + else { + APPEND_OLD_CHUNK + rval = join_list_string(chunks); + if (rval == NULL) { + goto bail; + } + Py_CLEAR(chunks); + } + + *next_end_ptr = end; + return rval; +bail: + *next_end_ptr = -1; + Py_XDECREF(chunk); + Py_XDECREF(chunks); + return NULL; +} + + +static PyObject * +scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr) +{ + /* Read the JSON string from PyUnicode pystr. + end is the index of the first character after the quote. + if strict is zero then literal control characters are allowed + *next_end_ptr is a return-by-reference index of the character + after the end quote + + Return value is a new PyUnicode + */ + PyObject *rval; + Py_ssize_t len = PyUnicode_GET_SIZE(pystr); + Py_ssize_t begin = end - 1; + Py_ssize_t next = begin; + const Py_UNICODE *buf = PyUnicode_AS_UNICODE(pystr); + PyObject *chunks = NULL; + PyObject *chunk = NULL; + + if (len == end) { + raise_errmsg("Unterminated string starting at", pystr, begin); + } + else if (end < 0 || len < end) { + PyErr_SetString(PyExc_ValueError, "end is out of bounds"); + goto bail; + } + while (1) { + /* Find the end of the string or the next escape */ + Py_UNICODE c = 0; + for (next = end; next < len; next++) { + c = buf[next]; + if (c == '"' || c == '\\') { + break; + } + else if (strict && c <= 0x1f) { + raise_errmsg("Invalid control character at", pystr, next); + goto bail; + } + } + if (!(c == '"' || c == '\\')) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + /* Pick up this chunk if it's not zero length */ + if (next != end) { + APPEND_OLD_CHUNK + chunk = PyUnicode_FromUnicode(&buf[end], next - end); + if (chunk == NULL) { + goto bail; + } + } + next++; + if (c == '"') { + end = next; + break; + } + if (next == len) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + c = buf[next]; + if (c != 'u') { + /* Non-unicode backslash escapes */ + end = next + 1; + switch (c) { + case '"': break; + case '\\': break; + case '/': break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + default: c = 0; + } + if (c == 0) { + raise_errmsg("Invalid \\escape", pystr, end - 2); + goto bail; + } + } + else { + c = 0; + next++; + end = next + 4; + if (end >= len) { + raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1); + goto bail; + } + /* Decode 4 hex digits */ + for (; next < end; next++) { + Py_UNICODE digit = buf[next]; + c <<= 4; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } +#ifdef Py_UNICODE_WIDE + /* Surrogate pair */ + if ((c & 0xfc00) == 0xd800) { + Py_UNICODE c2 = 0; + if (end + 6 >= len) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + if (buf[next++] != '\\' || buf[next++] != 'u') { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + end += 6; + /* Decode 4 hex digits */ + for (; next < end; next++) { + c2 <<= 4; + Py_UNICODE digit = buf[next]; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c2 |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c2 |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c2 |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } + if ((c2 & 0xfc00) != 0xdc00) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00)); + } + else if ((c & 0xfc00) == 0xdc00) { + raise_errmsg("Unpaired low surrogate", pystr, end - 5); + goto bail; + } +#endif + } + APPEND_OLD_CHUNK + chunk = PyUnicode_FromUnicode(&c, 1); + if (chunk == NULL) { + goto bail; + } + } + + if (chunks == NULL) { + if (chunk != NULL) + rval = chunk; + else + rval = PyUnicode_FromUnicode(NULL, 0); + } + else { + APPEND_OLD_CHUNK + rval = join_list_unicode(chunks); + if (rval == NULL) { + goto bail; + } + Py_CLEAR(chunks); + } + *next_end_ptr = end; + return rval; +bail: + *next_end_ptr = -1; + Py_XDECREF(chunk); + Py_XDECREF(chunks); + return NULL; +} + +PyDoc_STRVAR(pydoc_scanstring, + "scanstring(basestring, end, encoding, strict=True) -> (str, end)\n" + "\n" + "Scan the string s for a JSON string. End is the index of the\n" + "character in s after the quote that started the JSON string.\n" + "Unescapes all valid JSON string escape sequences and raises ValueError\n" + "on attempt to decode an invalid string. If strict is False then literal\n" + "control characters are allowed in the string.\n" + "\n" + "Returns a tuple of the decoded string and the index of the character in s\n" + "after the end quote." +); + +static PyObject * +py_scanstring(PyObject* self UNUSED, PyObject *args) +{ + PyObject *pystr; + PyObject *rval; + Py_ssize_t end; + Py_ssize_t next_end = -1; + char *encoding = NULL; + int strict = 1; + if (!PyArg_ParseTuple(args, "OO&|zi:scanstring", &pystr, _convertPyInt_AsSsize_t, &end, &encoding, &strict)) { + return NULL; + } + if (encoding == NULL) { + encoding = DEFAULT_ENCODING; + } + if (PyString_Check(pystr)) { + rval = scanstring_str(pystr, end, encoding, strict, &next_end); + } + else if (PyUnicode_Check(pystr)) { + rval = scanstring_unicode(pystr, end, strict, &next_end); + } + else { + PyErr_Format(PyExc_TypeError, + "first argument must be a string, not %.80s", + Py_TYPE(pystr)->tp_name); + return NULL; + } + return _build_rval_index_tuple(rval, next_end); +} + +PyDoc_STRVAR(pydoc_encode_basestring_ascii, + "encode_basestring_ascii(basestring) -> str\n" + "\n" + "Return an ASCII-only JSON representation of a Python string" +); + +static PyObject * +py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr) +{ + /* Return an ASCII-only JSON representation of a Python string */ + /* METH_O */ + if (PyString_Check(pystr)) { + return ascii_escape_str(pystr); + } + else if (PyUnicode_Check(pystr)) { + return ascii_escape_unicode(pystr); + } + else { + PyErr_Format(PyExc_TypeError, + "first argument must be a string, not %.80s", + Py_TYPE(pystr)->tp_name); + return NULL; + } +} + +static void +scanner_dealloc(PyObject *self) +{ + /* Deallocate scanner object */ + scanner_clear(self); + Py_TYPE(self)->tp_free(self); +} + +static int +scanner_traverse(PyObject *self, visitproc visit, void *arg) +{ + PyScannerObject *s; + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + Py_VISIT(s->encoding); + Py_VISIT(s->strict); + Py_VISIT(s->object_hook); + Py_VISIT(s->pairs_hook); + Py_VISIT(s->parse_float); + Py_VISIT(s->parse_int); + Py_VISIT(s->parse_constant); + Py_VISIT(s->memo); + return 0; +} + +static int +scanner_clear(PyObject *self) +{ + PyScannerObject *s; + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + Py_CLEAR(s->encoding); + Py_CLEAR(s->strict); + Py_CLEAR(s->object_hook); + Py_CLEAR(s->pairs_hook); + Py_CLEAR(s->parse_float); + Py_CLEAR(s->parse_int); + Py_CLEAR(s->parse_constant); + Py_CLEAR(s->memo); + return 0; +} + +static PyObject * +_parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON object from PyString pystr. + idx is the index of the first character after the opening curly brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing curly brace. + + Returns a new PyObject (usually a dict, but object_hook or + object_pairs_hook can change that) + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1; + PyObject *rval = NULL; + PyObject *pairs = NULL; + PyObject *item; + PyObject *key = NULL; + PyObject *val = NULL; + char *encoding = PyString_AS_STRING(s->encoding); + int strict = PyObject_IsTrue(s->strict); + int has_pairs_hook = (s->pairs_hook != Py_None); + Py_ssize_t next_idx; + if (has_pairs_hook) { + pairs = PyList_New(0); + if (pairs == NULL) + return NULL; + } + else { + rval = PyDict_New(); + if (rval == NULL) + return NULL; + } + + /* skip whitespace after { */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the object is non-empty */ + if (idx <= end_idx && str[idx] != '}') { + while (idx <= end_idx) { + PyObject *memokey; + + /* read key */ + if (str[idx] != '"') { + raise_errmsg( + "Expecting property name enclosed in double quotes", + pystr, idx); + goto bail; + } + key = scanstring_str(pystr, idx + 1, encoding, strict, &next_idx); + if (key == NULL) + goto bail; + memokey = PyDict_GetItem(s->memo, key); + if (memokey != NULL) { + Py_INCREF(memokey); + Py_DECREF(key); + key = memokey; + } + else { + if (PyDict_SetItem(s->memo, key, key) < 0) + goto bail; + } + idx = next_idx; + + /* skip whitespace between key and : delimiter, read :, skip whitespace */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + if (idx > end_idx || str[idx] != ':') { + raise_errmsg("Expecting ':' delimiter", pystr, idx); + goto bail; + } + idx++; + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* read any JSON data type */ + val = scan_once_str(s, pystr, idx, &next_idx); + if (val == NULL) + goto bail; + + if (has_pairs_hook) { + item = PyTuple_Pack(2, key, val); + if (item == NULL) + goto bail; + Py_CLEAR(key); + Py_CLEAR(val); + if (PyList_Append(pairs, item) == -1) { + Py_DECREF(item); + goto bail; + } + Py_DECREF(item); + } + else { + if (PyDict_SetItem(rval, key, val) < 0) + goto bail; + Py_CLEAR(key); + Py_CLEAR(val); + } + idx = next_idx; + + /* skip whitespace before } or , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the object is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == '}') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting ',' delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , delimiter */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + /* verify that idx < end_idx, str[idx] should be '}' */ + if (idx > end_idx || str[idx] != '}') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + + /* if pairs_hook is not None: rval = object_pairs_hook(pairs) */ + if (s->pairs_hook != Py_None) { + val = PyObject_CallFunctionObjArgs(s->pairs_hook, pairs, NULL); + if (val == NULL) + goto bail; + Py_DECREF(pairs); + *next_idx_ptr = idx + 1; + return val; + } + + /* if object_hook is not None: rval = object_hook(rval) */ + if (s->object_hook != Py_None) { + val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL); + if (val == NULL) + goto bail; + Py_DECREF(rval); + rval = val; + val = NULL; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(rval); + Py_XDECREF(key); + Py_XDECREF(val); + Py_XDECREF(pairs); + return NULL; +} + +static PyObject * +_parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON object from PyUnicode pystr. + idx is the index of the first character after the opening curly brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing curly brace. + + Returns a new PyObject (usually a dict, but object_hook can change that) + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1; + PyObject *rval = NULL; + PyObject *pairs = NULL; + PyObject *item; + PyObject *key = NULL; + PyObject *val = NULL; + int strict = PyObject_IsTrue(s->strict); + int has_pairs_hook = (s->pairs_hook != Py_None); + Py_ssize_t next_idx; + + if (has_pairs_hook) { + pairs = PyList_New(0); + if (pairs == NULL) + return NULL; + } + else { + rval = PyDict_New(); + if (rval == NULL) + return NULL; + } + + /* skip whitespace after { */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the object is non-empty */ + if (idx <= end_idx && str[idx] != '}') { + while (idx <= end_idx) { + PyObject *memokey; + + /* read key */ + if (str[idx] != '"') { + raise_errmsg( + "Expecting property name enclosed in double quotes", + pystr, idx); + goto bail; + } + key = scanstring_unicode(pystr, idx + 1, strict, &next_idx); + if (key == NULL) + goto bail; + memokey = PyDict_GetItem(s->memo, key); + if (memokey != NULL) { + Py_INCREF(memokey); + Py_DECREF(key); + key = memokey; + } + else { + if (PyDict_SetItem(s->memo, key, key) < 0) + goto bail; + } + idx = next_idx; + + /* skip whitespace between key and : delimiter, read :, skip + whitespace */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + if (idx > end_idx || str[idx] != ':') { + raise_errmsg("Expecting ':' delimiter", pystr, idx); + goto bail; + } + idx++; + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* read any JSON term */ + val = scan_once_unicode(s, pystr, idx, &next_idx); + if (val == NULL) + goto bail; + + if (has_pairs_hook) { + item = PyTuple_Pack(2, key, val); + if (item == NULL) + goto bail; + Py_CLEAR(key); + Py_CLEAR(val); + if (PyList_Append(pairs, item) == -1) { + Py_DECREF(item); + goto bail; + } + Py_DECREF(item); + } + else { + if (PyDict_SetItem(rval, key, val) < 0) + goto bail; + Py_CLEAR(key); + Py_CLEAR(val); + } + idx = next_idx; + + /* skip whitespace before } or , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the object is closed or we didn't get the , + delimiter */ + if (idx > end_idx) break; + if (str[idx] == '}') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting ',' delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , delimiter */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + + /* verify that idx < end_idx, str[idx] should be '}' */ + if (idx > end_idx || str[idx] != '}') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + + /* if pairs_hook is not None: rval = object_pairs_hook(pairs) */ + if (s->pairs_hook != Py_None) { + val = PyObject_CallFunctionObjArgs(s->pairs_hook, pairs, NULL); + if (val == NULL) + goto bail; + Py_DECREF(pairs); + *next_idx_ptr = idx + 1; + return val; + } + + /* if object_hook is not None: rval = object_hook(rval) */ + if (s->object_hook != Py_None) { + val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL); + if (val == NULL) + goto bail; + Py_DECREF(rval); + rval = val; + val = NULL; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(rval); + Py_XDECREF(key); + Py_XDECREF(val); + Py_XDECREF(pairs); + return NULL; +} + +static PyObject * +_parse_array_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON array from PyString pystr. + idx is the index of the first character after the opening brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing brace. + + Returns a new PyList + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1; + PyObject *val = NULL; + PyObject *rval = PyList_New(0); + Py_ssize_t next_idx; + if (rval == NULL) + return NULL; + + /* skip whitespace after [ */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the array is non-empty */ + if (idx <= end_idx && str[idx] != ']') { + while (idx <= end_idx) { + + /* read any JSON term and de-tuplefy the (rval, idx) */ + val = scan_once_str(s, pystr, idx, &next_idx); + if (val == NULL) { + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + raise_errmsg("Expecting object", pystr, idx); + } + goto bail; + } + + if (PyList_Append(rval, val) == -1) + goto bail; + + Py_CLEAR(val); + idx = next_idx; + + /* skip whitespace between term and , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the array is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == ']') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting ',' delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + + /* verify that idx < end_idx, str[idx] should be ']' */ + if (idx > end_idx || str[idx] != ']') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(val); + Py_DECREF(rval); + return NULL; +} + +static PyObject * +_parse_array_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON array from PyString pystr. + idx is the index of the first character after the opening brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing brace. + + Returns a new PyList + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1; + PyObject *val = NULL; + PyObject *rval = PyList_New(0); + Py_ssize_t next_idx; + if (rval == NULL) + return NULL; + + /* skip whitespace after [ */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the array is non-empty */ + if (idx <= end_idx && str[idx] != ']') { + while (idx <= end_idx) { + + /* read any JSON term */ + val = scan_once_unicode(s, pystr, idx, &next_idx); + if (val == NULL) { + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + raise_errmsg("Expecting object", pystr, idx); + } + goto bail; + } + + if (PyList_Append(rval, val) == -1) + goto bail; + + Py_CLEAR(val); + idx = next_idx; + + /* skip whitespace between term and , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the array is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == ']') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting ',' delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + + /* verify that idx < end_idx, str[idx] should be ']' */ + if (idx > end_idx || str[idx] != ']') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(val); + Py_DECREF(rval); + return NULL; +} + +static PyObject * +_parse_constant(PyScannerObject *s, char *constant, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON constant from PyString pystr. + constant is the constant string that was found + ("NaN", "Infinity", "-Infinity"). + idx is the index of the first character of the constant + *next_idx_ptr is a return-by-reference index to the first character after + the constant. + + Returns the result of parse_constant + */ + PyObject *cstr; + PyObject *rval; + /* constant is "NaN", "Infinity", or "-Infinity" */ + cstr = PyString_InternFromString(constant); + if (cstr == NULL) + return NULL; + + /* rval = parse_constant(constant) */ + rval = PyObject_CallFunctionObjArgs(s->parse_constant, cstr, NULL); + idx += PyString_GET_SIZE(cstr); + Py_DECREF(cstr); + *next_idx_ptr = idx; + return rval; +} + +static PyObject * +_match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) { + /* Read a JSON number from PyString pystr. + idx is the index of the first character of the number + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of that number: + PyInt, PyLong, or PyFloat. + May return other types if parse_int or parse_float are set + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1; + Py_ssize_t idx = start; + int is_float = 0; + PyObject *rval; + PyObject *numstr; + + /* read a sign if it's there, make sure it's not the end of the string */ + if (str[idx] == '-') { + idx++; + if (idx > end_idx) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + } + + /* read as many integer digits as we find as long as it doesn't start with 0 */ + if (str[idx] >= '1' && str[idx] <= '9') { + idx++; + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + /* if it starts with 0 we only expect one integer digit */ + else if (str[idx] == '0') { + idx++; + } + /* no integer digits, error */ + else { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + + /* if the next char is '.' followed by a digit then read all float digits */ + if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') { + is_float = 1; + idx += 2; + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + + /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */ + if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) { + + /* save the index of the 'e' or 'E' just in case we need to backtrack */ + Py_ssize_t e_start = idx; + idx++; + + /* read an exponent sign if present */ + if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++; + + /* read all digits */ + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + + /* if we got a digit, then parse as float. if not, backtrack */ + if (str[idx - 1] >= '0' && str[idx - 1] <= '9') { + is_float = 1; + } + else { + idx = e_start; + } + } + + /* copy the section we determined to be a number */ + numstr = PyString_FromStringAndSize(&str[start], idx - start); + if (numstr == NULL) + return NULL; + if (is_float) { + /* parse as a float using a fast path if available, otherwise call user defined method */ + if (s->parse_float != (PyObject *)&PyFloat_Type) { + rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL); + } + else { + /* rval = PyFloat_FromDouble(PyOS_ascii_atof(PyString_AS_STRING(numstr))); */ + double d = PyOS_string_to_double(PyString_AS_STRING(numstr), + NULL, NULL); + if (d == -1.0 && PyErr_Occurred()) + return NULL; + rval = PyFloat_FromDouble(d); + } + } + else { + /* parse as an int using a fast path if available, otherwise call user defined method */ + if (s->parse_int != (PyObject *)&PyInt_Type) { + rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL); + } + else { + rval = PyInt_FromString(PyString_AS_STRING(numstr), NULL, 10); + } + } + Py_DECREF(numstr); + *next_idx_ptr = idx; + return rval; +} + +static PyObject * +_match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) { + /* Read a JSON number from PyUnicode pystr. + idx is the index of the first character of the number + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of that number: + PyInt, PyLong, or PyFloat. + May return other types if parse_int or parse_float are set + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1; + Py_ssize_t idx = start; + int is_float = 0; + PyObject *rval; + PyObject *numstr; + + /* read a sign if it's there, make sure it's not the end of the string */ + if (str[idx] == '-') { + idx++; + if (idx > end_idx) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + } + + /* read as many integer digits as we find as long as it doesn't start with 0 */ + if (str[idx] >= '1' && str[idx] <= '9') { + idx++; + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + /* if it starts with 0 we only expect one integer digit */ + else if (str[idx] == '0') { + idx++; + } + /* no integer digits, error */ + else { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + + /* if the next char is '.' followed by a digit then read all float digits */ + if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') { + is_float = 1; + idx += 2; + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + + /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */ + if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) { + Py_ssize_t e_start = idx; + idx++; + + /* read an exponent sign if present */ + if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++; + + /* read all digits */ + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + + /* if we got a digit, then parse as float. if not, backtrack */ + if (str[idx - 1] >= '0' && str[idx - 1] <= '9') { + is_float = 1; + } + else { + idx = e_start; + } + } + + /* copy the section we determined to be a number */ + numstr = PyUnicode_FromUnicode(&str[start], idx - start); + if (numstr == NULL) + return NULL; + if (is_float) { + /* parse as a float using a fast path if available, otherwise call user defined method */ + if (s->parse_float != (PyObject *)&PyFloat_Type) { + rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL); + } + else { + rval = PyFloat_FromString(numstr, NULL); + } + } + else { + /* no fast path for unicode -> int, just call */ + rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL); + } + Py_DECREF(numstr); + *next_idx_ptr = idx; + return rval; +} + +static PyObject * +scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) +{ + /* Read one JSON term (of any kind) from PyString pystr. + idx is the index of the first character of the term + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of the term. + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t length = PyString_GET_SIZE(pystr); + PyObject *rval = NULL; + int fallthrough = 0; + if (idx >= length) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + if (Py_EnterRecursiveCall(" while decoding a JSON document")) + return NULL; + switch (str[idx]) { + case '"': + /* string */ + rval = scanstring_str(pystr, idx + 1, + PyString_AS_STRING(s->encoding), + PyObject_IsTrue(s->strict), + next_idx_ptr); + break; + case '{': + /* object */ + rval = _parse_object_str(s, pystr, idx + 1, next_idx_ptr); + break; + case '[': + /* array */ + rval = _parse_array_str(s, pystr, idx + 1, next_idx_ptr); + break; + case 'n': + /* null */ + if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') { + Py_INCREF(Py_None); + *next_idx_ptr = idx + 4; + rval = Py_None; + } + else + fallthrough = 1; + break; + case 't': + /* true */ + if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') { + Py_INCREF(Py_True); + *next_idx_ptr = idx + 4; + rval = Py_True; + } + else + fallthrough = 1; + break; + case 'f': + /* false */ + if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') { + Py_INCREF(Py_False); + *next_idx_ptr = idx + 5; + rval = Py_False; + } + else + fallthrough = 1; + break; + case 'N': + /* NaN */ + if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') { + rval = _parse_constant(s, "NaN", idx, next_idx_ptr); + } + else + fallthrough = 1; + break; + case 'I': + /* Infinity */ + if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') { + rval = _parse_constant(s, "Infinity", idx, next_idx_ptr); + } + else + fallthrough = 1; + break; + case '-': + /* -Infinity */ + if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') { + rval = _parse_constant(s, "-Infinity", idx, next_idx_ptr); + } + else + fallthrough = 1; + break; + default: + fallthrough = 1; + } + /* Didn't find a string, object, array, or named constant. Look for a number. */ + if (fallthrough) + rval = _match_number_str(s, pystr, idx, next_idx_ptr); + Py_LeaveRecursiveCall(); + return rval; +} + +static PyObject * +scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) +{ + /* Read one JSON term (of any kind) from PyUnicode pystr. + idx is the index of the first character of the term + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of the term. + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t length = PyUnicode_GET_SIZE(pystr); + PyObject *rval = NULL; + int fallthrough = 0; + if (idx >= length) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + if (Py_EnterRecursiveCall(" while decoding a JSON document")) + return NULL; + switch (str[idx]) { + case '"': + /* string */ + rval = scanstring_unicode(pystr, idx + 1, + PyObject_IsTrue(s->strict), + next_idx_ptr); + break; + case '{': + /* object */ + rval = _parse_object_unicode(s, pystr, idx + 1, next_idx_ptr); + break; + case '[': + /* array */ + rval = _parse_array_unicode(s, pystr, idx + 1, next_idx_ptr); + break; + case 'n': + /* null */ + if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') { + Py_INCREF(Py_None); + *next_idx_ptr = idx + 4; + rval = Py_None; + } + else + fallthrough = 1; + break; + case 't': + /* true */ + if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') { + Py_INCREF(Py_True); + *next_idx_ptr = idx + 4; + rval = Py_True; + } + else + fallthrough = 1; + break; + case 'f': + /* false */ + if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') { + Py_INCREF(Py_False); + *next_idx_ptr = idx + 5; + rval = Py_False; + } + else + fallthrough = 1; + break; + case 'N': + /* NaN */ + if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') { + rval = _parse_constant(s, "NaN", idx, next_idx_ptr); + } + else + fallthrough = 1; + break; + case 'I': + /* Infinity */ + if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') { + rval = _parse_constant(s, "Infinity", idx, next_idx_ptr); + } + else + fallthrough = 1; + break; + case '-': + /* -Infinity */ + if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') { + rval = _parse_constant(s, "-Infinity", idx, next_idx_ptr); + } + else + fallthrough = 1; + break; + default: + fallthrough = 1; + } + /* Didn't find a string, object, array, or named constant. Look for a number. */ + if (fallthrough) + rval = _match_number_unicode(s, pystr, idx, next_idx_ptr); + Py_LeaveRecursiveCall(); + return rval; +} + +static PyObject * +scanner_call(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* Python callable interface to scan_once_{str,unicode} */ + PyObject *pystr; + PyObject *rval; + Py_ssize_t idx; + Py_ssize_t next_idx = -1; + static char *kwlist[] = {"string", "idx", NULL}; + PyScannerObject *s; + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:scan_once", kwlist, &pystr, _convertPyInt_AsSsize_t, &idx)) + return NULL; + + if (PyString_Check(pystr)) { + rval = scan_once_str(s, pystr, idx, &next_idx); + } + else if (PyUnicode_Check(pystr)) { + rval = scan_once_unicode(s, pystr, idx, &next_idx); + } + else { + PyErr_Format(PyExc_TypeError, + "first argument must be a string, not %.80s", + Py_TYPE(pystr)->tp_name); + return NULL; + } + PyDict_Clear(s->memo); + return _build_rval_index_tuple(rval, next_idx); +} + +static PyObject * +scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyScannerObject *s; + s = (PyScannerObject *)type->tp_alloc(type, 0); + if (s != NULL) { + s->encoding = NULL; + s->strict = NULL; + s->object_hook = NULL; + s->pairs_hook = NULL; + s->parse_float = NULL; + s->parse_int = NULL; + s->parse_constant = NULL; + } + return (PyObject *)s; +} + +static int +scanner_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* Initialize Scanner object */ + PyObject *ctx; + static char *kwlist[] = {"context", NULL}; + PyScannerObject *s; + + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:make_scanner", kwlist, &ctx)) + return -1; + + if (s->memo == NULL) { + s->memo = PyDict_New(); + if (s->memo == NULL) + goto bail; + } + + /* PyString_AS_STRING is used on encoding */ + s->encoding = PyObject_GetAttrString(ctx, "encoding"); + if (s->encoding == NULL) + goto bail; + if (s->encoding == Py_None) { + Py_DECREF(Py_None); + s->encoding = PyString_InternFromString(DEFAULT_ENCODING); + } + else if (PyUnicode_Check(s->encoding)) { + PyObject *tmp = PyUnicode_AsEncodedString(s->encoding, NULL, NULL); + Py_DECREF(s->encoding); + s->encoding = tmp; + } + if (s->encoding == NULL || !PyString_Check(s->encoding)) + goto bail; + + /* All of these will fail "gracefully" so we don't need to verify them */ + s->strict = PyObject_GetAttrString(ctx, "strict"); + if (s->strict == NULL) + goto bail; + s->object_hook = PyObject_GetAttrString(ctx, "object_hook"); + if (s->object_hook == NULL) + goto bail; + s->pairs_hook = PyObject_GetAttrString(ctx, "object_pairs_hook"); + if (s->pairs_hook == NULL) + goto bail; + s->parse_float = PyObject_GetAttrString(ctx, "parse_float"); + if (s->parse_float == NULL) + goto bail; + s->parse_int = PyObject_GetAttrString(ctx, "parse_int"); + if (s->parse_int == NULL) + goto bail; + s->parse_constant = PyObject_GetAttrString(ctx, "parse_constant"); + if (s->parse_constant == NULL) + goto bail; + + return 0; + +bail: + Py_CLEAR(s->encoding); + Py_CLEAR(s->strict); + Py_CLEAR(s->object_hook); + Py_CLEAR(s->pairs_hook); + Py_CLEAR(s->parse_float); + Py_CLEAR(s->parse_int); + Py_CLEAR(s->parse_constant); + return -1; +} + +PyDoc_STRVAR(scanner_doc, "JSON scanner object"); + +static +PyTypeObject PyScannerType = { + PyObject_HEAD_INIT(NULL) + 0, /* tp_internal */ + "simplejson._speedups.Scanner", /* tp_name */ + sizeof(PyScannerObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + scanner_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + scanner_call, /* tp_call */ + 0, /* tp_str */ + 0,/* PyObject_GenericGetAttr, */ /* tp_getattro */ + 0,/* PyObject_GenericSetAttr, */ /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + scanner_doc, /* tp_doc */ + scanner_traverse, /* tp_traverse */ + scanner_clear, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + scanner_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + scanner_init, /* tp_init */ + 0,/* PyType_GenericAlloc, */ /* tp_alloc */ + scanner_new, /* tp_new */ + 0,/* PyObject_GC_Del, */ /* tp_free */ +}; + +static PyObject * +encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyEncoderObject *s; + s = (PyEncoderObject *)type->tp_alloc(type, 0); + if (s != NULL) { + s->markers = NULL; + s->defaultfn = NULL; + s->encoder = NULL; + s->indent = NULL; + s->key_separator = NULL; + s->item_separator = NULL; + s->sort_keys = NULL; + s->skipkeys = NULL; + s->key_memo = NULL; + s->item_sort_key = NULL; + s->Decimal = NULL; + } + return (PyObject *)s; +} + +static int +encoder_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* initialize Encoder object */ + static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", "key_memo", "use_decimal", "namedtuple_as_object", "tuple_as_array", "bigint_as_string", "item_sort_key", "Decimal", NULL}; + + PyEncoderObject *s; + PyObject *markers, *defaultfn, *encoder, *indent, *key_separator; + PyObject *item_separator, *sort_keys, *skipkeys, *allow_nan, *key_memo; + PyObject *use_decimal, *namedtuple_as_object, *tuple_as_array; + PyObject *bigint_as_string, *item_sort_key, *Decimal; + + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOOOOOOOOO:make_encoder", kwlist, + &markers, &defaultfn, &encoder, &indent, &key_separator, &item_separator, + &sort_keys, &skipkeys, &allow_nan, &key_memo, &use_decimal, + &namedtuple_as_object, &tuple_as_array, &bigint_as_string, + &item_sort_key, &Decimal)) + return -1; + + s->markers = markers; + s->defaultfn = defaultfn; + s->encoder = encoder; + s->indent = indent; + s->key_separator = key_separator; + s->item_separator = item_separator; + s->sort_keys = sort_keys; + s->skipkeys = skipkeys; + s->key_memo = key_memo; + s->fast_encode = (PyCFunction_Check(s->encoder) && PyCFunction_GetFunction(s->encoder) == (PyCFunction)py_encode_basestring_ascii); + s->allow_nan = PyObject_IsTrue(allow_nan); + s->use_decimal = PyObject_IsTrue(use_decimal); + s->namedtuple_as_object = PyObject_IsTrue(namedtuple_as_object); + s->tuple_as_array = PyObject_IsTrue(tuple_as_array); + s->bigint_as_string = PyObject_IsTrue(bigint_as_string); + s->item_sort_key = item_sort_key; + s->Decimal = Decimal; + + Py_INCREF(s->markers); + Py_INCREF(s->defaultfn); + Py_INCREF(s->encoder); + Py_INCREF(s->indent); + Py_INCREF(s->key_separator); + Py_INCREF(s->item_separator); + Py_INCREF(s->sort_keys); + Py_INCREF(s->skipkeys); + Py_INCREF(s->key_memo); + Py_INCREF(s->item_sort_key); + Py_INCREF(s->Decimal); + return 0; +} + +static PyObject * +encoder_call(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* Python callable interface to encode_listencode_obj */ + static char *kwlist[] = {"obj", "_current_indent_level", NULL}; + PyObject *obj; + PyObject *rval; + Py_ssize_t indent_level; + PyEncoderObject *s; + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:_iterencode", kwlist, + &obj, _convertPyInt_AsSsize_t, &indent_level)) + return NULL; + rval = PyList_New(0); + if (rval == NULL) + return NULL; + if (encoder_listencode_obj(s, rval, obj, indent_level)) { + Py_DECREF(rval); + return NULL; + } + return rval; +} + +static PyObject * +_encoded_const(PyObject *obj) +{ + /* Return the JSON string representation of None, True, False */ + if (obj == Py_None) { + static PyObject *s_null = NULL; + if (s_null == NULL) { + s_null = PyString_InternFromString("null"); + } + Py_INCREF(s_null); + return s_null; + } + else if (obj == Py_True) { + static PyObject *s_true = NULL; + if (s_true == NULL) { + s_true = PyString_InternFromString("true"); + } + Py_INCREF(s_true); + return s_true; + } + else if (obj == Py_False) { + static PyObject *s_false = NULL; + if (s_false == NULL) { + s_false = PyString_InternFromString("false"); + } + Py_INCREF(s_false); + return s_false; + } + else { + PyErr_SetString(PyExc_ValueError, "not a const"); + return NULL; + } +} + +static PyObject * +encoder_encode_float(PyEncoderObject *s, PyObject *obj) +{ + /* Return the JSON representation of a PyFloat */ + double i = PyFloat_AS_DOUBLE(obj); + if (!Py_IS_FINITE(i)) { + if (!s->allow_nan) { + PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant"); + return NULL; + } + if (i > 0) { + return PyString_FromString("Infinity"); + } + else if (i < 0) { + return PyString_FromString("-Infinity"); + } + else { + return PyString_FromString("NaN"); + } + } + /* Use a better float format here? */ + return PyObject_Repr(obj); +} + +static PyObject * +encoder_encode_string(PyEncoderObject *s, PyObject *obj) +{ + /* Return the JSON representation of a string */ + if (s->fast_encode) + return py_encode_basestring_ascii(NULL, obj); + else + return PyObject_CallFunctionObjArgs(s->encoder, obj, NULL); +} + +static int +_steal_list_append(PyObject *lst, PyObject *stolen) +{ + /* Append stolen and then decrement its reference count */ + int rval = PyList_Append(lst, stolen); + Py_DECREF(stolen); + return rval; +} + +static int +encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level) +{ + /* Encode Python object obj to a JSON term, rval is a PyList */ + int rv = -1; + if (Py_EnterRecursiveCall(" while encoding a JSON document")) + return rv; + do { + if (obj == Py_None || obj == Py_True || obj == Py_False) { + PyObject *cstr = _encoded_const(obj); + if (cstr != NULL) + rv = _steal_list_append(rval, cstr); + } + else if (PyString_Check(obj) || PyUnicode_Check(obj)) + { + PyObject *encoded = encoder_encode_string(s, obj); + if (encoded != NULL) + rv = _steal_list_append(rval, encoded); + } + else if (PyInt_Check(obj) || PyLong_Check(obj)) { + PyObject *encoded = PyObject_Str(obj); + if (encoded != NULL) { + if (s->bigint_as_string) { + encoded = maybe_quote_bigint(encoded, obj); + if (encoded == NULL) + break; + } + rv = _steal_list_append(rval, encoded); + } + } + else if (PyFloat_Check(obj)) { + PyObject *encoded = encoder_encode_float(s, obj); + if (encoded != NULL) + rv = _steal_list_append(rval, encoded); + } + else if (s->namedtuple_as_object && _is_namedtuple(obj)) { + PyObject *newobj = PyObject_CallMethod(obj, "_asdict", NULL); + if (newobj != NULL) { + rv = encoder_listencode_dict(s, rval, newobj, indent_level); + Py_DECREF(newobj); + } + } + else if (PyList_Check(obj) || (s->tuple_as_array && PyTuple_Check(obj))) { + rv = encoder_listencode_list(s, rval, obj, indent_level); + } + else if (PyDict_Check(obj)) { + rv = encoder_listencode_dict(s, rval, obj, indent_level); + } + else if (s->use_decimal && PyObject_TypeCheck(obj, s->Decimal)) { + PyObject *encoded = PyObject_Str(obj); + if (encoded != NULL) + rv = _steal_list_append(rval, encoded); + } + else { + PyObject *ident = NULL; + PyObject *newobj; + if (s->markers != Py_None) { + int has_key; + ident = PyLong_FromVoidPtr(obj); + if (ident == NULL) + break; + has_key = PyDict_Contains(s->markers, ident); + if (has_key) { + if (has_key != -1) + PyErr_SetString(PyExc_ValueError, "Circular reference detected"); + Py_DECREF(ident); + break; + } + if (PyDict_SetItem(s->markers, ident, obj)) { + Py_DECREF(ident); + break; + } + } + newobj = PyObject_CallFunctionObjArgs(s->defaultfn, obj, NULL); + if (newobj == NULL) { + Py_XDECREF(ident); + break; + } + rv = encoder_listencode_obj(s, rval, newobj, indent_level); + Py_DECREF(newobj); + if (rv) { + Py_XDECREF(ident); + rv = -1; + } + else if (ident != NULL) { + if (PyDict_DelItem(s->markers, ident)) { + Py_XDECREF(ident); + rv = -1; + } + Py_XDECREF(ident); + } + } + } while (0); + Py_LeaveRecursiveCall(); + return rv; +} + +static int +encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level) +{ + /* Encode Python dict dct a JSON term, rval is a PyList */ + static PyObject *open_dict = NULL; + static PyObject *close_dict = NULL; + static PyObject *empty_dict = NULL; + static PyObject *iteritems = NULL; + PyObject *kstr = NULL; + PyObject *ident = NULL; + PyObject *iter = NULL; + PyObject *item = NULL; + PyObject *items = NULL; + PyObject *encoded = NULL; + int skipkeys; + Py_ssize_t idx; + + if (open_dict == NULL || close_dict == NULL || empty_dict == NULL || iteritems == NULL) { + open_dict = PyString_InternFromString("{"); + close_dict = PyString_InternFromString("}"); + empty_dict = PyString_InternFromString("{}"); + iteritems = PyString_InternFromString("iteritems"); + if (open_dict == NULL || close_dict == NULL || empty_dict == NULL || iteritems == NULL) + return -1; + } + if (PyDict_Size(dct) == 0) + return PyList_Append(rval, empty_dict); + + if (s->markers != Py_None) { + int has_key; + ident = PyLong_FromVoidPtr(dct); + if (ident == NULL) + goto bail; + has_key = PyDict_Contains(s->markers, ident); + if (has_key) { + if (has_key != -1) + PyErr_SetString(PyExc_ValueError, "Circular reference detected"); + goto bail; + } + if (PyDict_SetItem(s->markers, ident, dct)) { + goto bail; + } + } + + if (PyList_Append(rval, open_dict)) + goto bail; + + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level += 1; + /* + newline_indent = '\n' + (_indent * _current_indent_level) + separator = _item_separator + newline_indent + buf += newline_indent + */ + } + + if (PyCallable_Check(s->item_sort_key)) { + if (PyDict_CheckExact(dct)) + items = PyDict_Items(dct); + else + items = PyMapping_Items(dct); + PyObject_CallMethod(items, "sort", "OO", Py_None, s->item_sort_key); + } + else if (PyObject_IsTrue(s->sort_keys)) { + /* First sort the keys then replace them with (key, value) tuples. */ + Py_ssize_t i, nitems; + if (PyDict_CheckExact(dct)) + items = PyDict_Keys(dct); + else + items = PyMapping_Keys(dct); + if (items == NULL) + goto bail; + if (!PyList_Check(items)) { + PyErr_SetString(PyExc_ValueError, "keys must return list"); + goto bail; + } + if (PyList_Sort(items) < 0) + goto bail; + nitems = PyList_GET_SIZE(items); + for (i = 0; i < nitems; i++) { + PyObject *key, *value; + key = PyList_GET_ITEM(items, i); + value = PyDict_GetItem(dct, key); + item = PyTuple_Pack(2, key, value); + if (item == NULL) + goto bail; + PyList_SET_ITEM(items, i, item); + Py_DECREF(key); + } + } + else { + if (PyDict_CheckExact(dct)) + items = PyDict_Items(dct); + else + items = PyMapping_Items(dct); + } + if (items == NULL) + goto bail; + iter = PyObject_GetIter(items); + Py_DECREF(items); + if (iter == NULL) + goto bail; + + skipkeys = PyObject_IsTrue(s->skipkeys); + idx = 0; + while ((item = PyIter_Next(iter))) { + PyObject *encoded, *key, *value; + if (!PyTuple_Check(item) || Py_SIZE(item) != 2) { + PyErr_SetString(PyExc_ValueError, "items must return 2-tuples"); + goto bail; + } + key = PyTuple_GET_ITEM(item, 0); + if (key == NULL) + goto bail; + value = PyTuple_GET_ITEM(item, 1); + if (value == NULL) + goto bail; + + encoded = PyDict_GetItem(s->key_memo, key); + if (encoded != NULL) { + Py_INCREF(encoded); + } + else if (PyString_Check(key) || PyUnicode_Check(key)) { + Py_INCREF(key); + kstr = key; + } + else if (PyFloat_Check(key)) { + kstr = encoder_encode_float(s, key); + if (kstr == NULL) + goto bail; + } + else if (key == Py_True || key == Py_False || key == Py_None) { + /* This must come before the PyInt_Check because + True and False are also 1 and 0.*/ + kstr = _encoded_const(key); + if (kstr == NULL) + goto bail; + } + else if (PyInt_Check(key) || PyLong_Check(key)) { + kstr = PyObject_Str(key); + if (kstr == NULL) + goto bail; + } + else if (skipkeys) { + Py_DECREF(item); + continue; + } + else { + /* TODO: include repr of key */ + PyErr_SetString(PyExc_TypeError, "keys must be a string"); + goto bail; + } + + if (idx) { + if (PyList_Append(rval, s->item_separator)) + goto bail; + } + + if (encoded == NULL) { + encoded = encoder_encode_string(s, kstr); + Py_CLEAR(kstr); + if (encoded == NULL) + goto bail; + if (PyDict_SetItem(s->key_memo, key, encoded)) + goto bail; + } + if (PyList_Append(rval, encoded)) { + goto bail; + } + Py_CLEAR(encoded); + if (PyList_Append(rval, s->key_separator)) + goto bail; + if (encoder_listencode_obj(s, rval, value, indent_level)) + goto bail; + Py_CLEAR(item); + idx += 1; + } + Py_CLEAR(iter); + if (PyErr_Occurred()) + goto bail; + if (ident != NULL) { + if (PyDict_DelItem(s->markers, ident)) + goto bail; + Py_CLEAR(ident); + } + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level -= 1; + /* + yield '\n' + (_indent * _current_indent_level) + */ + } + if (PyList_Append(rval, close_dict)) + goto bail; + return 0; + +bail: + Py_XDECREF(encoded); + Py_XDECREF(items); + Py_XDECREF(iter); + Py_XDECREF(kstr); + Py_XDECREF(ident); + return -1; +} + + +static int +encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level) +{ + /* Encode Python list seq to a JSON term, rval is a PyList */ + static PyObject *open_array = NULL; + static PyObject *close_array = NULL; + static PyObject *empty_array = NULL; + PyObject *ident = NULL; + PyObject *iter = NULL; + PyObject *obj = NULL; + int is_true; + int i = 0; + + if (open_array == NULL || close_array == NULL || empty_array == NULL) { + open_array = PyString_InternFromString("["); + close_array = PyString_InternFromString("]"); + empty_array = PyString_InternFromString("[]"); + if (open_array == NULL || close_array == NULL || empty_array == NULL) + return -1; + } + ident = NULL; + is_true = PyObject_IsTrue(seq); + if (is_true == -1) + return -1; + else if (is_true == 0) + return PyList_Append(rval, empty_array); + + if (s->markers != Py_None) { + int has_key; + ident = PyLong_FromVoidPtr(seq); + if (ident == NULL) + goto bail; + has_key = PyDict_Contains(s->markers, ident); + if (has_key) { + if (has_key != -1) + PyErr_SetString(PyExc_ValueError, "Circular reference detected"); + goto bail; + } + if (PyDict_SetItem(s->markers, ident, seq)) { + goto bail; + } + } + + iter = PyObject_GetIter(seq); + if (iter == NULL) + goto bail; + + if (PyList_Append(rval, open_array)) + goto bail; + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level += 1; + /* + newline_indent = '\n' + (_indent * _current_indent_level) + separator = _item_separator + newline_indent + buf += newline_indent + */ + } + while ((obj = PyIter_Next(iter))) { + if (i) { + if (PyList_Append(rval, s->item_separator)) + goto bail; + } + if (encoder_listencode_obj(s, rval, obj, indent_level)) + goto bail; + i++; + Py_CLEAR(obj); + } + Py_CLEAR(iter); + if (PyErr_Occurred()) + goto bail; + if (ident != NULL) { + if (PyDict_DelItem(s->markers, ident)) + goto bail; + Py_CLEAR(ident); + } + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level -= 1; + /* + yield '\n' + (_indent * _current_indent_level) + */ + } + if (PyList_Append(rval, close_array)) + goto bail; + return 0; + +bail: + Py_XDECREF(obj); + Py_XDECREF(iter); + Py_XDECREF(ident); + return -1; +} + +static void +encoder_dealloc(PyObject *self) +{ + /* Deallocate Encoder */ + encoder_clear(self); + Py_TYPE(self)->tp_free(self); +} + +static int +encoder_traverse(PyObject *self, visitproc visit, void *arg) +{ + PyEncoderObject *s; + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + Py_VISIT(s->markers); + Py_VISIT(s->defaultfn); + Py_VISIT(s->encoder); + Py_VISIT(s->indent); + Py_VISIT(s->key_separator); + Py_VISIT(s->item_separator); + Py_VISIT(s->sort_keys); + Py_VISIT(s->skipkeys); + Py_VISIT(s->key_memo); + Py_VISIT(s->item_sort_key); + return 0; +} + +static int +encoder_clear(PyObject *self) +{ + /* Deallocate Encoder */ + PyEncoderObject *s; + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + Py_CLEAR(s->markers); + Py_CLEAR(s->defaultfn); + Py_CLEAR(s->encoder); + Py_CLEAR(s->indent); + Py_CLEAR(s->key_separator); + Py_CLEAR(s->item_separator); + Py_CLEAR(s->sort_keys); + Py_CLEAR(s->skipkeys); + Py_CLEAR(s->key_memo); + Py_CLEAR(s->item_sort_key); + Py_CLEAR(s->Decimal); + return 0; +} + +PyDoc_STRVAR(encoder_doc, "_iterencode(obj, _current_indent_level) -> iterable"); + +static +PyTypeObject PyEncoderType = { + PyObject_HEAD_INIT(NULL) + 0, /* tp_internal */ + "simplejson._speedups.Encoder", /* tp_name */ + sizeof(PyEncoderObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + encoder_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + encoder_call, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + encoder_doc, /* tp_doc */ + encoder_traverse, /* tp_traverse */ + encoder_clear, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + encoder_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + encoder_init, /* tp_init */ + 0, /* tp_alloc */ + encoder_new, /* tp_new */ + 0, /* tp_free */ +}; + +static PyMethodDef speedups_methods[] = { + {"encode_basestring_ascii", + (PyCFunction)py_encode_basestring_ascii, + METH_O, + pydoc_encode_basestring_ascii}, + {"scanstring", + (PyCFunction)py_scanstring, + METH_VARARGS, + pydoc_scanstring}, + {NULL, NULL, 0, NULL} +}; + +PyDoc_STRVAR(module_doc, +"simplejson speedups\n"); + +void +init_speedups(void) +{ + PyObject *m; + PyScannerType.tp_new = PyType_GenericNew; + if (PyType_Ready(&PyScannerType) < 0) + return; + PyEncoderType.tp_new = PyType_GenericNew; + if (PyType_Ready(&PyEncoderType) < 0) + return; + + + m = Py_InitModule3("_speedups", speedups_methods, module_doc); + Py_INCREF((PyObject*)&PyScannerType); + PyModule_AddObject(m, "make_scanner", (PyObject*)&PyScannerType); + Py_INCREF((PyObject*)&PyEncoderType); + PyModule_AddObject(m, "make_encoder", (PyObject*)&PyEncoderType); +} diff --git a/script/test/diffcalc (copy)/simplejson/decoder.py b/script/test/diffcalc (copy)/simplejson/decoder.py new file mode 100644 index 0000000..1c1526c --- /dev/null +++ b/script/test/diffcalc (copy)/simplejson/decoder.py @@ -0,0 +1,427 @@ +"""Implementation of JSONDecoder +""" +import re +import sys +import struct + +from simplejson.scanner import make_scanner +def _import_c_scanstring(): + try: + from simplejson._speedups import scanstring + return scanstring + except ImportError: + return None +c_scanstring = _import_c_scanstring() + +__all__ = ['JSONDecoder'] + +FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL + +def _floatconstants(): + _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') + # The struct module in Python 2.4 would get frexp() out of range here + # when an endian is specified in the format string. Fixed in Python 2.5+ + if sys.byteorder != 'big': + _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] + nan, inf = struct.unpack('dd', _BYTES) + return nan, inf, -inf + +NaN, PosInf, NegInf = _floatconstants() + + +class JSONDecodeError(ValueError): + """Subclass of ValueError with the following additional properties: + + msg: The unformatted error message + doc: The JSON document being parsed + pos: The start index of doc where parsing failed + end: The end index of doc where parsing failed (may be None) + lineno: The line corresponding to pos + colno: The column corresponding to pos + endlineno: The line corresponding to end (may be None) + endcolno: The column corresponding to end (may be None) + + """ + def __init__(self, msg, doc, pos, end=None): + ValueError.__init__(self, errmsg(msg, doc, pos, end=end)) + self.msg = msg + self.doc = doc + self.pos = pos + self.end = end + self.lineno, self.colno = linecol(doc, pos) + if end is not None: + self.endlineno, self.endcolno = linecol(doc, end) + else: + self.endlineno, self.endcolno = None, None + + +def linecol(doc, pos): + lineno = doc.count('\n', 0, pos) + 1 + if lineno == 1: + colno = pos + else: + colno = pos - doc.rindex('\n', 0, pos) + return lineno, colno + + +def errmsg(msg, doc, pos, end=None): + # Note that this function is called from _speedups + lineno, colno = linecol(doc, pos) + if end is None: + #fmt = '{0}: line {1} column {2} (char {3})' + #return fmt.format(msg, lineno, colno, pos) + fmt = '%s: line %d column %d (char %d)' + return fmt % (msg, lineno, colno, pos) + endlineno, endcolno = linecol(doc, end) + #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})' + #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end) + fmt = '%s: line %d column %d - line %d column %d (char %d - %d)' + return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end) + + +_CONSTANTS = { + '-Infinity': NegInf, + 'Infinity': PosInf, + 'NaN': NaN, +} + +STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) +BACKSLASH = { + '"': u'"', '\\': u'\\', '/': u'/', + 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', +} + +DEFAULT_ENCODING = "utf-8" + +def py_scanstring(s, end, encoding=None, strict=True, + _b=BACKSLASH, _m=STRINGCHUNK.match): + """Scan the string s for a JSON string. End is the index of the + character in s after the quote that started the JSON string. + Unescapes all valid JSON string escape sequences and raises ValueError + on attempt to decode an invalid string. If strict is False then literal + control characters are allowed in the string. + + Returns a tuple of the decoded string and the index of the character in s + after the end quote.""" + if encoding is None: + encoding = DEFAULT_ENCODING + chunks = [] + _append = chunks.append + begin = end - 1 + while 1: + chunk = _m(s, end) + if chunk is None: + raise JSONDecodeError( + "Unterminated string starting at", s, begin) + end = chunk.end() + content, terminator = chunk.groups() + # Content is contains zero or more unescaped string characters + if content: + if not isinstance(content, unicode): + content = unicode(content, encoding) + _append(content) + # Terminator is the end of string, a literal control character, + # or a backslash denoting that an escape sequence follows + if terminator == '"': + break + elif terminator != '\\': + if strict: + msg = "Invalid control character %r at" % (terminator,) + #msg = "Invalid control character {0!r} at".format(terminator) + raise JSONDecodeError(msg, s, end) + else: + _append(terminator) + continue + try: + esc = s[end] + except IndexError: + raise JSONDecodeError( + "Unterminated string starting at", s, begin) + # If not a unicode escape sequence, must be in the lookup table + if esc != 'u': + try: + char = _b[esc] + except KeyError: + msg = "Invalid \\escape: " + repr(esc) + raise JSONDecodeError(msg, s, end) + end += 1 + else: + # Unicode escape sequence + esc = s[end + 1:end + 5] + next_end = end + 5 + if len(esc) != 4: + msg = "Invalid \\uXXXX escape" + raise JSONDecodeError(msg, s, end) + uni = int(esc, 16) + # Check for surrogate pair on UCS-4 systems + if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535: + msg = "Invalid \\uXXXX\\uXXXX surrogate pair" + if not s[end + 5:end + 7] == '\\u': + raise JSONDecodeError(msg, s, end) + esc2 = s[end + 7:end + 11] + if len(esc2) != 4: + raise JSONDecodeError(msg, s, end) + uni2 = int(esc2, 16) + uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) + next_end += 6 + char = unichr(uni) + end = next_end + # Append the unescaped character + _append(char) + return u''.join(chunks), end + + +# Use speedup if available +scanstring = c_scanstring or py_scanstring + +WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS) +WHITESPACE_STR = ' \t\n\r' + +def JSONObject((s, end), encoding, strict, scan_once, object_hook, + object_pairs_hook, memo=None, + _w=WHITESPACE.match, _ws=WHITESPACE_STR): + # Backwards compatibility + if memo is None: + memo = {} + memo_get = memo.setdefault + pairs = [] + # Use a slice to prevent IndexError from being raised, the following + # check will raise a more specific ValueError if the string is empty + nextchar = s[end:end + 1] + # Normally we expect nextchar == '"' + if nextchar != '"': + if nextchar in _ws: + end = _w(s, end).end() + nextchar = s[end:end + 1] + # Trivial empty object + if nextchar == '}': + if object_pairs_hook is not None: + result = object_pairs_hook(pairs) + return result, end + 1 + pairs = {} + if object_hook is not None: + pairs = object_hook(pairs) + return pairs, end + 1 + elif nextchar != '"': + raise JSONDecodeError( + "Expecting property name enclosed in double quotes", + s, end) + end += 1 + while True: + key, end = scanstring(s, end, encoding, strict) + key = memo_get(key, key) + + # To skip some function call overhead we optimize the fast paths where + # the JSON key separator is ": " or just ":". + if s[end:end + 1] != ':': + end = _w(s, end).end() + if s[end:end + 1] != ':': + raise JSONDecodeError("Expecting ':' delimiter", s, end) + + end += 1 + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + try: + value, end = scan_once(s, end) + except StopIteration: + raise JSONDecodeError("Expecting object", s, end) + pairs.append((key, value)) + + try: + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + end += 1 + + if nextchar == '}': + break + elif nextchar != ',': + raise JSONDecodeError("Expecting ',' delimiter", s, end - 1) + + try: + nextchar = s[end] + if nextchar in _ws: + end += 1 + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + + end += 1 + if nextchar != '"': + raise JSONDecodeError( + "Expecting property name enclosed in double quotes", + s, end - 1) + + if object_pairs_hook is not None: + result = object_pairs_hook(pairs) + return result, end + pairs = dict(pairs) + if object_hook is not None: + pairs = object_hook(pairs) + return pairs, end + +def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): + values = [] + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + # Look-ahead for trivial empty array + if nextchar == ']': + return values, end + 1 + _append = values.append + while True: + try: + value, end = scan_once(s, end) + except StopIteration: + raise JSONDecodeError("Expecting object", s, end) + _append(value) + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar == ']': + break + elif nextchar != ',': + raise JSONDecodeError("Expecting ',' delimiter", s, end) + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + return values, end + +class JSONDecoder(object): + """Simple JSON decoder + + Performs the following translations in decoding by default: + + +---------------+-------------------+ + | JSON | Python | + +===============+===================+ + | object | dict | + +---------------+-------------------+ + | array | list | + +---------------+-------------------+ + | string | unicode | + +---------------+-------------------+ + | number (int) | int, long | + +---------------+-------------------+ + | number (real) | float | + +---------------+-------------------+ + | true | True | + +---------------+-------------------+ + | false | False | + +---------------+-------------------+ + | null | None | + +---------------+-------------------+ + + It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as + their corresponding ``float`` values, which is outside the JSON spec. + + """ + + def __init__(self, encoding=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, strict=True, + object_pairs_hook=None): + """ + *encoding* determines the encoding used to interpret any + :class:`str` objects decoded by this instance (``'utf-8'`` by + default). It has no effect when decoding :class:`unicode` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as :class:`unicode`. + + *object_hook*, if specified, will be called with the result of every + JSON object decoded and its return value will be used in place of the + given :class:`dict`. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + *object_pairs_hook* is an optional function that will be called with + the result of any object literal decode with an ordered list of pairs. + The return value of *object_pairs_hook* will be used instead of the + :class:`dict`. This feature can be used to implement custom decoders + that rely on the order that the key and value pairs are decoded (for + example, :func:`collections.OrderedDict` will remember the order of + insertion). If *object_hook* is also defined, the *object_pairs_hook* + takes priority. + + *parse_float*, if specified, will be called with the string of every + JSON float to be decoded. By default, this is equivalent to + ``float(num_str)``. This can be used to use another datatype or parser + for JSON floats (e.g. :class:`decimal.Decimal`). + + *parse_int*, if specified, will be called with the string of every + JSON int to be decoded. By default, this is equivalent to + ``int(num_str)``. This can be used to use another datatype or parser + for JSON integers (e.g. :class:`float`). + + *parse_constant*, if specified, will be called with one of the + following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This + can be used to raise an exception if invalid JSON numbers are + encountered. + + *strict* controls the parser's behavior when it encounters an + invalid control character in a string. The default setting of + ``True`` means that unescaped control characters are parse errors, if + ``False`` then control characters will be allowed in strings. + + """ + self.encoding = encoding + self.object_hook = object_hook + self.object_pairs_hook = object_pairs_hook + self.parse_float = parse_float or float + self.parse_int = parse_int or int + self.parse_constant = parse_constant or _CONSTANTS.__getitem__ + self.strict = strict + self.parse_object = JSONObject + self.parse_array = JSONArray + self.parse_string = scanstring + self.memo = {} + self.scan_once = make_scanner(self) + + def decode(self, s, _w=WHITESPACE.match): + """Return the Python representation of ``s`` (a ``str`` or ``unicode`` + instance containing a JSON document) + + """ + obj, end = self.raw_decode(s) + end = _w(s, end).end() + if end != len(s): + raise JSONDecodeError("Extra data", s, end, len(s)) + return obj + + def raw_decode(self, s, idx=0, _w=WHITESPACE.match): + """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` + beginning with a JSON document) and return a 2-tuple of the Python + representation and the index in ``s`` where the document ended. + Optionally, ``idx`` can be used to specify an offset in ``s`` where + the JSON document begins. + + This can be used to decode a JSON document from a string that may + have extraneous data at the end. + + """ + try: + obj, end = self.scan_once(s, idx=_w(s, idx).end()) + except StopIteration: + raise JSONDecodeError("No JSON object could be decoded", s, idx) + return obj, end diff --git a/script/test/diffcalc (copy)/simplejson/encoder.py b/script/test/diffcalc (copy)/simplejson/encoder.py new file mode 100644 index 0000000..6b4a6a4 --- /dev/null +++ b/script/test/diffcalc (copy)/simplejson/encoder.py @@ -0,0 +1,567 @@ +"""Implementation of JSONEncoder +""" +import re +from decimal import Decimal + +def _import_speedups(): + try: + from simplejson import _speedups + return _speedups.encode_basestring_ascii, _speedups.make_encoder + except ImportError: + return None, None +c_encode_basestring_ascii, c_make_encoder = _import_speedups() + +from simplejson.decoder import PosInf + +ESCAPE = re.compile(ur'[\x00-\x1f\\"\b\f\n\r\t\u2028\u2029]') +ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') +HAS_UTF8 = re.compile(r'[\x80-\xff]') +ESCAPE_DCT = { + '\\': '\\\\', + '"': '\\"', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', + u'\u2028': '\\u2028', + u'\u2029': '\\u2029', +} +for i in range(0x20): + #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i)) + ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) + +FLOAT_REPR = repr + +def encode_basestring(s): + """Return a JSON representation of a Python string + + """ + if isinstance(s, str) and HAS_UTF8.search(s) is not None: + s = s.decode('utf-8') + def replace(match): + return ESCAPE_DCT[match.group(0)] + return u'"' + ESCAPE.sub(replace, s) + u'"' + + +def py_encode_basestring_ascii(s): + """Return an ASCII-only JSON representation of a Python string + + """ + if isinstance(s, str) and HAS_UTF8.search(s) is not None: + s = s.decode('utf-8') + def replace(match): + s = match.group(0) + try: + return ESCAPE_DCT[s] + except KeyError: + n = ord(s) + if n < 0x10000: + #return '\\u{0:04x}'.format(n) + return '\\u%04x' % (n,) + else: + # surrogate pair + n -= 0x10000 + s1 = 0xd800 | ((n >> 10) & 0x3ff) + s2 = 0xdc00 | (n & 0x3ff) + #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2) + return '\\u%04x\\u%04x' % (s1, s2) + return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' + + +encode_basestring_ascii = ( + c_encode_basestring_ascii or py_encode_basestring_ascii) + +class JSONEncoder(object): + """Extensible JSON encoder for Python data structures. + + Supports the following objects and types by default: + + +-------------------+---------------+ + | Python | JSON | + +===================+===============+ + | dict, namedtuple | object | + +-------------------+---------------+ + | list, tuple | array | + +-------------------+---------------+ + | str, unicode | string | + +-------------------+---------------+ + | int, long, float | number | + +-------------------+---------------+ + | True | true | + +-------------------+---------------+ + | False | false | + +-------------------+---------------+ + | None | null | + +-------------------+---------------+ + + To extend this to recognize other objects, subclass and implement a + ``.default()`` method with another method that returns a serializable + object for ``o`` if possible, otherwise it should call the superclass + implementation (to raise ``TypeError``). + + """ + item_separator = ', ' + key_separator = ': ' + def __init__(self, skipkeys=False, ensure_ascii=True, + check_circular=True, allow_nan=True, sort_keys=False, + indent=None, separators=None, encoding='utf-8', default=None, + use_decimal=True, namedtuple_as_object=True, + tuple_as_array=True, bigint_as_string=False, + item_sort_key=None): + """Constructor for JSONEncoder, with sensible defaults. + + If skipkeys is false, then it is a TypeError to attempt + encoding of keys that are not str, int, long, float or None. If + skipkeys is True, such items are simply skipped. + + If ensure_ascii is true, the output is guaranteed to be str + objects with all incoming unicode characters escaped. If + ensure_ascii is false, the output will be unicode object. + + If check_circular is true, then lists, dicts, and custom encoded + objects will be checked for circular references during encoding to + prevent an infinite recursion (which would cause an OverflowError). + Otherwise, no such check takes place. + + If allow_nan is true, then NaN, Infinity, and -Infinity will be + encoded as such. This behavior is not JSON specification compliant, + but is consistent with most JavaScript based encoders and decoders. + Otherwise, it will be a ValueError to encode such floats. + + If sort_keys is true, then the output of dictionaries will be + sorted by key; this is useful for regression tests to ensure + that JSON serializations can be compared on a day-to-day basis. + + If indent is a string, then JSON array elements and object members + will be pretty-printed with a newline followed by that string repeated + for each level of nesting. ``None`` (the default) selects the most compact + representation without any newlines. For backwards compatibility with + versions of simplejson earlier than 2.1.0, an integer is also accepted + and is converted to a string with that many spaces. + + If specified, separators should be a (item_separator, key_separator) + tuple. The default is (', ', ': '). To get the most compact JSON + representation you should specify (',', ':') to eliminate whitespace. + + If specified, default is a function that gets called for objects + that can't otherwise be serialized. It should return a JSON encodable + version of the object or raise a ``TypeError``. + + If encoding is not None, then all input strings will be + transformed into unicode using that encoding prior to JSON-encoding. + The default is UTF-8. + + If use_decimal is true (not the default), ``decimal.Decimal`` will + be supported directly by the encoder. For the inverse, decode JSON + with ``parse_float=decimal.Decimal``. + + If namedtuple_as_object is true (the default), objects with + ``_asdict()`` methods will be encoded as JSON objects. + + If tuple_as_array is true (the default), tuple (and subclasses) will + be encoded as JSON arrays. + + If bigint_as_string is true (not the default), ints 2**53 and higher + or lower than -2**53 will be encoded as strings. This is to avoid the + rounding that happens in Javascript otherwise. + + If specified, item_sort_key is a callable used to sort the items in + each dictionary. This is useful if you want to sort items other than + in alphabetical order by key. + """ + + self.skipkeys = skipkeys + self.ensure_ascii = ensure_ascii + self.check_circular = check_circular + self.allow_nan = allow_nan + self.sort_keys = sort_keys + self.use_decimal = use_decimal + self.namedtuple_as_object = namedtuple_as_object + self.tuple_as_array = tuple_as_array + self.bigint_as_string = bigint_as_string + self.item_sort_key = item_sort_key + if indent is not None and not isinstance(indent, basestring): + indent = indent * ' ' + self.indent = indent + if separators is not None: + self.item_separator, self.key_separator = separators + elif indent is not None: + self.item_separator = ',' + if default is not None: + self.default = default + self.encoding = encoding + + def default(self, o): + """Implement this method in a subclass such that it returns + a serializable object for ``o``, or calls the base implementation + (to raise a ``TypeError``). + + For example, to support arbitrary iterators, you could + implement default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + return JSONEncoder.default(self, o) + + """ + raise TypeError(repr(o) + " is not JSON serializable") + + def encode(self, o): + """Return a JSON string representation of a Python data structure. + + >>> from simplejson import JSONEncoder + >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) + '{"foo": ["bar", "baz"]}' + + """ + # This is for extremely simple cases and benchmarks. + if isinstance(o, basestring): + if isinstance(o, str): + _encoding = self.encoding + if (_encoding is not None + and not (_encoding == 'utf-8')): + o = o.decode(_encoding) + if self.ensure_ascii: + return encode_basestring_ascii(o) + else: + return encode_basestring(o) + # This doesn't pass the iterator directly to ''.join() because the + # exceptions aren't as detailed. The list call should be roughly + # equivalent to the PySequence_Fast that ''.join() would do. + chunks = self.iterencode(o, _one_shot=True) + if not isinstance(chunks, (list, tuple)): + chunks = list(chunks) + if self.ensure_ascii: + return ''.join(chunks) + else: + return u''.join(chunks) + + def iterencode(self, o, _one_shot=False): + """Encode the given object and yield each string + representation as available. + + For example:: + + for chunk in JSONEncoder().iterencode(bigobject): + mysocket.write(chunk) + + """ + if self.check_circular: + markers = {} + else: + markers = None + if self.ensure_ascii: + _encoder = encode_basestring_ascii + else: + _encoder = encode_basestring + if self.encoding != 'utf-8': + def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding): + if isinstance(o, str): + o = o.decode(_encoding) + return _orig_encoder(o) + + def floatstr(o, allow_nan=self.allow_nan, + _repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf): + # Check for specials. Note that this type of test is processor + # and/or platform-specific, so do tests which don't depend on + # the internals. + + if o != o: + text = 'NaN' + elif o == _inf: + text = 'Infinity' + elif o == _neginf: + text = '-Infinity' + else: + return _repr(o) + + if not allow_nan: + raise ValueError( + "Out of range float values are not JSON compliant: " + + repr(o)) + + return text + + + key_memo = {} + if (_one_shot and c_make_encoder is not None + and self.indent is None): + _iterencode = c_make_encoder( + markers, self.default, _encoder, self.indent, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, self.allow_nan, key_memo, self.use_decimal, + self.namedtuple_as_object, self.tuple_as_array, + self.bigint_as_string, self.item_sort_key, + Decimal) + else: + _iterencode = _make_iterencode( + markers, self.default, _encoder, self.indent, floatstr, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, _one_shot, self.use_decimal, + self.namedtuple_as_object, self.tuple_as_array, + self.bigint_as_string, self.item_sort_key, + Decimal=Decimal) + try: + return _iterencode(o, 0) + finally: + key_memo.clear() + + +class JSONEncoderForHTML(JSONEncoder): + """An encoder that produces JSON safe to embed in HTML. + + To embed JSON content in, say, a script tag on a web page, the + characters &, < and > should be escaped. They cannot be escaped + with the usual entities (e.g. &) because they are not expanded + within